From 52ea94cbeedc180f50c70dc7ab5864bad623be42 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 15 Feb 2013 15:30:35 +0000 Subject: [PATCH] using LABjs to load javascript files in order via egw.includeJS --- phpgwapi/inc/class.egw_framework.inc.php | 9 + phpgwapi/js/jsapi/egw_files.js | 226 +--------- phpgwapi/js/labjs/LAB-debug.min.js | 5 + phpgwapi/js/labjs/LAB.js | 5 + phpgwapi/js/labjs/LAB.min.js | 5 + phpgwapi/js/labjs/LAB.src.js | 514 +++++++++++++++++++++++ 6 files changed, 560 insertions(+), 204 deletions(-) create mode 100755 phpgwapi/js/labjs/LAB-debug.min.js create mode 100755 phpgwapi/js/labjs/LAB.js create mode 100755 phpgwapi/js/labjs/LAB.min.js create mode 100755 phpgwapi/js/labjs/LAB.src.js diff --git a/phpgwapi/inc/class.egw_framework.inc.php b/phpgwapi/inc/class.egw_framework.inc.php index d927a13420..419ae3d7bb 100644 --- a/phpgwapi/inc/class.egw_framework.inc.php +++ b/phpgwapi/inc/class.egw_framework.inc.php @@ -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 = '\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". + ' +';*/ } /** diff --git a/phpgwapi/js/jsapi/egw_files.js b/phpgwapi/js/jsapi/egw_files.js index fff8d1ba21..0a93f9e5f0 100644 --- a/phpgwapi/js/jsapi/egw_files.js +++ b/phpgwapi/js/jsapi/egw_files.js @@ -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'); diff --git a/phpgwapi/js/labjs/LAB-debug.min.js b/phpgwapi/js/labjs/LAB-debug.min.js new file mode 100755 index 0000000000..99e7b142fe --- /dev/null +++ b/phpgwapi/js/labjs/LAB-debug.min.js @@ -0,0 +1,5 @@ +/*! LAB.js (LABjs :: Loading And Blocking JavaScript) + v2.0.3 (c) Kyle Simpson + MIT License +*/ +(function(j){var N=j.$LAB,A="UseLocalXHR",B="AlwaysPreserveOrder",w="AllowDuplicates",C="CacheBust",l="Debug",D="BasePath",E=/^[^?#]*\//.exec(location.href)[0],F=/^\w+\:\/\/\/?[^\/]+/.exec(E)[0],i=document.head||document.getElementsByTagName("head"),O=(j.opera&&Object.prototype.toString.call(j.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),m=function(){},G=m,s=document.createElement("script"),H=typeof s.preload=="boolean",t=H||(s.readyState&&s.readyState=="uninitialized"),I=!t&&s.async===true,P=!t&&!I&&!O;if(j.console&&j.console.log){if(!j.console.error)j.console.error=j.console.log;m=function(a){j.console.log(a)};G=function(a,c){j.console.error(a,c)}}function J(a){return Object.prototype.toString.call(a)=="[object Function]"}function K(a){return Object.prototype.toString.call(a)=="[object Array]"}function Q(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?F:E)+a)}function u(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function R(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=q.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){j.$LAB=N;return p},sandbox:function(){return M()}};return p}j.$LAB=M();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this); \ No newline at end of file diff --git a/phpgwapi/js/labjs/LAB.js b/phpgwapi/js/labjs/LAB.js new file mode 100755 index 0000000000..e710dfea28 --- /dev/null +++ b/phpgwapi/js/labjs/LAB.js @@ -0,0 +1,5 @@ +/*! LAB.js (LABjs :: Loading And Blocking JavaScript) + v2.0.3 (c) Kyle Simpson + MIT License +*/ +(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this); \ No newline at end of file diff --git a/phpgwapi/js/labjs/LAB.min.js b/phpgwapi/js/labjs/LAB.min.js new file mode 100755 index 0000000000..e710dfea28 --- /dev/null +++ b/phpgwapi/js/labjs/LAB.min.js @@ -0,0 +1,5 @@ +/*! LAB.js (LABjs :: Loading And Blocking JavaScript) + v2.0.3 (c) Kyle Simpson + MIT License +*/ +(function(o){var K=o.$LAB,y="UseLocalXHR",z="AlwaysPreserveOrder",u="AllowDuplicates",A="CacheBust",B="BasePath",C=/^[^?#]*\//.exec(location.href)[0],D=/^\w+\:\/\/\/?[^\/]+/.exec(C)[0],i=document.head||document.getElementsByTagName("head"),L=(o.opera&&Object.prototype.toString.call(o.opera)=="[object Opera]")||("MozAppearance"in document.documentElement.style),q=document.createElement("script"),E=typeof q.preload=="boolean",r=E||(q.readyState&&q.readyState=="uninitialized"),F=!r&&q.async===true,M=!r&&!F&&!L;function G(a){return Object.prototype.toString.call(a)=="[object Function]"}function H(a){return Object.prototype.toString.call(a)=="[object Array]"}function N(a,c){var b=/^\w+\:\/\//;if(/^\/\/\/?/.test(a)){a=location.protocol+a}else if(!b.test(a)&&a.charAt(0)!="/"){a=(c||"")+a}return b.test(a)?a:((a.charAt(0)=="/"?D:C)+a)}function s(a,c){for(var b in a){if(a.hasOwnProperty(b)){c[b]=a[b]}}return c}function O(a){var c=false;for(var b=0;b0){for(var a=0;a=0;){d=n.shift();a=a[d.type].apply(null,d.args)}return a},noConflict:function(){o.$LAB=K;return m},sandbox:function(){return J()}};return m}o.$LAB=J();(function(a,c,b){if(document.readyState==null&&document[a]){document.readyState="loading";document[a](c,b=function(){document.removeEventListener(c,b,false);document.readyState="complete"},false)}})("addEventListener","DOMContentLoaded")})(this); \ No newline at end of file diff --git a/phpgwapi/js/labjs/LAB.src.js b/phpgwapi/js/labjs/LAB.src.js new file mode 100755 index 0000000000..99807cd222 --- /dev/null +++ b/phpgwapi/js/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 0) { + for (var i=0; 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