Capture Signature using Canvas in LWC

by Rijwan Mohmmed
capture-signature-using-canvas-in-lwc-salesforce-techdicer

Hello friends, today we will discuss Capture Signature using Canvas in LWC. With the power of HTML5 Canvas, we can enable users to create their digital signatures directly within your Lightning Web Components. It’s a modern, intuitive way to gather signatures in a paperless world.

In the digital age, handwritten signatures have evolved into their digital counterparts. Digital signatures offer convenience, efficiency, and compliance while reducing paper usage. Integrating signature capture into your Lightning Web Components can elevate your application’s user experience and streamline signature-dependent processes.

Also, check this: Link in the LWC Toast message

Key Highlights :

  1. User-Centric: Signature capture becomes user-friendly and natural, like pen and paper.
  2. Paperless Process: Transform traditional signing processes into digital workflows, saving time and resources.
  3. Enhanced Documentation: Improve document management by integrating captured signatures into records.
  4. Efficiency: Digital signatures eliminate the need for printing and scanning, saving time and resources.
  5. Compliance: Many industries require digital signatures for legal and compliance purposes. Integrating this capability directly into your LWC ensures adherence to regulations.
  6. User-Friendly: Signature capture in LWC offers a familiar and intuitive experience for users, ensuring smooth adoption.
  7. Enhanced Workflows: Incorporating signatures directly into your LWC allows for seamless and immediate integration with other Salesforce processes and records.

Code :

SignatureCtrl.cls :

public class SignatureCtrl {
    @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;
    }
}

SignatureLWC.HTML:

<template>
	<lightning-card variant="Narrow" title="Signature Section" icon-name="standard:record_signature_task">
        <template if:true={showLoading}>
            <lightning-spinner alternative-text="Loading" size="medium" class="spinnerClass"></lightning-spinner>
        </template>
		<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>
	</lightning-card>
</template>

SignatureLWC.JS:

import { LightningElement, api } from 'lwc';
import saveSignature from '@salesforce/apex/SignatureCtrl.saveSignature';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { RefreshEvent } from 'lightning/refresh';
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 SignatureLWC extends LightningElement {
    @api recordId;
    showLoading = false;

    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));
    }

    renderedCallback() {
        sCanvas = this.template.querySelector('canvas');
        context = sCanvas.getContext('2d');
    }

    handleMousedown(evt) {
        evt.preventDefault();
        mDown = true;
        this.getPos(evt);
    }

    handleSaveSignature(evt) {
        this.showLoading = true;
        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.showToast('Signature Captured!!', 'The signature has been saved!!', 'success', 'dismissable');
                this.handleClear();
                this.handleRefresh();
                this.showLoading = false;
            })
            .catch(error => {
                this.showToast('Error!!', error.body.message, 'error', 'dismissable');
                this.showLoading = false;
            })
    }

    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);
    }

    handleRefresh() {
        // refresh the standard related list
        this.dispatchEvent(new RefreshEvent());
    }

    showToast(title, message, variant, mode) {
        const evt = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode
        });
        this.dispatchEvent(evt);
    }
}

signatureLWC.js-meta.xml:

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
	<apiVersion>57.0</apiVersion>
	<isExposed>true</isExposed>
	<targets>
		<target>lightning__RecordPage</target>
	</targets>
</LightningComponentBundle>

Output :

capture-signature-using-canvas-in-lwc-salesforce-techdicer

Reference :

  1. Platform Show Toast Event
What’s your Reaction?
+1
5
+1
0
+1
0
+1
0
+1
1
+1
0

You may also like

2 comments

JC January 11, 2024 - 3:50 am

Hey Rijwan! Thanks you so much for this component, it helped me a lot on Salesforce for desktop, but for any reason I’m not getting it to work on mobile app. Have you encountered this situation or by any chance know how to solve it?

Reply
Rijwan Mohmmed January 11, 2024 - 4:43 am

Hi @JC, what issue you are getting in mobile

Reply

Leave a Comment