Chris Pearson
Tera Contributor

Disclaimer: The ideas, thoughts, and opinions in this post reflect my own views and do not necessarily represent the views of my employer, Accenture. 

Chances are we all have used getReference() before. If we're doing things properly, we are sure to use a callback function with it in order to force the call to the server side to be asynchronous. But recently, ServiceNow has declared this staple of the client side GlideForm API to be 'no longer recommended' due to its performance impact.

Where does the performance 'hit' come from? Well, when you retrieve an object using getReference(), you are returning the entire record, allowing you to access any field on that record from the client side. Here's the thing...when you need to retrieve something from the server, you probably don't need the entire record. Instead, you usually only need 1 or 2 attributes from that record for processing in the client. Thus, the extra data being sent to the client is where the performance hit comes from. Why pack a full suitcase of clothes for only a one-night trip, right?

While this is technically true, unless you have an absolutely absurd amount of client scripts using asynchronous getReference() calls, you'd be hard pressed to actually feel that performance difference in the real world. 

That being said, if you'd like to be a "good little developer" and never use getReference() again, what are the options? 

One option is: Any time you need to retrieve data from the server, you could write your own specific paired AJAX scripts (client script and script include) to get just the data you need. This is super time consuming. Also, just think of all of the script includes you'd have to create which do similar things. You might start running out of valid script include names before the end of your project!

Instead, why not build one script include which can act as your new go-to, AJAX-compatible way of retrieving any attribute(s) from any record on any table in ServiceNow?

Let me pause here to give credit where credit is due. Thanks to a colleague of mine, Michael Pryor for coming up with this idea!

Here's how we get it done. The below script has two parts...just like any other AJAX script. One part works as a client script, the other is a script include. Once the script include is in place, you can call that same script include over and over again from multiple client scripts instead of using the getReference() method.

Here's the client script part first:

This script assumes a scenario where we have three custom fields on the Incident form that we want to auto-populate whenever the assigned_to field changes. The three fields should populate the Company, Phone number, and Email address of the Assigned to user into three new fields on the Incident form: u_assignee_company, u_assignee_phone, u_assignee_email

// ************* CLIENT SCRIPT ************* //
// This example runs as an onChange script on the assigned_to field
// It sets three custom fields on the incident form when an 
// Assigned to value is chosen.
// Custom fields: u_assignee_company, u_assignee_phone, and u_assignee_email

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading) {
        return;
    }

    if (newValue != '') {
      // Call our AJAX helper script include
        var referenceHelperAjax = new GlideAjax('referenceHelper'); // The name of the script include
        referenceHelperAjax.addParam('sysparm_name', 'getReferenceRecord'); // The function name to call
        referenceHelperAjax.addParam('sysparm_table', 'sys_user'); // The table of reference field
        referenceHelperAjax.addParam('sysparm_ref_id', newValue); // The value (sys_id) from the reference field
        referenceHelperAjax.addParam('sysparm_fields', 'company,phone,email'); // A comma seperated list of the field names from the target table you want returned
        referenceHelperAjax.getXML(processAjax); // Execute the server side call
    } else {
      // The Assigned to field was blanked out, so clear our three fields
      g_form.clearValue('u_assignee_company');
      g_form.clearValue('u_assignee_phone');
      g_form.clearValue('u_assignee_email');
    }
}

function processAjax(response) {
    var answer = JSON.parse(response.responseXML.documentElement.getAttribute("answer"));
    // Value usage:
    // answer.fieldname.value returns the value for that field name
    // answer.fieldname.displayValue returns the display value, great for Reference fields and choice lists
   
    // Populate our three custom fields
    // Note that for our u_assignee_company field we have
    // both the value and display value of the data so that we don't force 
    // another server call when populating the reference field with a value
   if (answer.company.value) {
        g_form.setValue('u_assignee_company', answer.company.value, answer.company.displayValue);
    } else {
        g_form.clearValue('u_assignee_company');
    }

    if (answer.phone.value) {
        g_form.setValue('u_assignee_phone', answer.phone.value);
    } else {
        g_form.clearValue('u_assignee_phone');
    }

    if (answer.email.value) {
        g_form.setValue('u_assignee_email', answer.email.value);
    } else {
        g_form.clearValue('u_assignee_email');
    }
}

Next, create your script include which can be used over and over by multiple client scripts so long as they follow the same format as above.

Make sure to remember to set your 'Accessible from' field on the script include to All application scopes so that you can take advantage of this from any scope. Also...don't be the one who forgets to check the 'Client callable' checkbox on the script include! 

// ************* SCRIPT INCLUDE ************* //
// Reference Helper is used to get data from a record in place of
// using a the ServiceNow getReference() call which returns the entire object
var referenceHelper = Class.create();
referenceHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    /*
     * referenceHelper requires two parameters to be passed in and has one optional parameter
     *
     * @param   {string} sysparm_table        REQUIRED  Name of the table that will be used in the GlideRecord query
     * @param   {string} sysparm_ref_id       REQUIRED  Sys ID of the record that is being queried for
     * @param   {string} sysparm_fields       OPTIONAL  Comma seperated string of fields to return.  Used to filter down the returned JSON string
     *     If 'sysparm_fields' is empty, the entire GlideRecord object is returned in a JSON string.
     *     If 'sysparm_fields' is not empty, the GlideRecord object is returned in a JSON string with only the fields provided
     * @returns {string} JSON string of the GlideRecord object referenced.
     */
    getReferenceRecord: function() {
        var recordObjectData = {};
        var fieldArray = (this.getParameter('sysparm_fields') + '').split(',');
        var recordObject = new GlideRecordSecure(this.getParameter('sysparm_table'));
        if (recordObject.get(this.getParameter('sysparm_ref_id'))) {
            if (gs.nil(fieldArray)) {
            // We don't have a fieldArray so pass back the entire object
                for (var key in recordObject) {
                    var keyName = key;
                    recordObjectData[keyName] = {
                        value: recordObject.getValue(keyName),
                        displayValue: recordObject.getDisplayValue(keyName)
                    };
                }
            } else {
            // We have an array of fields. Iterate through them and build our return object
                for (var i = 0; i < fieldArray.length; i++) {
                    var fieldName = fieldArray[i];
                    recordObjectData[fieldName] = {
                        value: recordObject.getValue(fieldName),
                        displayValue: recordObject.getDisplayValue(fieldName)
                    };
                }
            }
        }
        return JSON.stringify(recordObjectData); //Encode the values into the object
    },

    type: 'referenceHelper'
});
4 Comments