4 * Copyright 2009, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://tinymce.moxiecode.com/license
8 * Contributing: http://tinymce.moxiecode.com/contributing
13 * 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.
15 * @class tinymce.dom.ScriptLoader
17 * // Load a script from a specific URL using the global script loader
18 * tinymce.ScriptLoader.load('somescript.js');
20 * // Load a script using a unique instance of the script loader
21 * var scriptLoader = new tinymce.dom.ScriptLoader();
23 * scriptLoader.load('somescript.js');
25 * // Load multiple scripts
26 * var scriptLoader = new tinymce.dom.ScriptLoader();
28 * scriptLoader.add('somescript1.js');
29 * scriptLoader.add('somescript2.js');
30 * scriptLoader.add('somescript3.js');
32 * scriptLoader.loadQueue(function() {
33 * alert('All scripts are now loaded.');
36 tinymce.dom.ScriptLoader = function(settings) {
42 scriptLoadedCallbacks = {},
43 queueLoadedCallbacks = [],
48 * Loads a specific script directly without adding it to the load queue.
51 * @param {String} url Absolute URL to script to add.
52 * @param {function} callback Optional callback function to execute ones this script gets loaded.
53 * @param {Object} scope Optional scope to execute callback in.
55 function loadScript(url, callback) {
56 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
58 // Execute callback when script is loaded
63 elm.onreadystatechange = elm.onload = elm = null;
69 // Report the error so it's easier for people to spot loading errors
70 if (typeof(console) !== "undefined" && console.log)
71 console.log("Failed to load: " + url);
73 // We can't mark it as done if there is a load error since
74 // A) We don't want to produce 404 errors on the server and
75 // B) the onerror event won't fire on all browsers.
82 uri = new tinymce.util.URI(url);
85 // If script is from same domain and we
86 // use IE 6 then use XHR since it's more reliable
87 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
88 tinymce.util.XHR.send({
89 url : tinymce._addVer(uri.getURI()),
90 success : function(content) {
91 // Create new temp script element
92 var script = dom.create('script', {
93 type : 'text/javascript'
96 // Evaluate script in global scope
97 script.text = content;
98 document.getElementsByTagName('head')[0].appendChild(script);
111 // Create new script element
112 elm = dom.create('script', {
114 type : 'text/javascript',
115 src : tinymce._addVer(url)
118 // Add onload listener for non IE browsers since IE9
119 // fires onload event before the script is parsed and executed
123 // Add onerror event will get fired on some browsers but not all of them
126 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
127 if (!tinymce.isOpera) {
128 elm.onreadystatechange = function() {
129 var state = elm.readyState;
131 // Loaded state is passed on IE 6 however there
132 // are known issues with this method but we can't use
133 // XHR in a cross domain loading
134 if (state == 'complete' || state == 'loaded')
139 // Most browsers support this feature so we report errors
140 // for those at least to help users track their missing plugins etc
141 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
142 /*elm.onerror = function() {
143 alert('Failed to load: ' + url);
146 // Add script to document
147 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
151 * Returns true/false if a script has been loaded or not.
154 * @param {String} url URL to check for.
155 * @return [Boolean} true/false if the URL is loaded.
157 this.isDone = function(url) {
158 return states[url] == LOADED;
162 * Marks a specific script to be loaded. This can be useful if a script got loaded outside
163 * the script loader or to skip it from loading some script.
166 * @param {string} u Absolute URL to the script to mark as loaded.
168 this.markDone = function(url) {
169 states[url] = LOADED;
173 * Adds a specific script to the load queue of the script loader.
176 * @param {String} url Absolute URL to script to add.
177 * @param {function} callback Optional callback function to execute ones this script gets loaded.
178 * @param {Object} scope Optional scope to execute callback in.
180 this.add = this.load = function(url, callback, scope) {
181 var item, state = states[url];
183 // Add url to load queue
184 if (state == undefined) {
186 states[url] = QUEUED;
190 // Store away callback for later execution
191 if (!scriptLoadedCallbacks[url])
192 scriptLoadedCallbacks[url] = [];
194 scriptLoadedCallbacks[url].push({
196 scope : scope || this
202 * Starts the loading of the queue.
205 * @param {function} callback Optional callback to execute when all queued items are loaded.
206 * @param {Object} scope Optional scope to execute the callback in.
208 this.loadQueue = function(callback, scope) {
209 this.loadScripts(queue, callback, scope);
213 * Loads the specified queue of files and executes the callback ones they are loaded.
214 * This method is generally not used outside this class but it might be useful in some scenarios.
216 * @method loadScripts
217 * @param {Array} scripts Array of queue items to load.
218 * @param {function} callback Optional callback to execute ones all items are loaded.
219 * @param {Object} scope Optional scope to execute callback in.
221 this.loadScripts = function(scripts, callback, scope) {
224 function execScriptLoadedCallbacks(url) {
225 // Execute URL callback functions
226 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
227 callback.func.call(callback.scope);
230 scriptLoadedCallbacks[url] = undefined;
233 queueLoadedCallbacks.push({
235 scope : scope || this
238 loadScripts = function() {
239 var loadingScripts = tinymce.grep(scripts);
241 // Current scripts has been handled
244 // Load scripts that needs to be loaded
245 tinymce.each(loadingScripts, function(url) {
246 // Script is already loaded then execute script callbacks directly
247 if (states[url] == LOADED) {
248 execScriptLoadedCallbacks(url);
252 // Is script not loading then start loading it
253 if (states[url] != LOADING) {
254 states[url] = LOADING;
257 loadScript(url, function() {
258 states[url] = LOADED;
261 execScriptLoadedCallbacks(url);
263 // Load more scripts if they where added by the recently loaded script
269 // No scripts are currently loading then execute all pending queue loaded callbacks
271 tinymce.each(queueLoadedCallbacks, function(callback) {
272 callback.func.call(callback.scope);
275 queueLoadedCallbacks.length = 0;
283 // Global script loader
284 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();