The right way to use jQuery with ServiceNowFriday, May 17, 2013
Update 2021: I really don't think that this is valid anymore. I haven't tested, so use at your own risk!
A few weeks back I thought I'd clear up some of the commotion around jQuery usage in ServiceNow. I've seen forum posts and read blog entries alluding to its use. I've also been on customer calls where customers have struggled to implement it, or have had problems with upgrading and seeing it conflict with the ServiceNow platform.
Why don't you include it already?
There are a couple of stories that are going around about the history behind this. Some will tell you that we used to include jQuery in our stack, and that we switched to Prototype. As far as I know, jQuery was never used heavily. PrototypeJS, the library that currently drives most of the platform UI, predates jQuery (by just a little).
In 2011, we saw a slight uptick in jQuery usage by ServiceNow development. A customer would note that we added a default UI script called jquery.min, which contained jQuery 1.4.2. We also released UI11 which included jQuery 1.4.2 and jQuery UI.
How were they meant to play together?
At that stage (which started with the June2011 release), everything was on jQuery 1.4.2. This made it nice for two reasons:
- If you included jQuery 1.4.2 twice on a page, it was handled with
- Since the two versions were the same, neither ServiceNow scripts nor customer scripts would notice an API difference.
In essence, the compatibility wasn't necessarily intentional. It was coincidental.
Nonetheless, here's how it worked:
- We include
js_includes.jsincludes your Global UI Scripts.
- If you are in UI11, we'd include the
Essentially, the UI11 jQuery would somewhat override the Global UI Script. Again, it didn't matter because they were both the same version.
The namespace was also conflicting between these two libraries. In the ServiceNow platform jQuery, we'd have a line like the following:
var $j = jQuery.noConflict();
And then again, in the jquery.min UI Script, we'd have a line like the following:
var $j = jQuery.noConflict();
I'll save you some time and point out that they are the same code. Basically, they would overwrite each other depending on the order in which they would run on the browser.
Wait now. Why is this a problem again?
This started a bad pattern. We used
$j for our jQuery, and ServiceNow customers used
$j for their jQuery. In a situation where both
$j's point to the same version of jQuery, there is no real harm. But what about if ServiceNow were to upgrade jQuery? Then we'd start having problems.
Along comes ServiceNow Calgary
As many of you know, ServiceNow Calgary comes with the a new Tablet Interface. This tablet interface also uses jQuery. However, it uses jQuery 1.8.2, which is roughly two years newer than the 1.4.2 version we had before. Since the Tablet UI is a hybrid sort of interface to the ServiceNow platform, this meant that we had to upgrade the entire platform UI code to jQuery 1.8.2. This means that even UI11 is now running on jQuery 1.8.2.
Now we introduced a new problem. If you depended on jQuery 1.4.2, we'd overwrite it. Additionally, depending on how you set up jQuery, ours would produce errors while instantiating. In the end, the only way to free up the contention for the
$j variable is to make sure that we always win. So here's what you can do to avoid the fued:
Best Practice #1 - Don't overwrite
For best results, use a variable other than
$j and other than
$. You can call it
$j_custom, you can call it
$rumpelstiltskin if it works for you.
var $j_custom = jQuery.noConflict(true)
Best Practice #2 - Use
I've found that using
jQuery.noConflict() alone doesn't do the trick. This will relinquish the
$ variable, but it will keep the jQuery variable populated. By adding the
true parameter and writing
jQuery.noConflict(true), we relinquish the jQuery variable as well.
Best Practice #3 - Point to
jquery_includes.js whenever possible
If you are writing Jelly code, you can use the
<g:requires> tag to include your script to include jQuery. In case you don't know, the
g:requires tag will help to include the script once on a page. This reduces load time of the page and also will load the ServiceNow platform version of jQuery.
<?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="true" xmlns:j="jelly:core" xmlns:g="glide" xmlns:g2="null" xmlns:j2="null"> <g:requires name="scripts/lib/jquery_includes.js"/> </j:jelly>
The Spirit of the Law - Don't use jQuery as a Global UI Script
The best idea for now is just to not use jQuery as a Global UI script. I'd ask myself long and hard what I'm trying to do. Chances are, you're using jQuery for one of the following:
eachis just too costly.
- DOM manipulation - You shouldn't be doing this in Client Scripts anyway. I'll probably write about this in the future. Imagine all of the features that the ServiceNow platform comes with, and all the nice features you're expecting to see in the near future. For those changes to happen, the DOM structure of the ServiceNow forms, lists and other pages is going to change. I guarantee it. By avoiding direct DOM manipulation, you avoid the risk of a lot of rework in the future.