- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago
Hi Team,
We have a requirement to add ' Action ' drop down on ESC page for the " My requests" records when opened by the caller . The expected actions drop down options are " Reopen, Cancel '.
We have earlier done this sort of customization for incidents (as attached) and now the requirement is to implement the same for RITM.
While doing it for incidents it was easy as we found existing widgets like ' Incident Standard Ticket Actions' and its related existing SNC script includes. However, for RITM these are not present to use so what do you suggest here?
Thanks
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
did you check the link I shared above?
try to use that as reference point and using an AI tool like Co-pilot etc you should be ready with the code but you will have to make some changes on your own as well.
If my response helped please mark it correct and close the thread so that it benefits future readers.
Ankur
✨ Certified Technical Architect || ✨ 9x ServiceNow MVP || ✨ ServiceNow Community Leader
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Hi @ayushmaan_b ,
I'll use the Incident Standard Ticket Action widget and modify some of the basics. However, you will need to make the necessary modifications if the basics don't meet your process needs. I put in comments to help guide you to understand what is going on in the code.
HTML Markup
<div>
<!-- The following div element nests the components that this drop down is made of. ng-if is an attribute that decides whether or not to
render the actions or not. It looks for a true or false value. data.showActions is determined in the server script-->
<div class="dropdown" id="child-case-tabs" ng-if="data.showActions">
<button type="button" id="actions-button" class="btn btn-default dropdown-toggle action-btn" data-toggle="dropdown" style="width : 100%" aria-haspopup="true" ng-init="setFocusOnActionButtons()">
${Actions}
<span class="fa fa-caret-down"></span>
</button>
<!--Remove "li" elements not needed. Change the text where needed-->
<ul class="dropdown-menu pull-right" id="actionList">
<li> <!-- you can see the name modifications here to reflect the requirement-->
<a href="javascript:void(0)" ng-click="$event.stopPropagation();reopenRITM()">${Reopen}</a>
</li>
<li> <!-- you can see the name modifications here to reflect the requirement-->
<a href="javascript:void(0)" ng-click="$event.stopPropagation();cancelRITM()">${Cancel}</a>
</li>
</ul>
</div>
</div>
Server Script
(function() {
//Setting up the GlideRecord Query
var scReqItemGr = new GlideRecord('sc_req_item');
//In this widget the sys_id can be set by the options
var scReqItemSysId = options.sys_id;
if (!scReqItemSysId && $sp.getParameter('table') == 'sc_req_item'){
//Or the sys_id can be set from a URL parameter called sys_id
scReqItemSysId = $sp.getParameter('sys_id');
}
/* Actions - Start */
//If there is an input object, the client side sent something , in this case it's checking if it's to reopen the item
if (input && input.action == 'reopenRITM' && scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr, "write")) {
scReqItemGr.state = 2;
data.isRITMReopened = scReqItemGr.update();
gs.addInfoMessage(gs.getMessage("RITM reopened"));
}
//If there is an input object, the client side sent something , in this case it's checking if it's to cancel the item
if (input && input.action == 'cancelRITM' && scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr, "write")) {
scReqItemGr.state = 7; //OOB there is no cancel. so here is Closed Skipped
data.isRITMClosed = scReqItemGr.update();
gs.addInfoMessage(gs.getMessage("RITM Cancelled"));
}
/* Actions - End */
/* Load RITM data */
//decide to show the Actions dropdown
//If there is an Item and the user has permissions
if (scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr)) {
//If the item is in either the Closed, Closed Incomplete, or Closed Skipped state they can Reopen
data.canReopen = "3,4,7".includes(scReqItemGr.state);
//If the item is in either Pending, Open or Work In Progress, the user can Cancel
data.canCancel = "1,2,-5".includes(scReqItemGr.state);
//If either are true display the actions dropdown
data.showActions = data.canReopen || data.canClose;
}
//The naming conventiion here is very self explanatory
function hasPermissions(gr, operation) {
if (operation == "read" && gr.canRead())
return true;
if (operation == "write" && gr.canWrite())
return true;
//checks if the logged in user is the requested_for or opened_by
return (gr.getValue("requested_for") == gs.getUserID()) || (gr.getValue("opened_by") == gs.getUserID());
}
data.i18n = {};
})();
Client Controller
function RitmTicketActions($scope, $http, spUtil, $timeout, spModal, i18n, $window, $uibModal, spAriaUtil) {
/* widget controller */
var c = this;
c.doneLoading = false;
c.closedRITMMsg = "${Closed RITM}";
c.reopenedRITMMsg = "${Reopened RITM}";
var MOBILE_DEVICE_SCREEN_WIDTH = 767;
$scope.mobileDevice = c.data.isMobile || ($window.innerWidth < MOBILE_DEVICE_SCREEN_WIDTH);
//executes when the Cancel option is clicked and sends to the server side
$scope.cancelRITM = function() {
$scope.data.action = 'cancelRITM';
$scope.server.update(init).then(function(response){
if (response.isRITMClosed)
spAriaUtil.sendLiveMessage(c.closedRITMMsg);
});
var elm = document.getElementById('short-desc')
elm.focus();
};
//executes when the Reopen option is clicked and sends to the server side
$scope.reopenRITM = function() {
$scope.data.action = 'reopenRITM';
$scope.server.update(init).then(function(response){
if (response.isRITMReopened)
spAriaUtil.sendLiveMessage(c.reopenedRITMMsg);
});
$scope.$emit('focusOnActions', {"isFocusRequired": true});
};
function init() {}
$(document).on('click', 'div.modal-footer button.btn, ul#child-case-tabs .dropdown-menu', function(e) {
e.stopPropagation();
});
$(document).bind('dragover drop', function(event) {
event.preventDefault();
return false;
});
$scope.$on('sp_loading_indicator', function(e, value) {
if (!value && !c.doneLoading) {
c.doneLoading = true;
}
});
}
The CSS and Link fields in the "Incident Standard Ticket Actions" widget aren't modified so you can just copy those and paste into your new widget.
NOTE:
You need to realize that the Req item is different than an Incident in that there are actually two records (possibly more) to think about 1) The Request and 2) The Request Item(s). So to reiterate, you'll need to be familiar with your process and make the modifications that suit your process.
I hope this helps.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago
Hi @ayushmaan_b ,
Hopefully the customization that was done for the 'Incident Standard Ticket Actions' was done by using the Standard Ticket Configurations module. This module will allow you to do the same thing for RITMS.
Find the correlating 'Standard Ticket Configuration' record for the 'sc_cat_item' table.
Open that record and click the "Action Region" tab and you'll see where to add the widget
Out-of-box it is blank because there isn't a widget specifically for the sc_cat_item. So you can use any of the others as a template to create one.
Result:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Hi Chris,
Thanks for the quick reply. Surely will try this on my pdi and would revert back.
Regards,
Ayushmaan
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Hi Chris,
I have followed your steps and tried to copy an existing widget 'Standard Ticket Actions' to ' RITM Standard Ticket Actions'.
However, im lost with Body HTML template,Server script,Client controller code.
Since im not that familiar with coding im getting some errors. Can you help with this.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
4 weeks ago
Hi @ayushmaan_b ,
I'll use the Incident Standard Ticket Action widget and modify some of the basics. However, you will need to make the necessary modifications if the basics don't meet your process needs. I put in comments to help guide you to understand what is going on in the code.
HTML Markup
<div>
<!-- The following div element nests the components that this drop down is made of. ng-if is an attribute that decides whether or not to
render the actions or not. It looks for a true or false value. data.showActions is determined in the server script-->
<div class="dropdown" id="child-case-tabs" ng-if="data.showActions">
<button type="button" id="actions-button" class="btn btn-default dropdown-toggle action-btn" data-toggle="dropdown" style="width : 100%" aria-haspopup="true" ng-init="setFocusOnActionButtons()">
${Actions}
<span class="fa fa-caret-down"></span>
</button>
<!--Remove "li" elements not needed. Change the text where needed-->
<ul class="dropdown-menu pull-right" id="actionList">
<li> <!-- you can see the name modifications here to reflect the requirement-->
<a href="javascript:void(0)" ng-click="$event.stopPropagation();reopenRITM()">${Reopen}</a>
</li>
<li> <!-- you can see the name modifications here to reflect the requirement-->
<a href="javascript:void(0)" ng-click="$event.stopPropagation();cancelRITM()">${Cancel}</a>
</li>
</ul>
</div>
</div>
Server Script
(function() {
//Setting up the GlideRecord Query
var scReqItemGr = new GlideRecord('sc_req_item');
//In this widget the sys_id can be set by the options
var scReqItemSysId = options.sys_id;
if (!scReqItemSysId && $sp.getParameter('table') == 'sc_req_item'){
//Or the sys_id can be set from a URL parameter called sys_id
scReqItemSysId = $sp.getParameter('sys_id');
}
/* Actions - Start */
//If there is an input object, the client side sent something , in this case it's checking if it's to reopen the item
if (input && input.action == 'reopenRITM' && scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr, "write")) {
scReqItemGr.state = 2;
data.isRITMReopened = scReqItemGr.update();
gs.addInfoMessage(gs.getMessage("RITM reopened"));
}
//If there is an input object, the client side sent something , in this case it's checking if it's to cancel the item
if (input && input.action == 'cancelRITM' && scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr, "write")) {
scReqItemGr.state = 7; //OOB there is no cancel. so here is Closed Skipped
data.isRITMClosed = scReqItemGr.update();
gs.addInfoMessage(gs.getMessage("RITM Cancelled"));
}
/* Actions - End */
/* Load RITM data */
//decide to show the Actions dropdown
//If there is an Item and the user has permissions
if (scReqItemGr.get(scReqItemSysId) && hasPermissions(scReqItemGr)) {
//If the item is in either the Closed, Closed Incomplete, or Closed Skipped state they can Reopen
data.canReopen = "3,4,7".includes(scReqItemGr.state);
//If the item is in either Pending, Open or Work In Progress, the user can Cancel
data.canCancel = "1,2,-5".includes(scReqItemGr.state);
//If either are true display the actions dropdown
data.showActions = data.canReopen || data.canClose;
}
//The naming conventiion here is very self explanatory
function hasPermissions(gr, operation) {
if (operation == "read" && gr.canRead())
return true;
if (operation == "write" && gr.canWrite())
return true;
//checks if the logged in user is the requested_for or opened_by
return (gr.getValue("requested_for") == gs.getUserID()) || (gr.getValue("opened_by") == gs.getUserID());
}
data.i18n = {};
})();
Client Controller
function RitmTicketActions($scope, $http, spUtil, $timeout, spModal, i18n, $window, $uibModal, spAriaUtil) {
/* widget controller */
var c = this;
c.doneLoading = false;
c.closedRITMMsg = "${Closed RITM}";
c.reopenedRITMMsg = "${Reopened RITM}";
var MOBILE_DEVICE_SCREEN_WIDTH = 767;
$scope.mobileDevice = c.data.isMobile || ($window.innerWidth < MOBILE_DEVICE_SCREEN_WIDTH);
//executes when the Cancel option is clicked and sends to the server side
$scope.cancelRITM = function() {
$scope.data.action = 'cancelRITM';
$scope.server.update(init).then(function(response){
if (response.isRITMClosed)
spAriaUtil.sendLiveMessage(c.closedRITMMsg);
});
var elm = document.getElementById('short-desc')
elm.focus();
};
//executes when the Reopen option is clicked and sends to the server side
$scope.reopenRITM = function() {
$scope.data.action = 'reopenRITM';
$scope.server.update(init).then(function(response){
if (response.isRITMReopened)
spAriaUtil.sendLiveMessage(c.reopenedRITMMsg);
});
$scope.$emit('focusOnActions', {"isFocusRequired": true});
};
function init() {}
$(document).on('click', 'div.modal-footer button.btn, ul#child-case-tabs .dropdown-menu', function(e) {
e.stopPropagation();
});
$(document).bind('dragover drop', function(event) {
event.preventDefault();
return false;
});
$scope.$on('sp_loading_indicator', function(e, value) {
if (!value && !c.doneLoading) {
c.doneLoading = true;
}
});
}
The CSS and Link fields in the "Incident Standard Ticket Actions" widget aren't modified so you can just copy those and paste into your new widget.
NOTE:
You need to realize that the Req item is different than an Incident in that there are actually two records (possibly more) to think about 1) The Request and 2) The Request Item(s). So to reiterate, you'll need to be familiar with your process and make the modifications that suit your process.
I hope this helps.
