How to Send Server Data to the Client using Jelly in ServiceNow

One thing that I’ve seen more than a few people struggle with is how to get data from the Server to the client when using Jelly in UI Pages and UI Macros. As such I wanted to give some examples today which would help. The “secret” is that you can use the same “Server-side” (Jelly) variables inside the Client script section (on UI pages) or within “script” tags on UI Macros.

So as in our examples below, let’s say that you had a Jelly variable called “serverArr” which you declare within the “g:evaluate” tags which gets processed on the Server-side. The evaluate code loops through a table and builds a collection of JSON objects which get added to this array variable. To use this object we need to first encode it into JSON on the server, then we can simply use the same variable name within the corresponding client script section of “${serverArr}” (without double quotes), and when ServiceNow goes to build the Page/Macro it will substitute the variable in the client script with our data from the server.

UI Page

<!--?xml version="1.0" encoding="utf-8" ?-->

    
        
        var serverArr = [];
        var gr = new GlideRecord('incident');
        gr.setLimit(5);
        gr.query();
        while (gr.next()) {
        var obj = {}
        obj.number = gr.number.toString()
        obj.short_desc = gr.short_description.toString();
        serverArr.push(obj);
        }

        serverArr = new global.JSON().encode(serverArr);
        serverArr; 
       

Working with the following “Server data” on the Client side:

Client Script Section of UI Page: Note that this must be pasted into this section via the list view of the page otherwise it will complain about the use of the ${serverArr} variable.

   var objArr = ${serverArr};
   jQuery('body').append(JSON.stringify(objArr) + '');
   for (var i= 0; i < objArr.length; i++) {
        var obj = objArr[i];
        jQuery('body').append(obj.number + ' - ' + obj.short_desc + '');
   }

Server Data - UI Page 1

UI Macro

<!--?xml version="1.0" encoding="utf-8" ?-->
    var serverArr = [];
    var gr = new GlideRecord('incident');
    gr.setLimit(5);
    gr.query();
    while (gr.next()) {
    var obj = {}
    obj.number = gr.number.toString()
    obj.short_desc = gr.short_description.toString();
    serverArr.push(obj);
    }

    serverArr = new global.JSON().encode(serverArr);
    serverArr;
 
/<script> /
var objArr = ${serverArr};
   jQuery('body').append(JSON.stringify(objArr) + '');
   for (var i= 0; i < objArr.length; i++) {
        var obj = objArr[i];
        jQuery('body').append(obj.number + ' - ' + obj.short_desc + '');
   }
/</script>/

Note that the above script sections do not have a “/” I just needed to do that as WordPress kept trying to render them as in-line scripts!
Server Data - UI Macro

Remove the Default “time” for DateTime fields in ServiceNow

Removed Default Time in DateTime Fields

As a ServiceNow Developer/Consultant one enhancement that I have received a number of times over the years (usually for the Change Management form) is the ability to “blank” the default timestamp (set to the current time) that is automatically pre-filled on all date-time fields. The customer wants to force the user to specify their own time and are worried that the user will simply choose a date and then click the checkbox to exit the control leaving in the current time. To mitigate this I have created a script that utilizes the DOM (Yeah, I know what you’re thinking but I have found no other way, and besides, ServiceNow uses the DOM via Prototype to create the control). The main thing to worry about is that this “could break after upgrades”, so you will want to add this to your list upgrade test cases as this has broken from Fuji to Geneva (see comments to find out the two lines that changed). If anyone has found a better way to do this which does not include modifying the DOM please let me know!

Ok, so on to the fun. Initially we define the list of fields we want to “blank” (must include the table as well) the time for, then we run a jQuery statement to select each element on the form which is of type “glide_element_date_time” (updated in Geneva, was “date_time” previously). From there we pull the data-ref attribute to match against our list of fields to set and add an event to call our controlDateTimeSelector function (with a timeout) when the user clicks the corresponding calendar button/icon. We will try a maximum of 5 times (500ms between each) to get the popup DateTime control before we give up. In my testing I have never had it reach the 2nd iteration but it is technically possible if the system is slow.

Enough talk, enjoy the code!

function onLoad() {
   var fields = ['change_request.start_date','change_request.end_date','change_request.work_start', 'change_request.work_end'];
   try {
      //jQuery('a[data-type="date_time"]') this worked in Fuji...
      jQuery('input[data-type="glide_element_date_time"]')
         .each(function (index, value) {
            var ref = jQuery(this).attr('data-ref');
            if (ref.length) {
               jslog('JM: found element at index: ' + index + ' value: ' + ref);
               var val = g_form.getValue(ref);
               if (val == '' && fields.indexOf(ref) > -1) {
                  jslog('JM: registering event for element at index: ' + index + ' value: ' + ref);
                  //need to call .next() in Geneva/Helsinki, not needed in prior versions, to get the calendar button beside the ref field
                  jQuery(this).next().on('click', function () {
                     setTimeout(controlDateTimeSelector, 500);
                  });
               }
            }
         });

   } catch (e){
      console.error('JM: Error occurred: ' + e.toString() + ' on line:' + e.lineNumber);
   }

   function controlDateTimeSelector(iterator) {
      jslog('JM: Starting controlDateTimeSelector.');
      var prefixId = '#GwtDateTimePicker';
      try {
         if (iterator === undefined) {
            iterator = 0;

         } else if (iterator > 5) {
            jslog('JM: Stopping; hit max iteration count.');
            return
         }

         if (!jQuery(prefixId + '_header').length) {
            jslog("JM: Didn't find DatePicker, trying again in 500ms. Iterator was: " + iterator);
            iterator++;
            setTimeout(controlDateTimeSelector.bind(null, iterator), 500);

         } else {
            jslog('JM: Found and Set DatePicker.');
            jQuery(prefixId + '_hh').val('--');
            jQuery(prefixId + '_mm').val('--');
            jQuery(prefixId + '_ss').val('--');
         }
      } catch (e){
            console.error('JM: Error occurred: ' + e.toString() + ' on line:' + e.lineNumber);
      }
   }
}
}

On-The-Fly Event Creation in ServiceNow

Recently I was doing some troubleshooting for an onChange script and trying to figure out why it (or rather it’s event) wasn’t firing. In my digging I came across the code that is responsible for registering events and then kicks off the corresponding scripts. I can’t think of a lot of great scenarios off the top of my head, but perhaps when one field changes, you want the system to then start monitoring for 2 other fields to change (maybe user has to do something in a sequence?); just throwing ideas against the wall but you get the idea; keep it in your virtual toolbox ;).

First we throw our script into a function (should look familiar) only difference to the usual is that we are giving it a name (instead of just “onChange”). In this case we’re naming it “onChange_incident_script” but the name can be whatever you want. Next, we will instantiate a GlideEventHandler object. This object will raise our event and call the script when it fires, it expects 3 parameters: The name of the event handler, the script to run, and the field (in the format of table.field) to monitor:

function onChange_incident_script(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue == '') {
        return;
    }
    jslog('JM: Running Script from on-the-fly event. Original Value was: ' + oldValue);
    //Your code here...
}
var thisHandler = new GlideEventHandler('onChange_incident_impact_change', onChange_incident_script, 'incident.impact');
g_event_handlers.push(thisHandler);

With that said, some of you may be thinking, well why can’t we just use jQuery and do the same thing? Yes, for the most part, however you wouldn’t get the nifty “control, oldValue, newValue, isLoading, and isTemplate” variable data to play with. Just for those curious minds out there, here’s how we could do the close to the same thing in jQuery (minus the extra SN objects):

//we need to use double backslashes to escape the periods in the selector
jQuery('#element\\.incident\\.impact').on('change', function() {
    jslog('JM: Running Script from on-the-fly event. Original Value was: ' + oldValue);
    //Your code here...
});