- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-29-2024 01:37 PM
In ServiceNow, I have a catalog item with two variables.
1. The first variable is called **select_application** and it's a lookup select box. It pulls data from the "cmdb_rel_group" table, specifically from the "ci" field, which is a reference to the "cmdb_ci" table.
2. The second variable is called **select_group_to_be_added_removed** and it's a list collector. It also uses the "cmdb_rel_group" table.
I want to set it up so that when a user selects a specific "ci" in the **select_application** variable, only the groups associated with that specific "ci" are shown in the **select_group_to_be_added_removed** variable.
The group names to be displayed in the **select_group_to_be_added_removed** variable are stored in the "group" field of the "cmdb_rel_group" table, which is a reference to the "sys_user_group" table.
For now I have this catalog client script:
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === '') {
return;
}
// Get the sys_id of the selected application (CI)
var selectedApplication = g_form.getValue('select_application');
// Call the script include to get the group options
var ga = new GlideAjax('FilterGroupsByApplication');
ga.addParam('sys_id', selectedApplication);
ga.getXMLAnswer(function(response) {
var groupOptions = response.responseXML.documentElement.getAttribute('answer');
var groupOptionsArr = groupOptions.split(',');
// Clear existing options
var selectElement = g_form.getControl('select_group_to_be_added_removed');
for (var i = selectElement.options.length - 1; i >= 0; i--) {
selectElement.remove(i);
}
// Add the filtered options
for (var j = 0; j < groupOptionsArr.length; j++) {
var option = groupOptionsArr[j];
if (option) {
selectElement.add(new Option(option.split(' - ')[1], option.split(' - ')[0]));
}
}
});
}
and this script include:
var FilterGroupsByApplication = Class.create();
FilterGroupsByApplication.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getGroupOptions: function() {
var applicationSysId = this.getParameter('sys_id');
var groups = [];
var gr = new GlideRecord('cmdb_rel_group');
gr.addQuery('ci', applicationSysId);
gr.query();
while (gr.next()) {
var groupSysId = gr.getValue('group');
var groupName = gr.group.getDisplayValue();
if (groupSysId) {
groups.push(groupSysId + ' - ' + groupName);
}
}
return groups.join(',');
},
type: 'FilterGroupsByApplication'
});
But it doesn't work - anybody can tell me why?
Best regards
Anders
If my answer has helped with your question, please mark my answer as the accepted solution and give a thumbs up.
Best regards
Anders
Rising star 2024
MVP 2025
linkedIn: https://www.linkedin.com/in/andersskovbjerg/
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-30-2024 02:01 AM - edited 08-30-2024 02:08 AM
Ah I understand. You have a List Collector here... I was under the assumption you have a Choice-type field.
In this case however, I'd suggest you to completely skip the client side stuff and go with the following:
- On the field "Select Group to be added...":
- "Type specification" -> Set the field "Variable attributes" to "ref_qual_elements=select_application"
- "Type specification" -> Set the field "Reference qualifier" to "javascript: new global.CUSTOMER_MyCatItem().groupRefQual(current);"
With the step 1 you basically make the reference qualifier reload every time you change the 'select_application' value.
- Create a new Script Include (or modify the existing to match):
- Name: CUSTOMER_MyCatItem (naming convention for maintenance, please modify according to your circumstances)
- Client: false (no longer requires ACL!)
- Script:
/* global Class, GlideRecord */
/* eslint no-undef: "error" */
var CUSTOMER_MyCatItem = Class.create();
CUSTOMER_MyCatItem.prototype = {
initialize: function() {
},
/**
* {sc_cart_item & GlideRecordGenerated} itemGr
* @returns {Array.<string>} List of Group SysIDs
*/
groupRefQual: function (cartItemGr) {
var cmdbRelGroupGr = new GlideRecord('cmdb_rel_group');
if (cartItemGr.variables.select_application) {
cmdbRelGroupGr.addQuery('ci', cartItemGr.variables.select_application);
} else {
cmdbRelGroupGr.addQuery('sys_id', 'x');
}
cmdbRelGroupGr.setLimit(50); // best practice to limit all queries, replace with a resonable value
cmdbRelGroupGr.query();
var groupSysIDs = [];
while (cmdbRelGroupGr.next()) {
groupSysIDs.push(cmdbRelGroupGr.getValue('group'));
}
return 'sys_idIN' + groupSysIDs;
},
type: 'CUSTOMER_MyCatItem'
};
The advantage of this solution is: It only requires one (!) additional artefact (the Script Include), while previously you'd additionaly have to have:
- One Client Script
- One ACL protecting the Client Callable Script Include
- The ACL needs to have one acl-role record at least
=> In total you have 4 artefacts to implement (and maintain!) this requirement, while compared with the one presented in this post, you have 1.
In theory you could even inline the whole ref-qual script, but I wouldn't recommend this as maintenance is a lot harder.
If you still want to though (in this case you wouldn't need any additional artefacts on top of your catalog item at all), this is the reference qualifier you have to use (Step 1.1):
javascript: (function (cartItemGr) {
var cmdbRelGroupGr = new GlideRecord('cmdb_rel_group');
if (cartItemGr.variables.select_application) {
cmdbRelGroupGr.addQuery('ci', cartItemGr.variables.select_application);
} else {
cmdbRelGroupGr.addQuery('sys_id', 'x');
}
cmdbRelGroupGr.setLimit(50); // best practice to limit all queries, replace with a resonable value
cmdbRelGroupGr.query();
var groupSysIDs = [];
while (cmdbRelGroupGr.next()) {
groupSysIDs.push(cmdbRelGroupGr.getValue('group'));
}
return 'sys_idIN' + groupSysIDs;
})(current);
Please let us know if this solves your issue, in case please mark the answer which helped you the most as the "solution" to this thread so other community members facing the same issue profit from it.
Edit: Thought some screenshots might help:

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-29-2024 01:51 PM
I see you are using getControl to perform choice operations. It's sort of cumbersome.
I think you should use g_form function instead : g_form.clearOptions(), and g_form.addOption()
Secondly, in your script include, you have combined the group name and sysid as a single string. Its so much better to use a JSON variable with 2 attributes here. The client script can parse out the JSON easily as well rather than use 2 split functions.
Finally, are you able log the values returned from the script include?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-29-2024 02:57 PM
Hi @AnirudhKumar ,
Thank you for the swift response. I changed a little in the client script code:
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === '') {
return;
}
console.log('Selected application: ' + newValue);
// Get the sys_id of the selected application (CI)
var selectedApplication = g_form.getValue('select_application');
// Call the script include to get the group options
var ga = new GlideAjax('FilterGroupsByApplication');
ga.addParam('sys_id', selectedApplication);
ga.getXMLAnswer(function(response) {
if (response) {
var groupOptions = response.responseXML ? response.responseXML.documentElement.getAttribute('answer') : '';
console.log('Group options received: ' + groupOptions); // Debug line
// Clear existing options in the list collector
var collector = g_form.getControl('select_group_to_be_added_removed');
if (collector && collector.options) {
for (var i = collector.options.length - 1; i >= 0; i--) {
collector.options[i] = null;
}
}
// Add the filtered options
var groupOptionsArr = groupOptions.split(',');
for (var j = 0; j < groupOptionsArr.length; j++) {
var option = groupOptionsArr[j];
if (option) {
var value = option.split(' - ')[0];
var label = option.split(' - ')[1];
var newOption = new Option(label, value);
if (collector) {
collector.add(newOption);
}
}
}
} else {
console.error('No response received from GlideAjax call.'); // Error logging
}
});
}
And I can see I get the last "console error" with no response.
Best regards
Anders
If my answer has helped with your question, please mark my answer as the accepted solution and give a thumbs up.
Best regards
Anders
Rising star 2024
MVP 2025
linkedIn: https://www.linkedin.com/in/andersskovbjerg/

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-29-2024 03:35 PM
Hmm.... I see your script include is not properly being called...
Could you change your client script code from:
var ga = new GlideAjax('FilterGroupsByApplication');
ga.addParam('sys_id', selectedApplication);
to:
var ga = new GlideAjax('FilterGroupsByApplication');
ga.addParam('sysparm_name', 'getGroupOptions');//Calling the function here
ga.addParam('sysparm_sys_id', selectedApplication);//renaming the param name
And the line in your script include from:
var applicationSysId = this.getParameter('sys_id');
to:
var applicationSysId = this.getParameter('sysparm_sys_id');//used new param name to get value
Please try this out and let me know... thanks
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-29-2024 04:07 PM
Debugging Client Side Script Includes is tricky and you cannot use the NOW script debugger since breakpoints wont trigger. For this reason I often use try-catch and log errors to the end-user (if they are admin).
Note that your Client Side Script is using getXMLAnswer and not getXML, but you have the xml handling. The reason your code failed is that the GlideAjax was primarily due to the missing ga.addParam('sysparm_name', ...) part.
Please update to the following code and let us know the results:
Script Include (ensure Client = true is set *and* you have a proper Script Include "execute" ACL -> name=FilterGroupsByApplication):
I've changed to following:
- return a normalized JSON
- return a status (success or error)
- print detailed error string (but only to admin - do not expose internal secrets to end-users)
- do not use 'var gr' -> this will decrease your health-scan rating, instead use the common NOW naming convention
var FilterGroupsByApplication = Class.create();
FilterGroupsByApplication.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getGroupOptions: function() {
var result = {
status: 'error',
message: ''
};
try {
var applicationSysId = this.getParameter('sys_id');
var groups = [];
var cmdRelGroupGr = new GlideRecord('cmdb_rel_group');
cmdRelGroupGr.addQuery('ci', applicationSysId);
cmdRelGroupGr.addNotNullQuery('group');
cmdRelGroupGr.query();
while (cmdRelGroupGr.next()) {
var groupSysId = cmdRelGroupGr.getValue('group');
var groupName = cmdRelGroupGr.group.getDisplayValue();
groups.push({
value: groupSysId,
name: groupName
});
}
result.groups = groups;
result.status = 'success';
} catch (e) {
result.message = 'Failed to get Group Options';
if (gs.hasRole('admin')) {
result.message += '\nError: ' + e + '\n' + e.stack;
}
}
return JSON.stringify(result);
},
type: 'FilterGroupsByApplication'
});
Client Script:
I have changed:
- getXMLAnswer does not need to parse handle the result xml document (this is only required for old-school getXML)
- adopted to use the script include above (does not rely on split)
- clearing the choice values on each change (should clear the groups if application is cleared)
- using standard g_form API (clearOptions and addOption) functions and no g_form.getControl (which is DOM manipulation which should be avoided)
- added a debugger; statement - when you trigger the onChange script while having the browser-console open, you can debug the result and client script step-by-step (remove this before pushing to prod/test)
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) {
return;
}
g_form.clearOptions('select_group_to_be_added_removed');
var selectedApplication = g_form.getValue('select_application');
if (selectedApplication) {
var ga = new GlideAjax('global.FilterGroupsByApplication');
ga.addParam('sysparm_name', 'getGroupOptions');
ga.addParam('sys_id', selectedApplication);
ga.getXMLAnswer(function(response) {
debugger;
var result = JSON.parse(response);
if (result.status == 'success') {
result.groups.forEach(function (group) {
g_form.addOption('select_group_to_be_added_removed', group.value, group.name);
});
} else if (result.message) {
g_form.addErrorMessage(result.message);
}
});
}
}