Pieces of my core javascript library: The Script Loader
Guest Author: Einar Otto Stangvik
Will Code for Nuts
I’m about to release a few more open source SharePoint solutions, including a core javascript library feature. The CoreJS is a required feature for the hierarchical navigation component, which will also be properly released as OSS on CodePlex before long.
A central piece of the CoreJS library and activation feature, is the Script Loader. This is in fact the only piece of the library which is automatically injected (as a delegate control) into any page in the site collection it’s activated for.
The Script Loader’s purpose is pretty much exactly what it sounds like: it’s a general purpose way of loading scripts from javascript code. A sample scenario would e.g. be if you need a few external utility library, but you don’t know if it has been loaded yet. Using the script loader, this would amount to:
function loadComplete()
{
alert("The scripts are loaded, and we're good to go!");
}
grep.scriptloader.loadScripts(
"http://www.json.org/json2.js",
"myJsonAjaxLibrary.js",
loadComplete
);
The Script Loader would ensure that all the scripts are loaded in order, so in this above scenario, where the fictional myJsonAjaxLibrary relies on json2.js to do its thing, myJsonAjaxLibrary would not be loaded at all until json2 is present and ready. Additionally, we can supply a function as a parameter to loadScripts, which is called once all previous tasks have completed.
Making more calls to loadScripts would queue up even more scripts to be loaded. Imagine the following turn of events:
// On top of your script
grep.scriptloader.loadScripts("http://www.json.org/json2.js");
// ... Meanwhile (actually later) in some distant part of the same script grep.scriptloader.loadScripts(
"myLibrary.js",
function() { alert("myLibrary loaded"); },
"http://www.json.org/json2.js",
function() { alert("json2 loaded"); });
It’s pretty common for different parts of your script setup to require different libraries, and yet other times they even require the same scripts (whew). The script loader library does two things here: It will load all scripts in order, even between calls to loadScripts. In the above case, myLibrary would *not* be loaded until json2.js has been loaded from json.org; since that was specified in the first call to loadScripts. Second, it it will check each source url, to make sure that it hasn’t already been loaded. If it’s already loaded; it will be skipped.
In the above case, the actual sequence of events would be:
- load json2
- load myLibrary
- alert(myLibrary loaded)
- alert(json2 loaded)
Here’s the full script, ready for inclusion. If you spot anything off, you’ll have plenty of time to suggest changes once I’ve moved this and other core javascript libraries, plus the SharePoint activation feature, to a project on CodePlex!
if (!window.grep) window.grep = {};
if (!window.grep.scriptloader)
{
window.grep.scriptloader =
{
nocache: false,
loadScripts: function()
{
for (var i = 0; i < arguments.length; ++i)
{
var arg = arguments[i];
switch (typeof (arg))
{
case "function":
case "string": window.grep.scriptloader._queue.push(arg); break;
case "object": if (arg.length > 0) for (var x = 0; x < arg.length; ++x) window.grep.scriptloader._queue.push(arg[x]); break;
}
}
if (!window.grep.scriptloader._active)
{
window.grep.scriptloader._active = true;
window.grep.scriptloader._processNextQueueItem();
}
},
/* Private interface */
_active: false,
_queue: [],
_loaded: {},
_processNextQueueItem: function()
{
if (window.grep.scriptloader._queue.length > 0)
{
var item = window.grep.scriptloader._queue.shift();
if (typeof (item) == "function")
{
item();
window.grep.scriptloader._processNextQueueItem();
}
else if (typeof (item) == "string")
{
if (!window.grep.scriptloader._loaded[item])
{
window.grep.scriptloader._loaded[item] = true;
window.grep.scriptloader._loadScript(item, window.grep.scriptloader._processNextQueueItem);
}
else
{
window.grep.scriptloader._processNextQueueItem();
}
}
}
else
{
window.grep.scriptloader._active = false;
}
},
_loadScript: function(src, onload)
{
var js = document.createElement('script');
if (window.grep.scriptloader.nocache)
{
js.src = src + "?" + Math.random();
}
else
{
js.src = src;
}
js.type = 'text/javascript';
if (js.readyState)
{
js.onerror = function() { alert("Error loading script: " + this.src); };
js.onreadystatechange = function()
{
if (js.readyState == "loaded" || js.readyState == "complete")
{
js.onreadystatechange = null;
if (onload)
{
onload(src);
}
}
};
}
else
{
js.onload = function()
{
if (onload)
{
onload(src);
}
};
}
var head = document.getElementsByTagName('head')[0];
if (head)
{
head.appendChild(js);
}
else
{
document.body.appendChild(js);
}
}
}
}
Guest Author: Einar Otto Stangvik
Will Code for Nuts
Einar is a SharePoint architect and avid developer based in Oslo, Norway. Once a deep diving C++ programmer, and previous Microsoft MVP for that, he’s now mostly turned to the world of .NET and web frontends, with primary focus on C#, WCF, Silverlight and JavaScript.
- Pieces of my core javascript library: The Script Loader
- Pieces of my core javascript library: The Lambda Framework
- Connecting SharePoint UI Elements: SPDrag and the DropBox
- Assigning dynamic ids to HTML and Script elements - Client Side!
I notice the script formatting is a little bit off here, but here’s a download link for the script file itself:
http://dl.getdropbox.com/u/1878671/grep.scriptloader.js