/** * ScriptLoader.js * * Copyright 2009, Moxiecode Systems AB * Released under LGPL License. * * License: http://tinymce.moxiecode.com/license * Contributing: http://tinymce.moxiecode.com/contributing */ (function(tinymce) { /** * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks when various items gets loaded. This class is useful to load external JavaScript files. * * @class tinymce.dom.ScriptLoader * @example * // Load a script from a specific URL using the global script loader * tinymce.ScriptLoader.load('somescript.js'); * * // Load a script using a unique instance of the script loader * var scriptLoader = new tinymce.dom.ScriptLoader(); * * scriptLoader.load('somescript.js'); * * // Load multiple scripts * var scriptLoader = new tinymce.dom.ScriptLoader(); * * scriptLoader.add('somescript1.js'); * scriptLoader.add('somescript2.js'); * scriptLoader.add('somescript3.js'); * * scriptLoader.loadQueue(function() { * alert('All scripts are now loaded.'); * }); */ tinymce.dom.ScriptLoader = function(settings) { var QUEUED = 0, LOADING = 1, LOADED = 2, states = {}, queue = [], scriptLoadedCallbacks = {}, queueLoadedCallbacks = [], loading = 0, undefined; /** * Loads a specific script directly without adding it to the load queue. * * @method load * @param {String} url Absolute URL to script to add. * @param {function} callback Optional callback function to execute ones this script gets loaded. * @param {Object} scope Optional scope to execute callback in. */ function loadScript(url, callback) { var t = this, dom = tinymce.DOM, elm, uri, loc, id; // Execute callback when script is loaded function done() { dom.remove(id); if (elm) elm.onreadystatechange = elm.onload = elm = null; callback(); }; function error() { // Report the error so it's easier for people to spot loading errors if (typeof(console) !== "undefined" && console.log) console.log("Failed to load: " + url); // We can't mark it as done if there is a load error since // A) We don't want to produce 404 errors on the server and // B) the onerror event won't fire on all browsers. // done(); }; id = dom.uniqueId(); if (tinymce.isIE6) { uri = new tinymce.util.URI(url); loc = location; // If script is from same domain and we // use IE 6 then use XHR since it's more reliable if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { tinymce.util.XHR.send({ url : tinymce._addVer(uri.getURI()), success : function(content) { // Create new temp script element var script = dom.create('script', { type : 'text/javascript' }); // Evaluate script in global scope script.text = content; document.getElementsByTagName('head')[0].appendChild(script); dom.remove(script); done(); }, error : error }); return; } } // Create new script element elm = dom.create('script', { id : id, type : 'text/javascript', src : tinymce._addVer(url) }); // Add onload listener for non IE browsers since IE9 // fires onload event before the script is parsed and executed if (!tinymce.isIE) elm.onload = done; // Add onerror event will get fired on some browsers but not all of them elm.onerror = error; // Opera 9.60 doesn't seem to fire the onreadystate event at correctly if (!tinymce.isOpera) { elm.onreadystatechange = function() { var state = elm.readyState; // Loaded state is passed on IE 6 however there // are known issues with this method but we can't use // XHR in a cross domain loading if (state == 'complete' || state == 'loaded') done(); }; } // Most browsers support this feature so we report errors // for those at least to help users track their missing plugins etc // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option /*elm.onerror = function() { alert('Failed to load: ' + url); };*/ // Add script to document (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); }; /** * Returns true/false if a script has been loaded or not. * * @method isDone * @param {String} url URL to check for. * @return [Boolean} true/false if the URL is loaded. */ this.isDone = function(url) { return states[url] == LOADED; }; /** * Marks a specific script to be loaded. This can be useful if a script got loaded outside * the script loader or to skip it from loading some script. * * @method markDone * @param {string} u Absolute URL to the script to mark as loaded. */ this.markDone = function(url) { states[url] = LOADED; }; /** * Adds a specific script to the load queue of the script loader. * * @method add * @param {String} url Absolute URL to script to add. * @param {function} callback Optional callback function to execute ones this script gets loaded. * @param {Object} scope Optional scope to execute callback in. */ this.add = this.load = function(url, callback, scope) { var item, state = states[url]; // Add url to load queue if (state == undefined) { queue.push(url); states[url] = QUEUED; } if (callback) { // Store away callback for later execution if (!scriptLoadedCallbacks[url]) scriptLoadedCallbacks[url] = []; scriptLoadedCallbacks[url].push({ func : callback, scope : scope || this }); } }; /** * Starts the loading of the queue. * * @method loadQueue * @param {function} callback Optional callback to execute when all queued items are loaded. * @param {Object} scope Optional scope to execute the callback in. */ this.loadQueue = function(callback, scope) { this.loadScripts(queue, callback, scope); }; /** * Loads the specified queue of files and executes the callback ones they are loaded. * This method is generally not used outside this class but it might be useful in some scenarios. * * @method loadScripts * @param {Array} scripts Array of queue items to load. * @param {function} callback Optional callback to execute ones all items are loaded. * @param {Object} scope Optional scope to execute callback in. */ this.loadScripts = function(scripts, callback, scope) { var loadScripts; function execScriptLoadedCallbacks(url) { // Execute URL callback functions tinymce.each(scriptLoadedCallbacks[url], function(callback) { callback.func.call(callback.scope); }); scriptLoadedCallbacks[url] = undefined; }; queueLoadedCallbacks.push({ func : callback, scope : scope || this }); loadScripts = function() { var loadingScripts = tinymce.grep(scripts); // Current scripts has been handled scripts.length = 0; // Load scripts that needs to be loaded tinymce.each(loadingScripts, function(url) { // Script is already loaded then execute script callbacks directly if (states[url] == LOADED) { execScriptLoadedCallbacks(url); return; } // Is script not loading then start loading it if (states[url] != LOADING) { states[url] = LOADING; loading++; loadScript(url, function() { states[url] = LOADED; loading--; execScriptLoadedCallbacks(url); // Load more scripts if they where added by the recently loaded script loadScripts(); }); } }); // No scripts are currently loading then execute all pending queue loaded callbacks if (!loading) { tinymce.each(queueLoadedCallbacks, function(callback) { callback.func.call(callback.scope); }); queueLoadedCallbacks.length = 0; } }; loadScripts(); }; }; // Global script loader tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); })(tinymce);