
A small component to help capture a signature or just drawing some terrible pictures. The component supports saving to Salesforce File or Attachment.
// SingatureUtils.cls
public with sharing class SignatureUtils {
@AuraEnabled
public static void saveSignature(string relatedId, string data, string type){
//No Id No save
if(String.isBlank(relatedId)) return;
if(type == 'Attachment'){
saveAttachment(relatedId, data);
}
if(type == 'SFFile'){
saveSFFile(relatedId,data);
}
}
//Save file as Attachment
private static void saveAttachment(string relatedId, string data){
Attachment att = new Attachment();
att.Name = 'Singed-'+System.now().getTime()+'.png';
att.Body = EncodingUtil.base64Decode(data);
att.ContentType = 'image/png';
att.ParentId = relatedId;
insert att;
}
private static void saveSFFile(string relatedId, string data){
ContentVersion cv = new ContentVersion();
cv.PathOnClient = 'Singed-'+System.now().getTime()+'.png';
cv.Title = 'Singed-'+System.now().getTime()+'.png';
cv.VersionData = EncodingUtil.base64Decode(data);
insert cv;
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = [select contentDocumentId from ContentVersion where id=:cv.id].contentDocumentId;
cdl.LinkedEntityId = relatedId;
cdl.ShareType = 'V';
insert cdl;
}
}
<!--signaturePanel.html-->
<template>
<article class="slds-card">
<h1>Signature: {recordId}</h1>
<div class="slds-align_absolute-center">
<canvas id="sPanel" style="border:1px solid black;">
</canvas>
</div>
<div>
<lightning-button variant="brand" label="Save" class="slds-m-left_x-small" onclick={handleSaveSignature}></lightning-button>
<lightning-button variant="brand" label="Clear" class="slds-m-left_x-small" onclick={handleClear}></lightning-button>
</div>
</article>
</template>
//signaturePanel.js
import { LightningElement,api } from 'lwc';
import saveSignature from '@salesforce/apex/SignatureUtils.saveSignature';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
let saveType = 'SFFile'; //'SFFile' 'Attachment'
let sCanvas , context; //canvas and it context 2d
let mDown = false;
let currPos = {x:0,y:0};
let prePos = {x:0,y:0};
export default class SignaturePanel extends LightningElement {
@api recordId;
constructor(){
super();
//mouse Events
this.template.addEventListener('mousedown',this.handleMousedown.bind(this));
this.template.addEventListener('mouseup',this.handleMouseup.bind(this));
this.template.addEventListener('mousemove',this.handleMousemove.bind(this));
this.template.addEventListener('mouseout',this.handleMouseend.bind(this));
//Touch Events
//this.template.addEventListener('touchstart',this.handleMousedown.bind(this))
//this.template.addEventListener('touchend',this.handleMouseup.bind(this));
//this.template.addEventListener('touchmove',this.handleMousemove.bind(this));
//this.template.addEventListener('touchcancel',this.handleMouseend.bind(this));
}
renderedCallback(){
sCanvas = this.template.querySelector('canvas');
context = sCanvas.getContext('2d');
}
handleMousedown(evt){
evt.preventDefault();
mDown = true;
this.getPos(evt);
}
handleSaveSignature(evt){
context.globalCompositeOperation = "destination-over";
context.fillStyle = "#FFF";
context.fillRect(0,0,sCanvas.width,sCanvas.height);
let imageURL = sCanvas.toDataURL('image/png');
let imageData = imageURL.replace(/^data:image\/(png|jpg);base64,/, "");
console.log(this.recordId);
saveSignature({relatedId:this.recordId,data:imageData,type:saveType})
.then(result =>{
this.dispatchEvent(new ShowToastEvent({
title:'Signature Captured',
message: 'The signature has been saved',
variant: 'success'
}));
this.handleClear();
})
.catch(error =>{
console.log('error : ', error.body.message);
})
}
handleMouseup(evt){
evt.preventDefault();
mDown = false;
}
handleMousemove(evt){
evt.preventDefault();
if(mDown){
this.getPos(evt);
this.draw();
}
}
getPos(evt){
let cRect = sCanvas.getBoundingClientRect();
prePos = currPos;
currPos = {x:(evt.clientX - cRect.left),y:(evt.clientY - cRect.top)};
}
handleMouseend(evt){
evt.preventDefault();
mDown = false;
}
draw(){
context.beginPath();
context.moveTo(prePos.x,prePos.y);
context.lineCap = 'round';//smooth line
context.lineWidth = 1.5;
context.strokeStyle = "#0000FF";//blue
context.lineTo(currPos.x,currPos.y);
context.closePath();
context.stroke();
}
handleClear(){
context.clearRect(0,0,sCanvas.width,sCanvas.height);
}
}
Results:

is there way to check if canvas is empty and nothing drawn on canvas ?
Simple solution, you can just create a flag isAnyStroke = false. Change it to true in the draw method. You can check if isAnyStroke = true in the Save method to decide whether you want to save.
Thank you i am able to use boolean trick. I have one more question ,signatures are not working in mobile due to touch events are not working for canvas