Unpacking Client Script Execution Flow: A Deep Dive for ServiceNow Developers
Ah, ServiceNow! The platform that powers so many of our daily workflows. As developers and administrators, we’re constantly interacting with its intricacies. Today, we’re going to pull back the curtain on a fundamental aspect of ServiceNow development: Client Script Execution Flow. Understanding how and when your client-side logic fires is crucial for building robust, responsive, and error-free applications. We’ll explore how client scripts interact with other client-side mechanisms like UI Policies and delve into some practical scenarios using provided examples to illustrate key concepts. Let’s get started!
When we talk about the ‘client side’ in ServiceNow, we’re essentially referring to what happens within the user’s web browser. This is where Client Scripts shine, allowing us to dynamically alter forms, validate user input, and generally enhance the user experience in real-time, without requiring a full page reload. It’s a powerful tool, but like any powerful tool, it needs to be wielded with understanding.
The Core Players: Client Scripts vs. UI Policies
Before we dive into the execution flow, it’s essential to distinguish between two primary client-side configuration tools in ServiceNow: Client Scripts and UI Policies. While both operate on the client-side and aim to influence the user interface, they have different strengths and use cases.
UI Policies: The Declarative Approach
UI Policies are ServiceNow’s declarative way of manipulating form elements. Think of them as a more visual and often simpler alternative to Client Scripts for common tasks. They allow you to:
- Make fields mandatory.
- Make fields read-only.
- Show or hide fields.
- Set default values.
- Make fields visible or invisible.
- Show or hide related lists.
The power of UI Policies lies in their simplicity and their ability to be configured without writing extensive code for many scenarios. They are generally easier to manage and understand for basic form behavior.
Let’s quickly touch upon some of their key attributes, drawing from our reference points:
The “Global” Checkbox in UI Policies (Reference 59)
When the “Global” checkbox is checked in a UI Policy, it means that the policy will apply to all views of the table it’s defined on. If unchecked, you’ll be prompted to specify a particular view, meaning the UI Policy will only execute within that designated view. This is crucial for maintaining consistent UI behavior across different user interfaces or personalized views.
“Reverse if False” in UI Policies (Reference 60)
The “Reverse if False” checkbox is a fantastic little feature. If you’ve defined actions to occur when a UI Policy’s condition is true (e.g., make a field mandatory), checking “Reverse if False” ensures that those actions are undone when the condition becomes false. So, if a field was mandatory and the condition is no longer met, it reverts to being optional. This simplifies managing state changes without needing separate UI Policies.
The “On Load” Checkbox in UI Policies (Reference 61)
The “On Load” checkbox determines whether the UI Policy’s actions are applied as soon as the form loads. If checked, the UI Policy runs when the form initially renders. If unchecked, the UI Policy’s actions are deferred and will only trigger when a specific field’s value changes or another event occurs that meets the UI Policy’s condition. This is key for dynamic form behaviors that shouldn’t necessarily apply at the very beginning.
The “Inherit” Checkbox in UI Policies (Reference 62)
The “Inherit” checkbox is a lifesaver when working with table hierarchies (where one table extends another). If checked, a UI Policy defined on a parent table will automatically be applied to its child tables. This promotes code reuse and ensures consistent behavior down the inheritance chain, saving you from duplicating configurations.
Can you write scripts in UI Policies? (Reference 63)
Absolutely! While UI Policies excel at declarative actions, they also have a “Run Scripts” option. When you enable this, you can embed custom JavaScript code within UI Policy Actions. This allows for more complex logic that goes beyond the standard UI manipulations. It’s a hybrid approach, offering the declarative benefits alongside scripting power when needed. However, remember that for very complex, reusable, or long-running scripts, Client Scripts might be a better fit.
Converting UI Policies to Data Policies (Reference 64 & 65)
ServiceNow allows you to convert UI Policies into Data Policies. This is often done to enforce business rules at the data layer, ensuring data integrity regardless of how the data is accessed (e.g., via forms, integrations, or other scripts). The conversion is usually straightforward, but there are specific cases where it’s not recommended or possible:
- Controlling Data Visibility: Data Policies are about data integrity, not visual presentation. If you’re hiding/showing fields or controlling their display, it’s a UI Policy job.
- Controlling Views: View-specific logic belongs to UI Policies.
- Controlling Related Lists: Managing related list visibility is a UI concern, handled by UI Policies.
- Controlling Scripts: If the core logic of your UI Policy involves complex scripting that directly manipulates UI elements in a way not possible with declarative rules, it might not be a clean fit for a Data Policy.
Data Policies: The Server-Side Guardian
Data Policies, on the other hand, operate at the data layer and can execute on both the client and server sides. They are primarily concerned with enforcing data integrity. Their core functions are similar to UI Policies (making fields mandatory, read-only, or controlling visibility), but their impact is broader. They ensure these rules are applied consistently, regardless of the user interface or access method.
Client Scripts: The Dynamic Behaviors
Client Scripts are JavaScript code snippets that run in the user’s browser when specific events occur, such as form load, record update, or field change. They offer a much higher degree of flexibility and power than UI Policies for custom logic. You can use them to:
- Perform complex validations.
- Dynamically alter field behavior (enable/disable, set values).
- Show custom messages or alerts.
- Make asynchronous calls to the server (using GlideAjax).
- Control the visibility and behavior of UI elements in ways not possible with declarative rules.
The key difference often comes down to complexity and control. For straightforward UI manipulations, UI Policies are preferred. For custom business logic, intricate validations, or dynamic interactions, Client Scripts are the go-to.
Client Script Execution Flow: When Things Happen
Now, let’s get to the heart of it: the execution flow. Client Scripts are triggered by specific events. The most common types of Client Scripts and their typical execution points are:
1. OnLoad Client Scripts
Purpose: These scripts run when a form loads. They are ideal for initial form setup, setting default values, or performing validations based on the existing record data before the user even interacts with it.
Execution: As the name suggests, they fire when the form is loaded and ready for the user. This happens after the page has fully rendered and the initial data is displayed.
Use Case Example: Imagine you want to automatically set a ‘Contact Type’ field on an incident to ‘Phone’ if the ‘Caller’ is a specific VIP user. An OnLoad script is perfect for this.
2. OnChange Client Scripts
Purpose: These scripts run when the value of a specific field changes. They are essential for creating dynamic forms where actions depend on user input.
Execution: Fired immediately after a user changes a value in a monitored field and then moves focus away from that field (or presses Enter).
Use Case Example: If a user changes the ‘State’ of an incident, you might want an OnChange script to automatically populate a ‘Resolution Notes’ field or make it mandatory if the state is set to ‘Closed’.
// Example: OnChange Client Script on 'State' field in Incident table
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === '') {
return;
} var newState = g_form.getValue('state'); // Get the new value of the state field
if (newState == 7) { // Assuming 7 is the value for 'Closed'
g_form.setMandatory('close_notes', true); // Make close notes mandatory
g_form.addInfoMessage('Please provide resolution notes for closing this incident.');
} else {
g_form.setMandatory('close_notes', false); // Make close notes optional if not closed
}
}
3. OnSubmit Client Scripts
Purpose: These scripts run just before a record is submitted (saved or updated). They are the last line of defense for client-side validations before the data is sent to the server.
Execution: Triggered when the user clicks ‘Submit’, ‘Update’, or ‘Save’ on the form. If an OnSubmit script returns false, the submission is cancelled.
Use Case Example: You might have a requirement that an ‘Impact’ must be selected if the ‘Priority’ is ‘High’. An OnSubmit script can check this condition and prevent the save if it’s not met.
// Example: OnSubmit Client Script for Incident validation
function onSubmit() {
var priority = g_form.getValue('priority');
var impact = g_form.getValue('impact'); if (priority == 1 && impact == '') { // Assuming 1 is 'High' priority
g_form.addErrorMessage('Impact must be selected when Priority is High.');
return false; // Prevent submission
}
// Example from reference: Preventing incident closure if tasks are open
var grTask = new GlideRecord('incident_task');
grTask.addQuery('incident', g_form.getUniqueValue()); // Get current incident sys_id
grTask.addQuery('state', '!=', 3); // Assuming 3 is the state value for 'Closed'
grTask.query();
if (grTask.hasNext()) {
g_form.addErrorMessage('Cannot close the incident because there are open tasks.');
return false; // Prevent submission
}
return true; // Allow submission
}
Getting User Information: Client vs. Server
A common requirement is to know who the current user is and what their permissions are. Let’s look at how you achieve this on both ends:
Client-Side User Information (Reference 13)
On the client side, you can access the current logged-in user’s System ID (their unique record identifier in the `sys_user` table) using the global object g_user.
var currentUserID = g_user.userID; // e.g., '6816f79cc0a8016401c5a33be04be441'
console.log("Current User System ID (Client-side): " + currentUserID);
g_user also provides other useful information like g_user.firstName, g_user.lastName, g_user.userName, and importantly, g_user.hasRole('role_name') to check for specific roles.
Server-Side User Information (Reference 14)
On the server side (e.g., in Business Rules, Script Includes, Workflow Scripts), you use the gs object. To get the current user’s System ID, you use gs.getUserID().
// Example in a Business Rule
var currentUserID = gs.getUserID(); // e.g., '6816f79cc0a8016401c5a33be04be441'
gs.info("Current User System ID (Server-side): " + currentUserID);
Similarly, on the server, you can use gs.getUser() to get the user object, which then allows you to check roles (gs.getUser().hasRole('role_name')) or group membership.
Checking Group Membership (Reference 15)
This is a very common requirement for controlling access or triggering actions based on user roles and group affiliations.
Server-Side Check:
// Example in a Business Rule or Script Include
var isMember = gs.getUser().isMemberOf('ITIL Users'); // Replace 'ITIL Users' with the actual group name
if (isMember) {
gs.info("User is a member of ITIL Users group.");
} else {
gs.info("User is NOT a member of ITIL Users group.");
}
Client-Side Check:
Checking group membership directly on the client-side using Client Scripts is a bit more involved. You typically can’t directly use gs.getUser().isMemberOf() in a Client Script because gs is a server-side object. The common approach is to use GlideAjax to call a Script Include that performs the server-side check and returns the result to the client script.
// Client Script (e.g., OnLoad)
function onLoad() {
var ga = new GlideAjax('MyUserUtils'); // Name of your Script Include
ga.addParam('sysparm_name', 'isUserMemberOfGroup');
ga.addParam('sysparm_groupName', 'ITIL Users'); // Pass the group name
ga.getXMLAnswer(function(answer) {
if (answer == 'true') {
g_form.addInfoMessage('You are part of the ITIL Users group.');
} else {
g_form.addInfoMessage('You are not part of the ITIL Users group.');
}
});
} // Script Include (e.g., MyUserUtils) - make it Client Callable
var MyUserUtils = Class.create();
MyUserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
isUserMemberOfGroup: function() {
var groupName = this.getParameter('sysparm_groupName');
return gs.getUser().isMemberOf(groupName); // This runs on the server
},
type: 'MyUserUtils'
});
Automating Actions with Business Rules
While this article focuses on client-side execution, it’s impossible to discuss ServiceNow logic without mentioning Business Rules. These are server-side scripts that run when records are displayed, inserted, updated, deleted, or queried. They are crucial for enforcing business logic, automating tasks, and maintaining data integrity.
Let’s look at how Business Rules can interact with related records, using examples from the reference:
Closing Child Incidents When Parent is Closed (Reference 26)
This scenario involves an “After Update” Business Rule on the Incident table. It triggers when an incident’s state changes to ‘Closed’ (state 7) and it’s not a child itself (i.e., it has no parent).
// Business Rule: After Update on Incident table
// Condition: current.state.changesTo(7);
// Script:
if (current.state == 7 && current.parent == '') { // Check if state is closed and it's a parent
var grChild = new GlideRecord('incident');
grChild.addQuery('parent', current.sys_id); // Find incidents whose parent is the current incident
grChild.query();
while (grChild.next()) {
grChild.state = 7; // Set child incident state to Closed
grChild.update(); // Update the child incident record
}
}
This is a perfect example of how server-side logic can cascade actions. When a parent incident is closed, the system automatically ensures all its dependent child incidents are also marked as closed.
Preventing Incident Closure if Tasks are Open (Reference 27)
This scenario uses an “Before Insert or Update” Business Rule. It runs *before* an incident is saved. If an incident is being closed (state is not 3, assuming 3 is ‘Closed’), it checks for any open associated incident tasks. If open tasks are found, it prevents the closure and displays an error message.
// Business Rule: Before Insert or Update on Incident table
// Condition: current.state.changesTo(3); // Or a broader condition if needed
// Script:
// Check for associated incident tasks
var grTask = new GlideRecord('incident_task');
grTask.addQuery('incident', current.sys_id); // Link to the current incident
grTask.addQuery('state', '!=', 3); // Assuming 3 is the state value for 'Closed'
grTask.query(); if (grTask.hasNext()) { // If any open tasks are found
gs.addErrorMessage('Cannot close the incident because there are open tasks.'); // Display error to user
current.setAbortAction(true); // Prevent the update/submission
}
This rule is crucial for maintaining data integrity and ensuring that no critical follow-up work is abandoned when a parent record is closed. The current.setAbortAction(true) is key here – it stops the database operation.
Closing Associated Incidents When a Problem is Closed (Reference 28)
This is another “After Update” Business Rule on the Problem table. When a problem is closed (state 7), it finds all associated incidents that are not yet closed and closes them.
// Business Rule: After Update on Problem table
// Condition: current.state.changesTo(7);
// Script:
if (current.state == 7) { // Check if the problem state is Closed
var grIncident = new GlideRecord('incident');
grIncident.addQuery('problem_id', current.sys_id); // Find incidents linked to this problem
grIncident.addQuery('state', '!=', 7); // Only consider incidents that are NOT already closed
grIncident.query();
while (grIncident.next()) {
grIncident.state = 7; // Set incident state to Closed
grIncident.update(); // Update the incident record
}
}
This rule demonstrates how a resolution at the ‘Problem’ level can automatically cascade to resolve all related ‘Incident’ records, streamlining the closure process.
Reference Qualifiers: Refining Your Choices (Reference 48)
Before we wrap up, let’s quickly revisit Reference Qualifiers. These are essential for controlling the data displayed in reference fields (fields that link to another table). They act like filters, ensuring users only see relevant options.
Types of Reference Qualifiers:
- Simple: Uses basic query conditions (e.g., `active=true`). Easy to set up for straightforward filtering.
- Dynamic: Leverages pre-defined “Dynamic Filter Options” which can adapt based on context. This allows for more flexible filtering than simple qualifiers without writing complex code.
- Advanced (JavaScript): This is where you write custom JavaScript code to define highly complex filtering logic. It’s the most powerful but requires coding expertise.
Difference between types:
- Simple vs. Dynamic: Simple is static filtering; Dynamic can change based on form context (like other field values).
- Dynamic vs. Advanced: Dynamic uses a configuration (Dynamic Filter Option); Advanced uses direct JavaScript code for maximum flexibility.
- Simple vs. Advanced: Simple is for basic, fixed filters; Advanced is for complex, dynamic, and context-aware filtering logic.
Conclusion: Mastering the Flow
Understanding the client script execution flow is not just about writing code; it’s about orchestrating user interactions and business processes effectively within ServiceNow. By knowing when your Client Scripts, UI Policies, and even supporting Business Rules fire, you can:
- Build intuitive and responsive user interfaces.
- Prevent common user errors through robust validation.
- Automate complex workflows.
- Ensure data integrity across your instance.
We’ve covered the distinct roles of Client Scripts and UI Policies, explored the timing of Client Script execution (OnLoad, OnChange, OnSubmit), learned how to access user information on both client and server sides, and touched upon the power of Business Rules for backend automation. By mastering these concepts, you’re well on your way to becoming a more proficient ServiceNow developer.
Keep practicing, keep experimenting, and always refer back to the execution order and your specific business requirements. Happy scripting!