      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 							// Some browsers, such as Safari, may call the onLoad function
    150 							// immediately. Which will break the loading sequence. (#3661)
    151 							setTimeout( function() { onLoad( url, true ); }, 0 );
    152 						};
    153
    154 						// FIXME: Opera and Safari will not fire onerror.
    155
    156 						/** @ignore */
    157 						script.$.onerror = function()
    158 						{
    159 							onLoad( url, false );
    160 						};
    161 					}
    162 				}
    163
    164 				// Append it to <head>.
    165 				script.appendTo( CKEDITOR.document.getHead() );
    166
    167 				CKEDITOR.fire( 'download', url );		// @Packager.RemoveLine
    168 			};
    169
    170 			for ( var i = 0 ; i < scriptCount ; i++ )
    171 			{
    172 				loadScript( scriptUrl[ i ] );
    173 			}
    174 		},
    175
    176 		/**
    177 		 * Executes a JavaScript code into the current document.
    178 		 * @param {String} code The code to be executed.
    179 		 * @example
    180 		 * CKEDITOR.scriptLoader.loadCode( 'var x = 10;' );
    181 		 * alert( x );  // "10"
    182 		 */
    183 		loadCode : function( code )
    184 		{
    185 			// Create the <script> element.
    186 			var script = new CKEDITOR.dom.element( 'script' );
    187 			script.setAttribute( 'type', 'text/javascript' );
    188 			script.appendText( code );
    189
    190 			// Append it to <head>.
    191 			script.appendTo( CKEDITOR.document.getHead() );
    192 		}
    193 	};
    194 })();
    195