Visualforce Signature Component with HTML5 Canvas

I’ve come across the scenario a number of times where a client would like to have the ability to capture someone’s signature and keep it on a record in Salesforce.  This post describes a simple way to capture signatures in Salesforce using Visualforce, HTML5 Canvas, and Javascript Remoting.

The resulting GetSignature page that the code in this post will create.
The resulting GetSignature page that the code in this post will create.

First, we start with a visualforce component that includes the canvas and the javascript required to draw on the canvas.  Note that there are resources that need to be included: jQuery, jQuery-Mobile (if you want the mobile-styling as in the sample picture above).

here is the code for the Visualforce Component called “SignatureComponent”:

<apex:component controller="SignatureComponentController" allowDML="true" >
 <apex:attribute name="parentId" type="id" description="The Id of the record for Signature" required="true" assignTo="{!objParentId}"/>

<head>
 <link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" ></link>
 <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
 <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
 <script>var $ = jQuery.noConflict();</script>
 </head> 
 
<body>
<div data-role="page" id="signatureCaptureHome"> 
  <div data-role="content">
    <h1 id="recordSigId">Record Signature:</h1>
    <canvas id="myCanvas" width="500" height="200" style="border:2px solid black"></canvas>
    <br /><br />
    <button onclick="javascript:clearArea();return false;">Clear Area</button>
    <button onclick="saveSignature();">Accept Signature</button>
    
    Line width : <select id="selWidth">
     <option value="1">1</option>
     <option value="3">3</option>
     <option value="5">5</option>
     <option value="7">7</option>
     <option value="9" selected="selected">9</option>
     <option value="11">11</option>
    </select>
    Color : <select id="selColor">
     <option value="black">black</option>
     <option value="blue" selected="selected">blue</option>
     <option value="red">red</option>
     <option value="green">green</option>
     <option value="yellow">yellow</option>
     <option value="gray">gray</option>
    </select>
  </div>
 </div>
</body>
 
 <script>
 
 var mousePressed = false;
 var lastX, lastY;
 var canvas;
 var ctx;
 var canSave = false;
 var parentId = '{!objParentId}';
 
$(document).ready(function() {
 canvas = document.getElementById('myCanvas');
 ctx = canvas.getContext("2d");

 $('#myCanvas').mousedown(function (e) {
   mousePressed = true;
   Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
 });

 $('#myCanvas').mousemove(function (e) {
   if (mousePressed) {
     var canSave = true;
     Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
   }
 });

 $('#myCanvas').mouseup(function (e) {
   mousePressed = false;
 });
 $('#myCanvas').mouseleave(function (e) {
   mousePressed = false;
 });
 $('#myCanvas').touchstart(function (e) {
   mousePressed = true;
   Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
 });
 $('#myCanvas').touchmove(function (e) {
   if (mousePressed) {
     var canSave = true;
     Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
   }
 });
 $('#myCanvas').touchend(function (e) {
   mousePressed = false;
 });
})
 
 function Draw(x, y, isDown) {
   if (isDown) {
     ctx.beginPath();
     ctx.strokeStyle = $('#selColor').val();
     ctx.lineWidth = $('#selWidth').val();
     ctx.lineJoin = "round";
     ctx.moveTo(lastX, lastY);
     ctx.lineTo(x, y);
     ctx.closePath();
     ctx.stroke();
   }
   lastX = x; lastY = y;
}
 
function clearArea() {
 // Use the identity matrix while clearing the canvas
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
 
 function saveSignature(){
   var strDataURI = canvas.toDataURL();
   console.log(strDataURI);
   strDataURI = strDataURI.replace(/^data:image\/(png|jpg);base64,/, "");
   console.log(strDataURI);
   SignatureComponentController.saveSignature(strDataURI,parentId,processResult);
 }
 
 function processResult(result){
   if(result){
     top.location.href='/'+parentId;
   }else{
     alert('An error occurred while saving the signature. Please try again.');
   }
   console.log(result);
 }
 </script>
</apex:component>

So the component contains the javascript that lets you draw lines on the canvas.  It also includes the saveSignature method which attempts to save an image of the signature as an attachment.  If the method succeeds and “true” is returned, the processResult() method redirects the user back to the detail page of the record.

Here is the controller code for the component:

global class SignatureComponentController {
 
 public id objParentId{get;set;}
 
 @RemoteAction
 global static boolean saveSignature(String signatureBody, id parentId){
  try{
   system.debug('Record Id == ' + parentId);
   system.debug(signatureBody);
   Attachment a = new Attachment();
   a.ParentId = parentId;
   a.Body = EncodingUtil.base64Decode(signatureBody);
   a.ContentType = 'image/png';
   a.Name = 'Signature Capture.png';
   insert a;
 
   Schema.SObjectType sobjectType = parentId.getSObjectType();
   String sobjectName = sobjectType.getDescribe().getName();
   sObject s = Schema.getGlobalDescribe().get(sobjectName).newSObject() ;
   s.put('Id',parentId);
   // at this point you can edit any fields you have on the parentId.
   // I have SignaureId__(text[18]) on objects where I want to capture the id of the signature
   // attachment on the record.
   s.put('SignatureId__c', a.Id);
   update s;
 
   return true;
   //return '{success:true, attachId:' + a.Id + ', sObject: ' + c + '}';
  }catch(Exception e){
   system.debug(e);
   return false;
  }
 return null;
 }
}

Note that the “objParentId” is set by the Component attribute on the Visualforce Component, and the “saveSignature” method constructs the sObject from the parentId you supply.  Also – you can add any additional field updates you may require.

And finally, here’s the simple Visualforce page called “GetSignature” that includes the custom VF Component:

<apex:page sidebar="false" docType="html-5.0" applyBodyTag="false">
  <c:SignagureComponent parentId="{!$CurrentPage.parameters.parentId}"/>
</apex:page>

As you can see, you can simply set the parentId as a parameter in the URL and use that in the component. This helps keep the pages more dynamic if you don’t want to bind them to a specific Standard sObject controller (i.e. StandardController property).  Also note that you have to have docType="html-5.0" in order for the canvas element to work properly.

I also added a formula field to show the signature, based on the Id of the attachment we set in the controller. The final output on the detail page looks like this:

The signature formula field now shows the signature captured on the canvas.
The signature formula field now shows the signature captured on the canvas.

Hope you find this useful 🙂

One thought on “Visualforce Signature Component with HTML5 Canvas

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s