text: ﻿/*
  Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
  For licensing, see LICENSE.html or http://ckeditor.com/license
  */
  
  /**
   * @fileOverview Defines the {@link CKEDITOR.scriptLoader} object, used to load scripts
   *		asynchronously.
   */
  
  /**
   * Load scripts asynchronously.
   * @namespace
   * @example
   */
  CKEDITOR.scriptLoader = (function()
  {
  	var uniqueScripts = {};
  	var waitingList = {};
  
  	return /** @lends CKEDITOR.scriptLoader */ {
  		/**
  		 * Loads one or more external script checking if not already loaded
  		 * previously by this function.
  		 * @param {String|Array} scriptUrl One or more URLs pointing to the
  		 *		scripts to be loaded.
  		 * @param {Function} [callback] A function to be called when the script
  		 *		is loaded and executed. If a string is passed to "scriptUrl", a
  		 *		boolean parameter is passed to the callback, indicating the
  		 *		success of the load. If an array is passed instead, two array
  		 *		parameters are passed to the callback; the first contains the
  		 *		URLs that have been properly loaded, and the second the failed
  		 *		ones.
  		 * @param {Object} [scope] The scope ("this" reference) to be used for
  		 *		the callback call. Default to {@link CKEDITOR}.
  		 * @param {Boolean} [noCheck] Indicates that the script must be loaded
  		 *		anyway, not checking if it has already loaded.
  		 * @example
  		 * CKEDITOR.scriptLoader.load( '/myscript.js' );
  		 * @example
  		 * CKEDITOR.scriptLoader.load( '/myscript.js', function( success )
  		 *     {
  		 *         // Alerts "true" if the script has been properly loaded.
  		 *         // HTTP error 404 should return "false".
  		 *         alert( success );
  		 *     });
  		 * @example
  		 * CKEDITOR.scriptLoader.load( [ '/myscript1.js', '/myscript2.js' ], function( completed, failed )
  		 *     {
  		 *         alert( 'Number of scripts loaded: ' + completed.length );
  		 *         alert( 'Number of failures: ' + failed.length );
  		 *     });
  		 */
  		load : function( scriptUrl, callback, scope, noCheck, showBusy )
  		{
  			var isString = ( typeof scriptUrl == 'string' );
  
  			if ( isString )
  				scriptUrl = [ scriptUrl ];
  
  			if ( !scope )
  				scope = CKEDITOR;
  
  			var scriptCount = scriptUrl.length,
  				completed = [],
  				failed = [];
  
  			var doCallback = function( success )
  			{
  				if ( callback )
  				{
  					if ( isString )
  						callback.call( scope, success );
  					else
  						callback.call( scope, completed, failed );
  				}
  			};
  
  			if ( scriptCount === 0 )
  			{
  				doCallback( true );
  				return;
  			}
  
  			var checkLoaded = function( url, success )
  			{
  				( success ? completed : failed ).push( url );
  
  				if ( --scriptCount <= 0 )
  				{
  					showBusy && CKEDITOR.document.getDocumentElement().removeStyle( 'cursor' );
  					doCallback( success );
  				}
  			};
  
  			var onLoad = function( url, success )
  			{
  				// Mark this script as loaded.
  				uniqueScripts[ url ] = 1;
  
  				// Get the list of callback checks waiting for this file.
  				var waitingInfo = waitingList[ url ];
  				delete waitingList[ url ];
  
  				// Check all callbacks waiting for this file.
  				for ( var i = 0 ; i < waitingInfo.length ; i++ )
  					waitingInfo[ i ]( url, success );
  			};
  
  			var loadScript = function( url )
  			{
  				if ( noCheck !== true && uniqueScripts[ url ] )
  				{
  					checkLoaded( url, true );
  					return;
  				}
  
  				var waitingInfo = waitingList[ url ] || ( waitingList[ url ] = [] );
  				waitingInfo.push( checkLoaded );
  
  				// Load it only for the first request.
  				if ( waitingInfo.length > 1 )
  					return;
  
  				// Create the <script> element.
  				var script = new CKEDITOR.dom.element( 'script' );
  				script.setAttributes( {
  					type : 'text/javascript',
  					src : url } );
  
  				if ( callback )
  				{
  					if ( CKEDITOR.env.ie )
  					{
  						// FIXME: For IE, we are not able to return false on error (like 404).
  
  						/** @ignore */
  						script.$.onreadystatechange = function ()
  						{
  							if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' )
  							{
  								script.$.onreadystatechange = null;
  								onLoad( url, true );
  							}
  						};
  					}
  					else
  					{
  						/** @ignore */
  						script.$.onload = function()
  						{
  							// Some browsers, such as Safari, may call the onLoad function
  							// immediately. Which will break the loading sequence. (#3661)
  							setTimeout( function() { onLoad( url, true ); }, 0 );
  						};
  
  						// FIXME: Opera and Safari will not fire onerror.
  
  						/** @ignore */
  						script.$.onerror = function()
  						{
  							onLoad( url, false );
  						};
  					}
  				}
  
  				// Append it to <head>.
  				script.appendTo( CKEDITOR.document.getHead() );
  
  				CKEDITOR.fire( 'download', url );		// @Packager.RemoveLine
  			};
  
  			showBusy && CKEDITOR.document.getDocumentElement().setStyle( 'cursor', 'wait' );
  			for ( var i = 0 ; i < scriptCount ; i++ )
  			{
  				loadScript( scriptUrl[ i ] );
  			}
  		},
  
  		/**
  		 * Executes a JavaScript code into the current document.
  		 * @param {String} code The code to be executed.
  		 * @example
  		 * CKEDITOR.scriptLoader.loadCode( 'var x = 10;' );
  		 * alert( x );  // "10"
  		 */
  		loadCode : function( code )
  		{
  			// Create the <script> element.
  			var script = new CKEDITOR.dom.element( 'script' );
  			script.setAttribute( 'type', 'text/javascript' );
  			script.appendText( code );
  
  			// Append it to <head>.
  			script.appendTo( CKEDITOR.document.getHead() );
  		}
  	};
  })();
  