THURSDAY, JANUARY 19, 2017

Creating a Custom Slushbucket Popup Dialog

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.

‘Add Approval Groups’ UI Action
Name: Add Approval Groups
Client: true
Form link: true
OnClick: addApprovalGroups()
Condition: gs.hasRole(‘itil’)
Script:

function addApprovalGroups(){
   //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.

‘add_approval_groups’ UI Page
Name: add_approval_groups
HTML:

<?xml version="1.0" encoding="utf-8" ?>
<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:

//Called when the 'OK' button gets clicked
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.

‘GroupSelectAjax’ Script Include
Name: GroupSelectAjax
Client callable: true
Script:

var GroupSelectAjax = Class.create();

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();
      }    
   }
});

31 Comments

Arlen 25-02-2011, 03:30

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 uri = action.getGlideURI();
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.

Reply
Mark Stanger 25-02-2011, 03:36

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.

Reply
Abhiram Bharadwaj 09-05-2011, 22:53

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 !

Reply
Mark Stanger 10-05-2011, 00:32

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…

<g:ui_reference name="YOUR_DIALOG_REFERENCE_FIELD"  id="YOUR_DIALOG_REFERENCE_FIELD" table="REFERENCED_TABLE_NAME">

To access the value of that field when the ‘OK’ button is clicked, you can use a client script like this…

If you want to filter the entries in the reference field using a reference qualifier then you have to include an encoded query string using a special syntax in the 'Name' parameter.  That syntax looks something 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. 🙂

//Get the value of the dialog reference field
var dialogFieldValue = gel('QUERY:active=true').value;
Reply
Michael Brown 06-12-2011, 14:42

Nice Reference Qualifier tip on the ui_reference fields. I needed that. The whole ID thing is dumb though.

Reply
Abhiram Bharadwaj 11-05-2011, 02:30

Thanks a ton ! 🙂

Reply
Abhiram Bharadwaj 03-06-2011, 21:59

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

Reply
Mark Stanger 04-06-2011, 02:35

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.

Reply
Abhiram Bharadwaj 06-06-2011, 23:58

Thanks.. Sure, will do that ..

Reply
Mike P. 10-08-2011, 09:35

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.

Reply
Mark Stanger 10-08-2011, 09:37

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.

Reply
Mike P. 10-08-2011, 10:15

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.

Reply
Marc 12-08-2011, 03:43

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

Reply
marc guy 12-08-2011, 03:56

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.

//      slush.addLeftChoice(item.getAttribute('value'), item.getAttribute('text'));
       var opt = cel("option");
       opt.value = item.getAttribute('value');
       opt.text = item.getAttribute('text');
slush.getRightSelect().options.add(opt);
   }
Reply
mjd 06-03-2012, 00:12

Thanks for this example.
Where can I find a detailed description about slush bucket functions like slush.addLeftChoice() , slush.clear() etc.

Reply
Mark Stanger 06-03-2012, 04:19

Might have to ask support on this one. You can find a lot of it with a dom inspector though.

Reply
Kai Lempinen 03-09-2012, 22:24

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

Reply
Mark Stanger 04-09-2012, 05:49

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.

Reply
R. Hodgins 08-01-2013, 13:40

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.

Reply
Mark Stanger 09-01-2013, 17:16

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.

Reply
R. Hodgins 11-01-2013, 19:22

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.

Reply
R. Hodgins 11-01-2013, 20:02

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’

Reply
Lou 24-01-2013, 15:37

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.

Reply
Lou 24-01-2013, 16:42

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();
}
}
});

Reply
Jason Thomas 08-07-2013, 01:52

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

function addGroupResponse(){
   gsftSubmit(gel('sysverb_update_and_stay'));
}

To this

function addGroupResponse(){
  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

Reply
Mark Stanger 08-07-2013, 06:28

Great suggestion. This is a great use of the ListV2 refresh capabilities. Thanks!

Reply
June 19-12-2013, 18:53

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 !!!

Reply
Mark Stanger 19-12-2013, 20:33

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.

Reply
Travis DePuy 27-01-2015, 11:21

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

Reply
Richard 29-05-2015, 20:38

Is it possible to use 2 slush buckets on a a single ui page, if so how do you populate/clear them both?

Reply
Richard 29-05-2015, 20:45

I figured it out. You add a name and in the client script you use the name instead of “slush”.

Reply

Leave a Reply


Latest Comments

  • David: It appears that I can hit sys_properties table with REST. This works, but I haven’t yet discovered the...
  • Mark Stanger: Hey David, It doesn’t surprise me that scoped apps have made this more difficult. I’m not...
  • David: Mark, do you have an example of how to do this in a scoped app? It seems there are many hoops to jump through...
  • Mark Stanger: The only possibility is to create a system property to override this in your application. Check out the...