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