Enhancing the Attachment User Experience in ServiceNow

Attachment Dialog

To attach a file to a record in ServiceNow the user needs to click the “paperclip” button on the top right corner of the screen. The icon is small and thus I usually have clients wanting to make it bigger, move it, or somehow make it more obvious. Note that the code we go through below uses jQuery to manipulate the paperclip element in the DOM; normally it’s best to use the API but unfortunately we do not have any API methods so this is necessary. To do this it depends on where it is, if it is a UI Page or UI Macro (usually Catalog Item) then attachment dialog can be triggered in HTML as below:

Generate a Link on a Form in a UI Macro or UI Page:

<img title="Attachments..." src="images/icons/attachment.gifx" width="16" height="16" border="0" /> Click HERE To Attach Your File.

This solves the issue of making it more obvious and makes it more intuitive. If you wanted to simply make the icon bigger then that is fairly straightforward using jQuery and CSS:
Catalog Item

 function onLoad() { 
    jQuery('button.sc_paperclip').css("font-size","37px");
}

Task Form (Incident/Problem/Change, etc.)

 function onLoad() { 
    jQuery("button#header_add_attachment").css("font-size","35px");
}

So these methods work well in making the Attachment functionality more obvious, but what about if you wanted to automatically “trigger” the Attachment dialog functionality (shown when user clicks the paperclip) when something changes, then you can do so using the following:

Client Script

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }
    saveAttachment(g_form.getTableName(), g_form.getUniqueValue());
}

Some Common Hurdles in Scoped Apps in ServiceNow

I was recently working with one of my mentors here at Crossfuze (big shout out to Jacob Anderson for his help!) to create a new scoped app: TFS Integration application. During this process I encountered a number of limitations I had to workaround in relation to scoped apps. Here is a quick summary of things that I found:

1. Script Includes: Never assume that you can reference an OOB “ServiceNow” Script Include, always check that it is “Accessible from:” is set to “All application scopes” (should not be “This application scope only”) before trying to use it (e.g. GlideEncrypter, ImportSetUtil, GlideHTTPRequest, XMLHelper, and GlideDBObjectManager, etc.)

  • Make sure when calling Script Includes outside of your app, that you use the API Name (contains scope prefix): e.g. new global.JSON()
  • A workaround to the “GlideEncrypter” is that you can now use the following code to retrieve the un-encrypted value: “gr.element_name.getDecryptedValue()”

2. Data Imports: When importing data you can’t use the ImportSetUtil so you must create a table that extends the Import Set Row (sys_import_set_row) table in order to load the data

3. Client Scripts: You should not use the DOM directly. Here’s a list of Client side APIs that have been turned off:

  • GlideRecord – can no longer use
  • window, document, $ (Prototype), $$ (Prototype selector), $j (jQuery), $F (Sizzle) – can only use them if you set the property below
  • All APIs above (except GlideRecord) can be re-enabled on a per-application basis. To do so, you need to set a True/False system property in your application named glide.script.block.client.globals with the value false

4. Server methods: Cant directly use methods such as “indexOf, toUpperCase, split…” functions, you must first convert the element “toString()” before using them (or use ArrayUtil if applicable).

5. Business Rules: You cannot use “Query” Business Rules

6. Client Scripts: You can only use the following g_form methods in the same scope as the calling script (don’t try using them on fields you didnt create in your app): “setReadOnly, setMandatory, setDisabled, setDisplay”

7. Long Running Transactions: Be aware of long-running queries (break your pulls/loads into “chunks”), if something in your apps runs longer than 10 minutes it will be killed based on the “Scoped Background Transactions” quota rule. Note that ServiceNow can override this on a per case basis.

8. Client Scripts: No longer can use “Global” Client Scripts within Client Scripts. Now must use UI Scripts for these (makes sense). Previously, if you wrote your own functions outside of the ServiceNow client script (e.g. outside the onChange/onLoad…), then that function was available to other client scripts as it was in the “global” context. That is no longer the case.

9. Forms: Can’t change forms or dictionary values of fields that are not in your scope
10. Fields: You cannot add new fields to the sys_user_group table

 

Have more issues you’ve found that I didn’t mention (very likely), please share them in the comments!

For more details please see the following links:
http://wiki.servicenow.com/index.php?title=Scoped_System_API#gsc.tab=0
https://community.servicenow.com/community/develop/blog/2015/10/01/scoped-applications-and-client-scripts-a-primer
https://community.servicenow.com/community/develop/blog/2016/05/25/client-side-gliderecord-replacement-for-scoped-applications
https://community.servicenow.com/message/848970#848970
https://community.servicenow.com/message/778501#778501
https://community.servicenow.com/message/927530#927530

How to Make Service Catalog Attachments Mandatory in ServiceNow

Recently I was working with a friend who was trying to make attachments mandatory on a catalog item. All of the code references we could find about how to do this were Server side or client side methods that no longer worked. In this case we needed a client-side solution as the “record” did not exist yet (catalog item hasn’t been ordered) therefore there was no reliable way to search for it (to look for it in the sys_attachment table). Luckily I had client side solution from a buddy here at Crossfuze as well, and after some tweaking we were able to get it working in Geneva/Helsinki.

Without further waiting, here’s what it looks like. Please note that it is using the DOM, so you will want to add it to your regression tests for new upgrades to ensure it keeps working.

function onSubmit() {
	//when type = file_needed, ensure an attachment was added
	
	function checkAttachments() {
		var display = $('header_attachment_list_label').getStyle('display');
		if (display == 'hidden' || display == 'none' ) {
			jslog('Attachment Not Found. Display value: ' + display);
			return false;
		} else {
			jslog('Found Attachment. Display value was: ' + display);
			return true;
		}
	}
	
	if(g_form.getValue('type') == 'file_needed' && !checkAttachments()){
		alert('You must add an attachment prior to submitting.');
		jslog(' You must add an attachment before submitting.');
		return false;
	}
	
}

A Hidden Way to Show Form Messages in ServiceNow

I was recently doing some research on g_form methods that ServiceNow provides to display messages on the form. The main two that I was familiar with were “addInfoMessage” and “addErrorMessage”; but what if there were more? I have definitely seen other messages displayed that was neither of these two… After doing some digging for the “hidden” method I was rewarded. The new method I found was called “_addFormMessage”. Note that this is not documented and is prefixed with an underscore indicating that it was meant to be used as an internal ServiceNow method so it is subject to change although it has been there since at least Eureka if that tells you anything :). Enjoy!

g_form._addFormMessage('Testing a form message at the top');

Ok, so that’s fine and dandy, but what if at some point in the future ServiceNow did deprecate this command; here’s one way you could re-implement it yourself using jQuery. Note that in order to make the command accessible via “g_form” we need to create it as a function off of the GlideForm object:

String.prototype.capitalizeFirstLetter = function() {
	return this.charAt(0).toUpperCase() + this.slice(1);
};

GlideForm.prototype.setNewFormMessage = function (message) {
	var tableId = g_form.getTableName().toString().capitalizeFirstLetter();

	var htmlBlock = '<span class="section " id="section-test" data-header-only="false"><div id="' + tableId + '.section_header_spacer"></div><div class="outputmsg_container" style="display: block;" id="output_messages"><button class="btn btn-icon close icon-cross" onclick="GlideUI.get().clearOutputMessages(this); return false;"><span class="sr-only">Close Messages"</span></button><div class="outputmsg_div"><div class="outputmsg outputmsg_ notification notification-"><img class="outputmsg_image" src="images/outputmsg__24.gifx" alt=""><span class="outputmsg_text">' + message + '</span></div></div></div>';

	jQuery("[tab_caption=" + tableId + "]").after(htmlBlock);
};


var message = 'testing 123';
g_form.setNewFormMessage(message);

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

How to Make Variables Read Only Using g_form in ServiceNow

Variables Read Only

I was recently working with a colleague who mentioned that he had seen a new (un-documented) g_form method which allows you to set all catalog item Variables to Read Only. As you can probably imagine, this is great news for all ServiceNow developers who in the past had to resort to DOM manipulation, custom Business Rules or lots of UI Policies (thanks to my mentor Mark Stanger for providing information on these options in the past) just perform this trick (makes it so fulfillers of tasks update variables but they can’t be updated from the catalog item request). Without further waiting; here’s the magic:

g_form.setVariablesReadOnly(true);

Easy Trick to Workaround ServiceNow’s Script Validation Checker

Sometimes you may want to load a script that is functionally correct but ServiceNow has an issue with it. One example of this would be if you are trying to load a 3rd party JavaScript library but there is some code in it that ServiceNow throws an error on that you know is correct. In these circumstances you can add the Script field to the List View. Then Paste in your script.
Load Client Script via List View

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...
});

How to Get the DisplayValue of a Reference on the “Client Side” in ServiceNow

I was working with a client recently and had the need to get the Display Value of a Reference field that was on the form. I didn’t want to have to make an AJAX call just to pull this value (on the server side you would use the “getDisplayValue()” function), and that’s when it hit me. The client already has this value, why not use it? Initially I was thinking about DOM/jQuery which was not going to be ideal. As I dug further I uncovered a little known method to pull the Display Value from a reference field. Drum roll please!

g_form.getDisplayBox('field_name').value

Hopefully this saves someone some time in the future.