ScriptLoader.js
6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/**
* ScriptLoader.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/*globals console*/
/**
* 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.');
* });
*/
define("tinymce/dom/ScriptLoader", [
"tinymce/dom/DOMUtils",
"tinymce/util/Tools"
], function(DOMUtils, Tools) {
var DOM = DOMUtils.DOM;
var each = Tools.each, grep = Tools.grep;
function ScriptLoader() {
var QUEUED = 0,
LOADING = 1,
LOADED = 2,
states = {},
queue = [],
scriptLoadedCallbacks = {},
queueLoadedCallbacks = [],
loading = 0,
undef;
/**
* 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.
*/
function loadScript(url, callback) {
var dom = DOM, elm, id;
// Execute callback when script is loaded
function done() {
dom.remove(id);
if (elm) {
elm.onreadystatechange = elm.onload = elm = null;
}
callback();
}
function error() {
/*eslint no-console:0 */
// 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();
// Create new script element
elm = document.createElement('script');
elm.id = id;
elm.type = 'text/javascript';
elm.src = Tools._addCacheSuffix(url);
// Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
if ("onreadystatechange" in elm) {
elm.onreadystatechange = function() {
if (/loaded|complete/.test(elm.readyState)) {
done();
}
};
} else {
elm.onload = done;
}
// Add onerror event will get fired on some browsers but not all of them
elm.onerror = error;
// 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} url 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 state = states[url];
// Add url to load queue
if (state == undef) {
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
});
}
};
this.remove = function(url) {
delete states[url];
delete scriptLoadedCallbacks[url];
};
/**
* 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
each(scriptLoadedCallbacks[url], function(callback) {
callback.func.call(callback.scope);
});
scriptLoadedCallbacks[url] = undef;
}
queueLoadedCallbacks.push({
func: callback,
scope: scope || this
});
loadScripts = function() {
var loadingScripts = grep(scripts);
// Current scripts has been handled
scripts.length = 0;
// Load scripts that needs to be loaded
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) {
each(queueLoadedCallbacks, function(callback) {
callback.func.call(callback.scope);
});
queueLoadedCallbacks.length = 0;
}
};
loadScripts();
};
}
ScriptLoader.ScriptLoader = new ScriptLoader();
return ScriptLoader;
});