scriptloader.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview Defines the {@link CKEDITOR.scriptLoader} object, used to load scripts
  7. * asynchronously.
  8. */
  9. /**
  10. * Load scripts asynchronously.
  11. *
  12. * @class
  13. * @singleton
  14. */
  15. CKEDITOR.scriptLoader = ( function() {
  16. var uniqueScripts = {},
  17. waitingList = {};
  18. return {
  19. /**
  20. * Loads one or more external script checking if not already loaded
  21. * previously by this function.
  22. *
  23. * CKEDITOR.scriptLoader.load( '/myscript.js' );
  24. *
  25. * CKEDITOR.scriptLoader.load( '/myscript.js', function( success ) {
  26. * // Alerts true if the script has been properly loaded.
  27. * // HTTP error 404 should return false.
  28. * alert( success );
  29. * } );
  30. *
  31. * CKEDITOR.scriptLoader.load( [ '/myscript1.js', '/myscript2.js' ], function( completed, failed ) {
  32. * alert( 'Number of scripts loaded: ' + completed.length );
  33. * alert( 'Number of failures: ' + failed.length );
  34. * } );
  35. *
  36. * @param {String/Array} scriptUrl One or more URLs pointing to the
  37. * scripts to be loaded.
  38. * @param {Function} [callback] A function to be called when the script
  39. * is loaded and executed. If a string is passed to `scriptUrl`, a
  40. * boolean parameter is passed to the callback, indicating the
  41. * success of the load. If an array is passed instead, two arrays
  42. * parameters are passed to the callback - the first contains the
  43. * URLs that have been properly loaded and the second the failed ones.
  44. * @param {Object} [scope] The scope (`this` reference) to be used for
  45. * the callback call. Defaults to {@link CKEDITOR}.
  46. * @param {Boolean} [showBusy] Changes the cursor of the document while
  47. * the script is loaded.
  48. */
  49. load: function( scriptUrl, callback, scope, showBusy ) {
  50. var isString = ( typeof scriptUrl == 'string' );
  51. if ( isString )
  52. scriptUrl = [ scriptUrl ];
  53. if ( !scope )
  54. scope = CKEDITOR;
  55. var scriptCount = scriptUrl.length,
  56. completed = [],
  57. failed = [];
  58. var doCallback = function( success ) {
  59. if ( callback ) {
  60. if ( isString )
  61. callback.call( scope, success );
  62. else
  63. callback.call( scope, completed, failed );
  64. }
  65. };
  66. if ( scriptCount === 0 ) {
  67. doCallback( true );
  68. return;
  69. }
  70. var checkLoaded = function( url, success ) {
  71. ( success ? completed : failed ).push( url );
  72. if ( --scriptCount <= 0 ) {
  73. showBusy && CKEDITOR.document.getDocumentElement().removeStyle( 'cursor' );
  74. doCallback( success );
  75. }
  76. };
  77. var onLoad = function( url, success ) {
  78. // Mark this script as loaded.
  79. uniqueScripts[ url ] = 1;
  80. // Get the list of callback checks waiting for this file.
  81. var waitingInfo = waitingList[ url ];
  82. delete waitingList[ url ];
  83. // Check all callbacks waiting for this file.
  84. for ( var i = 0; i < waitingInfo.length; i++ )
  85. waitingInfo[ i ]( url, success );
  86. };
  87. var loadScript = function( url ) {
  88. if ( uniqueScripts[ url ] ) {
  89. checkLoaded( url, true );
  90. return;
  91. }
  92. var waitingInfo = waitingList[ url ] || ( waitingList[ url ] = [] );
  93. waitingInfo.push( checkLoaded );
  94. // Load it only for the first request.
  95. if ( waitingInfo.length > 1 )
  96. return;
  97. // Create the <script> element.
  98. var script = new CKEDITOR.dom.element( 'script' );
  99. script.setAttributes( {
  100. type: 'text/javascript',
  101. src: url
  102. } );
  103. if ( callback ) {
  104. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 ) {
  105. // FIXME: For IE, we are not able to return false on error (like 404).
  106. script.$.onreadystatechange = function() {
  107. if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) {
  108. script.$.onreadystatechange = null;
  109. onLoad( url, true );
  110. }
  111. };
  112. } else {
  113. script.$.onload = function() {
  114. // Some browsers, such as Safari, may call the onLoad function
  115. // immediately. Which will break the loading sequence. (#3661)
  116. setTimeout( function() {
  117. onLoad( url, true );
  118. }, 0 );
  119. };
  120. // FIXME: Opera and Safari will not fire onerror.
  121. script.$.onerror = function() {
  122. onLoad( url, false );
  123. };
  124. }
  125. }
  126. // Append it to <head>.
  127. script.appendTo( CKEDITOR.document.getHead() );
  128. CKEDITOR.fire( 'download', url ); // %REMOVE_LINE%
  129. };
  130. showBusy && CKEDITOR.document.getDocumentElement().setStyle( 'cursor', 'wait' );
  131. for ( var i = 0; i < scriptCount; i++ ) {
  132. loadScript( scriptUrl[ i ] );
  133. }
  134. },
  135. /**
  136. * Loads a script in a queue, so only one is loaded at the same time.
  137. *
  138. * @since 4.1.2
  139. * @param {String} scriptUrl URL pointing to the script to be loaded.
  140. * @param {Function} [callback] A function to be called when the script
  141. * is loaded and executed. A boolean parameter is passed to the callback,
  142. * indicating the success of the load.
  143. *
  144. * @see CKEDITOR.scriptLoader#load
  145. */
  146. queue: ( function() {
  147. var pending = [];
  148. // Loads the very first script from queue and removes it.
  149. function loadNext() {
  150. var script;
  151. if ( ( script = pending[ 0 ] ) )
  152. this.load( script.scriptUrl, script.callback, CKEDITOR, 0 );
  153. }
  154. return function( scriptUrl, callback ) {
  155. var that = this;
  156. // This callback calls the standard callback for the script
  157. // and loads the very next script from pending list.
  158. function callbackWrapper() {
  159. callback && callback.apply( this, arguments );
  160. // Removed the just loaded script from the queue.
  161. pending.shift();
  162. loadNext.call( that );
  163. }
  164. // Let's add this script to the queue
  165. pending.push( { scriptUrl: scriptUrl, callback: callbackWrapper } );
  166. // If the queue was empty, then start loading.
  167. if ( pending.length == 1 )
  168. loadNext.call( this );
  169. };
  170. } )()
  171. };
  172. } )();