The CreatorCon Call for Content is officially open! Get started here.

Custom widget Signature Pad on approval form.

Meenal Gharat
Tera Guru

Hello Experts,

 

My Requirement is to create a Signature Pad Widget on Service portal approval form. There are 3 levels of approval on the form. That Signature Pad widget should be displayed to the current approval and once they sign that signature should get stored on RITM form when all three level of approvals are complete generate a PDF with all RITM details and all three signature on the form in the end.

 

Is this possible? Tried creating this widget, but the save signature is disbled that should store the signature image on RITM

MeenalGharat_0-1760075128597.png

server side code

 


(function() {
data.user_id = gs.getUserID();
data.user_name = gs.getUserName();

// Set table to sys_approval_approver
data.table_name = 'sysapproval_approver';
data.sys_id = $sp.getParameter('approval_id');

var approvalGR = new GlideRecord('sysapproval_approver');
if (approvalGR.get(data.sys_id)) {
data.isCurrentApprover = approvalGR.approver == gs.getUserID();
data.ritm_id = approvalGR.document_id;
}

if (input && input.action == 'acceptSignatureImage') {
var decoded = GlideStringUtil.base64Decode(input.image.replace(/^data:image\/png;base64,/, ""));
var ritmGR = new GlideRecord('sc_req_item');
if (ritmGR.get(input.ritm_id)) {
var attachment = new GlideSysAttachment();
attachment.write(ritmGR, gs.getUserDisplayName() + "_signature.png", "image/png", decoded);
}

var approvalGR = new GlideRecord('sysapproval_approver');
if (approvalGR.get(input.approval_id)) {
approvalGR.state = 'approved';
approvalGR.update();
}

data.signatureImage = input.image;
data.signedBy = gs.getUserDisplayName();
data.signedOn = new GlideDateTime().getDisplayValue();
}
})();

 

Client code-

 

<div id="sigpad_{{data.padId}}">
<div class="signature-pad">
<div class="message">
{{::data.message}}
</div>
<grc-tabset id="save-and-sign-tabset" aria-label="${Save and sign}">
<grc-tabpanel id="type-area-tabpanel" heading="${Type}" select-callback="c.openTab('type');">
<div class="tabContent">
<label for="name">${Full name}</label>
<input ng-keyup="isSigned()"
type="text"
aria-label="Print your full name here"
name="signed_name"
class="signed_name xs-col-12"
maxlength="25"
ng-model="c.signed_name"
placeholder="${Type your full name here}"
ng-change="c.clearCanvas()" />
<canvas class="typedCanvas"></canvas>
</div>
</grc-tabpanel>
<grc-tabpanel id="draw-area-tabpanel" heading="${Draw}" select-callback="c.openTab('draw');">
<div class="tabContent">
<div>
<button
class="clearButton padding-bottom-5"
ng-click="c.clearCanvas();">${Clear signature}</button>
</div>
<div class="sigPad">
<span class="animated">
<div class="sig sigWrapper" style="height: 100%">
<canvas class="drawPad" height="150"></canvas>
<input type="hidden" name="output" class="drawPad_image output" />
</div>
</span>
</div>
</div>
</grc-tabpanel>
</grc-tabset>

<div class="text-muted sign_disclaimer">
${This constitutes your electronic signature and has the same legal impact as signing a printed version of this document.}
</div>
<div class="sigPadButtonBar">
<button class="agree-button btn btn-primary btn-view"
ng-click="c.saveSignature(data.sys_id, data.ritm_id);"
ng-disabled="!isSigned()">Sign to Complete</button>

</div>
</div>

 

Can someone help to correct this

 

Best Regards,

Meenal Gharat

 

 

 

 

1 REPLY 1

Ravi Gaurav
Giga Sage
Giga Sage

Hi @Meenal Gharat 

 

 

The Main Issue: Disabled Save Button and Image Handling

In your current widget:

 

 
var decoded = GlideStringUtil.base64Decode(input.image.replace(/^data&colon;image\/png;base64,/, ""));

The problem is the HTML entity (&colon;).
When the base64 data URL is passed from client to server, it should start with data&colon;image/png;base64, — not data&colon;....

Fix:
Change it to:

var decoded = GlideStringUtil.base64Decode(input.image.replace(/^data&colon;image\/png;base64,/, ""));

----------

Corrected Version :

 

(function() {
data.user_id = gs.getUserID();
data.user_name = gs.getUserName();

data.table_name = 'sysapproval_approver';
data.sys_id = $sp.getParameter('approval_id');

var approvalGR = new GlideRecord('sysapproval_approver');
if (approvalGR.get(data.sys_id)) {
data.isCurrentApprover = (approvalGR.approver == gs.getUserID());
data.ritm_id = approvalGR.document_id;
}

// Handle signature image
if (input && input.action == 'acceptSignatureImage') {
var imageData = input.image.replace(/^data&colon;image\/png;base64,/, "");
var decoded = GlideStringUtil.base64Decode(imageData);

// Save signature as attachment on RITM
var ritmGR = new GlideRecord('sc_req_item');
if (ritmGR.get(input.ritm_id)) {
var attachment = new GlideSysAttachment();
var fileName = gs.getUserDisplayName() + "_signature.png";
attachment.write(ritmGR, fileName, "image/png", decoded);
}

// Approve the record
var appr = new GlideRecord('sysapproval_approver');
if (appr.get(input.approval_id)) {
appr.state = 'approved';
appr.update();
}

data.signatureImage = input.image;
data.signedBy = gs.getUserDisplayName();
data.signedOn = new GlideDateTime().getDisplayValue();
}
})();

 

-------------------

c.saveSignature = function(approval_id, ritm_id) {
var canvas = document.querySelector('.drawPad');
var image = canvas.toDataURL('image/png');

c.server.update({
action: 'acceptSignatureImage',
image: image,
approval_id: approval_id,
ritm_id: ritm_id
}).then(function(response) {
spModal.open({
title: "Signature Saved",
message: "Your signature has been captured successfully."
});
});
};

 

 

--------------------------------------------------------------------------------------------------------------------------


If you found my response helpful, I would greatly appreciate it if you could mark it as "Accepted Solution" and "Helpful."
Your support not only benefits the community but also encourages me to continue assisting. Thank you so much!

Thanks and Regards
Ravi Gaurav | ServiceNow MVP 2025,2024 | ServiceNow Practice Lead | Solution Architect
CGI
M.Tech in Data Science & AI

 YouTube: https://www.youtube.com/@learnservicenowwithravi
 LinkedIn: https://www.linkedin.com/in/ravi-gaurav-a67542aa/