In November, I wrote a blog post called Background and Philosophy of Scoped Applications. It recently generated an interesting discussion about the limits of the available APIs for scoped apps, with focus on one specific function: gs.dateGenerate. This post will hopefully help you figure out how to work within the available APIs to achieve the same goals in a scoped application that you could achieve in a global one.

 

The scoped API

The scoped API is mostly a subset of the existing Glide API that we all know and love. Both the legacy and the scoped API set are documented on our ServiceNow Developers  site. Since the scoped API is essentially a subset of the legacy API, there will necessarily be some classes and functions which are not available. One such method is "gs.dateGenerate". The scoped API includes GlideDateTime and GlideDuration, and is capable of doing all of the same calculations that gs.dateGenerate can do, but we have to do a little bit of work to make that happen.

 

Utility classes

I'm a fan of writing utility classes. I like making tools, probably more than I like making things with those tools. A utility class is typically the first thing I start with when I make a new scoped application. It starts out empty, and I add to it as I find myself needed the same generic functions multiple times during the course of development. When I find myself using the same things across multiple applications, I'll pull those out into a new application, and make my app depend on that. I don't want to reinvent the wheel every time I need to make a wagon.

 

Creating the Utility

The most basic utility class is really just an object. This is a good pattern to use, because it prevents you from having to new it every time you need to use it. For our example, we're going to create an object with a single function. We'll give it a short name so it's easy to use, and we won't be using the default template it auto-generates for us.

Screen Shot 2016-04-15 at 11.38.49 AM.PNG

I named my include "Tools" and marked it "Client callable" and "Accessible from: All application scopes". Client-callable enables us to use this include in URLs, which is important for our use-case. Making it available to other application scopes means that I am committing to making this an API point for my application. I won't introduce breaking changes without prior notification to my potential users, and the code I am providing should be side-effect-free. You don't need to make your tools available to other scopes unless you are writing an application specifically to provide a toolset to other applications.

 

I've also changed the Script. By default the Script Include form auto-generates a template for our include based on the anme we provided it. The template looked like this:

var Tools = Class.create();
Tools.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
    type: 'Tools'
});

 

I've decided that this particular set of tools doesn't need to maintain state internally, so there is no reason to use Class.create(). If multiple people want to work with the methods on this object, they can do so without colliding. That's why we're starting with an empty object:

var Tools = {};

 

The first function we want to add is a replacement for gs.dateGenerate. It helps to know that the original dateGenerate function is really just a convenience method that works on GlideDateTime objects. If we were to convert the function from Java to JavaScript, it would look like this:

gs.dateGenerate = function(date, range) {
   var gdt = new GlideDateTime();
   if (range.equals("start"))
      gdt.setDisplayValueInternal(date + " 00:00:00");
   else if (range.equals("end"))
      gdt.setDisplayValueInternal(date + " 23:59:59");
   else
      gdt.setDisplayValueInternal(date + " " + range);

   return gdt.getValue();
}

 

That is pretty simple. We'll copy that nearly verbatim into our Tools include, since every object type it access and every option it does is supported within the scoped API. When we copy that into our include, it looks like this:

var Tools = {};


Tools.dateGenerate = function(date, range) {
    if (!range)
        range = '12:00:00';
    
    var gdt = new GlideDateTime();
    if (range.equals("start"))
        gdt.setDisplayValueInternal(date + " 00:00:00");
    else if (range.equals("end"))
        gdt.setDisplayValueInternal(date + " 23:59:59");
    else
        gdt.setDisplayValueInternal(date + " " + range);
    
    return gdt.getValue();
}

 

We added a default value for the range. The original Java function doesn't supply a default value and returns an empty result if you call it that way:

var generatedDate = gs.dateGenerate('2016-04-14');
gs.print(generatedDate);

 

[0:00:00.001] Script completed in scope global: script


*** Script: 

If we left out the default, we would have the same result. That's up to you- it's your utility.

 

Using the utility

Now that we have our replacement for dateGenerate, we want to make use of it. There is one part of the system that relies on dateGenerate pretty heavily: encoded queries. If you open the Incident list and filter between two dates you can see this happen:

Screen Shot 2016-04-15 at 2.02.46 PM.PNG

The encoded query being run on this list is:

opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')

 

If we attempted to run that same query in a script in our application, we would get an error:

var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";
var inc = new GlideRecord('incident');
inc.addEncodedQuery(enq);
inc.query();

gs.info(inc.getRowCount());

[0:00:00.248] Script completed in scope sn_custom_app: script

Security restricted: Read operation against 'incident' from scope 'sn_custom_app' was granted and added to 'sn_custom_app' cross-scope privileges

Evaluator: com.glide.script.fencing.MethodNotAllowedException: Function dateGenerate is not allowed in scope sn_custom_app

   Caused by error in script at line 1

 

 

==>   1: var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";

      2: var inc = new GlideRecord('incident');

      3: inc.addEncodedQuery(enq);

      4: inc.query();

 

 

Evaluator: com.glide.script.fencing.MethodNotAllowedException: Function dateGenerate is not allowed in scope sn_custom_app

   Caused by error in script at line 4

 

 

      1: var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";

      2: var inc = new GlideRecord('incident');

      3: inc.addEncodedQuery(enq);

==>   4: inc.query();

      5:

      6: gs.info(inc.getRowCount());

 

 

Background message, type:error, message: Function dateGenerate is not allowed in scope sn_custom_app

Evaluator: com.glide.script.fencing.MethodNotAllowedException: Function dateGenerate is not allowed in scope sn_custom_app

   Caused by error in script at line 1

 

 

==>   1: var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";

      2: var inc = new GlideRecord('incident');

      3: inc.addEncodedQuery(enq);

      4: inc.query();

 

 

Evaluator: com.glide.script.fencing.MethodNotAllowedException: Function dateGenerate is not allowed in scope sn_custom_app

   Caused by error in script at line 4

 

 

      1: var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";

      2: var inc = new GlideRecord('incident');

      3: inc.addEncodedQuery(enq);

==>   4: inc.query();

      5:

      6: gs.info(inc.getRowCount());

 

 

Background message, type:error, message: Function dateGenerate is not allowed in scope sn_custom_app

sn_custom_app: 0

 

But when we substitute in our dateGenerate function, the query returns the right value:

var enq = "opened_atBETWEENjavascript:Tools.dateGenerate('2016-02-01','12:00:00')@javascript:Tools.dateGenerate('2016-03-31','12:00:00')";
var inc = new GlideRecord('incident');
inc.addEncodedQuery(enq);
inc.query();

gs.info(inc.getRowCount());

 

[0:00:00.005] Script completed in scope sn_custom_app: script


*** Script: 8

 

Manually substituting "Tools" for "gs" in encoded queries is fine, but we can make this a bit more user-friendly. We can write a second utility function to do it for us:

var Tools = {};

Tools.dateGenerate = function(date, range) {
    if (!range)
        range = '12:00:00';

    var gdt = new GlideDateTime();
    if (range.equals("start"))
        gdt.setDisplayValueInternal(date + " 00:00:00");
    else if (range.equals("end"))
        gdt.setDisplayValueInternal(date + " 23:59:59");
    else
        gdt.setDisplayValueInternal(date + " " + range);

    return gdt;
}


Tools.fixFilter = function(filterString) {
    if (!filterString)
        return filterString;

    //lets ensure we are working on a string:
    filterString = filterString + '';

    //fix dateGenerate
    var dateGenerateRegex = /gs\.dateGenerate/gm;
    var ourDateFunction = "Tools.dateGenerate";

    return filterString.replace(dateGenerateRegex, ourDateFunction);
}

 

Then we can use our fixFilter utility on our encoded query:

var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";
var inc = new GlideRecord('incident');
inc.addEncodedQuery(Tools.fixFilter(enq));
inc.query();

gs.info(inc.getRowCount());

[0:00:00.004] Script completed in scope sn_custom_app: script


sn_custom_app: 8

 

And because we made this open to all application scopes, other applications can take advantage of them too:

var enq = "opened_atBETWEENjavascript:gs.dateGenerate('2016-02-01','12:00:00')@javascript:gs.dateGenerate('2016-03-31','12:00:00')";
var inc = new GlideRecord('incident');
var Tools = sn_custom_app.Tools;

inc.addEncodedQuery(Tools.fixFilter(enq));
inc.query();

gs.info(inc.getRowCount());

[0:00:00.004] Script completed in scope sn_other_app: script


sn_other_app: 8

 

We are expanding the scoped API with each release, and gs.dateGenerate should be natively available in the feature release that comes after Helsinki. Until then, a utility script like the one above can provide the same functionality.

 

I hope this has been a useful primer on Utility scripts. They are a great way to provide reusable, encapsulated code for all of your applications.