E
very now and then I come across a question dealing with the order of execution for client-side code (Client Scripts and UI Policies). In my experience, the need to order client-side code is pretty rare and really only applies in a few ‘onLoad’ scenarios and even fewer ‘onChange’ scenarios. Usually, the way that a browser parses and executes client-side code means that the ordering is pretty unpredictable. It’s worth mentioning that this issue doesn’t exist with server-side code precisely because the browser isn’t involved (which is why you can neatly order all of your business rules without issue).
In this post I’ll show you some of the techniques I’ve used in the past to provide some control over the order of execution of client-side code. I’ll also show you a pretty cool trick that you can use to ensure that a piece of client-side code runs before any other client script or UI policy…or after ALL of those scripts finish running.
Solving the ‘before…and after’ problem
The first thing you should be aware of is that you do have some limited ability built-in to control the order of execution for client-side code with the ‘Order’ field on UI policies.
There are a few caveats with the ordering of client-side code though.
- Ordering IS NOT available to client scripts, only UI policies
- Client scripts will start their execution before UI policies
- Even with the order field on the UI policies, it only controls the start order of execution for the scripts. The actual order that various portions of those scripts run depends on the complexity and variety of the scripts so you can’t really guarantee that a particular piece of client-side code will run before or after another.
You can also set up a wait or delay in your script by using the ‘setTimeout’ method (or something similar).
Guaranteeing absolute before…and after…
I was recently asked to help solve a problem with a long-loading form. The form took several seconds to render and hide various form sections, fields, and related lists. The problem was that during this time, users could interact with the form before it was ready to use. This can cause issues with incorrect submissions, mis-routed calls, and the general undoing of all of the great things you designed the form to do in the first place. Before I explain this solution you should know that the overwhelming majority of issues in this area can be prevented by following a few simple rules…
- Limit the amount of information on your form to what is absolutely necessary. Dozens of form sections and related lists and hundreds of fields on a form just aren’t going to load quickly no matter what you do…even if they’re hidden!
- Stay away from ‘GlideRecord’, ‘getReference’, and ‘GlideAJAX’ in your client scripts and UI policies! If you do need to use any of these then use asynchronous processing if at all possible!
- Follow these best practice guidelines in your client scripts.
Sometimes process and bureaucracy win out over performance and sensibility however, and you have to deal with a problem that you would rather avoid in the first place. One idea would be to use the loading dialog that I described in a previous post on ServiceNowGuru. This would prevent user interaction with the form until the entire thing finished loading and all of the scripts had finished their execution.
The dialog is easy enough to execute, but the real problem is guaranteeing that the dialog displays before anything else on the form (which you can’t do with client scripts or UI policies) and knowing definitively when the form finishes loading so that you can close the dialog.
There’s simply no way to guarantee that a client script or UI policy will run before the form loads. The actual fields (and formatters) will be rendered before the scripts run though. The solution I came up with was to create a custom hidden field (by utilizing a UI macro and UI formatter) to execute some code before any other client scripts run. The script is actually very simple. It just shows the loading dialog, uses Prototype’s ‘Event.observe’ function to identify when the form has finished loading, and hides the dialog.
var loadingDialog = new GlideDialogWindow("dialog_loading", true);
loadingDialog.setPreference('table', 'loading');
loadingDialog.setTitle('Loading...'); //Set the loading dialog title here...
loadingDialog.render();
//Wait until the form has finished loading
addLateLoadEvent(function(){
loadingDialog.destroy();
});
You can set up the UI macro and UI formatter as follows…
Name: form_loading_dialog
XML:
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_show_dialog" expression="!RP.isPopup()" />
<j:if test="${jvar_show_dialog}" >
<script>
//Show the loading dialog immediately as the form loads
var loadingDialog = new GlideDialogWindow("dialog_loading", true);
loadingDialog.setPreference('table', 'loading');
loadingDialog.setTitle('Loading...'); //Set the loading dialog title here...
loadingDialog.render();
//Wait until the form has finished loading
addLateLoadEvent(function(){
loadingDialog.destroy();
});
</script>
</j:if>
</j:jelly>
Name: Form Loading Dialog
Formatter: form_loading_dialog (or the name of your UI macro)
Table: Task
Type: Formatter
Once you’ve set up the UI macro and UI formatter for the table(s) of your choice, all you have to do is personalize the form for any long-loading form and add the ‘Form Loading Dialog’ formatter to the first form section on the form. Enjoy!
Great Article. Easy to implement and very effective. I like the comment about keeping the form simple as an alternative to using this solution. Limitied use of complex client side scripts and synchronous Ajax calls to the Server.
Thank You.
Thanks Steve! It’s definitely better to avoid a long-loading form in the first place.
Awesome solution! I love it and it works great.
The only issue I ran into with is that it didn’t seem to play nicely with reference icons. When hovering over a reference icon the form would begin to load, the dialog would pop-up and then the form would disappear. At this point the loading dialog would be on screen and just sit there, never to stop loading. 🙂
By removing the form loading dialog from the sys_popup view I was able to solve the issue.
Thanks again Mark!
Yep, I noticed that too a little bit after I posted the solution. I updated the script a couple of days ago with a fix. Thanks for the feedback!
Mark!
This seems to solve the number one annoyance I have with ServiceNow!!!
Thank you so much.
Now on to number two . . . I’ll let you know when I figure out what it is.
Hi,
thanks for this wonderful solution.
I just want to warn you that with some user and with internet explorer we have an issue, the form is stuck loading…
There is an error message in ie :
Message: HTML Parsing Error: Unable to modify the parent container element before the child element is closed (KB927917)
I will have to remove this loading form from everywhere until I found where it comes from 🙁
Thanks for the feedback. If you can give me more details on where you’re using the script, seeing the problem, and how to reproduce the problem I can try to investigate. My guess is that there’s some conflict with another client script on that particular form but I’ll need more details to even begin to give a good guess.
in fact I cannot give a lot more details as I cannot reproduce it myself :s.
It only happens with some users, not every users.
It’s only with internet explorer.
I use your loading solution on the change_request, change_task, incident and problem.
For the users impacted, the issue is on every form with the loading form
Let me know if you find anything. The only issue I know of was the issue with hover icon popups (see the comment from Matt Benson). That issue has been resolved however if you use the latest version of the script found in the article above.
Yes, I use the last version of your script…
thanks anyway
The loading dialog is the perfect solution to our overly complex Incident form. Thanks Mark.
It appears that some users have issues when the form loading box does not disappear. Following error message is displayed:
“Message: HTML Parsing Error: Unable to modify the parent container element before the child element is closed (KB927917)”
It appears that the issue is prominent in IE6 and some installs of IE7 & IE8. (IE specific issue).
Is there any solution for this issue that you could suggest?
I think I’ve seen this issue reported before and it was caused by more than one dialog being displayed at the same time. Check to make sure you’re not calling or displaying multiple dialogs. Other than that, I’ve never seen a problem with the loading dialog unless you’ve got some custom script running that breaks and causes the code to exit before it can close the dialog.
I have checked all the scripts on the forms. There are no conflicting dialog boxes. This error shows up only for some users on specific computers. Other users are able to access the form normally.
Hi Mark,
this is proving really useful on my current implementation assignment. Do you know if it is possible to amend the content of the pop-up? I found an entry in the sys_ui_message table with the Key “Loading…” and changed this – but it doesn’t have any effect. Also, I would like to change the dialogue header (currently “Dialog”).
Brian
Hey Brian,
Thanks for the feedback. I’m not aware of any way to easily customize the contents of the generic loading dialog. You can replicate the same functionality with a completely custom dialog though. Your dialog could use whatever UI page you wanted and set the title however you wanted as well.
Mark
Hello Mark,
Great post.
I was trying to run a script which runs after all the options of Slush Bucket gets filled(This is the ajax call populateSelect()), but wasn’t able to succeed, Can we extend this post to handle my scenario? Thanks.
It’s going to be more challenging with a slushbucket, especially if the slushbucket isn’t a catalog list collector. The only way to get client scripts to run in the regular slushbucket interface is with a UI script. Even then it’s a pretty big hack so I wouldn’t attempt it. If you’re working with a list collector variable then it would be easier, but you still have to attach to several different elements in that interface. I don’t have any solution to do what you’re asking and I would advise against this modification unless it’s something that you absolutely can’t do without.
As always, Thanks a ton ! 🙂
Hello Mark,
Thanks for your post, great and useful as always.
I have a question: do you know if all the ‘onLoad’ scripts are guaranteed to be executed before the ‘onChange’ scripts on page load?
My use case is the following: I have 2 onchange scripts (on different fields) that have very similar code. I would like to put that common code in a single place, in a function somewhere where it can be called by the ‘onChange’ scripts.
Normally a UI Script is good for this but it creates another .js file that has to be downloaded by the browser, and is present in every page, not just on the required table.
So I thought of making an ‘onLoad’ script which stores the function in the “window” object:
window.myfunction = function( ) { … }
And then just call it from the onChange scripts:
… myfunction() …
And it works well, but I am wondering and I was just lucky that the ‘onLoad’ script is executed before the ‘onChange’ scripts by luck or if it is always like that. If the ‘onChange’ scripts got executed before the ‘onLoad’ scripts, they would use a function that is not yet defined.
Cheers,
David
This is a good question, but one that I haven’t proven out an answer for yet so I’ll give you my best guess and a couple of suggestions. 🙂 The solution you’re proposing was recently put into the product to facilitate the incident priority calculation via the impact and urgency fields. However, the onChange scripts in this case are specifically set to not run onLoad so they bypass the potential problem. Another option to consider would be to avoid onChange scripts completely and set up everything (including field-specific onChange handlers) in an onLoad client script. This approach should work because the fields and values should always be in place for the onLoad script. I’ve actually considered changing my assignment and priority lookup solutions to use this type of technique.
Hi,
With Firebug, I have put breakpoints in a lot of onload scripts and a lot of onchange scripts. All the onload ones got executed before the onchange ones. I also noticed that they are defined differently. The last step would be to go up the function call stack and discover how the Service-Now client-side “core” is calling all the stuff.
In any case I think it’s pretty safe to assume that the onload scripts get executed before the onchange scripts – and I would doubt that this changes in the future.
Cheers,
David
Thanks David! That’s great information.
Hi Mark,
I have a business rule on table A. This is an after update business rule.
One of the columns in table A is a reference to hardware table.
Action which causes update on Table A, SOMETIMES CAN change hardware status on the CI and when this happens I want business rule to fire after the change to hardware status.
There is a delay of around 3 seconds between update to Table A and change of hardware status to CI, so I am not able to use new hardware status in my business rule.
Is there a way I can create some delay inside my business rule or script include so that it can fire 3 seconds after the update – something like setTimeout?
Hi Sandy,
This article is all about client-side transactions. If you want a delay in a server-side script, you could try a ‘gs.sleep(3000)’ command or an asynchronous business rule.
Thanks so much Mark!
Can you tell me how I can control async business rule – will it not fire at random time!
In Berlin, the Loading dialog disappears before the form finishes rendering/processing the Client Scripts and UI Policies and on Before Business Rules and Display Business rules. Client Transaction shows browser (client scripts) are taking the longest to load but when I disable the two client scripts it does not resolve the issue. Looks like Event.observe(window, ‘load’, function() { doesn’t wait for all the processing to finish anymore. Does that sound right? Is there a new event that would identify when the form is done loading? I have switched the UI Macro to use window.setTimeout at 4 seconds but that won’t help if the form takes longer than that to finish rendering/loading. Any suggestions?
Thanks,
Michele
I haven’t seen this before, but I am able to reproduce it. I think the issue is that ServiceNow isn’t returning the window load event at the correct time anymore. Unfortunately, I don’t know what else you could trigger on. If you figure anything out, please let me know.
This seems to work better…
[code]
showLoadingDialog();
addLateLoadEvent(function(){
hideLoadingDialog();
});
[/code]
Same problem in my testing with this code. I’ve got it set up on the incident form on demo010 right now if you want to take a look.
I know this is an old topic and the reply might be a tad late, but anyway here it goes 🙂
I just had the same issue with the Loading dialog disappearing before the form was loaded. My Loading dialog is called from an onSubmit Catalog Client Script and the script is attached to an Order Guide (actually also to all Catalog items in the Order Guide via Variable set).
There is two possible solutions for waiting the form to be loaded:
document.observe("dom:loaded", function() {
hideLoadingDialog();
});
or, if you’re using jQuery:
$( document ).ready(function() {
hideLoadingDialog();
});
I’m looking at it in demo010 and it seems to be dropping the “Loading” dialog box when the form finishes loading for me. I notice demo010 has a several errors on the pages so maybe that is part of the issue?
I’m also testing it on a client instance where they have a bunch of client scripts and UI Policies and the fields all “resolve” then the loading dialog goes away.
And I’m testing this in Internet Explorer.
Anyway, if that code isn’t going to be reliable then I’ll keep looking.
Thanks-Michele
Looks like your solution handles most situations, but lots of embedded related lists seem to throw it off for some reason. I’m going to adjust the code above with your fix since that does seem to work better. Thanks!
is it possible to visible the Loading Dialog Box, when we change some values in the field?
Mark, once again, excellent solution, kudos for you man.
It worked like a charm, thanks so much!!
Thanks Felipe! I’m glad it helped.
Hi Mark, have not been in touch for ages…. sorry about that, I guess, well meet at K15? I just had a re-read of your post which I have used in the past. Howeer today I am looking to use this for a very complex catalog item… The item (don’t ask) has >200 Fields and Policies an client scripts (and load time) to match. I can’t get this to execute for a catalog item…
It may be a timing issue. You can try using a ‘setTimeout’ call to make sure ‘showLoadingDialog’ is available to call. Something like this in your UI macro maybe.
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_show_dialog" expression="!RP.isPopup()" />
<j:if test="${jvar_show_dialog}" >
<script>
//Show the loading dialog immediately as the form loads
setTimeout(function(){ showLoadingDialog(); }, 500);
//Wait until the form has finished loading
addLateLoadEvent(function(){
hideLoadingDialog();
});
</script>
</j:if>
</j:jelly>
Hello,
This does not seem to work in FUJI. Is there any other way to make it work? Thanks!
This issue has to do with the fact that ‘showLoadingDialog’ isn’t available to call right when the form loads due to some changes ServiceNow has made in Fuji. The only way I know of to attempt to work around the issue is to delay the display of the loading dialog. I don’t know if it works 100% of the time, but you can see an example of that type of script at the bottom of the comments section here.
This works on Helsinki:
var loadingDialog = new GlideDialogWindow("dialog_loading", true);
loadingDialog.setPreference('table', 'loading');
loadingDialog.setTitle('Loading...'); //Set the loading dialog title here...
loadingDialog.render();
//Wait until the form has finished loading
addLateLoadEvent(function(){
loadingDialog.destroy();
});
Yes it does! Thanks so much for posting this solution here. I was pretty disappointed when the old functionality was broken so this is very nice to have something that works again. I’ve updated the article above with this code and some new screenshots.
Does this work in UI15?
I am also interested if this works in UI15 with Fuji. I wasn’t able to run this workaround. Thanks in advance.
Great post Mark, just implemented in Geneva.
Will try it out in Istanbul.
Thanks! Should work great in Istanbul as well.