Custom Element Pattern

Once a week I will try and publish some of my Patterns.  These will be reusable templates for everything from building apps to creating widgets.  This first week I am going to cover custom elements in forms using Formatters.  Be sure to check back weekly for new patterns.

Many times customers want custom functionality that goes beyond the standard form.  A UI Page is good but it requires the user to leave the form or have the content displayed in a pop-up.  Neither of these options are particularly appealing but there is another method which is not widely used, Formatters.

While this is a fairly common term thrown around I see few people actually using this method so here I will provide an overview (with code  of course) on how to implement various styles of custom formatters.

First a quick review of the process.  Start by creating a UI Macro, then navigate to the Formatters list and add a new one.  Each Formatter can link one UI Macro to one table.  You can create new Formatters if you want to link your macro to multiple tables.

The Name of the Formatter can be a friendly name of your new element.  The Table will be the target table to add the functionality to.  The Formatter is your actual UI Macro name with .xml appended to the end.  Since it is a “file name” your UI Macro should not contain spaces, it’s best practice to use an underscore in place of spaces.

Let’s start off easy with just a simple Formatter, adding an image to a form.

  1. Create a new UI Macro: my_image
  2. Add this to the body of the Macro between the Jelly tags
    <img src="http://replygif.net/i/1403.gif" />
  3. Create a new Formatter
    Name: My Image
    Table: Incident
    Formatter: my_image.xml
  4. Navigate to the Incident form and Personalize Form
  5. Add the My Image formatter to the form
    Formatters are usually located after the Fields in the List

If everything worked out you should have an image on your form.  This is not really a pattern yet but is still the basic way to get static information on a form.

Note: If you ever change the Formatter record (not the underlying UI Macro) you need to re-add it to the form.  

Now that we have gone through creating a basic Formatter let’s add some real functionality to it.  There are three patterns I’d like to walkthrough here: first is a custom Formatter that writes to a field and the second is a dynamic Formatter that displays a list and third is a completely client side Formatter.

Custom Field
This pattern creates a formatter that acts like a standard ServiceNow field, reading and writing to a field like normal.

  1. Create a new String field on a form ( I am using Incident for my examples).  The field should have a length of 100
  2. Once the field is added you want to make sure it is NOT displayed on the form since it would conflict with our Formatter
  3. Create a new UI Macro and add this to the body of the macro between the Jelly tags
    <table cellspacing="2" width="100%">
    <tbody>
    <tr>
    <td id="label.incident.u_custom" title="" data-type="label" choice="0" nowrap="true" class="label label_spacing" type="color">
    <span id="status.incident.u_custom" class="label_description" title="" mandatory="false" oclass="">$[SP]</span>
    <label for="u_custom" onclick="return labelClicked(this);" >Custom Element:</label>
    </td>
    <td>
    <input id="incident.u_custom" type="color" onchange="onChange('incident.u_custom');" name="incident.u_custom" value="$[current.u_custom]" autocomplete="off" />
    </td>
    </tr>
    </tbody>
    </table>
  4. Replace u_custom with the name of your new field and incident with whichever form you are working on.
  5. Create a Formatter, Personalize your form and add the new Formatter

You now should have a custom Color Picker element that acts like a ServiceNow field.

Custom Dynamic List
This pattern demonstrates creating a custom list view based on the current record.

  1. Create a New UI Macro and add this as the body of the Macro between the Jelly Tags
    <g2:evaluate>var sys_id = RP.getParameterValue('sys_id').toString();
    var auditRec = new GlideRecord('sys_audit');
    auditRec.addQuery('tablename', 'incident' );
    auditRec.addQuery('documentkey', sys_id );
    auditRec.query();
    var canViewAudit = gs.getSession().getRoles().toString().indexOf("admin") != -1;
    var row_class = "list_odd";
    </g2:evaluate><br/>
    
    <j2:if test="$[canViewAudit]">
    <table style="width:50%">
    <thead>
    <tr class="header">
    <td class="column_head">User</td>
    <td class="column_head">Date</td>
    <td class="column_head">Field</td>
    <td class="column_head">Previous Value</td>
    </tr>
    </thead>
    
    <j2:while test="$[auditRec.next()]">
    <tr class="list_row $[row_class]">
    <td>$[auditRec.user.toString()]</td>
    <td>$[auditRec.sys_created_on.getDisplayValue()]</td>
    <td>$[auditRec.fieldname.toString()]</td>
    <td>$[auditRec.oldvalue.toString()]</td>
    </tr>
    
    <g2:evaluate>
    if( row_class == "list_odd" ){
    row_class = "list_even";
    } else {
    row_class = "list_odd";
    }
    </g2:evaluate>
    </j2:while>
    </table>
    </j2:if>
    <j2:if test="$[canViewAudit==false]">
    <p>$[SP]Audit View is Only viewable by Administrators</p>
    </j2:if>
    
  2. If you are using a form other than incident change it in the script
    This could be generalized to just grab the table name and be applicable to any table
  3. Create the Formatter and add it to the form.

You should now have an Audit list that will display for Admins.   If nothing is showing up make sure auditing is turned on for the table and a change is made.

Client Side Element
This pattern is the most “wide open” in terms of what can be done.  In this example I will recreate the Short Description field but dynamically.  One key thing to keep in mind is the “dynamic” part here refers to performing some AJAX operation.  These calls should always be asynchronous and not block the User.

  1. Create a new UI Macro and add this to the Macro body between the Jelly tags
    <table cellspacing="2" width="100%">
    <tbody>
    <tr>
    <td choice="0" nowrap="true" class="label label_spacing" type="color">
    <label for="dynamic_custom">Short Description:</label>
    </td>
    <td>
    <input id="dynamic_custom" type="text"/>
    </td>
    </tr>
    </tbody>
    </table>
    <script src="https://code.jquery.com/jquery-2.1.1.min.js" />
    <script>
    jQuery.noConflict();(function($){var sys_id = gel('sys_uniqueValue').value;
    
    getDynamicValue();
    
    function getDynamicValue(){
    var incidentRec = new GlideRecord('incident');
    incidentRec.addQuery('sys_id', sys_id);
    
    incidentRec.query( function( incidentRec ){
    if( incidentRec.next() ){
    $("#dynamic_custom").val( incidentRec.short_description.toUpperCase() );
    }
    })
    
    }
    
    })(jQuery)
    </script>
  2. Change references to incident to whichever table you are using
  3. Create the Formatter and add it to the form

When the form loads it will dynamically fetch the Short Description and fill it in.  Keep in mind this is read only, but you could combine this pattern with the others to create that link.

Obviously in practice you will want to do more than get the Short Description and you really are not limited.  You may load in your UI Macro any JavaScript library so I have used this same method combined with DataTables, Handlebars, AngularJS and many more libraries to create rich functionality.

You may also use this technique to load data via Web Service dynamically, creating the effect of having all data from external systems within a ServiceNow form without needing to store it.

Almost every custom element derives from these patterns, if I come up with more I will be sure to share and am interested in seeing what everyone else comes up with!

Generate an Angular Service from a Script Include

I figured I would dedicate Fridays to Angular and Jelly articles, first up here is generating an Angular Service automatically from a Script Include.

You might be tempted to just put GlideAjax calls right within your Angular Controller but that’s bad.  It clutters your controller and if you ever need to use that same function you would need to copy and paste that code.  On the other hand if all you need to do is create a Service that calls a Script Include it makes no sense to create UI Scripts for each that all essentially do the same thing.  To me this is not a problem but a chance to simplify creation of these Services by using a Processor.

To start I need to programatically extract the functions and parameters from a given Script Include.  Initially I thought a Regular Expression might work but it’s not a good solution.  Instead I chose to use Esprima to generate an Abstract Syntax Tree (a sort of representation of the code as a tree) and use that to find functions and parameters.

Esprima (http://esprima.org) actually loads right into ServiceNow as a Script Include without any issues.   Once loaded you can just include that Script Include and create syntax trees from any code in the system.

I created a Processor which generates an AST from a Script Include.  I look through the AST to find the main object (the one that is assigned to a variable of the same name as the Script Include) and then looked through that object for functions.

Each function not prefixed with _ is expected to be a public function and will be added to the returned Angular Service.

Within the body of that function if I find any assignments that have “getParameter” in the expression I pull out the name and use that as a parameter for the Angular Service method.

I also included a function to parse the XML response that is automatically called when the response comes back from ServiceNow.

Now for any Script Include, which extends AbstractAjaxProcessor, I can link to my processor which will generate an Angular Service with all the functions from the Script Include automatically.  I can inject that service into my apps controllers and call the functions as if they were on the client side.

Instead of using GlideAjax I use the Angular $http provider which returns a promise object.  So when you use the result of the function call you must do so through a `then` function.  Check out the examples below to see this in action.

github:gist https://gist.github.com/salcosta/c68686e4e82c237dd627


Example

The Angular Service that should be generated from this Script Include should have 2 methods with one accepting `sysparm_sys_id` as a parameter.

var IncidentTest= Class.create();

IncidentTest.prototype = Object.extendsObject(AbstractAjaxProcessor, {

getIncidents : function(){
var sys_id = this.getParameter("sysparm_sys_id").toString().toString();

return sys_id
},

getIncidents2 : function(){
return "test"
}

});

Here is the example output that would be generated.


angular.module('IncidentTestService', []).factory('IncidentTestServ', ['$http', function($http) {

return {
config : { headers: {'Accept' : '*/*', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8','X-UserToken': g_ck } },
url : 'xmlhttp.do',

getIncidents : function ( sysparm_sys_id ){
var data = 'sysparm_processor=IncidentTest&sysparm_name=getIncidents&ni.nolog.x_referer=ignore&x_referer=home.do&sysparm_sys_id=' + sysparm_sys_id + ''

return $http.post( this.url , data , this.config ).then( getAnswer );
},

getIncidents2 : function ( ){
var data = 'sysparm_processor=IncidentTest&sysparm_name=getIncidents2&ni.nolog.x_referer=ignore&x_referer=home.do'

return $http.post( this.url , data , this.config ).then( getAnswer );
}
}

}]);

Here’s a sample usage in a UI Page.

HTML

<script src="IncidentTest?ng-service"></script>

Script

angular.module("Inventory",['InventoryCtrl','IncidentTestService']);

angular.module("InventoryCtrl",[]).controller("InventoryController", ['$scope', 'IncidentTestServ', function($scope, IncidentTestService){
IncidentTestService.getIncidents("Test").then(function(res){
console.log(res)
});
}]);

ServiceNow Coding Style Guide Part 1

I thought I would kick of this new blog with a topic that is very important to me,  Coding Style in ServiceNow.  Here I will present a short list of rules and principles to guide your coding.

Following this guide will make your code more readable which will make maintaining the code much easier.  That means less time trying to decipher your or co-workers code and lower costs to maintain code.  This is true of any code base but especially in ServiceNow where the developer ecosystem is so varied.

The end goal is to have all code appear to be written by a single person.

Once a good track record can be established on following these practices then you can start to introduce higher level concepts and principles.  More on those in future articles.

Personally, I disagree with long style guides and best practices documents.  If there are too many rules to start it is impractical for new developers to follow them all.  My approach is to start with the fundamentals.  Once they are mastered then you can layer in more advanced practices.  So for now I am keeping the list short. (It should fit on an index card)

Naming
Functions should be named like verbNoun, using camel case. This will make it easier to remember as well as forcing the function to perform a specific purpose.

Variables should not have abbreviations, ever.  It is preferable to use camel case when creating variables although underscores have started to be used instead.  It does increase readability but most existing code will not follow this.

Classes should always be camel case with the first initial capitalized.

Constants should be all upper case.

All record names should follow a convention.  I prefer Prefix – Script Name, where the prefix is the company name or application name.  This is applicable only where the record name is not the script name such as on Business Rules and Client Scripts.

Script Includes should follow the same convention as classes.  UI Scripts should be all lower case using underscores to separate words.

Formatting
There are two options for brackets, same line or not.  I prefer same line.  As long as you are consistent it is a matter of opinion

Whitespace should be used liberally to increase readability.  I favor using spaces within parentheses.  Line breaks to separate blocks of code within functions and two line breaks between functions.

Indentation should be 2 or 4 spaces but be consistent.

Semicolons should always be used.

Commenting
By all means, if you feel your code is to complicated use comments to clarify, but before you do that ask, “Can I rewrite this to make it clearer?”  If the answer is yes, rewrite the section and see if the comment is still necessary.  The goal is for the code to document itself.

Odds and Ends
Always use === instead of == to compare values.  This will prevent accidental coercion of variables.

Try and use ‘ instead of ”

Avoid ternary operators

Avoid eval

Principles
Don’t Repeat Yourself.  If you find yourself writing the same code over and over generalize it and move it to a separate function.

Never just copy and paste code.  Always rewrite it and try to understand line by line what it’s doing.  While it’s tempting to just inject a bunch of functionality, that is not going to help if there is an error or you need to modify the code.

Try to keep your code separated by intention.  For instance don’t have a single function that does a database call and then immediately updates the UI.  This is called Separation of Concerns and is the basis for other more specific principles like MVC.  Separated code will make it easier to update and reuse.

Finally, care about your craft.  Your code will likely never hang in a museum but the functionality that you are building matters to your users and it should matter to you as well.

Bonus Cheat Sheet!

Cheat Sheet
Coding Style Guide Part 1 Cheat Sheet

 

Reboot

After a ton of issues with the old site I have decided to reboot on WordPress 4.0.  This is going to be a lot cleaner and a lot easier to manage.

Most of the old content is no longer useful but what is I will selectively import and update.

In addition I will be bringing new content featuring tutorials on JavaScript and web development, integrations, best practices, administration tips and tricks and the latest ServiceNow news.

Thanks for visiting and check back often!  New content will be published weekly.

Visit Us On Twitter