H
appy New Year! Hopefully everybody had a great holiday. Mine was spent mostly helping my kids to break in some new toys :). I did get some time to play with some new Service-now ideas as well. I’ll be sharing some very cool stuff here on SNCGuru over the next couple of weeks.
I’ve seen a couple requests recently for a way to allow users to select items from a slushbucket popup dialog. The most common reason for this is to help manage manual group approvals on a task record. If you’ve worked with group approvals at all, you’ve probably noticed that they work a little bit differently than regular approval records do. Group approval records are really just task records so you can’t just hit an ‘Edit’ button and add groups to be approvers on a task. Instead, you have to repeatedly click the ‘New’ button and create a new task record for each approval group. Normally this isn’t an issue because group approvals are typically managed in workflow but if you’re manually adding a lot of these, the process can be fairly tedious.
This article shows how you can provide a better UI by creating a slushbucket popup dialog that allows users to select one or many groups to add as approvers on a task. Even though the solution is designed for a specific use case, I’ve tried to make the example shown here generic enough so that you can easily modify it for other uses as well.
The first piece to this solution is to create a trigger for the dialog. For this solution, a UI action probably makes the most sense. The end user will click a UI action to display the popup dialog and make the necessary selections there.
Name: Add Approval Groups
Client: true
Form link: true
OnClick: addApprovalGroups()
Condition: gs.hasRole(‘itil’)
Script:
//Open a dialog window to select Approval Groups
var dialog = new GlideDialogWindow('add_approval_groups');
dialog.setTitle('Add Approval Groups');
dialog.setPreference('sysparm_groupQuery', 'active=true');
dialog.render();
//Make sure to not submit the form when button gets clicked
return false;
}
The UI action opens the dialog with a call to a specific UI page. The UI page is what contains most of the logic for the slushbucket. It includes the actual HTML (which pulls in the slushbucket and UI buttons from UI macros) as well as the client script that loads the groups and makes a call to insert group approval records.
Name: add_approval_groups
HTML:
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<TABLE BORDER="0">
<TR>
<TD>
Please select the groups you wish to add as approvers.
</TD>
</TR>
<TR>
<TD>
<!-- Include the 'ui_slushbucket' UI macro -->
<g:ui_slushbucket/>
</TD>
</TR>
<TR>
<TD align="right">
<!-- Include the 'dialog_buttons_ok_cancel' UI macro -->
<g:dialog_buttons_ok_cancel ok="return continueOK()" cancel="return continueCancel()" ok_type="button" cancel_type="button"/>
</TD>
</TR>
</TABLE>
</j:jelly>
Client script:
function continueOK(){
//Get the selected values from the right slushbucket
var values = slush.getValues(slush.getRightSelect());
//Get the sys_id of the current record
var taskID = g_form.getUniqueValue();
//Make sure we have at least one selection
if(values == ''){
alert("At least one group must be selected");
return;
}
//Add the group approval records
var ajax = new GlideAjax('GroupSelectAjax');
ajax.addParam('sysparm_name', 'groupsAdd');
ajax.addParam('sysparm_taskID', taskID);
ajax.addParam('sysparm_values', values);
ajax.getXML(addGroupResponse);
}
//Called when we get a response from the 'continueOK' function
function addGroupResponse(){
GlideDialogWindow.get().destroy();
GlideList2.get('').setFilterAndRefresh('');
return false;
}
//Called when the 'Cancel' button gets clicked
function continueCancel(){
//Close the dialog window
GlideDialogWindow.get().destroy();
return false;
}
//Called when the form loads
addLoadEvent(function(){
//Load the groups when the form loads
slush.clear();
var ajax = new GlideAjax('GroupSelectAjax');
ajax.addParam('sysparm_name', 'getGroups');
ajax.getXML(loadResponse);
return false;
});
//Called when we get a response from the 'addLoadEvent' function
function loadResponse(response){
//Process the return XML document and add groups to the left select
var xml = response.responseXML;
var e = xml.documentElement;
var items = xml.getElementsByTagName("item");
if(items.length == 0)
return;
//Loop through item elements and add each item to left slushbucket
for (var i = 0; i < items.length; i++) {
var item = items[i];
slush.addLeftChoice(item.getAttribute('value'), item.getAttribute('text'));
}
}
Many times, you can stop with the UI page. In this case, it makes sense for us to do some of our heavy-lifting for populating the groups in the slushbucket and creating group approval records at the server. The client scripts in the UI page make GlideAjax calls to the functions in a script include. The script include performs the query and returns an XML response back to the client so that it can continue.
Name: GroupSelectAjax
Client callable: true
Script:
GroupSelectAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
//Get and return a list of groups (name and sys_id)
getGroups: function() {
var gr = new GlideRecord("sys_user_group");
gr.orderBy('name');
gr.addQuery('active', true);
gr.query();
//Add the available groups to select from
while (gr.next()) {
var item = this.newItem();
item.setAttribute('value', gr.getValue('sys_id'));
item.setAttribute('text', gr.getValue('name'));
}
},
//Take a taskID and group sys_id values and add 'sysapproval_group' records
groupsAdd: function() {
var taskID = this.getParameter('sysparm_taskID');
var values = this.getParameter('sysparm_values').split(',');
//Iterate through the group sys_id values
for(x in values){
var rec = new GlideRecord('sysapproval_group');
rec.initialize();
rec.parent = taskID;
rec.assignment_group = values[x];
rec.approval = 'requested';
rec.insert();
}
}
});
Hello Mark and hope all is well over there in sunny Cali 🙂
Not sure if our requirements deem a new slushbucket pop up but I’m hoping you can provide some steer.
On the approver related list in the incident module the ability exists to click on the ‘Edit’ global UI action and add manual approvers. This is pretty much OOB functionality. However, if the need arises to add the same approver again later in the process, the slushbucket does not allow it since the person already exists on the right hand side. Therein lies our challenge. To have the ability to add the same approver again.
We have narrowed down the global ‘Edit’ UI action and even tampered with one to many and many to many options none of which yielded the desired outcome.
Here’s the OOB script for one to many Edit slushbucket which I feel needs to have some code added/removed:
var path = uri.getFileFromPath();
uri.set('sysparm_m2m_ref', current.getTableName());
uri.set('sysparm_collection_related_file', current.getTableName());
uri.set('sysparm_form_type', 'o2m');
uri.set('sysparm_stack', 'no');
action.setRedirectURL(uri.toString('sys_m2m_template.do'));
Any help or ideas will be greatly appreciated.
I doubt if you’ll be able to do what you want by modifying the edit button functionality. I’ve never seen that done before. The popup dialog solution should work for you though. All you would have to do is modify it to use the ‘sys_user’ table and the ‘sysapproval_approver’ table.
Hey mark,
If i want to use a reference field, in place of slush button, I have to use the UI reference field is it?
How do i access this in the Client Script?
Thanks !
Yes, if you want to use a reference field in a custom popup dialog, you should include the ‘ui_reference’ macro instead of the ‘ui_slushbucket’ macro. The basic code to include in your UI page looks something like this…
To access the value of that field when the ‘OK’ button is clicked, you can use a client script like this…
[cc lang="javascript"]
<g:ui_reference name="QUERY:active=true" table="REFERENCED_TABLE_NAME">
Because the ‘Name’ parameter ends up being the ID of the form element too in this case, you need to reference the field by this custom query string to get the value in your client script if you’ve applied a filter. This is a terrible design, but that’s just how it was developed. 🙂
var dialogFieldValue = gel('QUERY:active=true').value;
Nice Reference Qualifier tip on the ui_reference fields. I needed that. The whole ID thing is dumb though.
Thanks a ton ! 🙂
Hey Mark,
I know this requirement is weird :-s,but then thought you have some fix for this
There is a field on the service-request form, which value we have to use to filter the reference field which is populated through a Glide window. The filtering should happen before even the service request form is submitted. i.e once the field on the form is filled.
Can that be done?
Thanks
I think it can, but I don’t have a specific example of that at the moment. You’ll need to ask this one on the forums if you need more help.
Thanks.. Sure, will do that ..
Hi Mark,
Do you think this is something that can be tailored to allow users to select multiple CI’s to a Change/Incident/Etc?
Kind Regards,
Mike P.
I suppose it could, but I don’t think it’s necessary in that case. The ‘Edit’ button on the ‘Affected CIs’ related list has always worked well for me.
I agree that the Edit button for the related list works well. Although my understanding of the ‘Affected CIs’ are the CIs that are impacted by the Change/Incident/Etc. As an example, I would like to have a list of CIs that will be used in a single Change Request and also have the list of impacted CIs in the “Affected CIs” related list.
I hope that makes sense.
Thanks for your time.
Hi Mark,
do you know if there is a way to auto-populate the right side of the slush with the left values, i.e. values start on the right as selected instead of left as available?
The business case is I’ve built this for a change task and approval chase UI, but the customer wants to opt out of whom should receive the chase, instead of opt in.
cheers,
Marc
sorry, I figured it out but here’s what I done for anybody else, so this populates the right instead of the left so you can unselect items rather than select them.
this goes in the UI Page client script, right at the very bottom, I’ve left the uncommented line there so you can see where it goes.
var opt = cel("option");
opt.value = item.getAttribute('value');
opt.text = item.getAttribute('text');
slush.getRightSelect().options.add(opt);
}
Thanks for this example.
Where can I find a detailed description about slush bucket functions like slush.addLeftChoice() , slush.clear() etc.
Might have to ask support on this one. You can find a lot of it with a dom inspector though.
Hi Mark and thanks for a great post!
Is there possibility to add “Add Filter”, “Run Filter” and the actual filters – similar to Configuration Item relationship UI page?
Kind Regards,
Kai
Thanks. I’m sure it’s possible, but it’s not something I’ve built before. I imagine that type of thing would be fairly complex to implement.
I am working on a requirement that has put us in need of putting a List Collector into a change form — in several places. I am not much of a Jelly developer (a few simple UI Pages but that is about it) but I ‘know’ that it should be possible to write a control in place in a form.
The requirement we have is to provide a filtered list of choices from which the user will multi-select. We need up to 6 in a few of the forms we are building and need to locate the Collectors in the form context.
Can you suggest how this might be done? I suspect that this has come up before. I like the popup solution but having a string of UI Action buttons located away from the relevant form sections will not work for the client. Additionally, we need to have the selections persist and be editable by the end user in several approval passes.
There’s really only a couple of ways that you could do this. The first (and probably best) solution would be to create those changes using a record producer that contained the list collector variables you need. These variables could even be hidden from the end user on the initial form. Using the record producer would allow you to display the variables on your change form in the variable editor so they could be used throughout the change flow.
The other option would be to set these options up in related lists or list fields on the change form.
Actually, we went a third way….
1. Create a New UI Macro for the control
a. Give it a meaningful name and description
b. Add a no-frills wrapper to the SNC Slushbucket control…
2. Create a Formatter to make control available in your Form.
UI Formatter:
a. Name: something useful
b. Formatter: .xml
c. Table: The table of the Form you are building
d. Type: Formatter
e. Active: True
3. Insert the control (formatter) in your form page.
a. Personalize -> Form Layout
b. Add the new Formatter to your layout where you want it
4. Add a new string variable to hold selected sys_ids ( judge the size accordingly).
a. Name the variable to something easily recognizeable.
b. Create a UI Policy and hide it from the end user [view it only to debug]
Client Script the control behaviors
onLoad – Fill the control with previous data, if any.
function onLoad() {
setTimeout(addAccessDeptChoices, 1000)
}
function addAccessDeptChoices() {
//accessdept.clear() Clears out the two sides of the slushbucket
// our query filter requirements reside in u_dsa_department
var departments = new GlideRecord(‘cmn_department’);
departments.addQuery(‘parent’, g_form.getValue(‘u_dsa_department’) );
departments.query();
while(departments.next()) {
accessdept.addLeftChoice(departments.sys_id, departments.name);
}
var selectedValues = (g_form.getValue(‘u_accessdept_value’)+”).split(‘,’);
jQuery(‘#accessdept_left’).val(selectedValues);
accessdept.moveLeftToRight();
}
OnChange() for the query Filter requirement.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
filterAccessDeptChoices(newValue);
}
function filterAccessDeptChoices(value) {
accessdept.clear(); //Clears out the two sides of the slushbucket
if (value==”){
return;
}
// our query filter requirements reside in u_dsa_department
var departments = new GlideRecord(‘cmn_department’);
departments.addQuery(‘parent’, g_form.getValue(‘u_dsa_department’) );
departments.query();
while(departments.next()) {
accessdept.addLeftChoice(departments.sys_id, departments.name);
}
var selectedValues = (g_form.getValue(‘u_accessdept_value’)+”).split(‘,’);
jQuery(‘#accessdept_left’).val(selectedValues);
accessdept.moveLeftToRight();
}
OnSubmit – Save the user selections.
function onSubmit() {
var slushBucketNames = [‘accessdept’];
slushBucketNames.each(function(name){
var values = [];
jQuery(‘#’ + name + ‘_right option’).each(function() {
values.push(jQuery(this).attr(‘value’))
});
g_form.setValue(‘u_’ + name + ‘_value’, values.join(‘,’));
});
}
/*————————————–*/
It works, too. There are two pieces I have not worked out yet; a search like the List Collector has and how to make the control wider for the rather long-named choices the user must select among.
Any thoughts? (and help on either of the two items on my to-do list would be most welcome.
The previous post ate the scripts for the first piece:
//
//
//
//
Hopefully it comes thru… it is just a glide declaration of a ui_slushbucket named ‘accessdept’
can this work on adding CI to the Affected CI related list instead of approvers, that is referencing a specific cmdb table, i.e. CMDB_CI_GROUPS.
if what would be the changes, if anyone can help, greatly appreciate.
I am thinking the only change to make using this is to change the script include – GrouAdd function. here’s what I have tried but it is not working,,
//Take a taskID and group sys_id values and add ‘cmdb_ci_group’ records
groupsAdd: function() {
var taskID = this.getParameter(‘sysparm_taskID’);
var values = this.getParameter(‘sysparm_values’).split(‘,’);
//Iterate through the group sys_id values
for(i in values){
var rec = new GlideRecord(‘task_ci’);
rec.initialize();
rec.task = taskID;
rec.ci_item = values[i];
rec.insert();
}
}
});
Hi Mark
I’d like to suggest a modification to the addGroupResponse function, as I found that users will and do click this button before completing the form
and are greeted with a message that mandatory field x isn’t completed.
From this
gsftSubmit(gel('sysverb_update_and_stay'));
}
To this
GlideDialogWindow.get().destroy();
GlideList2.get('').setFilterAndRefresh('');
return false;
}
Instead of saving the form, it simply refreshes the related list, therefore the user doesn’t need to complete the form before clicking the add approval groups
Great suggestion. This is a great use of the ListV2 refresh capabilities. Thanks!
Hi Mark,
This dialog box is great but I have one question. How do I show the approval groups that are already on the Change Request on the right hand side of the slushbucket, the way the approvers are listed. Any help would be appreciated. Thanks !!!
It’s possible, but not easily explained. I don’t have a pre-built example for it either unfortunately. The ‘addLoadEvent’ function will need to be modified to call a new function in the script include to get the current groups from the group approval records. The ‘loadResponse’ section in the client script is what takes that information and populates it in the left side. You’ll need to extend that to receive items for the other side and parse and populate them similarly.
I added a search/filter input with an onchange to help manage a large list. However, i found that both sides of the slushbucket were getting cleared. I did some digging and added a note here: https://community.servicenow.com/message/751901#751901
Is it possible to use 2 slush buckets on a a single ui page, if so how do you populate/clear them both?
I figured it out. You add a name and in the client script you use the name instead of “slush”.
Hi Mark Thanks for the script . I used the same UI page and script include for my requirement .
My requirement is If I select some groups in the slush bucket and then click OK. Approval groups should appear Approved in the related list .
But until i refresh the page the approval record in the related list is not getting updated,.
Can u pls help me with this ?
I used exactly same code of urs with some changes
You might give this link a try. It shows how to refresh a related list or form from a client script. You could do this as soon as you get a return from your AJAX call.
https://servicenowguru.wpengine.com/scripting/client-scripts-scripting/reload-form-related-list-client-script/
Mark,
You are just amazing !!!!. Thanku
Thanks
Divya
Hi Mark,
Im trying to export the tabel data from a custom UI page in excel sheet, it works for chrome but not for IE. If you could look in to the issue then plz let me know. I will share the codes here.
Hi Manisha,
This is an issue that is probably best addressed to the ServiceNow Community.