c2df992d4ca8e57f8d7ecdcdfb8ae9e24bacf5c4
[ckeditor.git] / _source / core / scriptloader.js
1 /*
2 Copyright (c) 2003-2011, 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 })();