JQuery for Everyone: Degrading Dynamic Script Loader
When we develop a ton of one-off scripts and add them to the page with Content Editor Web Parts, we slowly kill the page’s performance because <script> tags block each other–they wait for each other to load. Inline scripts can also slow rendering by blocking other elements. By placing the scripts we want to load in a configuration file, we can load them dynamically and drastically increase performance.

Warning: This article documents my most complicated project to date with jQuery. I will try to keep things brief but digestible. Feel free to ask questions so I only expand where needed.
What I know about performance:
I can not explain it as well as Steve Souders, but basically:
- several external file references = bad
- several inline scripts = bad (we start with an average of 8 from WSS alone!)
- loading scripts asynchronously = good
- loading scripts as part of the onLoad handler = good
- keeping scripts with dependencies in order = good
The Bad and the Ugly 
The Good 
What I know about administering dozens of scripts:
Managing dozens of scripts, with versions, web parts, and inline configuration will give an administrator nightmares. A common programming technique can rescue us–the configuration file. We need a configuration file that describes what we need to load and when.
How I solved performance and administration problems:
I wrote a JSON file (works like XML but looks like a JavaScript object) to hold some configuration information.
Next, I used jQuery’s updated getJSON function to load my configuration. The getJSON callback function then processes the configuration items. This starts the loading process of all configured scripts almost simultaneously using the getScript function.
While gathering the external scripts, we check for dependencies based on our configuration file. If dependencies are not ready, we set a wait timer for 50 milliseconds and start checking again. Any script with a configured callback gets run after the script loads.
Lastly, we can make use of John Resig’s Degrading Script pattern to give our jQuery script tag a callback function of sorts, reducing our code to a single script tag.
The final product:
- external file references – just one new one for jQuery
- external scripts – all custom ones loaded asynchronously (WSS still has a bunch)
- scripts loaded - during onLoad event (document.ready)
- dependencies - managed as needed during asynchronous load
- custom scripts - managed by a central configuration file for reuse
- inline script blocks – none! (just the 8 or so we started with from WSS)
Click “Read more” for the sample JSON file, bootstrapped jQuery 1.3 library, and sample scripts (JSAPI show name)…
First, download the SPAPI files and update the configuration file’s path to each file.
http://darrenjohnstone.net/download/12/
JSON configuration file:
config-json
({ "title": "Show User Name Test Configuration", "description": "Loads two libraries and a custom function", "version": "1.0", "items": [{ "filename": "SPAPI_Core.js", "src": "/PaulGrenier/js/SPAPI/SPAPI_Core.js" },{ "filename": "SPAPI_Lists.js", "src": "/PaulGrenier/js/SPAPI/SPAPI_Lists.js" },{ "filename": "Show User Name function", "src": "/PaulGrenier/js/showUserName.js", "callback": "showUserName()", "requires": [{ "filename": "SPAPI_Core.js" },{ "filename": "SPAPI_Lists.js" }] }] })
You also need to update the jQuery 1.3 library with the following addition so it can run our pseudo-callback function:
jquery-13minbootstrap
var scripts=document.getElementsByTagName("script");var script=scripts[scripts.length-1].innerHTML;if (script){jQuery(document).ready(function(){eval(script)})}
Save the following script to a file (no configuration needed) and edit the configuration file as needed:
showusername
function showUserName(){ var userName = getCurrentUserName(); if (userName != null){ document.getElementById('username_div').innerHTML = 'Welcome: ' + userName; }else{ document.getElementById('username_div').innerHTML = 'User name not found'; } } function getCurrentUserName(){ var lists = new SPAPI_Lists('https://endusersharepoint.securespsites.com/PaulGrenier'); var items = lists.getListItems( 'UserInfo', //other lists can be called by name '', '<Query><Where><Eq><FieldRef Name="ID"/><Value Type="Counter">' + _spUserId + '</Value></Eq></Where></Query>', // query '', 1, // rowLimit '' // queryOptions ); if (items.status == 200){ var rows = items.responseXML.getElementsByTagName('z:row'); if (rows.length == 1){ return rows[0].getAttribute('ows_Title'); //field name of data to return }else{ return null; } }else{ return null; } }
Last step (I promise!). Copy and paste this function into a CEWP on your page. Make sure you update the first two lines with the correct path and filename of your modified jQuery library and your config file.
Include the <div> at the top so your name appears:
<div id="username_div"></div> <script src="/PaulGrenier/js/jquery-1.3.min.bootstrap.js" type="text/javascript"> $.getJSON("/PaulGrenier/js/config-json.js", function(config){ var loaded = []; function checkRequires(i,item) { this.item = item; this.call = function () { var j; var a=item.requires; var b=a.length; for (j=0;j<b;j++){ if (jQuery.inArray(a[j].filename, loaded) > -1) { }else{ jitem = new checkRequires(i,item); jitem.callbackTimeout(50); return false; } } $.getScript(item.src, function(){ loaded[i] = item.filename; var c = item.callback; if(c) eval(c); }); }; this.callbackTimeout = function (ms){ var _self = this; setTimeout(function(ms){ _self.call(); }, ms); }; }; $.each(config.items, function (i,item){ if (i >= config.items.length ) return false; if (item.requires) { jitem = new checkRequires(i,item); jitem.callbackTimeout(50); }else{ $.getScript(item.src, function(){ loaded[i] = item.filename; var c = item.callback; if(c) eval(c); }); } }); }); </script>
Update 1/20/2009:
I just test this with the config file saves as an .aspx; it still worked. That means we can probably create a dynamic config building page and pass it parameters in the getJSON query string. That could be sweet.
- JQuery for Everyone: Accordion Left Nav
- JQuery for Everyone: Print (Any) Web Part
- JQuery for Everyone: HTML Calculated Column
- JQuery for Everyone: Dressing-up Links Pt1
- JQuery for Everyone: Dressing-up Links Pt2
- JQuery for Everyone: Dressing-up Links Pt3
- JQuery for Everyone: Cleaning Windows Pt1
- JQuery for Everyone: Cleaning Windows Pt2
- JQuery for Everyone: Fixing the Gantt View
- JQuery for Everyone: Dynamically Sizing Excel Web Parts
- JQuery for Everyone: Manually Resizing Web Parts
- JQuery for Everyone: Total Calculated Columns
- JQuery for Everyone: Total of Time Differences
- JQuery for Everyone: Fixing Configured Web Part Height
- JQuery for Everyone: Expand/Collapse All Groups
- JQuery for Everyone: Preview Pane for Multiple Lists
- JQuery for Everyone: Preview Pane for Calendar View
- JQuery for Everyone: Degrading Dynamic Script Loader
- JQuery for Everyone: Force Checkout
- JQuery for Everyone: Replacing [Today]
- JQuery for Everyone: Whether They Want It Or Not
- JQuery for Everyone: Linking the Attachment Icon
- JQuery for Everyone: Aspect-Oriented Programming with jQuery
- JQuery for Everyone: AOP in Action - loadTip Gone Wild
- JQuery for Everyone: Wiki Outbound Links
- JQuery for Everyone: Collapse Text in List View
- JQuery for Everyone: AOP in Action - Clone List Header
- JQuery for Everyone: $.grep and calcHTML Revisited
- JQuery for Everyone: Evolution of the Preview
- JQuery for Everyone: Create a Client-Side Object Model
- JQuery for Everyone: Print (Any) Web Part(s) Plugin
- JQuery for Everyone: Minimal AOP and Elegant Modularity
- JQuery for Everyone: Cookies and Plugins
- JQuery for Everyone: Live Events vs. AOP
- JQuery for Everyone: Live Preview Pane
- JQuery for Everyone: Pre-populate Form Fields
- JQuery for Everyone: Get XML List Data with OWSSVR.DLL (RPC)
- Use Firebug in IE
- JQuery for Everyone: Extending OWS API for Calculated Columns
- JQuery for Everyone: Accordion Left-nav with Cookies Speed Test
- JQuery for Everyone: Email a List of People with OWS
- JQuery for Everyone: Faster than Document.Ready
- jQuery for Everyone: Collapse or Prepopulate Form Fields
- jQuery for Everyone: Hourly Summary Web Part
- jQuery for Everyone: "Read More..." On a Blog Site
- jQuery for Everyone: Slick Speed Test
- jQuery for Everyone: The SharePoint Game Changer
- JQuery For Everyone: Live LoadTip
Have you had a look at how JMVC solves script loading? It’s pretty nice. It allows you to compress any application automatically into a single payload.
@Justin,
Thanks for the tip, I’ll check it out.
Hi Paul,
I have one doubt, To where I have to copy the .Js files,Config file and JQuery Builder. Please let me know…
Error! in IE8
on this string (CEWP code):
Help, please!!!