GlideRecord & GlideAjax: Client-Side Vs. Server-Side

It is easy to shoot yourself in the foot when it comes to performance, using client side scripts. For this reason, it is generally good practice to move any complex processing, large requests (more than a hundred or so records), and multi-level dot-walking to server-side scripts such as business rules which can present the data to the client. This can be done in a few ways, such as by using GlideAjax. It's also a good idea to utilize callback functions in almost every case where you're doing record lookups client-side. 

In this article, we'll go over some of the best ways to get the greatest client-side performance out of ServiceNow, by using good coding practices, callback functions, and GlideAjax to push some of the work off onto the server, rather than the client and prevent the client from having to wait around for the server to respond. 

This article got a little bit away from me, and ended up being kind of enormous. Therefore, I'll include a little 'index' here, to help you navigate: 

  1. Understanding Client-Side GlideRecord
  2. Callback Functions (Asynchronous Queries)
  3. GlideAjax
    1. Introduction
    2. GlideAjax From the Client
    3. GlideAjax Script Include
    4. Private GlideAjax Functions
    5. Returning Multiple Values
  4. Client-Side Encoded Queries
  5. GlideRecord/JavaScript Operators

Understanding Client-Side GlideRecord

GlideRecord is a ServiceNow API, and object type. It provides functions for us to interact with the database, run queries, and so on. Client scripts, are scripts that run inside the user's browser (the 'client') when a page is loaded, when a form is modified, or when the form is submitted. 

Client-side scripts that execute when a page loads or a field is modified, "block" the user from interacting with the page. For this reason, any AJAX calls that happen "onLoad" are undesirable and almost always bad practice. This includes the client-side GlideRecord API, which makes an AJAX request to the server, and generally waits for a response. 

Pro Tip: While most scripts only take microseconds or milliseconds to execute, most client scripts that run when the page loads should generally be changed to display business rules, since they prevent the browser from having to load and work with the form while the user is still waiting. 

Callback Functions

Callback functions, if you aren't familiar with them, are wondrous things. Once they 'click' in your mind and start making sense, they become the solution to a great many questions. Callback functions enable asynchronous queries, which means queries that don't wait for the server to respond, before returning control of the browser to the user. 

The down-side to client scripts, is that (if they don't use a callback function) the user must wait for them to finish executing before they can interact with the page. Their browser effectively 'locks up' the page until all scripts are finished executing. 
You've probably seen the effects of this yourself when a page SEEMS to be finished loading, but its' browser icon is still "spinning", and you can't interact with it. 

Pro Tip: Do not use a callback function with an onSubmit() script. The reason for this is that your browser is the one executing the scripts, and if your script runs "on submit", the browser may leave the page and go about its' business, before your script has finished executing! 

So, when an onLoad or onChange function executes, the user can't interact with the page until it's finished. This is normally not a big deal, because most of our scripts execute in microseconds or a few milliseconds. However, what about when we do something like .query() from the client? In that case, we submit an encoded query representing the query we've built to the server, wait for the server to retrieve the results and process any logic, then return that data to us. Round-trip, this can take several hundred milliseconds, up to even a few seconds on a slow internet connection. This may not sound like much but trust me, it's a long time to wait, and users feel entitled to faster performance than that. 

For this reason, we have a handy-dandy callback function option built right into the client-side GlideRecord API, in the .query() method. Rather than calling .query() with no arguments, we can actually pass in the name (just the name, no arguments or parentheses) of our callback function. The query will then submit the request to the server, and carry on without waiting for a response. When the response is received, it will call our callback function and pass the response GlideRecord object as an argument, which we can use and even iterate over, just like any other GlideRecord object. 

Here is a step-by-step break-down of the process when not using a callback function: 

  1. Submit the query to the database over the internet
  2. Server processes the query and any additional logic
  3. Server builds a response AJAX
  4. Server sends the response AJAX back over the internet
  5. Any additional client-side work is done on the response
  6. The user can now interact with the browser

Compare that with the step-by-step for when we do use a callback function:

  1. Submit the query to the database over the internet
  2. The user can now interact with the browser
  3. Server processes the query and any additional logic
  4. Server builds a response AJAX
  5. Server sends the response AJAX back over the internet
  6. The callback function is called
  7. Any additional client-side work is done on the response, inside the callback function

As for usage, we'll skip right to an example of using a callback function to iterate over a bunch of records.

var gr = new GlideRecord('a_table');
gr.addQuery('some', 'conditions');
gr.query(myCallBack); //Note that the callback function is passed in without parentheses

//define the callback function
function myCallBack(gr) {
    while (gr.next()) {
        //do some client-side work
    }
}

As you can see on line 3, we simply pass in the name of our callback function. This is also referred to as an asynchronous query
On line 6, we declare our callback function and expect a single argument. This will be an object of type GlideRecord. 

As it happens, the .getReference() method of g_form supports a callback function as well, and the usage is almost the same! The only difference is that getReference accepts two arguments (the first being the field name as a string, and the second being a reference to your callback function). Here's an example: 

//g_form.getReference('assigned_to'); <-- Synchronous usage. Bad for performance! 
g_form.getReference('assigned_to', cbFunc);

function cbFunc(gr) {
    var assignedToName = gr.getValue('name');
    Alert('This ticket is assigned to: ' + assignedToName + '.');
}

And here's the result...

servicenow getreference callback function example script

GlideAjax

Introduction

I would not be surprised if the least well understood API in common use in ServiceNow were GlideAjax. This is probably because the documentation is a little light on this, but also because it is genuinely a complicated API to use. This is because it has both a client-side, and a server-side component to it. In this section, I will do my best to explain it as clearly as possible. 

For starters, GlideAjax is not just a replacement for the GlideRecord API. However, it can replace that functionality (and more) on the client, by asking the server to do most of the work. In this way, GlideAjax can be a massive boon to your client-side performance, if used correctly and where appropriate. 

What GlideAjax is, is essentially just a way to construct an AJAX call which triggers a client-callable Script Include on the ServiceNow servers, which then returns some value(s) back to the client. I say "value(s)", because the response is actually XML, and can contain multiple values, so you're not locked down to just returning one static value. 

GlideAjax From the Client

To understand GlideAjax, let's first look at how to build the client-side script that will call our script include. Here is an example client script, followed by an explanation: 

//client script
var ga = new GlideAjax('AjaxScriptInclude'); //This argument should be the exact name of the script include. 
ga.addParam('sysparm_name', 'greetingFunction'); //sysparm_name is the name of the function in the script include to call.
ga.addParam('sysparm_my_name', "Tim"); //This is an optional parameter which we can reference in the script include. 
ga.getXML(myCallBack); //This is our callback function, which will process the response.

function myCallBack(response) { //the argument 'response' is automatically provided when the callback funciton is called by the system.
    //Dig out the 'answer' attribute, which is what our function returns. 
    var greeting = response.responseXML.documentElement.getAttribute('answer'); 
    alert(greeting); //alert the result to make sure all is well.
}

As you can see, on line 2, we instantiate a GlideAjax object called ga, and pass in a string argument. That argument is actually the name of the Script Include on the server that we've created -- don't worry, we'll get to that shortly! For now, all you need to know is that we'll need to create a Script Include with the name AjaxScriptInclude, which extends AbstractAjaxProcessor, and make sure it's client callable by checking that box when we create it.

On line 3, we call the addParam method of the GlideAjax API, and specify the value for sysparm_name. This parameter tells GlideAjax which function inside our Script Include to run. This is a mandatory parameter for any GlideAjax call. I think I'll have a function called greetingFunction in the script include, so I've specified that above. 

Next, on line 4, we specify an optional parameter: sysparm_my_name. I figure when I write the Script Include, I'll have it parse my name out of this parameter, and send me back a nice greeting. When you see the script include code in a moment, you'll see how that's done. 

On line 5, we call getXML which has very similar behavior to the ".query()" in the GlideRecord API. Like .query, .getXML accepts one argument: a callback function (without parentheses). (Remember callback functions from above?)

Our callback function will do any work we want it to do with the result of the GlideAjax call once the result comes in. Meanwhile, the user's browser is not locked up, and they're free to faff about. There is a way to use GlideAjax synchronously (sans callback; as opposed to asynchronously, or with a callback), and that is to use the getXMLWait() method, rather than getXML(). Off the top of my head, the only use case for this that I can think of, is an onSubmit() script. You should never use a callback function in an onSubmit script

On line 7, we declare our callback function and have it accept one argument, which will contain the XML response from the server as a result of our script include running. 

On line 9, we declare 'greeting' and set it to the answer attribute of the response XML. The "answer" attribute will always be whatever value is returned from our function. 

In order to understand the "answer" attribute a little bit better, let's replace all the code in the callback function above with the console.log(response);, and find the value we returned in the XML response:

Understanding the GlideAjax XML Response in ServiceNow (Animated GIF)

As you can see, response (the variable passed into our callback function when we get the AJAX response from .getXML()) is just an XML document, and response.responseXML.documentElement.getAttribute('answer') will always get us the value returned from our function. 

Actually, we can return multiple values if we're clever with our script include, but first let's learn the basics. We've just seen how to write a client-side script that makes a GlideAjax call to a script include and does some work with a value returned from the function we called in that script include. But, how do we build the script include? 

GlideAjax Script Include

First, navigate to System Definition -> Script Includes, then click on New to create a new script include. If you aren't familiar with script includes, they're basically chunks of code that sit on the server, and can be called from elsewhere; similar to UI scripts, except server-side. 

Give your new script include a name. Since we've already told our client script that our script include's name will be AjaxScriptInclude, let's use that. Once you click outside of the name field, you'll notice that the Script field populates itself with some initial configuration. Go ahead and click the Client Callable tick-box, and the script field will change again. The first line declares your script include as a new class. The second line sets the prototype so that your script include extends another script include, called AbstractAjaxProcessor. This is because AbstractAjaxProcessor already has most of the methods and properties that we're going to need. It makes our job easier! 

Pro Tip: On Fuji and later, ticking the Client Callable box on a script include will automatically add a line extending the AbstractAjaxProcessor class. On Eureka, the AbstractAjaxProcessor class does exist, but you'll need to add the line to make your prototype extend that class yourself. You can also learn more about the AJAX processor, by exploring the AbstractAjaxProcessor script include! 

On line 3 is where you'll start writing your code, but remember that we're in a class now. Classes are different from what you might be used to. For example, you declare properties and functions like name: value. Rather than function funcName(args) { }, we would say funcName: function(args) { }. We also separate each property/function with a comma, if we have more than one. You'll see what I mean in the code below: 

//client-callable script include
var AjaxScriptInclude = Class.create(); //(auto generated)
AjaxScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, { //(auto generated)
    greetingFunction: function() { //Declare a function inside our class.
        var userName = this.getParameter('sysparm_my_name'); //Get the parameter we passed in from the client script
        return 'Hiya, ' + userName + '.'; //return a friendly greeting
    }
});
Note: You will probably have a line at the end of your script include like type: 'AbstractAjaxProcessor'. Don't worry if you don't see that in my code, it isn't required. This just gives the system a name for the type of object/class you've made. 

As you can see, we've got those declarative and prototype extending statements on lines 3 and 4. On line 5, I've declared a function called greetingFunction. This is the same function that we referenced in line 3 of our client script as the value of the sysparm_name parameter. This is the function that will execute when we initiate our GlideAjax call using .getXML() from our client script. This function will execute on the server. As such, you can dot-walk, use server-side APIs, and otherwise just go nuts using the server's resources... Well, don't go completely nuts. 

Private GlideAjax Functions

Your GlideAjax script include (AjaxScriptInclude in our case) does not have to consist only of the one function you call with the sysparm_name parameter. Of course, you can have many functions. But you don't always want to expose the ability for anyone to call your utility functions. To make an internal function private in a script include that extends AbstractAjaxProcessor, just add an underscore to the beginning of the function name. Here's an example:

//client-callable script include
var AjaxScriptInclude = Class.create(); 
AjaxScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, { 
    greetingFunction: function() { 
        var userName = this.getParameter('sysparm_my_name'); client script
        var msg = _getMsg(userName);
        return msg;
    },
    _getMsg: function(userName) { //This function is private
        return 'Hey there, ' + userName + '.';
    }
});

As you can see on line 9, I declare _getMsg, a function that is used to construct the message that will be returned from the greetingFunction function. _getMsg cannot be accessed from outside of this script include though, and is therefore private

Note: This only works on script includes that extend AbstractAjaxProcessor, because the AbstractAjaxProcessor class contains a method which checks for the underscore, and returns null if it exists without running the function. 

While you're looking at the above code, I want to point out the little comma on line 8 (just above the _getMsg function). This comma is necessary because we're working inside of a Class, rather than the global space. Don't worry too much about this, just remember that functions and properties inside a class need to be separated by commas, or JS freaks out. 

Returning Multiple Values

Returning multiple values is actually pretty easy with GlideAjax because, as we explored above, the response is merely an XML document. In fact, there is even a built-in function allowing you to add a node to the XML doc, and give it attributes. Think of attributes as property-value, or variable-value pairs. It's actually quite similar to returning an object in JavaScript. 

There is a slightly different syntax when you want to set or get more than one "returned" value, so let's modify our existing scripts to return greetings in English, Spanish, and French. First, we'll modify the script include

//client-callable script include
var AjaxScriptInclude = Class.create(); 
AjaxScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, { 
    
    greetingFunction: function() { 
        var userName = this.getParameter('sysparm_my_name'); 
        this._getEnglishMsg(userName);
        this._getFrenchMsg(userName);
        this._getSpanishMsg(userName);
    },
    
    _getEnglishMsg: function(userName) { 
        var greeting = 'Howdy, ' + userName + '. How are you?';
        var msg = this.newItem('greeting');
        msg.setAttribute('lang', 'English');
        msg.setAttribute('value', greeting);
    },
    
    _getFrenchMsg: function(userName) { 
        var greeting = 'Bonjour, ' + userName + '. Ca-va?';
        var msg = this.newItem('greeting');
        msg.setAttribute('lang', 'French');
        msg.setAttribute('value', greeting);
    },
    
    _getSpanishMsg: function(userName) { 
        var greeting = 'Hola, ' + userName + '. Cómo estás?';
        var msg = this.newItem('greeting');
        msg.setAttribute('lang', 'Spanish');
        msg.setAttribute('value', greeting);
    }

});

As before, on lines 2 and 3, we declare our class and extend AbstractAjaxProcessor, then we declare our function and get the username that was passed in from the client script. 

On lines 7-9, we call private functions (I'm using 'this.' before the function call to specify that these functions are part of our current class, or superclass/parent class). 

Inside these private functions/methods (lines 12-31), we construct the 'greeting' string in the language of our choice, then we declare a new variable, msg, from calling this.newItem, which creates a new node in the XML document that we're effectively building using GlideAjax. Finally, we set a property of each of those nodes called lang to the language we're using, and another property called value to the greeting itself. 

There were a few changes in this version. For example, you may have noticed that we don't return any values from our function. We can if we want, but it is not necessary. The AbstractAjaxProcessor class (which is our superclass - which means we're extending it) has built-in functionality that returns the entire XML document when we're through. 

Now for the client-side script: 

//client script
var ga = new GlideAjax('AjaxScriptInclude');
ga.addParam('sysparm_name', 'greetingFunction'); 
ga.addParam('sysparm_my_name', 'Tim'); 
ga.getXML(myCallBack); 

function myCallBack(response) { 
    var language;
    var grtng;
    var greetings = response.responseXML.getElementsByTagName('greeting');
    
    for (var i = 0; i < greetings.length; i++) {
        language = greetings[i].getAttribute('lang');
        grtng = greetings[i].getAttribute('value');
        console.log(language + ': ' + grtng);
    }
}

Again, not a whole lot different, but there are some changes. For one thing, when we used .newItem() in our script include, we actually created a whole new node, rather than just a document property, which is what happens when we just use return from our function. For this reason, as you can see, we've used .getElementsByTagName() to get the greeting node(s) on line 10. After that (lines 12-16), we iterate over each instance of the greeting node and use the lang and value properties to build a string, which we log.

The result of this script looks like this: 

Reference Fields

As mentioned briefly in a tip above, using a client script to retrieve a GlideRecord value from a reference field using g_form.getReference() allows you to use a callback function in order to perform the necessary query to retrieve that GlideRecord asynchronously. However, many are unaware that setting a reference field value (such as using g_form.setValue('fieldName', 'value'))usually results in a query to the server as well; and this query always runs synchronously (meaning that the user's browser locks up for the duration). 

There is however, a way around this -- Simply call the .getValue method with a third argument: The display value. If your reference field points to the incident table for example, the display value might be the incident ticket number. For example:

g_form.setValue('some_ref_field', varContainingSysID, 'The Display Value');

This allows you to run a GlideRecord query asynchronously (or, far better yet, a GlideAjax request to a script include that runs the query on the server) and retrieve a sys_id, and a display value to set the reference field with, without locking up the user's browser unnecessarily. 

Client-Side Encoded Queries

We're probably all familiar with the .addEncodedQuery() method of the server-side GlideRecord class, but you may have run into issues when attempting to use encoded queries client-side.

As it turns out, the client-side GlideRecord API does not have the .addEncodedQuery() method. That's alright though, because if we simply call .addQuery() with only one argument, that argument is treated as an encoded query! For this reason, instead of writing out our queries line-by-line, like this...

var gr = new GlideRecord('table_name');
gr.addQuery('active', 'true'); //Same as .addActiveQuery(), server-side!
gr.addQuery('approval', 'approved');
gr.addQuery('assigned_to', '62826bf03710200044e0bfc8bcbe5df1'); //sys_ID of user
gr.query();
while (gr.next()) {
    //do some work
}

We can use a single encoded query like this...

var gr = new GlideRecord('table_name');
gr.addQuery('active=true^approval=approved^assigned_to=62826bf03710200044e0bfc8bcbe5df1');
gr.query();
while (gr.next()) {
    //do some work
}

This is especially useful, since more complex queries might require the .addOrCondition() method, which is not available client-side!

Pro Tip: Passing only one argument to the server-side .addQuery() method, also causes that argument to be treated as an encoded query! This is separate from the behavior of .get(), which treats a single argument as the sys_id of a single record to return.

GlideRecord/JavaScript Operators

The .addQuery() method of the GlideRecord class can accept various types of arguments. As we mentioned above, a single argument will be treated like an encoded query. Two arguments will be treated as an equivalency test (that is, the first argument being the field, and the second argument being the value to which the field is compared for equivalency). Specifying three arguments however, allows you to do more granular queries. For example: 

gr.addQuery('number', 'STARTSWITH', 'INC');

The middle argument is a JavaScript operator. Here is a list of all of the operators you can use: 

  • Standard operators (=, >, <, >=, <=, !=)
  • STARTSWITH: The field on the left must contain a value that starts with the string on the right.
  • ENDSWITH: The field on the left must contain a value that ends with the string on the right.
  • CONTAINS: The field on the left must contain the value specified on the right. 
    • Example: gr.addQuery('short_description', 'CONTAINS', 'Help');
  • DOES NOT CONTAIN: The opposite of CONTAINS.
  • IN: The field on the left must contain one of the values specified in the array (or comma-separated list) passed in on the right. 
  • INSTANCEOF: The field on the left (which should be 'sys_class_name') must be an instance of the field on the right. 
    • This means it has to extend that table at some level. 
    • Example: gr.addQuery('sys_class_name', 'INSTANCEOF', 'task');

SN Pro Tips is owned and written by The SN Guys, ServiceNow consultants. If your organization could benefit from our services, we humbly welcome you to schedule some time to chat and share your goals with us, so we can help your business Grow With Purpose


Want to learn more? Check out some of our other articles below!