Populate choices from a Decision Table
## TL;DR: GitHub
In case you’re just here to take some code snippets with basic explanation, head over to the GitHub repository maintained by the ServiceNow Developer Program and the community: [GitHub link](https://github.com/ServiceNowDevProgram/code-snippets/tree/main/GlideAjax/Get%20choices%20from%20Decision%20Table).
## Concept
Decision Tables offer a very powerful, yet easily maintainable way to drive workflow automation logic in ServiceNow. The purpose of this article however is not to explain how to use them - it was done by someone more competent than me, [@Lisa Holenstein](https://www.servicenow.com/community/user/viewprofilepage/user-id/172837): check and bookmark [this article](https://www.servicenow.com/community/workflow-automation-articles/getting-started-with-decision-builder-workflow-automation-coe/ta-p/2359919) in the Workflow Automation CoE that links to some awesome posts on this topic!
## Background
A few weeks ago I had a requirement to add a new Selectbox variable to a bunch of catalog items, and show different choice options depending on which item this variable is displayed on. In case of some items, we would have to look at the value of another variable as well to determine the actual choices. I needed a solution that can easily scale to additional items in the future, and can be maintained by a less experienced administrator, or even a process user directly. I immediately thought of Decision Tables, but wasn’t sure that it would actually work out as I hoped. It did, and here are the steps I took.
## Prerequisites
First of all, you will need a **Decision Table** with a result column of type 'Choice', and at least one, mandatory input that is a reference to sc\_cat\_item.
You will have to define the decisions for each catalog item separately, and obviously one catalog item can have multiple results - this is how you will get multiple choices for your Selectbox variable at the end. If you have other inputs, i.e. for different values from other variables, you simply need to add those conditions for each decision line for the relevant catalog item.
Note: if you created the choices inside the Decision Table, make sure the values are not too long. They end up getting truncated in the sys\_decision\_answer table, and sebsequently the values stored in sys\_choice will not match. So if necessary, change the default value to something short and unique (or use an existing choice list if you can).
If it sounds too complicated, bear with me, I will show you [below](https://www.servicenow.com/community/workflow-automation-articles/populate-choices-from-a-decision-table/ta-p/2839346?nobounce#dt).
Besides the Decision Table, you will need (at least) 2 scripts:
* A client callable **Script Include** to which you will pass the catalog item sys\_id and any variable values your decision table may need. The Script Include will use the [Decision Table API](https://docs.servicenow.com/csh?topicname=DecisionTableAPI.html&version=latest) to get all defined choices for the catalog item and return a stringified array of objects with the choice values and labels.
* An onLoad (if you only want to consider the catalog item as an input) or an onChange (if you need to catch values from other variables too) catalog **client script** that will call the script include, and add the options (choices) to the target variable based on the returned results, using GlideAjax.
And finally, you will of course need the variable itself, of type Selectbox.
## Decision Table
In every decision table, we focus on 3 main elements: [inputs](https://www.servicenow.com/community/workflow-automation-articles/populate-choices-from-a-decision-table/ta-p/2839346?nobounce#inputs), [conditions](https://www.servicenow.com/community/workflow-automation-articles/populate-choices-from-a-decision-table/ta-p/2839346?nobounce#conditions), [results](https://www.servicenow.com/community/workflow-automation-articles/populate-choices-from-a-decision-table/ta-p/2839346?nobounce#results). Let’s look through them one by one.
### Inputs
We have one mandatory input, which is a reference to the actual catalog item our variable is displayed on. If you want to evaluate values from other variables on the catalog form as well, add an additional input for that, either as one generic string field, or if you prefer, dedicated ones for each specific variable (in which case, make sure they are not mandatory).
Here is what my example looks like:
### Conditions
This is fairly simple. Add a condition column for the Catalog Item input you added above:
If you have more inputs, then add columns for each of those as well, e.g.:
### Results
The result column must be of type Choice. I have defined new choices for my use case, but you should be able to use an existing choice list too. If you have long strings as your choice labels, make sure you update the choice value manually to something reasonably short. I am not sure what the actual character limit is (leave a comment if you do!), but I had problems with values getting truncated and not matching with the sys\_choice values (on a Utah instance). You will want to have something like this:
### Decision Rows
Now that all main elements are configured, it’s time to populate our table with data. You will select the required catalog item in your condition column, and choose a (single) choice for that item. If you need more choices as options for your target Selectbox variable, you need to add multiple rows for the same item. The DecisionTableApi will return each matching result for that item.
Hint: since the Washington DC release, you have the ability to [duplicate decision rows](https://docs.servicenow.com/bundle/washingtondc-build-workflows/page/administer/decision-table/concept/modify-decision-table-rules.html#title%5Fduplicate-rows-decision-table)! (Credits to [@Bimschleger](https://www.servicenow.com/community/user/viewprofilepage/user-id/74220)for pointing this out for me!)
Here is an example with some demo data, with 2 condition columns:
## Script Include
As mentioned above, the script include must be client callable, and it will:
1. Take the inputs from a catalog client script
2. Pass the inputs to the DecisionTableAPI
3. Use the results from the DecisionTableAPI to look-up the actual choice records from the sys\_choice table
4. Construct an array of objects (with choice labels and values) to be returned to the client script
Hint: using the DecisionTableAPI in scripts became much easier with the Washington release, as you can create code snippets right from the the Decision Builder ([link](https://docs.servicenow.com/csh?topicname=create-decision-table-code-snippets.html&version=latest)).
Furthermore, you will need to collect some technical values from your decision table, and make it available to your script include, either by hardcoding them, storing them in system properties, other decision tables(!), etc.
* Decision Table sys\_id {var decisionTableId}: you can get it from the _sys\_decision_ table
* Technical names of your decision inputs {var dtInput1, dtInput2, etc.}: you can get these from the _sys\_decision\_input_ table. Filter for: Table ends with \[Decision Table sys\_id from above\].
Note that these will always start with u\_ for your decision tables, e.g. u\_catalog\_item.
* The technical name of the result column that has our choices {var resultColumn}: you can get this from the _sys\_decision\_multi\_result\_element_ table. The same things apply as above, both for how to filter and that the column name will start with u\_.
### Example
Here’s an example script include to get you started with the above variables left empty:
```
var GetChoicesFromDT = Class.create();
GetChoicesFromDT.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
getChoices: function() {
/**
* Gets the defined choices for the passed in catalog item
*
* @author Laszlo Balla
* @param {String} sysparm_cat_item
* The sys_id of the catalog item to get choices for - mandatory
* @param {String} sysparm_cat_variable
* Value from an additional catalog variable to evaluate as part of your decision - optional
* @return {String}
* A stringified array (since it goes to client script) of choices
*/
/**
* In addition to the above, the following variable MUST be set for the script to work:
*
** decisionTableId : Sys ID of the decision table. Store in a system property and set with gs.getProperty()
** dtInput1, 2, etc. : the technical names of the Decision Table inputs
** resultColumn : the technical name of the result column of your Decision Table that has the choices set
*/
var catItem = gs.nil(this.getParameter('sysparm_cat_item')) ? null : this.getParameter('sysparm_cat_item'); // Mandatory parameter
var catVar = gs.nil(this.getParameter('sysparm_cat_variable')) ? null : this.getParameter('sysparm_cat_variable'); // Optional parameter example (variable from record producer). Multiple as needed, or remove if not.
var decisionTableId = ''; //Sys ID of the decision table. Store in a system property and set with gs.getProperty()
var dtInput1 = ''; // Make sure you set this to the technical name of the first input of your Decision Table
var dtInput2 = ''; // Make sure you set this to the technical name of the second input of your Decision Table, if you have one. Multiply as needed, or remove if not.
var resultColumn = ''; // Set this to the technical name of the result column that contains your choices
var answerArray = [];
var choiceArr = [];
var iter1 = 0;
if (!gs.nil(catItem) && !gs.nil(decisionTableId)) {
var choiceQuery = 'var__m_sys_decision_multi_result_element_' + decisionTableId;
var decisonTable = new sn_dt.DecisionTableAPI();
var inputs = new Object();
inputs[dtInput1] = '' + catItem;
// Repeat this block as necessary with additional parameters and inputs
if (!gs.nil(catVar)) {
inputs[dtInput2] = '' + catVar;
}
var dtResponse = decisonTable.getDecisions(decisionTableId, inputs);
while (iter1 < dtResponse.length) {
answerArray.push(dtResponse[iter1]['result_elements'][resultColumn].toString());
iter1++;
}
// Now find the the actual choices with labels
var choiceGr = new GlideRecord('sys_choice');
choiceGr.addQuery('name', choiceQuery);
choiceGr.addQuery('value', 'IN', answerArray.toString());
choiceGr.setLimit(30); // The Choice table is huge, so I recommend setting a reasonable query limit. You should have an idea of the max # of results anyway.
choiceGr.query();
while (choiceGr.next()) {
var choice = {};
choice['value'] = choiceGr.getValue('value');
choice['label'] = choiceGr.getValue('label');
choiceArr.push(choice);
}
return JSON.stringify(choiceArr); // Return a stringified array to the client
} else {
gs.error('GetChoicesFromDT Script include did not run as the catItem mandatory variable is null: ' + catItem + ' or decision table sys_id is empty: ' + decisionTableId);
return;
}
},
type: 'GetChoicesFromDT'
});
```
##
## Catalog Client Script
You need a client script to do 3 main things:
1. Collect the necessary inputs from the form, such the sys\_id of the catalog item and any variable values
2. Send the inputs as parameters to the above Script Include
3. Parse the response and add the choices to your field
### onLoad
If you don’t need to wait for the user to fill in some variables to capture those as inputs, but this is already known when the form loads (such as the sys\_id of the catalog item), then you can keep it simple and use an onLoad catalog client script to do all the above tasks. You will have to use [GlideAjax](https://developer.servicenow.com/dev.do#!/reference/api/washingtondc/client/c%5FGlideAjaxAPI?navFilter=glideajax) to call your script include and process the response in a function.
In this case, I recommend that you add the variable along with the onLoad script into a variable set, so it can be very easily added to multiple catalog items.
### onChange
An onChange catalog client script (only?) makes sense if you need to capture one or more values from other variables. You will need to decide which variable’s change will trigger the call of the script include, and you will have to account for the scenario of that (triggering) variable changing multiple times, even after you added the choices already.
###
### Example
Here is an example that you can tailor to your needs for both an onLoad or an onChange script. It also includes some additional logic, such as setting the field read-only if there is only one choice, adding the “-- None --’’ option if there are more, etc. These are all examples - update it as per your own requirements.
```
var targetChoiceField = ''; // Set this to the name of the Selectbox variable you want to populate
g_form.clearOptions(targetChoiceField);
var catItem = g_form.getUniqueValue();
var dtChoiceAjax = new GlideAjax('global.GetChoicesFromDT'); // Set this to the name of the script include with the relevant scope
dtChoiceAjax.addParam('sysparm_name', 'getChoices');
dtChoiceAjax.addParam('sysparm_cat_item', catItem);
/*
* Add an other option parameter, e.g.:
* dtChoiceAjax.addParam('sysparm_cat_variable', g_form.getValue('some_variable'));
*/
dtChoiceAjax.getXMLAnswer(setChoices);
function setChoices(answer) {
if (answer) {
var choiceArray = JSON.parse(answer);
if (choiceArray.length == 0) {
// Do something if the response is empty
g_form.setReadOnly(targetChoiceField, false);
g_form.setMandatory(targetChoiceField, false);
g_form.setDisplay(targetChoiceField, false);
} else {
g_form.setDisplay(targetChoiceField, true);
// Similarly, you might want to do something if there is only one choice, e.g. set that by default and make the field read-only.
var isSingleChoice = choiceArray.length == 1 ? true : false;
if (isSingleChoice) {
g_form.addOption(targetChoiceField, choiceArray[0].value, choiceArray[0].label);
g_form.setValue(targetChoiceField, choiceArray[0].value);
g_form.setReadOnly(targetChoiceField, true);
} else {
// And finally, if you have multiple options, decide how you want your field to behave
g_form.setReadOnly(targetChoiceField, false);
g_form.addOption(targetChoiceField, '', '-- None --'); // Adding None option - this is also optional
for (i = 0; i < choiceArray.length; i++) {
g_form.addOption(targetChoiceField, choiceArray[i].value, choiceArray[i].label, i + 1);
}
g_form.setMandatory(targetChoiceField, true);
}
}
} else {
// What if there was no answer return from the script include at all?
g_form.setReadOnly(targetChoiceField, false);
g_form.setMandatory(targetChoiceField, false);
g_form.setDisplay(targetChoiceField, false);
}
}
```
##
Depending on the complexity of requirements, you may have this variable inside of a variable set along with an onLoad script, or standalone with a supporting onChange script. You can see how the different choices got rendered for the specific catalog items.
As reminder, here are the conditions defined in the decision table:
3M Privacy Filter - MacBook Pro:
3M Privacy Filter - MacBook Pro Retina - notice that the variable is read only since we only have once choice (as defined in the client script):
Acrobat:
QuickTime Pro:
Firewall Rule Change - for this one, we are considering another variable (namely the business\_purpose variable inside the it\_to\_it variable set added to this item). As you can see, I have defined 2 specific options with one choice, and another scenario for the other two options, with two choices.
Special projects selected:
Testing selected:
KLTO or Other selected:
## Summary
I hope this article serves as another illustration for the ultimate power that lies in leveraging Decision Tables. The biggest advantage is that you can delegate the maintenance of the table itself to any admin or business user, and let your underlying complex logic react to the changes in real time.
Let me know in the comments if you think this example made sense, if you were able to use or re-use it, or share further suggestions of how you would extend or optimize it further (there is clearly room for that)!
https://www.servicenow.com/community/workflow-automation-articles/populate-choices-from-a-decision-table/ta-p/2839346
Laszlo Balla