In this blog post I show how one can control the list of available choices in a ServiceNow form depending on the current choice of a record.
Setting the context
For a practical illustration, we will be controlling the list of available choices of the State field of an incident.
In the out of the box installation of ServiceNow (for example on demo.service-now.com), an Incident can have the following states:
We will be controlling the list of choices for the incident state as per the following state diagram:
As per the diagram, we can only move into the Active state either from the New state or if the incident is just being created.
Step 1: creating a states transition table
In order to make the solution as dynamic as possible we create a new table in ServiceNow named incident_state_matrix having two columns (source_state, destination_state).
This table has two choice fields ‘Source State’ and ‘Destination State’ that allow inserting valid transitions. These fields are choice lists that are are based on the incident states from the Incident [incident] table as per the screenshot below.
In this table we insert rows that represent the allowed transitions in the above diagram. Below is a screenshot of the populated table:
Step 2: Business rule that propagates the allowed states of an incident to the client browser
Create the following business rule:
- Name: Allowed Incident States
- Table: Incident
- When: display
- Active: true
In the script section paste the following:
getAllowedStates(); function getAllowedStates() { var current_state = current.state; var ismgr = new GlideRecord('u_incident_states_matrix'); ismgr.addQuery('u_source_state', current_state); ismgr.query(); var allowed_states = []; var index = 0; while (ismgr.next()) { allowed_states[index++] = ismgr.u_destination_state.toString(); } g_scratchpad.allowed_states = allowed_states; }
What is the above business rule does is generate an array of allowable states that is sends to the client browser as a scratchpad object.
A client script on the client side will have to process the propagated array and control the list of selectable choices.
Step 3: Create a helper UI Script that facilitates enabling/disabling choice list options
Note: this script is inspired from a post that appeared on the SNCGuru blog at http://www.servicenowguru.com/scripting/client-scripts-scripting/removing-disabling-choice-list-options/
Create the following UI Script:
- Name: DisableEnableOptions
- Active: true
- Global: true
Paste the following script:
function disableAllOptions(fieldName) { fieldName = g_form.removeCurrentPrefix(fieldName); var control = g_form.getControl(fieldName); if (control && !control.options) { var name = 'sys_select.' + this.tableName + '.' + fieldName; control = gel(name); } if (!control) return; if (!control.options) return; var options = control.options; for (var i = 0; i < options.length; i++) { var option = options[i]; control.options[i].disabled = 'true'; } } function disableOption(fieldName, choiceValue) { fieldName = g_form.removeCurrentPrefix(fieldName); var control = g_form.getControl(fieldName); if (control && !control.options) { var name = 'sys_select.' + this.tableName + '.' + fieldName; control = gel(name); } if (!control) return; if (!control.options) return; var options = control.options; for (var i = 0; i < options.length; i++) { var option = options[i]; if (option.value == choiceValue) { control.options[i].disabled = 'true'; break; } } } function enableOption(fieldName, choiceValue) { fieldName = g_form.removeCurrentPrefix(fieldName); var control = g_form.getControl(fieldName); if (control && !control.options) { var name = 'sys_select.' + this.tableName + '.' + fieldName; control = gel(name); } if (!control) return; if (!control.options) return; var options = control.options; for (var i = 0; i < options.length; i++) { var option = options[i]; if (option.value == choiceValue) { control.options[i].disabled = ''; break; } } }
Step 4: Client script that controls the list of choices
Create the following client script:
- Name: Allowed Incident States
- Active: true
- Global: true
- Type: onLoad
- Table: Incident
In the script section paste the following:
function onLoad() { try { var current_state = g_form.getValue('state'); disableAllOptions('state'); enableOption('state', current_state); var allowed_states = g_scratchpad.allowed_states; var nb_states = allowed_states.length; for (var index = 0; index < nb_states; index++) { enableOption('state', allowed_states[index]); } } catch (err) { jslog(err.message); } }
Step 5: Business rule that checks if a state is allowed before saving a record
Create the following business rule:
- Name: Check If Incident State Allowed
- Table: Incident
- Active: true
- When: before
- Insert: true
- Update: true
Paste the following script:
</pre> checkIfStateAllowed(); function checkIfStateAllowed() { var bad_state = true; //If the record is being inserted then previous.state is null var previous_state = previous.state.toString() == 0 ? 1 : previous.state.toString(); var current_state = current.state.toString(); if (previous_state != current_state) { var ismgr = new GlideRecord('u_incident_states_matrix'); ismgr.addQuery('u_source_state', previous_state); ismgr.query(); while (ismgr.next()) { if (current_state == ismgr.u_destination_state.toString()) { bad_state = false; break; } } if (bad_state == true) { //If current.state did not match any destination state then we have to abort gs.addErrorMessage('Incident State Not Allowed'); current.setAbortAction(true); } } } <pre>
Testing
To test the solution create a new incident as per the below screenshot:
On the form you will notice that only the New and Active states are available:
If we set the incident state to Active and save the form then the list of available choices changes:
I hope this blog post has been useful for you.