Reuse the approval e-signature for change cancellation process
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday - last edited yesterday
How to enforce e-signature validation for change cancellation actions in the same way as Approval/Reject ?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
Yes my Friend, you can enforce e-signature on Cancel the same way you see it on Approve/Reject, but ServiceNow won’t do it automatically for cancellation.
Approve/Reject works because the platform calls the e-signature check behind the scenes on the approval record.
For Change cancellation, you have to add that same check yourself.
What to do:
Find the Cancel UI Action or whatever button/flow is doing the cancel.
Before you change the state to Canceled, call the e-signature requirement the same mechanism Approve/Reject uses.
If the user fails e-signature, stop the action. If they pass, proceed with the cancel and let it write the audit record.
If your cancellation is done via Flow Designer instead of a button, you’d put the e-signature step at the beginning of the flow and only continue to set state = Canceled after it succeeds.
@naresh831 - Please mark as Accepted Solution and Thumbs Up if you found Helpful!!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
Thank you, @Matthew_13 !
Here is the out of box solution.
Approve UI Action
function onApproveClick(){
var sysID = '';
if (gel('sys_uniqueValue'))
sysID = gel('sys_uniqueValue').value;
var ga = new GlideAjax('ESignatureUtils');
ga.addParam('sysparm_name','formUpdateCheck');
ga.addParam('sysparm_target_state', 'approved');
ga.addParam('sysparm_sys_ids', sysID);
ga.getXMLAnswer(function(answer){
if (answer == "prompt_v2") {
displayGreyout();
promptCheck(true, 'approve', function(doPrompt, sysID) {
if (doPrompt != "true")
processFormUpdateApprovalCheck("no_prompt", sysID, "sysverb_update");
else {
g_form.previousState = g_form.getValue("state");
g_form.setValue("state", "approved");
processFormUpdateApprovalCheck("prompt_v2", sysID, "sysverb_update");
}
}, sysID);
} else {
g_form.previousState = g_form.getValue("state");
g_form.setValue("state", "approved");
var button = gel("sysverb_update");
gsftSubmit(button);
}
});
}
Authenticate Approver - On Submit Client script on Approval table
var w = window;
var esignOW;
var windowWidth;
var windowHeight;
var loginURL;
w['g_i_already_ran_and_was_changed'] = false;
w['displayGreyout'] = function() {
var div = $(cel("div"));
div.setAttribute("id", "screen_grey_out");
div.addClassName("screen_grey_out");
document.body.appendChild(div);
}
w['hideGreyOut'] = function() {
var div = $("screen_grey_out");
if (div)
document.body.removeChild(div);
}
w['notAuthorisedMessageDialog'] = function() {
var dialog = new GlideDialogWindow('message_window', false);
dialog.setTitle(getMessage("Unauthorised"));
dialog.setAriaLabel(getMessage("Unauthorised"));
dialog.setBody("<div style='padding: 10px'>" + getMessage("You are not authorised to approve or reject this record") + "</div>", false, false);
$("message_window").style.visibility = "visible";
}
w['reClick'] = function(id) {
hideGreyOut();
var button = gel(id);
g_i_already_ran_and_was_changed = true;
gsftSubmit(button);
}
w['resetState'] = function() {
if (g_form.previousState != null && g_form.previousState != "")
g_form.setValue("state", g_form.previousState);
}
w['processFormUpdateApprovalCheck'] = function(answer, sysId, action){
w['esingAction'] = action;
hideGreyOut();
if (answer == "not_authorised"){
resetState();
notAuthorisedMessageDialog();
} else if (answer == "no_prompt"){
reClick(action);
} else if (answer == "prompt_v2"){
var ga = new GlideAjax('ESignatureAuthUtils');
ga.addParam('sysparm_name', 'isWindowChosen');
ga.getXML(function(response){
if(response.responseXML.documentElement.getAttribute("answer") == "true"){
initiateESignature();
}else {
var gDialog = new GlideDialogWindow("saml2_esignature_login");
gDialog.setPreference('sysparm_approval_id', "");
gDialog.setPreference('onLoginSuccess', function(){ reClick(action) });
gDialog.setPreference('onLoginCancel', function(){ resetState(); });
gDialog.setPreference('focusTrap', true);
gDialog.setTitle(getMessage('Approver Authentication'));
gDialog.setAriaLabel(getMessage('Approver Authentication'));
gDialog.setFocus('userName');
gDialog.render();
}
});
} else {
var gDialog = new GlideDialogWindow("form_login_validate_dialog");
gDialog.setPreference('sysparm_approval_id', "");
gDialog.setPreference('onLoginSuccess', function(){ reClick(action); });
gDialog.setPreference('onLoginCancel', function(){ resetState(); });
gDialog.setPreference('sysparm_name', 'authorise');
gDialog.setPreference('sys_ids', sysId);
gDialog.setPreference('focusTrap', true);
gDialog.setTitle(getMessage('Approver Authentication'));
gDialog.setAriaLabel(getMessage('Approver Authentication'));
gDialog.setFocus('userName');
gDialog.render();
}
}
w['checkActionName'] = function(name) {
return name == "reject" || name == "approve"
}
w['checkComments'] = function() {
var comments = g_form.getControl("comments");
if (comments && g_form.getValue('comments') == '') {
g_form.hideFieldMsg('comments');
$("status.sysapproval_approver.comments").addClassName("mandatory");
g_form.showFieldMsg('comments', getMessage('Comments are required when rejecting an approval'), 'error');
g_form.setValue('state', 'rejected');
return false;
}
return true;
}
w['promptCheck'] = function(isApproving, buttonId, doPromptFunc, sysID) {
if (!sysID || sysID.length == 0) {
hideGreyOut();
return;
}
var ga = new GlideAjax('ESigPromptChecker');
ga.addParam('sysparm_name', 'doPrompt');
ga.addParam('sysparm_sys_ids', sysID);
ga.addParam('sysparm_is_approving', '' + isApproving);
ga.getXMLAnswer(function(answer) {
doPromptFunc(answer, sysID);
});
}
function onSubmit() {
if (!g_form.getActionName()){
var action = $("sysapproval_approver.do").sys_action;
if (action && checkActionName(action.value))
return true;
} else if (checkActionName(g_form.getActionName()))
return true;
if (g_i_already_ran_and_was_changed)
return true;
var newState = g_form.getValue("state");
if (g_form.previousState != newState && newState == "rejected" && !checkComments())
return false;
displayGreyout();
var sysId;
if (gel('sys_uniqueValue'))
sysId = gel('sys_uniqueValue').value;
if (!g_form.isNewRecord()){
var ga = new GlideAjax('ESignatureUtils');
ga.addParam('sysparm_name','formUpdateCheck');
ga.addParam('sysparm_sys_ids', sysId);
ga.addParam('sysparm_approver', g_form.getValue("approver"));
ga.addParam('sysparm_target_state', newState);
ga.getXMLAnswer(function(answer){
processFormUpdateApprovalCheck(answer, sysId, g_form.getActionName());
});
}else{
var table = g_form.getValue("source_table");
var ga = new GlideAjax('ESignatureUtils');
ga.addParam('sysparm_name','checkNewRecord');
ga.addParam('sysparm_table', table)
ga.addParam('sysparm_approver', g_form.getValue("approver"));
ga.addParam('sysparm_target_state', newState);
ga.addParam('sysparm_sys_ids', sysId);
ga.getXMLAnswer(function(answer){
if (answer == "proceed") {
reClick('sysverb_insert');
} else if (answer == "not_authorised") {
resetState();
hideGreyOut();
notAuthorisedMessageDialog();
} else {
processFormUpdateApprovalCheck("prompt", "", g_form.getActionName());
}
});
}
return false;
}
function initiateESignature() {
var ga = new GlideAjax('ESignatureAuthUtils');
ga.addParam('sysparm_name', 'fetchAuthDetails');
ga.getXML(function(answer) {
process(answer);
});
function process(answer) {
var result = answer.responseXML.getElementsByTagName("result")[0];
var shouldLogoutFirst = (result.getAttribute('logoutFirst') == "true");
w['windowHeight'] = result.getAttribute('popup_window_height');
w['windowWidth'] = result.getAttribute('popup_window_width');
w['loginURL'] = result.getAttribute('loginURL');
if (shouldLogoutFirst) {
openEsingatureWindow(result.getAttribute('logoutURL'));
} else {
authenticate();
}
}
}
function openEsingatureWindow(url){
w['esignOW'] = window.open(url, 'esignatureAuthentication', 'height='+w['windowHeight']+',width='+w['windowWidth']+',top=100,left=100,toolbar=0,location=0,menubar=0');
}
w['authenticate'] = function(){
if(w['loginURL']){
if(w['esignOW'])//when we signed out previously, a window is already opened. reuse it.
w['esignOW'].location.href = w['loginURL'];
else
openEsingatureWindow(w['loginURL']);
}
};
w['evaluateRedirect'] = function(msg) {
w['esignOW'].close();
if (msg == "oidc login complete" || msg == "saml2 login complete") {
reClick(w['esingAction'] || g_form.getActionName());
}else {
resetState();
}
};
w['sessionTimeout'] = function(){
if (window.confirm(new GwtMessage().getMessage("ajax_session_timeout_goto_login_confirm"))) {
// refresh the screen when user clicks ok
getTopWindow().location.href = "/navpage.do";
}else{
//Do it anyway
getTopWindow().location.href = "/navpage.do";
}
};
I've created same OnSubmit client script on change table and created below Cancel UI Action.
For OOB logic, when we click on Approve/Reject, it popups a SSO window, for this Cancel UI Action, SSO window is not opening. Not sure What's am I missing here.
//Client-side 'onclick' function
function runClientCode() {
if (g_form.getValue('work_notes') == '') {
g_form.setMandatory('work_notes', true);
g_form.addErrorMessage('Work Notes are mandatory when canceling a Change.');
return false; //Abort submission
}
var w = window;
var esignOW;
var windowWidth;
var windowHeight;
var loginURL;
var ga = new GlideAjax('ESignatureAuthUtils');
ga.addParam('sysparm_name', 'fetchAuthDetails');
ga.getXML(function(answer) {
process(answer);
});
function process(answer) {
var result = answer.responseXML.getElementsByTagName("result")[0];
var result1 = '{"logoutFirst": "false","popup_window_width": "500","popup_window_height": "300","loginURL": "/samlv2_esig_processor.do","logoutURL": "/saml2_esignature_logout.do?sysparm_nostack=true"}';
result = JSON.parse(result1);
g_form.addErrorMessage(JSON.stringify(result));
var shouldLogoutFirst = (result.logoutFirst == "true");
w['windowHeight'] = result.popup_window_height;
w['windowWidth'] = result.popup_window_width;
w['loginURL'] = result.loginURL;
if (shouldLogoutFirst) {
openEsingatureWindow(result.logoutURL);
} else {
authenticate();
}
}
function openEsingatureWindow(url) {
w['esignOW'] = window.open(url, 'esignatureAuthentication', 'height=' + w['windowHeight'] + ',width=' + w['windowWidth'] + ',top=100,left=100,toolbar=0,location=0,menubar=0');
}
w['authenticate'] = function() {
if (w['loginURL']) {
if (w['esignOW']) //when we signed out previously, a window is already opened. reuse it.
w['esignOW'].location.href = w['loginURL'];
else
openEsingatureWindow(w['loginURL']);
}
};
//Call the UI Action and skip the 'onclick' function
gsftSubmit(null, g_form.getFormElement(), 'insmed_cancel_change'); //MUST call the 'Action name' set in this UI Action
}
//Ensure call to server-side function with no browser errors
if (typeof window == 'undefined')
moveToCancel();
function moveToCancel() {
if (new ChangeRequestStateHandler(current).moveTo("canceled")) {
current.u_substate = '';
current.update();
}
action.setRedirectURL(current);
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
The cancel button is not opening the SSO/eSignature window basically because your UI Action is bypassing the out-of-box decision process.
Approve/Reject works only after ServiceNow first asks:
“Is this state change allowed and does it require eSignature authentication?”
That check happens in the script include ESignatureUtils.formUpdateCheck.
Your current Cancel action jumps straight to the processor URL using hardcoded JSON, so the platform never gets the chance to launch the correct login dialog.
Think of it like this:
OOB flow = Check → Decide → Open SSO window if required → Submit.
Your flow = Validate notes → Force JSO N → Submit (no Check/Decide).
To match the standard behavior, the Cancel onclick must:
call ESignSignatureUtils.formUpdateCheck with target state canceled,
wait for the answer,
and then reuse the same OOB functions to open the authentication dialog or proceed without it.
If that check always answers no_prompt, it basically means your instance is configured not to require eSignature for Cancel—so the issue would be policy instead of code.
@naresh831 - Please mark as Accepted Solution and Thumbs Up if you found Helpful!!
