--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/lab.js Wed Dec 21 11:22:47 2011 +0100
@@ -0,0 +1,514 @@
+/*! LAB.js (LABjs :: Loading And Blocking JavaScript)
+ v2.0.3 (c) Kyle Simpson
+ MIT License
+*/
+
+(function(global){
+ var _$LAB = global.$LAB,
+
+ // constants for the valid keys of the options object
+ _UseLocalXHR = "UseLocalXHR",
+ _AlwaysPreserveOrder = "AlwaysPreserveOrder",
+ _AllowDuplicates = "AllowDuplicates",
+ _CacheBust = "CacheBust",
+ /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/
+ _BasePath = "BasePath",
+
+ // stateless variables used across all $LAB instances
+ root_page = /^[^?#]*\//.exec(location.href)[0],
+ root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0],
+ append_to = document.head || document.getElementsByTagName("head"),
+
+ // inferences... ick, but still necessary
+ opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style),
+
+/*!START_DEBUG*/
+ // console.log() and console.error() wrappers
+ log_msg = function(){},
+ log_error = log_msg,
+/*!END_DEBUG*/
+
+ // feature sniffs (yay!)
+ test_script_elem = document.createElement("script"),
+ explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29
+ real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append?
+ script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
+
+ // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers)
+ xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko
+ ;
+
+/*!START_DEBUG*/
+ // define console wrapper functions if applicable
+ if (global.console && global.console.log) {
+ if (!global.console.error) global.console.error = global.console.log;
+ log_msg = function(msg) { global.console.log(msg); };
+ log_error = function(msg,err) { global.console.error(msg,err); };
+ }
+/*!END_DEBUG*/
+
+ // test for function
+ function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; }
+
+ // test for array
+ function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; }
+
+ // make script URL absolute/canonical
+ function canonical_uri(src,base_path) {
+ var absolute_regex = /^\w+\:\/\//;
+
+ // is `src` is protocol-relative (begins with // or ///), prepend protocol
+ if (/^\/\/\/?/.test(src)) {
+ src = location.protocol + src;
+ }
+ // is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /)
+ else if (!absolute_regex.test(src) && src.charAt(0) != "/") {
+ // prepend `base_path`, if any
+ src = (base_path || "") + src;
+ }
+ // make sure to return `src` as absolute
+ return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src);
+ }
+
+ // merge `source` into `target`
+ function merge_objs(source,target) {
+ for (var k in source) { if (source.hasOwnProperty(k)) {
+ target[k] = source[k]; // TODO: does this need to be recursive for our purposes?
+ }}
+ return target;
+ }
+
+ // does the chain group have any ready-to-execute scripts?
+ function check_chain_group_scripts_ready(chain_group) {
+ var any_scripts_ready = false;
+ for (var i=0; i<chain_group.scripts.length; i++) {
+ if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) {
+ any_scripts_ready = true;
+ chain_group.scripts[i].exec_trigger();
+ chain_group.scripts[i].exec_trigger = null;
+ }
+ }
+ return any_scripts_ready;
+ }
+
+ // creates a script load listener
+ function create_script_load_listener(elem,registry_item,flag,onload) {
+ elem.onload = elem.onreadystatechange = function() {
+ if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return;
+ elem.onload = elem.onreadystatechange = null;
+ onload();
+ };
+ }
+
+ // script executed handler
+ function script_executed(registry_item) {
+ registry_item.ready = registry_item.finished = true;
+ for (var i=0; i<registry_item.finished_listeners.length; i++) {
+ registry_item.finished_listeners[i]();
+ }
+ registry_item.ready_listeners = [];
+ registry_item.finished_listeners = [];
+ }
+
+ // make the request for a scriptha
+ function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) {
+ // setTimeout() "yielding" prevents some weird race/crash conditions in older browsers
+ setTimeout(function(){
+ var script, src = script_obj.real_src, xhr;
+
+ // don't proceed until `append_to` is ready to append to
+ if ("item" in append_to) { // check if `append_to` ref is still a live node list
+ if (!append_to[0]) { // `append_to` node not yet ready
+ // try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()`
+ setTimeout(arguments.callee,25);
+ return;
+ }
+ // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
+ append_to = append_to[0];
+ }
+ script = document.createElement("script");
+ if (script_obj.type) script.type = script_obj.type;
+ if (script_obj.charset) script.charset = script_obj.charset;
+
+ // should preloading be used for this script?
+ if (preload_this_script) {
+ // real script preloading?
+ if (real_preloading) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/
+ registry_item.elem = script;
+ if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal)
+ script.preload = true;
+ script.onpreload = onload;
+ }
+ else {
+ script.onreadystatechange = function(){
+ if (script.readyState == "loaded") onload();
+ };
+ }
+ script.src = src;
+ // NOTE: no append to DOM yet, appending will happen when ready to execute
+ }
+ // same-domain and XHR allowed? use XHR preloading
+ else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) {
+ xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ xhr.onreadystatechange = function(){}; // fix a memory leak in IE
+ registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
+ onload();
+ }
+ };
+ xhr.open("GET",src);
+ xhr.send();
+ }
+ // as a last resort, use cache-preloading
+ else {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/
+ script.type = "text/cache-script";
+ create_script_load_listener(script,registry_item,"ready",function() {
+ append_to.removeChild(script);
+ onload();
+ });
+ script.src = src;
+ append_to.insertBefore(script,append_to.firstChild);
+ }
+ }
+ // use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
+ else if (script_ordered_async) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/
+ script.async = false;
+ create_script_load_listener(script,registry_item,"finished",onload);
+ script.src = src;
+ append_to.insertBefore(script,append_to.firstChild);
+ }
+ // otherwise, just a normal script element
+ else {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/
+ create_script_load_listener(script,registry_item,"finished",onload);
+ script.src = src;
+ append_to.insertBefore(script,append_to.firstChild);
+ }
+ },0);
+ }
+
+ // create a clean instance of $LAB
+ function create_sandbox() {
+ var global_defaults = {},
+ can_use_preloading = real_preloading || xhr_or_cache_preloading,
+ queue = [],
+ registry = {},
+ instanceAPI
+ ;
+
+ // global defaults
+ global_defaults[_UseLocalXHR] = true;
+ global_defaults[_AlwaysPreserveOrder] = false;
+ global_defaults[_AllowDuplicates] = false;
+ global_defaults[_CacheBust] = false;
+ /*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/
+ global_defaults[_BasePath] = "";
+
+ // execute a script that has been preloaded already
+ function execute_preloaded_script(chain_opts,script_obj,registry_item) {
+ var script;
+
+ function preload_execute_finished() {
+ if (script != null) { // make sure this only ever fires once
+ script = null;
+ script_executed(registry_item);
+ }
+ }
+
+ if (registry[script_obj.src].finished) return;
+ if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true;
+
+ script = registry_item.elem || document.createElement("script");
+ if (script_obj.type) script.type = script_obj.type;
+ if (script_obj.charset) script.charset = script_obj.charset;
+ create_script_load_listener(script,registry_item,"finished",preload_execute_finished);
+
+ // script elem was real-preloaded
+ if (registry_item.elem) {
+ registry_item.elem = null;
+ }
+ // script was XHR preloaded
+ else if (registry_item.text) {
+ script.onload = script.onreadystatechange = null; // script injection doesn't fire these events
+ script.text = registry_item.text;
+ }
+ // script was cache-preloaded
+ else {
+ script.src = script_obj.real_src;
+ }
+ append_to.insertBefore(script,append_to.firstChild);
+
+ // manually fire execution callback for injected scripts, since events don't fire
+ if (registry_item.text) {
+ preload_execute_finished();
+ }
+ }
+
+ // process the script request setup
+ function do_script(chain_opts,script_obj,chain_group,preload_this_script) {
+ var registry_item,
+ registry_items,
+ ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); },
+ finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); }
+ ;
+
+ script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]);
+ script_obj.real_src = script_obj.src +
+ // append cache-bust param to URL?
+ (chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "")
+ ;
+
+ if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false};
+ registry_items = registry[script_obj.src].items;
+
+ // allowing duplicates, or is this the first recorded load of this script?
+ if (chain_opts[_AllowDuplicates] || registry_items.length == 0) {
+ registry_item = registry_items[registry_items.length] = {
+ ready:false,
+ finished:false,
+ ready_listeners:[ready_cb],
+ finished_listeners:[finished_cb]
+ };
+
+ request_script(chain_opts,script_obj,registry_item,
+ // which callback type to pass?
+ (
+ (preload_this_script) ? // depends on script-preloading
+ function(){
+ registry_item.ready = true;
+ for (var i=0; i<registry_item.ready_listeners.length; i++) {
+ registry_item.ready_listeners[i]();
+ }
+ registry_item.ready_listeners = [];
+ } :
+ function(){ script_executed(registry_item); }
+ ),
+ // signal if script-preloading should be used or not
+ preload_this_script
+ );
+ }
+ else {
+ registry_item = registry_items[0];
+ if (registry_item.finished) {
+ finished_cb();
+ }
+ else {
+ registry_item.finished_listeners.push(finished_cb);
+ }
+ }
+ }
+
+ // creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains
+ function create_chain() {
+ var chainedAPI,
+ chain_opts = merge_objs(global_defaults,{}),
+ chain = [],
+ exec_cursor = 0,
+ scripts_currently_loading = false,
+ group
+ ;
+
+ // called when a script has finished preloading
+ function chain_script_ready(script_obj,exec_trigger) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/
+ script_obj.ready = true;
+ script_obj.exec_trigger = exec_trigger;
+ advance_exec_cursor(); // will only check for 'ready' scripts to be executed
+ }
+
+ // called when a script has finished executing
+ function chain_script_executed(script_obj,chain_group) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/
+ script_obj.ready = script_obj.finished = true;
+ script_obj.exec_trigger = null;
+ // check if chain group is all finished
+ for (var i=0; i<chain_group.scripts.length; i++) {
+ if (!chain_group.scripts[i].finished) return;
+ }
+ // chain_group is all finished if we get this far
+ chain_group.finished = true;
+ advance_exec_cursor();
+ }
+
+ // main driver for executing each part of the chain
+ function advance_exec_cursor() {
+ while (exec_cursor < chain.length) {
+ if (is_func(chain[exec_cursor])) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/
+ try { chain[exec_cursor++](); } catch (err) {
+ /*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/
+ }
+ continue;
+ }
+ else if (!chain[exec_cursor].finished) {
+ if (check_chain_group_scripts_ready(chain[exec_cursor])) continue;
+ break;
+ }
+ exec_cursor++;
+ }
+ // we've reached the end of the chain (so far)
+ if (exec_cursor == chain.length) {
+ scripts_currently_loading = false;
+ group = false;
+ }
+ }
+
+ // setup next chain script group
+ function init_script_chain_group() {
+ if (!group || !group.scripts) {
+ chain.push(group = {scripts:[],finished:true});
+ }
+ }
+
+ // API for $LAB chains
+ chainedAPI = {
+ // start loading one or more scripts
+ script:function(){
+ for (var i=0; i<arguments.length; i++) {
+ (function(script_obj,script_list){
+ var splice_args;
+
+ if (!is_array(script_obj)) {
+ script_list = [script_obj];
+ }
+ for (var j=0; j<script_list.length; j++) {
+ init_script_chain_group();
+ script_obj = script_list[j];
+
+ if (is_func(script_obj)) script_obj = script_obj();
+ if (!script_obj) continue;
+ if (is_array(script_obj)) {
+ // set up an array of arguments to pass to splice()
+ splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in
+ splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array
+ [].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice()
+ j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value
+ continue;
+ }
+ if (typeof script_obj == "string") script_obj = {src:script_obj};
+ script_obj = merge_objs(script_obj,{
+ ready:false,
+ ready_cb:chain_script_ready,
+ finished:false,
+ finished_cb:chain_script_executed
+ });
+ group.finished = false;
+ group.scripts.push(script_obj);
+
+ do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading));
+ scripts_currently_loading = true;
+
+ if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait();
+ }
+ })(arguments[i],arguments[i]);
+ }
+ return chainedAPI;
+ },
+ // force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding
+ wait:function(){
+ if (arguments.length > 0) {
+ for (var i=0; i<arguments.length; i++) {
+ chain.push(arguments[i]);
+ }
+ group = chain[chain.length-1];
+ }
+ else group = false;
+
+ advance_exec_cursor();
+
+ return chainedAPI;
+ }
+ };
+
+ // the first chain link API (includes `setOptions` only this first time)
+ return {
+ script:chainedAPI.script,
+ wait:chainedAPI.wait,
+ setOptions:function(opts){
+ merge_objs(opts,chain_opts);
+ return chainedAPI;
+ }
+ };
+ }
+
+ // API for each initial $LAB instance (before chaining starts)
+ instanceAPI = {
+ // main API functions
+ setGlobalDefaults:function(opts){
+ merge_objs(opts,global_defaults);
+ return instanceAPI;
+ },
+ setOptions:function(){
+ return create_chain().setOptions.apply(null,arguments);
+ },
+ script:function(){
+ return create_chain().script.apply(null,arguments);
+ },
+ wait:function(){
+ return create_chain().wait.apply(null,arguments);
+ },
+
+ // built-in queuing for $LAB `script()` and `wait()` calls
+ // useful for building up a chain programmatically across various script locations, and simulating
+ // execution of the chain
+ queueScript:function(){
+ queue[queue.length] = {type:"script", args:[].slice.call(arguments)};
+ return instanceAPI;
+ },
+ queueWait:function(){
+ queue[queue.length] = {type:"wait", args:[].slice.call(arguments)};
+ return instanceAPI;
+ },
+ runQueue:function(){
+ var $L = instanceAPI, len=queue.length, i=len, val;
+ for (;--i>=0;) {
+ val = queue.shift();
+ $L = $L[val.type].apply(null,val.args);
+ }
+ return $L;
+ },
+
+ // rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB
+ noConflict:function(){
+ global.$LAB = _$LAB;
+ return instanceAPI;
+ },
+
+ // create another clean instance of $LAB
+ sandbox:function(){
+ return create_sandbox();
+ }
+ };
+
+ return instanceAPI;
+ }
+
+ // create the main instance of $LAB
+ global.$LAB = create_sandbox();
+
+
+ /* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
+ NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
+
+ The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does
+ proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked
+ document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready.
+ For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or
+ fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
+ */
+ (function(addEvent,domLoaded,handler){
+ if (document.readyState == null && document[addEvent]){
+ document.readyState = "loading";
+ document[addEvent](domLoaded,handler = function(){
+ document.removeEventListener(domLoaded,handler,false);
+ document.readyState = "complete";
+ },false);
+ }
+ })("addEventListener","DOMContentLoaded");
+
+})(this);
\ No newline at end of file