How to Show Images in Info Messages in ServiceNow

I was recently working with a client who wanted to show an image whenever a specific addInfoMessage was displayed on the incident form. My first thought was that this was going to be a piece of cake since I knew that Info Messages (used in an “onDisplay” Business Rule in this case) could contain HTML.

So I began down my path. I first went to the “image picker” to find a suitable image; while there were a number of potential candidates, nothing really “popped”; so I decided I would add my own image/icon:

    1. First I went to Google’s image search and quickly snagged a great looking icon
    2. Opened the icon up in Gimp, and resized it to 20×20 (was 256×256 which was way too big for an info message)
    3. I then went to System UI -> Images -> New
    4. Specified a Category of “General”, and “Name”: IconName.png
    5. Then uploaded the image
    6. Now with the image in place we theoretically just needed to reference it in the infoMessage
          gs.addInfoMessage('<img src="IconName.pngx" /> This task message includes an icon')

And All Done… Right? Well, not so fast. Apparently, ServiceNow doesn’t display the image correctly when it is called in this manner. In order to workaround this behavior we have to perform some inline CSS styling (Nasty I know…) in order to display it “inline” with a width of 20px:

          gs.addInfoMessage('<img style="display: inline !important; width: 20px;" src="IconName.pngx" /> This task message includes an icon')

After adding in the styling the image was displayed properly and all again was right with the world.

 

Watch Out for DELETE Actions in Update Sets in ServiceNow

Typically when you see a “DELETE” in an Update Set it’s a bad thing. Either you didn’t mean to delete whatever it is deleting, or you’ve deleted a “brand new” element (something that was both created and then deleted in the new update set which does not exist in Test/Prod). For these reasons it is always a good idea to review your update sets prior to pushing them up to the Test instance to ensure that there are not any unexpected surprises lurking behind the covers.

Ok, now that we’ve discussed the usual scenario; it’s time for an edge case. In some rare cases you may actually want to delete something that exists in your other instances. One such use case would be that you have a field that is in the format of a “Glide DateTime” and you want it instead to be a “String” but you need to keep the name the same since there are a lot of other scripts referring to it. The system does not allow you to just change the type in the dictionary; instead you have to delete it and re-create the field as a “String”.

The problem is that by deleting the element and re-creating it with exactly the same name the system will record the DELETE and then when you re-create the field with the same name it will overwrite the DELETE with an “INSERT_OR_UPDATE” at which point you have now lost your previous DELETE update. When you try to then apply your Update Set to the target system it will throw an error “Cannot convert Glide DateTime field to type String…” and the Preview of that Update Set will fail. In this case, the workaround to this issue is to record your DELETE in a separate Update Set which will be applied first, then record your INSERT_UPDATE in another Update Set to be applied afterwards. By doing it in this manner the system will not overwrite the DELETE and therefore the Preview and commit on both Update Sets will now succeed. Hopefully you never run into this scenario but I hopefully it will save someone some head scratching at some point.

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

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