mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 16:49:10 +01:00
using LABjs to load javascript files in order via egw.includeJS
This commit is contained in:
parent
0b7e7de01d
commit
52ea94cbee
@ -89,6 +89,7 @@ abstract class egw_framework
|
||||
public static function init_static()
|
||||
{
|
||||
self::$js_include_mgr = new egw_include_mgr(array(
|
||||
'/phpgwapi/js/labjs/LAB.src.js',
|
||||
// allways load jquery (not -ui) and egw_json first
|
||||
'/phpgwapi/js/jquery/jquery.js',
|
||||
'/phpgwapi/js/./egw_json.js',
|
||||
@ -1302,6 +1303,14 @@ abstract class egw_framework
|
||||
$start = '<script type="text/javascript" src="'. $GLOBALS['egw_info']['server']['webserver_url'];
|
||||
$end = '">'."</script>\n";
|
||||
return "\n".$start.implode($end.$start, $to_include).$end;
|
||||
|
||||
// using LABjs to load all javascript would require all other script-tags to run in wait() of queue!
|
||||
/*return "\n".$start.'/phpgwapi/js/labjs/LAB.src.js'.$end."\n".
|
||||
'<script type="text/javascript">
|
||||
$LAB.setOptions({AlwaysPreserveOrder:true,BasePath:"'.$GLOBALS['egw_info']['server']['webserver_url'].'/"}).script(
|
||||
'.json_encode(array_map(function($str){return substr($str,1);}, $to_include, array(1))).').wait();
|
||||
</script>
|
||||
';*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,223 +43,41 @@ egw.extend('files', egw.MODULE_WND_LOCAL, function(_app, _wnd) {
|
||||
return _src.replace(/\?[0-9]+&?/, '?').replace(/\?$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file to list of loaded files
|
||||
*
|
||||
* @param src url of file
|
||||
* @param dom optional dom node to trac loading status of javascript files
|
||||
*/
|
||||
function addFile(src, dom)
|
||||
{
|
||||
if (src)
|
||||
{
|
||||
files[removeTS(src)] = dom || true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a source file is already loaded or loading
|
||||
*
|
||||
* @param _src url of file (already run throught removeTS!)
|
||||
* @return false: not loaded, true: loaded or "loading"
|
||||
*/
|
||||
function isLoaded(_src)
|
||||
{
|
||||
switch (typeof files[_src])
|
||||
{
|
||||
case 'undefined':
|
||||
return false;
|
||||
case 'boolean':
|
||||
return files[_src];
|
||||
default:
|
||||
return files[_src].readyState == 'complete' || files[_src].readyState == 'loaded';
|
||||
}
|
||||
return "loading";
|
||||
}
|
||||
|
||||
/**
|
||||
* object with array of callbacks or contexts by source/url
|
||||
*/
|
||||
var callbacks = {};
|
||||
var contexts = {};
|
||||
|
||||
/**
|
||||
* Attach onLoad callback to given js source
|
||||
*
|
||||
* @param _src url of file (already run throught removeTS!)
|
||||
* @param _callback
|
||||
* @param _context
|
||||
* @return true if callback got attached, false if it was run directly
|
||||
*/
|
||||
function attachCallback(_src, _callback, _context)
|
||||
{
|
||||
if (typeof _callback === 'undefined') return;
|
||||
|
||||
switch (typeof files[_src])
|
||||
{
|
||||
case 'undefined':
|
||||
case 'boolean':
|
||||
_callback.call(_context);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof callbacks[_src] === 'undefined')
|
||||
{
|
||||
callbacks[_src] = [];
|
||||
contexts[_src] = [];
|
||||
callbacks[_src].push(_callback);
|
||||
contexts[_src].push(_context);
|
||||
|
||||
var scriptnode = files[_src];
|
||||
|
||||
// Setup the 'onload' handler for FF, Opera, Chrome
|
||||
scriptnode.onload = function(e) {
|
||||
egw.debug('info', 'Retrieved JS file "%s" from server', _src);
|
||||
runCallbacks.call(this, _src);
|
||||
};
|
||||
|
||||
// IE
|
||||
if (typeof scriptnode.readyState != 'undefined')
|
||||
{
|
||||
if (scriptnode.readyState != 'complete' &&
|
||||
scriptnode.readyState != 'loaded')
|
||||
{
|
||||
scriptnode.onreadystatechange = function() {
|
||||
var node = _wnd.event.srcElement;
|
||||
if (node.readyState == 'complete' || node.readyState == 'loaded')
|
||||
{
|
||||
egw.debug('info', 'Retrieved JS file "%s" from server', _src);
|
||||
runCallbacks.call(this, _src);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
runCallbacks.call(this, _src);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callbacks[_src].push(_callback);
|
||||
contexts[_src].push(_context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all callbacks of a given source
|
||||
*
|
||||
* @param _src url of file (already run throught removeTS!)
|
||||
*/
|
||||
function runCallbacks(_src)
|
||||
{
|
||||
if (typeof callbacks[_src] === 'undefined') return;
|
||||
|
||||
egw.debug('info', 'Running %d callbacks for JS file "%s"', callbacks[_src].length, _src);
|
||||
|
||||
for(var i = 0; i < callbacks[_src].length; i++)
|
||||
{
|
||||
callbacks[_src][i].call(contexts[_src][i]);
|
||||
}
|
||||
delete callbacks[_src];
|
||||
delete contexts[_src];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all already loaded JavaScript and CSS files on document load.
|
||||
*/
|
||||
// Iterate over the script tags
|
||||
var scripts = _wnd.document.getElementsByTagName('script');
|
||||
for (var i = 0; i < scripts.length; i++)
|
||||
{
|
||||
addFile(scripts[i].getAttribute('src'), scripts[i]);
|
||||
}
|
||||
|
||||
// Iterate over the link tags
|
||||
var links = _wnd.document.getElementsByTagName('link');
|
||||
for (var i = 0; i < links.length; i++)
|
||||
{
|
||||
addFile(links[i].getAttribute('href'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a single javascript file and call given callback once it's done
|
||||
*
|
||||
* If file is already loaded, _callback gets called imediatly
|
||||
*
|
||||
* @param _jsFile url of file
|
||||
* @param _callback
|
||||
* @param _context for callback
|
||||
*/
|
||||
function includeJSFile(_jsFile, _callback, _context)
|
||||
{
|
||||
var _src = removeTS(_jsFile);
|
||||
var alreadyLoaded = isLoaded(_src);
|
||||
|
||||
if (alreadyLoaded === false)
|
||||
{
|
||||
// Create the script node which contains the new file
|
||||
var scriptnode = _wnd.document.createElement('script');
|
||||
scriptnode.type = "text/javascript";
|
||||
scriptnode.src = _jsFile;
|
||||
scriptnode._originalSrc = _jsFile;
|
||||
|
||||
files[_src] = scriptnode;
|
||||
|
||||
// Append the newly create script node to the head
|
||||
var head = _wnd.document.getElementsByTagName('head')[0];
|
||||
head.appendChild(scriptnode);
|
||||
|
||||
// Request the given javascript file
|
||||
egw.debug('info', 'Requested JS file "%s" from server', _jsFile);
|
||||
}
|
||||
else if (alreadyLoaded === true)
|
||||
{
|
||||
egw.debug('info', 'JS file "%s" already loaded', _jsFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
egw.debug('info', 'JS file "%s" currently loading', _jsFile);
|
||||
}
|
||||
|
||||
// attach (or just run) callback
|
||||
attachCallback(_src, _callback, _context);
|
||||
}
|
||||
|
||||
return {
|
||||
includeJS: function(_jsFiles, _callback, _context, _prefix) {
|
||||
// Also allow including a single javascript file
|
||||
if (typeof _jsFiles === 'string')
|
||||
{
|
||||
_jsFiles = [_jsFiles];
|
||||
}
|
||||
if (typeof _prefix === 'undefined')
|
||||
{
|
||||
_prefix = '';
|
||||
}
|
||||
|
||||
var loaded = 0;
|
||||
|
||||
// Include all given JS files, if all are successfully loaded, call
|
||||
// the context function
|
||||
for (var i = 0; i < _jsFiles.length; i++)
|
||||
// LABjs uses prefix only if url is not absolute, so removing leading / if necessary and add it to prefix
|
||||
if (_prefix)
|
||||
{
|
||||
includeJSFile.call(this, _prefix+_jsFiles[i], function(_file) {
|
||||
loaded++;
|
||||
if (loaded == _jsFiles.length && _callback) {
|
||||
_callback.call(_context);
|
||||
}
|
||||
});
|
||||
// Also allow including a single javascript file
|
||||
if (typeof _jsFiles === 'string')
|
||||
{
|
||||
_jsFiles = [_jsFiles];
|
||||
}
|
||||
for(var i=0; i < _jsFiles.length; ++i)
|
||||
{
|
||||
if (_jsFiles[i].charAt(0) == '/') _jsFiles[i] = _jsFiles[i].substr(1);
|
||||
}
|
||||
if (_prefix.charAt(_prefix.length-1) != '/')
|
||||
{
|
||||
_prefix += '/';
|
||||
}
|
||||
}
|
||||
// setting AlwaysPreserverOrder: true, 'til we have some other means of ensuring dependency resolution
|
||||
$LAB.setOptions({AlwaysPreserveOrder:true,BasePath:_prefix}).script(_jsFiles).wait(function(){
|
||||
_callback.call(_context);
|
||||
});
|
||||
},
|
||||
|
||||
includeCSS: function(_cssFile) {
|
||||
//Check whether the requested file has already been included
|
||||
if (typeof files[_cssFile] === 'undefined')
|
||||
var file = removeTS(_cssFile);
|
||||
if (typeof files[file] === 'undefined')
|
||||
{
|
||||
files[_cssFile] = true;
|
||||
files[file] = true;
|
||||
|
||||
// Create the node which is used to include the css fiel
|
||||
var cssnode = _wnd.document.createElement('link');
|
||||
|
5
phpgwapi/js/labjs/LAB-debug.min.js
vendored
Executable file
5
phpgwapi/js/labjs/LAB-debug.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
5
phpgwapi/js/labjs/LAB.js
Executable file
5
phpgwapi/js/labjs/LAB.js
Executable file
File diff suppressed because one or more lines are too long
5
phpgwapi/js/labjs/LAB.min.js
vendored
Executable file
5
phpgwapi/js/labjs/LAB.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
514
phpgwapi/js/labjs/LAB.src.js
Executable file
514
phpgwapi/js/labjs/LAB.src.js
Executable file
@ -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);
|
Loading…
Reference in New Issue
Block a user