ScriptLoader.js 6.7 KB

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