diff options
| author | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2011-09-14 11:07:46 +0530 |
|---|---|---|
| committer | Vidhin Mehta (OpenERP) <vme@tinyerp.com> | 2011-09-14 11:07:46 +0530 |
| commit | b6076c3cc3ca5141349b99821268b8fd5e148b77 (patch) | |
| tree | bb875d3ef5127cf5b8c35de6475de14e5477dbbd /addons/web/static/lib/labjs/LAB.src.js | |
| parent | 243e1bdb0079ea9f214696ead8228d464ecb2cfc (diff) | |
| parent | c7c2b5846d3775780bbaa9a84a93ad755bdb8756 (diff) | |
[Merge] upto revno 1024.
bzr revid: vme@tinyerp.com-20110914053746-ri6d5s341mm6tc7n
Diffstat (limited to 'addons/web/static/lib/labjs/LAB.src.js')
| -rw-r--r-- | addons/web/static/lib/labjs/LAB.src.js | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/addons/web/static/lib/labjs/LAB.src.js b/addons/web/static/lib/labjs/LAB.src.js new file mode 100644 index 00000000000..99807cd222e --- /dev/null +++ b/addons/web/static/lib/labjs/LAB.src.js @@ -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 |
