1,804 articles and 14,687 comments as of Wednesday, March 23rd, 2011

EndUserSharePoint has combined resources with NothingButSharePoint.com. You can now find End User (Mark Miller), Developer (Jeremy Thake) and IT Pro SharePoint (Joel Oleson) content all in one place!

This site is a historical archive and is no longer being updated. Please update your favorites, bookmarks and RSS feeds.

NothingButSharePoint.com
Tuesday, January 20, 2009

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.

Paul Grenier

View all entries in this series: PaulGrenier-JQuery for Everyone»
Entries in this series:
  1. JQuery for Everyone: Accordion Left Nav
  2. JQuery for Everyone: Print (Any) Web Part
  3. JQuery for Everyone: HTML Calculated Column
  4. JQuery for Everyone: Dressing-up Links Pt1
  5. JQuery for Everyone: Dressing-up Links Pt2
  6. JQuery for Everyone: Dressing-up Links Pt3
  7. JQuery for Everyone: Cleaning Windows Pt1
  8. JQuery for Everyone: Cleaning Windows Pt2
  9. JQuery for Everyone: Fixing the Gantt View
  10. JQuery for Everyone: Dynamically Sizing Excel Web Parts
  11. JQuery for Everyone: Manually Resizing Web Parts
  12. JQuery for Everyone: Total Calculated Columns
  13. JQuery for Everyone: Total of Time Differences
  14. JQuery for Everyone: Fixing Configured Web Part Height
  15. JQuery for Everyone: Expand/Collapse All Groups
  16. JQuery for Everyone: Preview Pane for Multiple Lists
  17. JQuery for Everyone: Preview Pane for Calendar View
  18. JQuery for Everyone: Degrading Dynamic Script Loader
  19. JQuery for Everyone: Force Checkout
  20. JQuery for Everyone: Replacing [Today]
  21. JQuery for Everyone: Whether They Want It Or Not
  22. JQuery for Everyone: Linking the Attachment Icon
  23. JQuery for Everyone: Aspect-Oriented Programming with jQuery
  24. JQuery for Everyone: AOP in Action - loadTip Gone Wild
  25. JQuery for Everyone: Wiki Outbound Links
  26. JQuery for Everyone: Collapse Text in List View
  27. JQuery for Everyone: AOP in Action - Clone List Header
  28. JQuery for Everyone: $.grep and calcHTML Revisited
  29. JQuery for Everyone: Evolution of the Preview
  30. JQuery for Everyone: Create a Client-Side Object Model
  31. JQuery for Everyone: Print (Any) Web Part(s) Plugin
  32. JQuery for Everyone: Minimal AOP and Elegant Modularity
  33. JQuery for Everyone: Cookies and Plugins
  34. JQuery for Everyone: Live Events vs. AOP
  35. JQuery for Everyone: Live Preview Pane
  36. JQuery for Everyone: Pre-populate Form Fields
  37. JQuery for Everyone: Get XML List Data with OWSSVR.DLL (RPC)
  38. Use Firebug in IE
  39. JQuery for Everyone: Extending OWS API for Calculated Columns
  40. JQuery for Everyone: Accordion Left-nav with Cookies Speed Test
  41. JQuery for Everyone: Email a List of People with OWS
  42. JQuery for Everyone: Faster than Document.Ready
  43. jQuery for Everyone: Collapse or Prepopulate Form Fields
  44. jQuery for Everyone: Hourly Summary Web Part
  45. jQuery for Everyone: "Read More..." On a Blog Site
  46. jQuery for Everyone: Slick Speed Test
  47. jQuery for Everyone: The SharePoint Game Changer
  48. JQuery For Everyone: Live LoadTip
 

Please Join the Discussion

6 Responses to “JQuery for Everyone: Degrading Dynamic Script Loader”
  1. Justin says:

    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.

  2. AutoSponge says:

    @Justin,

    Thanks for the tip, I’ll check it out.

  3. Rajendra says:

    Hi Paul,

    I have one doubt, To where I have to copy the .Js files,Config file and JQuery Builder. Please let me know…

  4. Aland says:

    Error! in IE8
    on this string (CEWP code):

    27    setTimeout(function(ms){
    28                    _self.call();
    29    }, ms); 

    Help, please!!!

Trackbacks

Check out what others are saying about this post...
  1. [...] commented with no warranty. Do with it what you will. My personal plans for this code involve the JSON loader and a very happy Content Editor Web [...]

  2. [...] JQuery for Everyone: Degrading Dynamic Script Loader [...]




Notify me of comments to this article:


Speak and you will be heard.

We check comments hourly.
If you want a pic to show with your comment, go get a gravatar!