If you didn’t pay close attention to the recent Fall 2010 Stable 1 ServiceNow release you probably missed a really cool new piece of functionality. I just used it for the first time today and I really feel like it’s something that everybody needs to have in their ServiceNow Client Scripting tool belt. The functionality I’m talking about is the g_form getReference callback API.
The getReference() method is something that’s been part of g_form for a long time and is something that most ServiceNow administrators and consultants are familiar with. It’s extremely useful, but can also be a huge performance killer if not used correctly. The Fall 2010 Stable 1 release makes a slight tweak to the way you can use getReference() that can really improve the end user experience from a performance perspective.

UPDATE: This same function applies to client-side GlideRecord queries! If at all possible, you should use an asynchronous query from the client as follows…

var gr = new GlideRecord('sys_user');
gr.addQuery('name', 'Joe Employee');
gr.query(myCallbackFunction); //Execute the query with callback function

//After the server returns the query recordset, continue here
function myCallbackFunction(gr){
   while (gr.next()) { //While the recordset contains records, iterate through them
      alert(gr.user_name);
   }
}

To understand why this new functionality is so important, you must first understand the difference between synchronous and asynchronous processing. When something runs synchronously, it runs step-by-step and step 2 doesn’t start until step 1 finishes. Asynchronous processing, on the other hand, allows for step 2 to start even when step 1 is still in process.

An example of this in the ServiceNow client scripting world might be when you load your incident form and you want to have the ‘Caller’ field show that the currently-populated user is a VIP. You might also want the location of that caller to populate as the ‘Caller’ field changes. These are 2 common out-of-box configurations that most every client ends up using. Both of these functions require a ‘getReference’ or ‘GlideRecord’ call to get the necessary information about the caller from the server. While the functions work great, they can also be very expensive from a performance perspective in part because they both use synchronous processing to call back to the server and request information. When this happens, the entire user session has to wait for this processing to complete. Step 2 (populating the location and indicating a VIP) cannot start until Step 1 (querying the server for user information) finishes. In fact, NOTHING can happen until Step 1 finishes. Of course, the end user doesn’t understand this. All they know is that they can’t do anything with the form for a couple of seconds after they change the ‘Caller’ field.

Here’s where the getReference callback function comes in. Instead of calling getReference like this…
var caller = g_form.getReference(‘caller_id’);

You can call it with an optional callback function like this…
var caller = g_form.getReference(‘caller_id’, myCallbackFunction);

What this does is allow you to use asynchronous processing with your getReference call. So when your script executes, the system initiates the necessary query (Step 1), but continues right along with everything else instead of waiting for the result from the getReference call. Eventually, that call will return with a result though. At that time, the optional callback function kicks in and finishes Step 2 and the end user isn’t waiting on a locked up form.

EXAMPLE

Today I created the script below. It is a combination of the ‘Set Location to User’ and ‘Hilight VIP Caller’ client scripts that are set up on the ServiceNow incident form out-of-box. The major benefit to this script is that it combines the 2 scripts and completely eliminates one of the GlideRecord queries to the server. That alone is a pretty big deal. The second thing this script does is use the new getReference callback function to process the server call asynchronously and keep the form from locking up while your user waits for a result.
There are 4 major pieces to the script below that I think are really important…

  1. The initial ‘getReference’ call that calls my ‘popCallerInfo’ callback function. (var caller = g_form.getReference(‘caller_id’, popCallerInfo);)
  2. The ‘popCallerInfo’ callback function that gets executed when the getReference call returns a result. (function popCallerInfo(caller))
  3. You MUST pull in the ‘getReference’ GlideRecord variable result into your callback function so that you can evaluate the result. In my example below, this variable is named ‘caller’. The ‘getReference’ call will take care of passing the value if you store it in a variable. You need to make sure you pull that in as a parameter in your function like this…(function popCallerInfo(caller))
  4. You can only pass a single GlideRecord variable into your callback function from the ‘getReference’ call. In the script below, I needed access to several variables from my ‘onChange’ function. In order to access those variables I had to position my callback function (popCallerInfo) INSIDE of the ‘onChange’ function that made the initial ‘getReference’ call.
‘Highlight VIP Caller / Set Location’ Client Script

function onChange(control, oldValue, newValue, isLoading) {
   //Get the 'caller_id' field and label elements
   var callerLabel = $('label.incident.caller_id');
   var callerField = $('sys_display.incident.caller_id');
   //Get the 'location' field
   var loc = g_form.getElement('location');
   
   //If the 'caller_id' field contains a valid reference
   if(newValue){
      //Get the caller object so we can access fields
      //Calls the 'popCallerInfo' function when the value is returned
      var caller = g_form.getReference('caller_id', popCallerInfo);
   }
   //If the 'caller_id' field does not contain a valid reference
   else{
      //Clear the value of the 'location' field
      if(loc){
         g_form.setValue('location', '');
      }
     
      //Remove any VIP styles present
      callerLabel.setStyle({backgroundImage: ""});
      callerField.setStyle({color: ""});
   }
   
   //Nested 'getReference' callback function for variable access
   function popCallerInfo(caller){
      //Location setting section
      if(loc && !isLoading && newValue != oldValue){
         g_form.setValue('location', caller.location);
      }
     
      //VIP formatting section
      if(caller.vip == 'true'){
         //Change the 'caller_id' label to red background with VIP icon
     var bgPosition = "95% 55%";
     if (document.documentElement.getAttribute('data-doctype') == 'true')
        bgPosition = "5% 45%";
                   
            callerLabel.setStyle({backgroundImage: "url(images/icons/vip.gif)", backgroundRepeat: "no-repeat", backgroundPosition: bgPosition});
        //Change the 'caller_id' name field to red text
        callerField.setStyle({color: "red"});
     }
     else{
        //Not a VIP, remove temporary styles
        callerLabel.setStyle({backgroundImage: ""});
        callerField.setStyle({color: ""});
     }
   }
}