1 /*
  2 Copyright (c) 2003-2009, 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 	var 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} [noCheck] Indicates that the script must be loaded
 37 		 *		anyway, not checking if it has already 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, noCheck )
 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 					doCallback( success );
 91 			};
 92
 93 			var onLoad = function( url, success )
 94 			{
 95 				// Mark this script as loaded.
 96 				uniqueScripts[ url ] = 1;
 97
 98 				// Get the list of callback checks waiting for this file.
 99 				var waitingInfo = waitingList[ url ];
100 				delete waitingList[ url ];
101
102 				// Check all callbacks waiting for this file.
103 				for ( var i = 0 ; i < waitingInfo.length ; i++ )
104 					waitingInfo[ i ]( url, success );
105 			};
106
107 			var loadScript = function( url )
108 			{
109 				if ( noCheck !== true && uniqueScripts[ url ] )
110 				{
111 					checkLoaded( url, true );
112 					return;
113 				}
114
115 				var waitingInfo = waitingList[ url ] || ( waitingList[ url ] = [] );
116 				waitingInfo.push( checkLoaded );
117
118 				// Load it only for the first request.
119 				if ( waitingInfo.length > 1 )
120 					return;
121
122 				// Create the <script> element.
123 				var script = new CKEDITOR.dom.element( 'script' );
124 				script.setAttributes( {
125 					type : 'text/javascript',
126 					src : url } );
127
128 				if ( callback )
129 				{
130 					if ( CKEDITOR.env.ie )
131 					{
132 						// FIXME: For IE, we are not able to return false on error (like 404).
133
134 						/** @ignore */
135 						script.$.onreadystatechange = function ()
136 						{
137 							if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' )
138 							{
139 								script.$.onreadystatechange = null;
140 								onLoad( url, true );
141 							}
142 						};
143 					}
144 					else
145 					{
146 						/** @ignore */
147 						script.$.onload = function()
148 						{
149 							onLoad( url, true );
150 						};
151
152 						// FIXME: Opera and Safari will not fire onerror.
153
154 						/** @ignore */
155 						script.$.onerror = function()
156 						{
157 							onLoad( url, false );
158 						};
159 					}
160 				}
161
162 				// Append it to <head>.
163 				script.appendTo( CKEDITOR.document.getHead() );
164
165 				CKEDITOR.fire( 'download', url );		// @Packager.RemoveLine
166 			};
167
168 			for ( var i = 0 ; i < scriptCount ; i++ )
169 			{
170 				loadScript( scriptUrl[ i ] );
171 			}
172 		},
173
174 		/**
175 		 * Executes a JavaScript code into the current document.
176 		 * @param {String} code The code to be executed.
177 		 * @example
178 		 * CKEDITOR.scriptLoader.loadCode( 'var x = 10;' );
179 		 * alert( x );  // "10"
180 		 */
181 		loadCode : function( code )
182 		{
183 			// Create the <script> element.
184 			var script = new CKEDITOR.dom.element( 'script' );
185 			script.setAttribute( 'type', 'text/javascript' );
186 			script.appendText( code );
187
188 			// Append it to <head>.
189 			script.appendTo( CKEDITOR.document.getHead() );
190 		}
191 	};
192 })();
193