You are viewing limited content. For full access, please sign in.

Question

Question

Draw on image modern designer

asked on February 28

Would it be possible to get an option/function to be able to draw on an image and submit via the modern designer?

I am converting a PDF form which includes the following section:

Basically, the applicant will draw on an area of an image and the resulting image gets saved on the form.

I can see there has been discussion of this using the classic designer (Is it possible to draw on a picture in Laserfiche Form? - Laserfiche Answers), but we want to use the modern designer as it works much better for us.

Thanks

0 0

Replies

replied on February 29 Show version history

This looked like a really fun challenge, and I wanted to see what (if anything) I could do to make it work.  And I've got something working!

This is a round-about way to handle it, because it creates two versions of the image (one on the form itself, and one hidden in the iFrame where the Javascript is running), but it appears to work in my testing.  This is on Version 11.0.2311.50553.

Preparation work:

  1. Determine the exact width and height of the original image (reduce your image size if it is too big for use on your Form - I tested this with an image that was 500 x 500 and it worked well).  You want your original image to be the size you are actually using on the form, it'll go kind of weird if the Javascript later is using a different size than the original image.
  2. Convert your image to base64.  Just Google "convert image to base64" and you should find several sites that can do it.  Save that base64 code to a text editor for use later in your form.

 

On your form, you are going to add three elements:

  1. Custom HTML element.  Don't worry about the content as it'll be changed later.  Give it a class name of canvasContainer.
  2. Multi-Line field with variable name of default_image_as_base64.  For the default value on this field, enter the base64 code of your image that you got before and saved to a text editor.
  3. Multi-Line field with variable name of image_as_base64.  Leave the default value on this one blank.

It should look something like this:

Add field rules to hide the two multi-line fields.  Make sure you set it to save their values.

Finally, add this Javascript to your form, making sure to list the width and height of your image that you determined at the beginning in lines 4 and 5. 

//These are variables used throughout the process.
//The width and height must be an exact match for the original image 
//or it will behave in an unexpected manner.
var imgWidth = 500;
var imgHeight = 500;
var initialImage = LFForm.getFieldValues( {variableName: "image_as_base64"} );

//Function to load the default image into the current image field.
async function CopyDefaultImage() {
  var defaultImage = await LFForm.getFieldValues( {variableName: "default_image_as_base64"} );
  await LFForm.setFieldValues({variableName: "image_as_base64"}, defaultImage);
  await CreateAndLoadVisibleCanvasAndImage();
  initialImage = await LFForm.getFieldValues( {variableName: "image_as_base64"} );
  await LoadImageToHiddenCanvas();
}

//Function to create and display the image and canvas that are visible on screen.
//These cannot be modified directly by the code, but are helpful for tracking,
//and handling the user interaction clicking on the image.
async function CreateAndLoadVisibleCanvasAndImage() {
  var currentImage = LFForm.getFieldValues( {variableName: "image_as_base64"} );
  var customHTMLField = LFForm.findFieldsByClassName("canvasContainer")[0];
  var newContent = '<div style="display: grid;">';
  newContent = newContent + '<img width="' + imgWidth + '" height="' + imgHeight + '" style="grid-column: 1; grid-row: 1;" src="' + currentImage + '">';
  newContent = newContent + '<canvas width="' + imgWidth + '" height="' + imgHeight + '" style="border:black 2px solid; grid-column: 1; grid-row: 1;" onclick="canvasClick(event)"></canvas>';
  newContent = newContent + '</div><br><button onclick="resetClick(event)">Reset/Clear the Image</button>';
  await LFForm.changeFieldSettings( customHTMLField, {content: newContent} );
}

//When the form is loaded on screen, call the CopyDefaultImage() function and 
//the CreateAndLoadVisibleCanvasAndImage() function as needed.
//Then load the image for the hidden canvas.
var formLoadTasks = async function() {
  if (initialImage == '') {
    await CopyDefaultImage();
  }
  else {
    await CreateAndLoadVisibleCanvasAndImage();
  }
  await LoadImageToHiddenCanvas();
}
formLoadTasks();

//Variables and function to handle a second canvas that is not visible on screen.
//This canvas is used for the actual modifications of the image.  That is because 
//the code can modify this element, but not the visible element on screen.
const hiddenCanvas = document.createElement('canvas');
const ctx = hiddenCanvas.getContext('2d');
const hiddenImage = new Image();
async function LoadImageToHiddenCanvas() {
  hiddenImage.crossOrigin = "";
  hiddenImage.src = initialImage;
  hiddenImage.onload = () => {
    hiddenCanvas.width = imgWidth;
    hiddenCanvas.height = imgHeight;
    ctx.drawImage(hiddenImage, 0, 0);
  };
  document.body.appendChild(hiddenCanvas);
}

//When the user clicks on the visible canvas, record the X and Y positions
//from the click and then edit the hidden canvas at those coordinates.
//Then save the edited image and reload the visible canvas and image.
window.canvasClick = async function(event) {
  DrawXOnCanvas(event.offsetX, event.offsetY);
  await updateImage();
  await CreateAndLoadVisibleCanvasAndImage();
}

//When the user clicks the button to reset the image, it is reloaded
//from the default.
window.resetClick = async function(event) {
  await CopyDefaultImage();
}

//Function to save the hidden canvas image to the base64 field.
async function updateImage() {
  await LFForm.setFieldValues({variableName: "image_as_base64"}, hiddenCanvas.toDataURL());
}

//Function that draws an X on the Hidden Canvas.
function DrawXOnCanvas(x, y) {
  ctx.beginPath();
  ctx.moveTo(x - 10, y - 10);
  ctx.lineTo(x + 10, y + 10);
  ctx.stroke();
  ctx.moveTo(x + 10, y - 10);
  ctx.lineTo(x - 10, y + 10);
  ctx.stroke();
}

 

And that should be it.  This worked for me on testing both with the initial form loading, but also on subsequent user tasks, including a read-only user task (which should be an indication that it will save the image to the repository, but I didn't specifically test that).

Good luck!

3 0
replied on February 29

This is a really interesting work around, as I was not aware we could create elements in the custom HTML that can be interacted with JavaScript or listen to events from the window object.

So basically when a user clicks on the image (and subsequently the canvas overlayed on top of it) we do:

Update hidden canvas > hidden canvas to base64 > Update img displayed to new base64

I wonder if this behaviour is intended or if it is a bug? As I was under the impression with the advent of the LFObject and JavaScript in the modern designer, it was supposed to be sandboxed fully (vs the classic designer).

Although, I guess since we still have actual access to JavaScript, there wouldn't be much room for them to disable these kinds of events.

0 0
replied on February 29

I reviewed the code and it is not using anything we do not support. The event object being referenced is using the sanitized event object. A few months back we enabled interaction with custom html events. For example, the button has an onclick event that triggers code in the sandbox 'onclick="resetClick(event)" '. The key is all code needs to run in the sandbox, this type of interaction is exactly what we DO still want to support.

 

I still want to support some other events that require data to be transferred, but the x/y coordinates in the current event object seem to make this use case possible.

2 0
replied on February 29

Thank you @████████ - I'm glad I didn't run afoul of unsupported functionality this time.  haha

@████████- you are correct with the flow of what it is doing.  The visible image is held behind the canvas because the sandboxed code cannot edit the canvas to load the image into it - so it's solely using that canvas to track the click positions.  The hidden canvas, which is inside the sandboxed iFrame where the Javascript is actually running, is accessible to the code (because it is inside that iFrame) so it can be manipulated by the code.  The image is loaded into that hidden canvas and edited inside that canvas, and then loaded back to the field for permanent storage and to reload the visible image.

To be clear - as Zachary mentioned, the onclick events that are happening from within the visible canvas and button have only been supported since Forms 11 Update 5 (version 11.0.2311) so you need to be on that version for this to function.

1 0
replied on February 29

Ya, this would unfortunately not be possible with the form designer. I have an upcoming feature I am working on specking out that would solve this. How often do you require this type of interaction?

1 0
replied on February 29

It would be great to have something that is built-in to handle these types of scenarios.

I'd say for us, this would increase the options we have in making forms more "user friendly", as we have quite a few other forms that include information such as floor plans and the like.

Currently, we have users attempt to describe where stuff can be positioned or where things are marked (either by text input or checkboxes with pre-select options). However, being able to just physically mark on an image to indicate the user's intent would be really helpful for all involved in such business processes. 

0 0
replied on February 28

Hi Jay,

It seems impossible for the modern designer, as the draw picture is relying on sketch.js to work, however modern form is not able to load in such js lib easily, at least from my research, I can't make it work.

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.