Service Portal widget with Modal window issue
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-08-2018 06:12 AM
Hi Community,
I am hoping you guys can help me figure out what's going on with my widget. I am using Modal Windows in Service Portal (https://serviceportal.io/modal-windows-service-portal/) and Portal Diaries (https://community.servicenow.com/community?id=community_blog&sys_id=228da669dbd0dbc01dcaf3231f9619f3) as references.
So I added this approval widget to the service portal and I want to make comments mandatory when the user rejects the approval and I want to pass those comments to the comments field of the approval record. The modal window is working fine, it triggers whenever the user rejects the approval and it requires comments but then comments are not being passed down to the comments field like I wanted to. Below you will see the code and some screen shots.
Widget HTML for modal window:
<script type="text/ng-template" id="rejectionTemplate">
<h4 class="panel-title">Add comments</h4>
<div class="panel-body wrapper-xl">
Comments are required for rejections
<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Add Comments" class="form-control" rows="2">
</textarea>
</div>
<div class="panel-footer text-right">
<button class="btn btn-primary" ng-click="c.closeModal()">${Submit}</button>
</div>
</script>
Widget Server script:
I am using this to pass the comment info 😕
if (input.comment)
var gc = new GlideRecord("sysapproval_approver");
if(gc.get(input.target)){
gc.comments = input.comment;
gc.update();
}
}
if (input && input.op) {
var app = new GlideRecord("sysapproval_approver");
if (app.get(input.target)) {
app.state = input.op;
app.update();
}
}
if (input.comment) {
var gc= new GlideRecord("sysapproval_approver");
if (gc.get(input.target)) {
gc.comments= input.comment;
gc.update();
}
}
var currTable = input.currTable || '';
var gr = new GlideRecord('sysapproval_approver');
var qc1 = gr.addQuery("state", "requested");
if (input)
qc1.addOrCondition("sys_id", "IN", input.ids);
gr.addQuery("approver", gs.getUserID());
gr.addQuery("sysapproval.active","true");
gr.orderByDesc("sys_created_on");
gr.query();
var tableCategories = {};
var approvals = [];
var ids = [];
while (gr.next()) {
var task = getRecordBeingApproved(gr);
if (!task.isValidRecord())
continue;
var task_table = task.getLabel();
if (currTable != '' && task_table.toLowerCase() != currTable)
continue;
if(gr.getValue("state") == 'requested')
ids.push(gr.getUniqueValue());
var t = {};
t.number = task.getDisplayValue();
t.short_description = task.short_description.toString();
if (task.isValidField("opened_by") && !task.opened_by.nil())
t.opened_by = task.opened_by.getDisplayValue();
// requestor >> opener
if (task.isValidField("requested_by") && !task.requested_by.nil())
t.opened_by = task.requested_by.getDisplayValue();
t.start_date = task.start_date.toString();
t.end_date = task.end_date.toString();
t.table = task.getLabel();
if (task.getValue("price") > 0)
t.price = task.getDisplayValue("price");
if (task.getValue("recurring_price") > 0)
t.recurring_price = task.getDisplayValue("recurring_price");
t.recurring_frequency = task.getDisplayValue("recurring_frequency");
var j = {};
j.sys_id = gr.getUniqueValue();
j.table = gr.getRecordClassName();
j.task = t;
var wfact = gr.wf_activity.getRefRecord();
j.wf_act_name = wfact.name.toString();
if (task)
j.variables = $sp.getRecordVariablesArray(task);
j.state = gr.getValue("state");
j.stateLabel = gr.state.getDisplayValue();
approvals.push(j);
}
data.ids = ids;
data.approvals = approvals;
data.tableCategories = tableCategories;
function getRecordBeingApproved(gr) {
if (!gr.sysapproval.nil())
return gr.sysapproval.getRefRecord();
return gr.document_id.getRefRecord();
}
function findObject(obj, key, val) {
obj.map(function (item) {
if (item.table == val) {
return true;
} else {
return false;
}
});
}
Widget Client script:
I am using this to open the modal, gather data and close the modal:
var x = this;
$scope.reject = function(id) {
x.id = id
x.modalInstance = $uibModal.open({
templateUrl: 'rejectionTemplate',
scope: $scope
});
}
x.closeModal = function() {
if(c.data.comment != ''){
$window.alert(x.id);
//return false;
$scope.data.op = "rejected";
$scope.data.target = x.id;
//$scope.data.comments = c.data.comment;
get();
x.modalInstance.close();
}
}
function ($scope, spUtil, snRecordWatcher, $timeout, $uibModal, $window) {
var c = this;
$scope.buttonColClass = "col-sm-2";
$scope.contentColClass = "col-sm-10";
c.data.currTable = "requested item";
// getTopCategories
$scope.groupedData = _.countBy($scope.data.approvals, function(d){return d.task.table});
if(Object.keys($scope.groupedData).length > 0){
c.data.currTable = Object.keys($scope.groupedData)[0].toLowerCase();
$scope.totalItems = c.data.currTableCount = $scope.groupedData[Object.keys($scope.groupedData)[0]];
}
if ($scope.options.portal) {
$scope.contentColClass = "col-xs-12";
}
snRecordWatcher.initList("sysapproval_approver", "state=requested^approver=" + window.NOW.user_id);
function get() {
spUtil.update($scope).then(function(){
$('#loaddiv1').hide();
});
}
$scope.$on('record.updated', function(name, data) {
get();
})
$scope.approve = function(id) {
$scope.data.op = "approved";
$scope.data.target = id;
get();
}
/*$scope.reject = function(id) {
$scope.data.op = "rejected";
$scope.data.target = id;
get();
}*/
var x = this;
$scope.reject = function(id) {
x.id = id
x.modalInstance = $uibModal.open({
templateUrl: 'rejectionTemplate',
scope: $scope
});
}
x.closeModal = function() {
if(c.data.comment != ''){
$window.alert(x.id);
//return false;
$scope.data.op = "rejected";
$scope.data.target = x.id;
//$scope.data.comments = c.data.comment;
get();
x.modalInstance.close();
}
}
$scope.getApprovalsFor = function(tblname,reccount) {
$('#loaddiv1').show();
c.data.currTable = tblname.toLowerCase() || $scope.defaultTable ;
$scope.totalItems = c.data.currTableCount = reccount;
$scope.currentPage = 1; //reset to first page
get();
}
$(document).ready(function() {
$('#loaddiv1').hide();
$('input[type="text"]').keyup(function(e){
if(e.keyCode == 27) {
$scope.searchText = null;
$(this).val('');
}
});
});
$scope.viewby = 10;
//$scope.viewby = 3;
$scope.currentPage = 1;
$scope.itemsPerPage = $scope.viewby;
$scope.maxSize = 5; //Number of pager buttons to show
$scope.setPage = function (pageNo) {
$scope.currentPage = pageNo;
};
}
this is what the user sees:
if they click on Reject, the window appears, they input their comments and hit submit.
Any help/suggestions are greatly appreciated!
Thank you,
Yeny
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-08-2018 11:54 AM
Hey Yeny,
Did you try writing c.server.update(); in the client code?
I have the implemented the same for approval page. In my case when someone rejects without a comment it alerts and asks to put a comment in the below text area.
After that people rejects and comments are stored in the gr.comments.
HTML:
<div class="panel panel-{{::c.options.color}} b">
<div class="panel-heading">
<h4 class="panel-title" ng-if="c.data.state == 'requested'">${This {{c.data.label}} requires your approval}</h4>
<h4 class="panel-title" ng-if="c.data.state == 'approved'">${Approved} <!--<sn-time-ago timestamp="::c.data.sys_updated_on" />--></h4>
<h4 class="panel-title" ng-if="c.data.state == 'rejected'">${Rejected} <!--<sn-time-ago timestamp="::c.data.sys_updated_on" />--></h4>
</div>
<div class="panel-body">
<form ng-submit="$event.preventDefault()" class="form-horizontal">
<div ng-if="c.data.fields.length > 0">
<div ng-repeat="field in c.data.fields" class="m-b-xs" ng-if="field.value">
<table style="width: 100%;">
<tr><td style="width: 30%;"><label class="m-n whb label label-default">{{field.label}}</label></td>
<td><span ng-switch="field.type">
<div ng-switch-when="glide_date_time" title="{{field.display_value}}"><sn-time-ago timestamp="::field.value" /></div>
<div ng-switch-default >{{field.display_value}}</div>
</span></td></tr>
</table>
</div>
<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>
</div>
<div ng-if="c.data.state == 'requested' && data.canApprove==true" class="question">
<button type="button" name="approve" class="btn btn-success btn-question" ng-click="c.action('approved')">${Approve}</button>
<div class="spacer"></div>
<button type="button" name="reject" class="btn btn-default btn-question" ng-click="c.action('rejected')">${Reject}</button>
</div>
</form>
</div>
</div>
Server :
var gr = $sp.getRecord();
data.canApprove = isApprovalMine(gr);
if (input && input.op && gr) {
gr.state = input.op;
gr.update();
}
if (input.comment){
gr.comments = input.comment;
gr.update();
}
var fields = $sp.getFields(gr, 'state,sys_created_on');
if (gr) {
if (gr.sys_mod_count > 0)
fields.push($sp.getField(gr, 'sys_updated_on'));
data.fields = fields;
data.state = gr.state.toString();
data.sys_updated_on = gr.sys_updated_on.toString();
data.sys_id = gr.getUniqueValue();
data.table = gr.getTableName();
data.label = getRecordBeingApproved(gr).getLabel();
}
function getRecordBeingApproved(gr) {
if (!gr.sysapproval.nil())
return gr.sysapproval.getRefRecord();
return gr.document_id.getRefRecord();
}
Client:
function () {
var c = this;
//OOB Code
// c.action = function(state) {
// c.data.op = state;
// c.data.state = state;
// c.server.update();
// }
c.action = function(state) {
if( (c.data.comment == undefined || c.data.comment == '' )&& state == 'rejected'){
alert('Comments are required when rejecting an approval');
return false;
}
c.data.op = state;
c.data.state = state;
c.server.update();
}
}
So In short:
Important lines are:
1.
<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>
2.
<button type="button" name="reject" class="btn btn-default btn-question" ng-click="c.action('rejected')">${Reject}</button>
3.
c.action = function(state) {
if( (c.data.comment == undefined || c.data.comment == '' )&& state == 'rejected'){
alert('Comments are required when rejecting an approval');
return false;
}
c.data.op = state;
c.data.state = state;
c.server.update();
}
4.
if (input && input.op && gr) {
gr.state = input.op;
gr.update();
}
if (input.comment){
gr.comments = input.comment;
gr.update();
}
Let me know if that helps you!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-08-2018 12:04 PM
Hi Yeny,
Did you try writing c.server.update(); in the client side.
I have implemented kind of same thing in the approval page. I have a textarea before approve/reject button. If someone rejects without a comment it alerts saying "Comments are mandatory while rejecting".
And after rejection it paste the comments in the gr.comment.
I can share my code if that helps you.
HTML:
<div class="panel panel-{{::c.options.color}} b">
<div class="panel-heading">
<h4 class="panel-title" ng-if="c.data.state == 'requested'">${This {{c.data.label}} requires your approval}</h4>
<h4 class="panel-title" ng-if="c.data.state == 'approved'">${Approved} <!--<sn-time-ago timestamp="::c.data.sys_updated_on" />--></h4>
<h4 class="panel-title" ng-if="c.data.state == 'rejected'">${Rejected} <!--<sn-time-ago timestamp="::c.data.sys_updated_on" />--></h4>
</div>
<div class="panel-body">
<form ng-submit="$event.preventDefault()" class="form-horizontal">
<div ng-if="c.data.fields.length > 0">
<div ng-repeat="field in c.data.fields" class="m-b-xs" ng-if="field.value">
<table style="width: 100%;">
<tr><td style="width: 30%;"><label class="m-n whb label label-default">{{field.label}}</label></td>
<td><span ng-switch="field.type">
<div ng-switch-when="glide_date_time" title="{{field.display_value}}"><sn-time-ago timestamp="::field.value" /></div>
<div ng-switch-default >{{field.display_value}}</div>
</span></td></tr>
</table>
</div>
<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>
</div>
<div ng-if="c.data.state == 'requested' && data.canApprove==true" class="question">
<button type="button" name="approve" class="btn btn-success btn-question" ng-click="c.action('approved')">${Approve}</button>
<div class="spacer"></div>
<button type="button" name="reject" class="btn btn-default btn-question" ng-click="c.action('rejected')">${Reject}</button>
</div>
</form>
</div>
</div>
Client:
function () {
var c = this;
//OOB Code
// c.action = function(state) {
// c.data.op = state;
// c.data.state = state;
// c.server.update();
// }
c.action = function(state) {
if( (c.data.comment == undefined || c.data.comment == '' )&& state == 'rejected'){
alert('Comments are required when rejecting an approval');
return false;
}
c.data.op = state;
c.data.state = state;
c.server.update();
}
}
Server:
var gr = $sp.getRecord();
data.canApprove = isApprovalMine(gr);
if (input && input.op && gr) {
gr.state = input.op;
gr.update();
}
if (input.comment){
gr.comments = input.comment;
gr.update();
}
var fields = $sp.getFields(gr, 'state,sys_created_on');
if (gr) {
if (gr.sys_mod_count > 0)
fields.push($sp.getField(gr, 'sys_updated_on'));
data.fields = fields;
data.state = gr.state.toString();
data.sys_updated_on = gr.sys_updated_on.toString();
data.sys_id = gr.getUniqueValue();
data.table = gr.getTableName();
data.label = getRecordBeingApproved(gr).getLabel();
}
function getRecordBeingApproved(gr) {
if (!gr.sysapproval.nil())
return gr.sysapproval.getRefRecord();
return gr.document_id.getRefRecord();
}
So in short,
Important pieces of codes are:
<textarea ng-model="c.data.comment" style="color: grey; width: 100%; margin-top: .5em;" placeholder="Rejection Comments" class="form-control" rows="5"></textarea>
<div class="spacer"></div>
<button type="button" name="reject" class="btn btn-default btn-question" ng-click="c.action('rejected')">${Reject}</button>
------------------------------
c.action = function(state) {
if( (c.data.comment == undefined || c.data.comment == '' )&& state == 'rejected'){
alert('Comments are required when rejecting an approval');
return false;
}
c.data.op = state;
c.data.state = state;
c.server.update();
}
------------------------------------
if (input && input.op && gr) {
gr.state = input.op;
gr.update();
}
if (input.comment){
gr.comments = input.comment;
gr.update();
}
----------------------------------
Let me know if that helps!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎12-28-2019 08:42 PM
Hi yeny,
For updating the comments from the spModal prompt to the approval record, you might have to modify the Server script and Client Controller to get that working.
Server script : (add the below lines at the top)
if (input.op == 'approved' || input.op == 'rejected') {
var app = new GlideRecord("sysapproval_approver");
if (app.get(input.target) && app.state.canWrite()) {
app.state = input.op;
//Adding rejection comment
if(input.comments){
app.comments = input.comments;
}
app.update();
}
}
Client Controller : add spModal in the function(). In the OOB reject function, $scope.reject, add the below code
if($scope.data.esignature.e_sig_required && esigRequired) {
spUIActionsExecuter.executeFormAction(ESIGNATURE.REJECT_SYS, "sysapproval_approver" , id, [] , "", requestParams).then(function(response) {
});
} else {
//Prompt for comments
spModal.prompt("Please enter reason for rejection").then(function(rejectionReason) {
$scope.data.op = "rejected";
$scope.data.target = id;
$scope.data.comments = rejectionReason;
get();
});
PS: It's a late reply to the post. In case anybody else find it useful while browsing for a solution.
Thanks!