|
1 /** |
|
2 * ScriptLoader.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /*globals console*/ |
|
12 |
|
13 /** |
|
14 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks |
|
15 * when various items gets loaded. This class is useful to load external JavaScript files. |
|
16 * |
|
17 * @class tinymce.dom.ScriptLoader |
|
18 * @example |
|
19 * // Load a script from a specific URL using the global script loader |
|
20 * tinymce.ScriptLoader.load('somescript.js'); |
|
21 * |
|
22 * // Load a script using a unique instance of the script loader |
|
23 * var scriptLoader = new tinymce.dom.ScriptLoader(); |
|
24 * |
|
25 * scriptLoader.load('somescript.js'); |
|
26 * |
|
27 * // Load multiple scripts |
|
28 * var scriptLoader = new tinymce.dom.ScriptLoader(); |
|
29 * |
|
30 * scriptLoader.add('somescript1.js'); |
|
31 * scriptLoader.add('somescript2.js'); |
|
32 * scriptLoader.add('somescript3.js'); |
|
33 * |
|
34 * scriptLoader.loadQueue(function() { |
|
35 * alert('All scripts are now loaded.'); |
|
36 * }); |
|
37 */ |
|
38 define("tinymce/dom/ScriptLoader", [ |
|
39 "tinymce/dom/DOMUtils", |
|
40 "tinymce/util/Tools" |
|
41 ], function(DOMUtils, Tools) { |
|
42 var DOM = DOMUtils.DOM; |
|
43 var each = Tools.each, grep = Tools.grep; |
|
44 |
|
45 function ScriptLoader() { |
|
46 var QUEUED = 0, |
|
47 LOADING = 1, |
|
48 LOADED = 2, |
|
49 states = {}, |
|
50 queue = [], |
|
51 scriptLoadedCallbacks = {}, |
|
52 queueLoadedCallbacks = [], |
|
53 loading = 0, |
|
54 undef; |
|
55 |
|
56 /** |
|
57 * Loads a specific script directly without adding it to the load queue. |
|
58 * |
|
59 * @method load |
|
60 * @param {String} url Absolute URL to script to add. |
|
61 * @param {function} callback Optional callback function to execute ones this script gets loaded. |
|
62 * @param {Object} scope Optional scope to execute callback in. |
|
63 */ |
|
64 function loadScript(url, callback) { |
|
65 var dom = DOM, elm, id; |
|
66 |
|
67 // Execute callback when script is loaded |
|
68 function done() { |
|
69 dom.remove(id); |
|
70 |
|
71 if (elm) { |
|
72 elm.onreadystatechange = elm.onload = elm = null; |
|
73 } |
|
74 |
|
75 callback(); |
|
76 } |
|
77 |
|
78 function error() { |
|
79 /*eslint no-console:0 */ |
|
80 |
|
81 // Report the error so it's easier for people to spot loading errors |
|
82 if (typeof console !== "undefined" && console.log) { |
|
83 console.log("Failed to load: " + url); |
|
84 } |
|
85 |
|
86 // We can't mark it as done if there is a load error since |
|
87 // A) We don't want to produce 404 errors on the server and |
|
88 // B) the onerror event won't fire on all browsers. |
|
89 // done(); |
|
90 } |
|
91 |
|
92 id = dom.uniqueId(); |
|
93 |
|
94 // Create new script element |
|
95 elm = document.createElement('script'); |
|
96 elm.id = id; |
|
97 elm.type = 'text/javascript'; |
|
98 elm.src = Tools._addCacheSuffix(url); |
|
99 |
|
100 // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly |
|
101 if ("onreadystatechange" in elm) { |
|
102 elm.onreadystatechange = function() { |
|
103 if (/loaded|complete/.test(elm.readyState)) { |
|
104 done(); |
|
105 } |
|
106 }; |
|
107 } else { |
|
108 elm.onload = done; |
|
109 } |
|
110 |
|
111 // Add onerror event will get fired on some browsers but not all of them |
|
112 elm.onerror = error; |
|
113 |
|
114 // Add script to document |
|
115 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); |
|
116 } |
|
117 |
|
118 /** |
|
119 * Returns true/false if a script has been loaded or not. |
|
120 * |
|
121 * @method isDone |
|
122 * @param {String} url URL to check for. |
|
123 * @return {Boolean} true/false if the URL is loaded. |
|
124 */ |
|
125 this.isDone = function(url) { |
|
126 return states[url] == LOADED; |
|
127 }; |
|
128 |
|
129 /** |
|
130 * Marks a specific script to be loaded. This can be useful if a script got loaded outside |
|
131 * the script loader or to skip it from loading some script. |
|
132 * |
|
133 * @method markDone |
|
134 * @param {string} u Absolute URL to the script to mark as loaded. |
|
135 */ |
|
136 this.markDone = function(url) { |
|
137 states[url] = LOADED; |
|
138 }; |
|
139 |
|
140 /** |
|
141 * Adds a specific script to the load queue of the script loader. |
|
142 * |
|
143 * @method add |
|
144 * @param {String} url Absolute URL to script to add. |
|
145 * @param {function} callback Optional callback function to execute ones this script gets loaded. |
|
146 * @param {Object} scope Optional scope to execute callback in. |
|
147 */ |
|
148 this.add = this.load = function(url, callback, scope) { |
|
149 var state = states[url]; |
|
150 |
|
151 // Add url to load queue |
|
152 if (state == undef) { |
|
153 queue.push(url); |
|
154 states[url] = QUEUED; |
|
155 } |
|
156 |
|
157 if (callback) { |
|
158 // Store away callback for later execution |
|
159 if (!scriptLoadedCallbacks[url]) { |
|
160 scriptLoadedCallbacks[url] = []; |
|
161 } |
|
162 |
|
163 scriptLoadedCallbacks[url].push({ |
|
164 func: callback, |
|
165 scope: scope || this |
|
166 }); |
|
167 } |
|
168 }; |
|
169 |
|
170 /** |
|
171 * Starts the loading of the queue. |
|
172 * |
|
173 * @method loadQueue |
|
174 * @param {function} callback Optional callback to execute when all queued items are loaded. |
|
175 * @param {Object} scope Optional scope to execute the callback in. |
|
176 */ |
|
177 this.loadQueue = function(callback, scope) { |
|
178 this.loadScripts(queue, callback, scope); |
|
179 }; |
|
180 |
|
181 /** |
|
182 * Loads the specified queue of files and executes the callback ones they are loaded. |
|
183 * This method is generally not used outside this class but it might be useful in some scenarios. |
|
184 * |
|
185 * @method loadScripts |
|
186 * @param {Array} scripts Array of queue items to load. |
|
187 * @param {function} callback Optional callback to execute ones all items are loaded. |
|
188 * @param {Object} scope Optional scope to execute callback in. |
|
189 */ |
|
190 this.loadScripts = function(scripts, callback, scope) { |
|
191 var loadScripts; |
|
192 |
|
193 function execScriptLoadedCallbacks(url) { |
|
194 // Execute URL callback functions |
|
195 each(scriptLoadedCallbacks[url], function(callback) { |
|
196 callback.func.call(callback.scope); |
|
197 }); |
|
198 |
|
199 scriptLoadedCallbacks[url] = undef; |
|
200 } |
|
201 |
|
202 queueLoadedCallbacks.push({ |
|
203 func: callback, |
|
204 scope: scope || this |
|
205 }); |
|
206 |
|
207 loadScripts = function() { |
|
208 var loadingScripts = grep(scripts); |
|
209 |
|
210 // Current scripts has been handled |
|
211 scripts.length = 0; |
|
212 |
|
213 // Load scripts that needs to be loaded |
|
214 each(loadingScripts, function(url) { |
|
215 // Script is already loaded then execute script callbacks directly |
|
216 if (states[url] == LOADED) { |
|
217 execScriptLoadedCallbacks(url); |
|
218 return; |
|
219 } |
|
220 |
|
221 // Is script not loading then start loading it |
|
222 if (states[url] != LOADING) { |
|
223 states[url] = LOADING; |
|
224 loading++; |
|
225 |
|
226 loadScript(url, function() { |
|
227 states[url] = LOADED; |
|
228 loading--; |
|
229 |
|
230 execScriptLoadedCallbacks(url); |
|
231 |
|
232 // Load more scripts if they where added by the recently loaded script |
|
233 loadScripts(); |
|
234 }); |
|
235 } |
|
236 }); |
|
237 |
|
238 // No scripts are currently loading then execute all pending queue loaded callbacks |
|
239 if (!loading) { |
|
240 each(queueLoadedCallbacks, function(callback) { |
|
241 callback.func.call(callback.scope); |
|
242 }); |
|
243 |
|
244 queueLoadedCallbacks.length = 0; |
|
245 } |
|
246 }; |
|
247 |
|
248 loadScripts(); |
|
249 }; |
|
250 } |
|
251 |
|
252 ScriptLoader.ScriptLoader = new ScriptLoader(); |
|
253 |
|
254 return ScriptLoader; |
|
255 }); |