Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / docs / source / Loader.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-Loader'>/**
19 </span> * @author Jacky Nguyen &lt;jacky@sencha.com&gt;
20  * @docauthor Jacky Nguyen &lt;jacky@sencha.com&gt;
21  * @class Ext.Loader
22  *
23
24 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
25 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
26 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
27
28 # Asynchronous Loading #
29
30 - Advantages:
31         + Cross-domain
32         + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
33  .html`)
34         + Best possible debugging experience: error messages come with the exact file name and line number
35
36 - Disadvantages:
37         + Dependencies need to be specified before-hand
38
39 ### Method 1: Explicitly include what you need: ###
40
41     // Syntax
42     Ext.require({String/Array} expressions);
43
44     // Example: Single alias
45     Ext.require('widget.window');
46
47     // Example: Single class name
48     Ext.require('Ext.window.Window');
49
50     // Example: Multiple aliases / class names mix
51     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
52
53     // Wildcards
54     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
55
56 ### Method 2: Explicitly exclude what you don't need: ###
57
58     // Syntax: Note that it must be in this chaining format.
59     Ext.exclude({String/Array} expressions)
60        .require({String/Array} expressions);
61
62     // Include everything except Ext.data.*
63     Ext.exclude('Ext.data.*').require('*'); 
64
65     // Include all widgets except widget.checkbox*,
66     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
67     Ext.exclude('widget.checkbox*').require('widget.*');
68
69 # Synchronous Loading on Demand #
70
71 - *Advantages:*
72         + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
73  before
74
75 - *Disadvantages:*
76         + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
77         + Must be from the same domain due to XHR restriction
78         + Need a web server, same reason as above
79
80 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
81
82     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
83
84     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
85
86     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
87
88 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
89  existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
90  class and all its dependencies.
91
92 # Hybrid Loading - The Best of Both Worlds #
93
94 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
95
96 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
97  dependencies on demand as they're needed during run-time. For example: ###
98
99     Ext.onReady(function(){
100         var window = Ext.createWidget('window', {
101             width: 500,
102             height: 300,
103             layout: {
104                 type: 'border',
105                 padding: 5
106             },
107             title: 'Hello Dialog',
108             items: [{
109                 title: 'Navigation',
110                 collapsible: true,
111                 region: 'west',
112                 width: 200,
113                 html: 'Hello',
114                 split: true
115             }, {
116                 title: 'TabPanel',
117                 region: 'center'
118             }]
119         });
120
121         window.show();
122     })
123
124 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
125
126     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
127     ClassManager.js:432
128     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
129
130 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
131
132     Ext.require('Ext.window.Window');
133     Ext.require('Ext.layout.container.Border');
134
135     Ext.onReady(...);
136
137 Everything should now load via asynchronous mode.
138
139 # Deployment #
140
141 It's important to note that dynamic loading should only be used during development on your local machines.
142 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
143 the whole process of transitioning from / to between development / maintenance and production as easy as
144 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
145 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
146 then include it on top of your application.
147
148 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
149
150  * @singleton
151  * @markdown
152  */
153
154 (function(Manager, Class, flexSetter, alias) {
155
156     var
157         //&lt;if nonBrowser&gt;
158         isNonBrowser = typeof window === 'undefined',
159         isNodeJS = isNonBrowser &amp;&amp; (typeof require === 'function'),
160         isPhantomJS = (typeof phantom !== 'undefined' &amp;&amp; phantom.fs),
161         //&lt;/if&gt;
162         dependencyProperties = ['extend', 'mixins', 'requires'],
163         Loader;
164
165     Loader = Ext.Loader = {
166 <span id='Ext-Loader-property-documentHead'>        /**
167 </span>         * @private
168          */
169         documentHead: typeof document !== 'undefined' &amp;&amp; (document.head || document.getElementsByTagName('head')[0]),
170
171 <span id='Ext-Loader-property-isLoading'>        /**
172 </span>         * Flag indicating whether there are still files being loaded
173          * @private
174          */
175         isLoading: false,
176
177 <span id='Ext-Loader-property-queue'>        /**
178 </span>         * Maintain the queue for all dependencies. Each item in the array is an object of the format:
179          * {
180          *      requires: [...], // The required classes for this queue item
181          *      callback: function() { ... } // The function to execute when all classes specified in requires exist
182          * }
183          * @private
184          */
185         queue: [],
186
187 <span id='Ext-Loader-property-isFileLoaded'>        /**
188 </span>         * Maintain the list of files that have already been handled so that they never get double-loaded
189          * @private
190          */
191         isFileLoaded: {},
192
193 <span id='Ext-Loader-property-readyListeners'>        /**
194 </span>         * Maintain the list of listeners to execute when all required scripts are fully loaded
195          * @private
196          */
197         readyListeners: [],
198
199 <span id='Ext-Loader-property-optionalRequires'>        /**
200 </span>         * Contains optional dependencies to be loaded last
201          * @private
202          */
203         optionalRequires: [],
204
205 <span id='Ext-Loader-property-requiresMap'>        /**
206 </span>         * Map of fully qualified class names to an array of dependent classes.
207          * @private
208          */
209         requiresMap: {},
210
211 <span id='Ext-Loader-property-numPendingFiles'>        /**
212 </span>         * @private
213          */
214         numPendingFiles: 0,
215
216 <span id='Ext-Loader-property-numLoadedFiles'>        /**
217 </span>         * @private
218          */
219         numLoadedFiles: 0,
220
221 <span id='Ext-Loader-property-hasFileLoadError'>        /** @private */
222 </span>        hasFileLoadError: false,
223
224 <span id='Ext-Loader-property-classNameToFilePathMap'>        /**
225 </span>         * @private
226          */
227         classNameToFilePathMap: {},
228
229 <span id='Ext-Loader-property-history'>        /**
230 </span>         * An array of class names to keep track of the dependency loading order.
231          * This is not guaranteed to be the same everytime due to the asynchronous
232          * nature of the Loader.
233          *
234          * @property history
235          * @type Array
236          */
237         history: [],
238
239 <span id='Ext-Loader-property-config'>        /**
240 </span>         * Configuration
241          * @private
242          */
243         config: {
244 <span id='Ext-Loader-cfg-enabled'>            /**
245 </span>             * Whether or not to enable the dynamic dependency loading feature
246              * Defaults to false
247              * @cfg {Boolean} enabled
248              */
249             enabled: false,
250
251 <span id='Ext-Loader-cfg-disableCaching'>            /**
252 </span>             * @cfg {Boolean} disableCaching
253              * Appends current timestamp to script files to prevent caching
254              * Defaults to true
255              */
256             disableCaching: true,
257
258 <span id='Ext-Loader-cfg-disableCachingParam'>            /**
259 </span>             * @cfg {String} disableCachingParam
260              * The get parameter name for the cache buster's timestamp.
261              * Defaults to '_dc'
262              */
263             disableCachingParam: '_dc',
264
265 <span id='Ext-Loader-cfg-paths'>            /**
266 </span>             * @cfg {Object} paths
267              * The mapping from namespaces to file paths
268     {
269         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
270                     // loaded from ./layout/Container.js
271
272         'My': './src/my_own_folder' // My.layout.Container will be loaded from
273                                     // ./src/my_own_folder/layout/Container.js
274     }
275              * Note that all relative paths are relative to the current HTML document.
276              * If not being specified, for example, &lt;code&gt;Other.awesome.Class&lt;/code&gt;
277              * will simply be loaded from &lt;code&gt;./Other/awesome/Class.js&lt;/code&gt;
278              */
279             paths: {
280                 'Ext': '.'
281             }
282         },
283
284 <span id='Ext-Loader-method-setConfig'>        /**
285 </span>         * Set the configuration for the loader. This should be called right after ext-core.js
286          * (or ext-core-debug.js) is included in the page, i.e:
287
288     &lt;script type=&quot;text/javascript&quot; src=&quot;ext-core-debug.js&quot;&gt;&lt;/script&gt;
289     &lt;script type=&quot;text/javascript&quot;&gt;
290       Ext.Loader.setConfig({
291           enabled: true,
292           paths: {
293               'My': 'my_own_path'
294           }
295       });
296     &lt;script&gt;
297     &lt;script type=&quot;text/javascript&quot;&gt;
298       Ext.require(...);
299
300       Ext.onReady(function() {
301           // application code here
302       });
303     &lt;/script&gt;
304
305          * Refer to {@link Ext.Loader#configs} for the list of possible properties
306          *
307          * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
308          * @return {Ext.Loader} this
309          * @markdown
310          */
311         setConfig: function(name, value) {
312             if (Ext.isObject(name) &amp;&amp; arguments.length === 1) {
313                 Ext.Object.merge(this.config, name);
314             }
315             else {
316                 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
317             }
318
319             return this;
320         },
321
322 <span id='Ext-Loader-method-getConfig'>        /**
323 </span>         * Get the config value corresponding to the specified name. If no name is given, will return the config object
324          * @param {String} name The config property name
325          * @return {Object/Mixed}
326          */
327         getConfig: function(name) {
328             if (name) {
329                 return this.config[name];
330             }
331
332             return this.config;
333         },
334
335 <span id='Ext-Loader-method-setPath'>        /**
336 </span>         * Sets the path of a namespace.
337          * For Example:
338
339     Ext.Loader.setPath('Ext', '.');
340
341          * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
342          * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
343          * @return {Ext.Loader} this
344          * @method
345          * @markdown
346          */
347         setPath: flexSetter(function(name, path) {
348             //&lt;if nonBrowser&gt;
349             if (isNonBrowser) {
350                 if (isNodeJS) {
351                     path = require('fs').realpathSync(path);
352                 }
353             }
354             //&lt;/if&gt;
355             this.config.paths[name] = path;
356
357             return this;
358         }),
359
360 <span id='Ext-Loader-method-getPath'>        /**
361 </span>         * Translates a className to a file path by adding the
362          * the proper prefix and converting the .'s to /'s. For example:
363
364     Ext.Loader.setPath('My', '/path/to/My');
365
366     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
367
368          * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
369
370     Ext.Loader.setPath({
371         'My': '/path/to/lib',
372         'My.awesome': '/other/path/for/awesome/stuff',
373         'My.awesome.more': '/more/awesome/path'
374     });
375
376     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
377
378     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
379
380     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
381
382     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
383
384          * @param {String} className
385          * @return {String} path
386          * @markdown
387          */
388         getPath: function(className) {
389             var path = '',
390                 paths = this.config.paths,
391                 prefix = this.getPrefix(className);
392
393             if (prefix.length &gt; 0) {
394                 if (prefix === className) {
395                     return paths[prefix];
396                 }
397
398                 path = paths[prefix];
399                 className = className.substring(prefix.length + 1);
400             }
401
402             if (path.length &gt; 0) {
403                 path += '/';
404             }
405
406             return path.replace(/\/\.\//g, '/') + className.replace(/\./g, &quot;/&quot;) + '.js';
407         },
408
409 <span id='Ext-Loader-method-getPrefix'>        /**
410 </span>         * @private
411          * @param {String} className
412          */
413         getPrefix: function(className) {
414             var paths = this.config.paths,
415                 prefix, deepestPrefix = '';
416
417             if (paths.hasOwnProperty(className)) {
418                 return className;
419             }
420
421             for (prefix in paths) {
422                 if (paths.hasOwnProperty(prefix) &amp;&amp; prefix + '.' === className.substring(0, prefix.length + 1)) {
423                     if (prefix.length &gt; deepestPrefix.length) {
424                         deepestPrefix = prefix;
425                     }
426                 }
427             }
428
429             return deepestPrefix;
430         },
431
432 <span id='Ext-Loader-method-refreshQueue'>        /**
433 </span>         * Refresh all items in the queue. If all dependencies for an item exist during looping,
434          * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
435          * empty
436          * @private
437          */
438         refreshQueue: function() {
439             var ln = this.queue.length,
440                 i, item, j, requires;
441
442             if (ln === 0) {
443                 this.triggerReady();
444                 return;
445             }
446
447             for (i = 0; i &lt; ln; i++) {
448                 item = this.queue[i];
449
450                 if (item) {
451                     requires = item.requires;
452
453                     // Don't bother checking when the number of files loaded
454                     // is still less than the array length
455                     if (requires.length &gt; this.numLoadedFiles) {
456                         continue;
457                     }
458
459                     j = 0;
460
461                     do {
462                         if (Manager.isCreated(requires[j])) {
463                             // Take out from the queue
464                             requires.splice(j, 1);
465                         }
466                         else {
467                             j++;
468                         }
469                     } while (j &lt; requires.length);
470
471                     if (item.requires.length === 0) {
472                         this.queue.splice(i, 1);
473                         item.callback.call(item.scope);
474                         this.refreshQueue();
475                         break;
476                     }
477                 }
478             }
479
480             return this;
481         },
482
483 <span id='Ext-Loader-method-injectScriptElement'>        /**
484 </span>         * Inject a script element to document's head, call onLoad and onError accordingly
485          * @private
486          */
487         injectScriptElement: function(url, onLoad, onError, scope) {
488             var script = document.createElement('script'),
489                 me = this,
490                 onLoadFn = function() {
491                     me.cleanupScriptElement(script);
492                     onLoad.call(scope);
493                 },
494                 onErrorFn = function() {
495                     me.cleanupScriptElement(script);
496                     onError.call(scope);
497                 };
498
499             script.type = 'text/javascript';
500             script.src = url;
501             script.onload = onLoadFn;
502             script.onerror = onErrorFn;
503             script.onreadystatechange = function() {
504                 if (this.readyState === 'loaded' || this.readyState === 'complete') {
505                     onLoadFn();
506                 }
507             };
508
509             this.documentHead.appendChild(script);
510
511             return script;
512         },
513
514 <span id='Ext-Loader-method-cleanupScriptElement'>        /**
515 </span>         * @private
516          */
517         cleanupScriptElement: function(script) {
518             script.onload = null;
519             script.onreadystatechange = null;
520             script.onerror = null;
521
522             return this;
523         },
524
525 <span id='Ext-Loader-method-loadScriptFile'>        /**
526 </span>         * Load a script file, supports both asynchronous and synchronous approaches
527          *
528          * @param {String} url
529          * @param {Function} onLoad
530          * @param {Scope} scope
531          * @param {Boolean} synchronous
532          * @private
533          */
534         loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
535             var me = this,
536                 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
537                 fileName = url.split('/').pop(),
538                 isCrossOriginRestricted = false,
539                 xhr, status, onScriptError;
540
541             scope = scope || this;
542
543             this.isLoading = true;
544
545             if (!synchronous) {
546                 onScriptError = function() {
547                     onError.call(scope, &quot;Failed loading '&quot; + url + &quot;', please verify that the file exists&quot;, synchronous);
548                 };
549
550                 if (!Ext.isReady &amp;&amp; Ext.onDocumentReady) {
551                     Ext.onDocumentReady(function() {
552                         me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
553                     });
554                 }
555                 else {
556                     this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
557                 }
558             }
559             else {
560                 if (typeof XMLHttpRequest !== 'undefined') {
561                     xhr = new XMLHttpRequest();
562                 } else {
563                     xhr = new ActiveXObject('Microsoft.XMLHTTP');
564                 }
565
566                 try {
567                     xhr.open('GET', noCacheUrl, false);
568                     xhr.send(null);
569                 } catch (e) {
570                     isCrossOriginRestricted = true;
571                 }
572
573                 status = (xhr.status === 1223) ? 204 : xhr.status;
574
575                 if (!isCrossOriginRestricted) {
576                     isCrossOriginRestricted = (status === 0);
577                 }
578
579                 if (isCrossOriginRestricted
580                 //&lt;if isNonBrowser&gt;
581                 &amp;&amp; !isPhantomJS
582                 //&lt;/if&gt;
583                 ) {
584                     onError.call(this, &quot;Failed loading synchronously via XHR: '&quot; + url + &quot;'; It's likely that the file is either &quot; +
585                                        &quot;being loaded from a different domain or from the local file system whereby cross origin &quot; +
586                                        &quot;requests are not allowed due to security reasons. Use asynchronous loading with &quot; +
587                                        &quot;Ext.require instead.&quot;, synchronous);
588                 }
589                 else if (status &gt;= 200 &amp;&amp; status &lt; 300
590                 //&lt;if isNonBrowser&gt;
591                 || isPhantomJS
592                 //&lt;/if&gt;
593                 ) {
594                     // Firebug friendly, file names are still shown even though they're eval'ed code
595                     new Function(xhr.responseText + &quot;\n//@ sourceURL=&quot; + fileName)();
596
597                     onLoad.call(scope);
598                 }
599                 else {
600                     onError.call(this, &quot;Failed loading synchronously via XHR: '&quot; + url + &quot;'; please &quot; +
601                                        &quot;verify that the file exists. &quot; +
602                                        &quot;XHR status code: &quot; + status, synchronous);
603                 }
604
605                 // Prevent potential IE memory leak
606                 xhr = null;
607             }
608         },
609
610 <span id='Ext-Loader-method-exclude'>        /**
611 </span>         * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
612          * Can be chained with more `require` and `exclude` methods, eg:
613
614     Ext.exclude('Ext.data.*').require('*');
615
616     Ext.exclude('widget.button*').require('widget.*');
617
618          * @param {Array} excludes
619          * @return {Object} object contains `require` method for chaining
620          * @markdown
621          */
622         exclude: function(excludes) {
623             var me = this;
624
625             return {
626                 require: function(expressions, fn, scope) {
627                     return me.require(expressions, fn, scope, excludes);
628                 },
629
630                 syncRequire: function(expressions, fn, scope) {
631                     return me.syncRequire(expressions, fn, scope, excludes);
632                 }
633             };
634         },
635
636 <span id='Ext-Loader-method-syncRequire'>        /**
637 </span>         * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
638          * @param {String/Array} expressions Can either be a string or an array of string
639          * @param {Function} fn (Optional) The callback function
640          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
641          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
642          * @markdown
643          */
644         syncRequire: function() {
645             this.syncModeEnabled = true;
646             this.require.apply(this, arguments);
647             this.refreshQueue();
648             this.syncModeEnabled = false;
649         },
650
651 <span id='Ext-Loader-method-require'>        /**
652 </span>         * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
653          * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
654          * @param {String/Array} expressions Can either be a string or an array of string
655          * @param {Function} fn (Optional) The callback function
656          * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
657          * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
658          * @markdown
659          */
660         require: function(expressions, fn, scope, excludes) {
661             var filePath, expression, exclude, className, excluded = {},
662                 excludedClassNames = [],
663                 possibleClassNames = [],
664                 possibleClassName, classNames = [],
665                 i, j, ln, subLn;
666
667             expressions = Ext.Array.from(expressions);
668             excludes = Ext.Array.from(excludes);
669
670             fn = fn || Ext.emptyFn;
671
672             scope = scope || Ext.global;
673
674             for (i = 0, ln = excludes.length; i &lt; ln; i++) {
675                 exclude = excludes[i];
676
677                 if (typeof exclude === 'string' &amp;&amp; exclude.length &gt; 0) {
678                     excludedClassNames = Manager.getNamesByExpression(exclude);
679
680                     for (j = 0, subLn = excludedClassNames.length; j &lt; subLn; j++) {
681                         excluded[excludedClassNames[j]] = true;
682                     }
683                 }
684             }
685
686             for (i = 0, ln = expressions.length; i &lt; ln; i++) {
687                 expression = expressions[i];
688
689                 if (typeof expression === 'string' &amp;&amp; expression.length &gt; 0) {
690                     possibleClassNames = Manager.getNamesByExpression(expression);
691
692                     for (j = 0, subLn = possibleClassNames.length; j &lt; subLn; j++) {
693                         possibleClassName = possibleClassNames[j];
694
695                         if (!excluded.hasOwnProperty(possibleClassName) &amp;&amp; !Manager.isCreated(possibleClassName)) {
696                             Ext.Array.include(classNames, possibleClassName);
697                         }
698                     }
699                 }
700             }
701
702             // If the dynamic dependency feature is not being used, throw an error
703             // if the dependencies are not defined
704             if (!this.config.enabled) {
705                 if (classNames.length &gt; 0) {
706                     Ext.Error.raise({
707                         sourceClass: &quot;Ext.Loader&quot;,
708                         sourceMethod: &quot;require&quot;,
709                         msg: &quot;Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. &quot; +
710                              &quot;Missing required class&quot; + ((classNames.length &gt; 1) ? &quot;es&quot; : &quot;&quot;) + &quot;: &quot; + classNames.join(', ')
711                     });
712                 }
713             }
714
715             if (classNames.length === 0) {
716                 fn.call(scope);
717                 return this;
718             }
719
720             this.queue.push({
721                 requires: classNames,
722                 callback: fn,
723                 scope: scope
724             });
725
726             classNames = classNames.slice();
727
728             for (i = 0, ln = classNames.length; i &lt; ln; i++) {
729                 className = classNames[i];
730
731                 if (!this.isFileLoaded.hasOwnProperty(className)) {
732                     this.isFileLoaded[className] = false;
733
734                     filePath = this.getPath(className);
735
736                     this.classNameToFilePathMap[className] = filePath;
737
738                     this.numPendingFiles++;
739
740                     //&lt;if nonBrowser&gt;
741                     if (isNonBrowser) {
742                         if (isNodeJS) {
743                             require(filePath);
744                         }
745                         // Temporary support for hammerjs
746                         else {
747                             var f = fs.open(filePath),
748                                 content = '',
749                                 line;
750
751                             while (true) {
752                                 line = f.readLine();
753                                 if (line.length === 0) {
754                                     break;
755                                 }
756                                 content += line;
757                             }
758
759                             f.close();
760                             eval(content);
761                         }
762
763                         this.onFileLoaded(className, filePath);
764
765                         if (ln === 1) {
766                             return Manager.get(className);
767                         }
768
769                         continue;
770                     }
771                     //&lt;/if&gt;
772                     this.loadScriptFile(
773                         filePath,
774                         Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
775                         Ext.Function.pass(this.onFileLoadError, [className, filePath]),
776                         this,
777                         this.syncModeEnabled
778                     );
779                 }
780             }
781
782             return this;
783         },
784
785 <span id='Ext-Loader-method-onFileLoaded'>        /**
786 </span>         * @private
787          * @param {String} className
788          * @param {String} filePath
789          */
790         onFileLoaded: function(className, filePath) {
791             this.numLoadedFiles++;
792
793             this.isFileLoaded[className] = true;
794
795             this.numPendingFiles--;
796
797             if (this.numPendingFiles === 0) {
798                 this.refreshQueue();
799             }
800
801             //&lt;debug&gt;
802             if (this.numPendingFiles &lt;= 1) {
803                 window.status = &quot;Finished loading all dependencies, onReady fired!&quot;;
804             }
805             else {
806                 window.status = &quot;Loading dependencies, &quot; + this.numPendingFiles + &quot; files left...&quot;;
807             }
808             //&lt;/debug&gt;
809
810             //&lt;debug&gt;
811             if (!this.syncModeEnabled &amp;&amp; this.numPendingFiles === 0 &amp;&amp; this.isLoading &amp;&amp; !this.hasFileLoadError) {
812                 var queue = this.queue,
813                     requires,
814                     i, ln, j, subLn, missingClasses = [], missingPaths = [];
815
816                 for (i = 0, ln = queue.length; i &lt; ln; i++) {
817                     requires = queue[i].requires;
818
819                     for (j = 0, subLn = requires.length; j &lt; ln; j++) {
820                         if (this.isFileLoaded[requires[j]]) {
821                             missingClasses.push(requires[j]);
822                         }
823                     }
824                 }
825
826                 if (missingClasses.length &lt; 1) {
827                     return;
828                 }
829
830                 missingClasses = Ext.Array.filter(missingClasses, function(item) {
831                     return !this.requiresMap.hasOwnProperty(item);
832                 }, this);
833
834                 for (i = 0,ln = missingClasses.length; i &lt; ln; i++) {
835                     missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
836                 }
837
838                 Ext.Error.raise({
839                     sourceClass: &quot;Ext.Loader&quot;,
840                     sourceMethod: &quot;onFileLoaded&quot;,
841                     msg: &quot;The following classes are not declared even if their files have been &quot; +
842                             &quot;loaded: '&quot; + missingClasses.join(&quot;', '&quot;) + &quot;'. Please check the source code of their &quot; +
843                             &quot;corresponding files for possible typos: '&quot; + missingPaths.join(&quot;', '&quot;) + &quot;'&quot;
844                 });
845             }
846             //&lt;/debug&gt;
847         },
848
849 <span id='Ext-Loader-method-onFileLoadError'>        /**
850 </span>         * @private
851          */
852         onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
853             this.numPendingFiles--;
854             this.hasFileLoadError = true;
855
856             //&lt;debug error&gt;
857             Ext.Error.raise({
858                 sourceClass: &quot;Ext.Loader&quot;,
859                 classToLoad: className,
860                 loadPath: filePath,
861                 loadingType: isSynchronous ? 'synchronous' : 'async',
862                 msg: errorMessage
863             });
864             //&lt;/debug&gt;
865         },
866
867 <span id='Ext-Loader-method-addOptionalRequires'>        /**
868 </span>         * @private
869          */
870         addOptionalRequires: function(requires) {
871             var optionalRequires = this.optionalRequires,
872                 i, ln, require;
873
874             requires = Ext.Array.from(requires);
875
876             for (i = 0, ln = requires.length; i &lt; ln; i++) {
877                 require = requires[i];
878
879                 Ext.Array.include(optionalRequires, require);
880             }
881
882             return this;
883         },
884
885 <span id='Ext-Loader-method-triggerReady'>        /**
886 </span>         * @private
887          */
888         triggerReady: function(force) {
889             var readyListeners = this.readyListeners,
890                 optionalRequires, listener;
891
892             if (this.isLoading || force) {
893                 this.isLoading = false;
894
895                 if (this.optionalRequires.length) {
896                     // Clone then empty the array to eliminate potential recursive loop issue
897                     optionalRequires = Ext.Array.clone(this.optionalRequires);
898
899                     // Empty the original array
900                     this.optionalRequires.length = 0;
901
902                     this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
903                     return this;
904                 }
905
906                 while (readyListeners.length) {
907                     listener = readyListeners.shift();
908                     listener.fn.call(listener.scope);
909
910                     if (this.isLoading) {
911                         return this;
912                     }
913                 }
914             }
915
916             return this;
917         },
918
919 <span id='Ext-Loader-method-onReady'>        /**
920 </span>         * Add a new listener to be executed when all required scripts are fully loaded
921          *
922          * @param {Function} fn The function callback to be executed
923          * @param {Object} scope The execution scope (&lt;code&gt;this&lt;/code&gt;) of the callback function
924          * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
925          */
926         onReady: function(fn, scope, withDomReady, options) {
927             var oldFn;
928
929             if (withDomReady !== false &amp;&amp; Ext.onDocumentReady) {
930                 oldFn = fn;
931
932                 fn = function() {
933                     Ext.onDocumentReady(oldFn, scope, options);
934                 };
935             }
936
937             if (!this.isLoading) {
938                 fn.call(scope);
939             }
940             else {
941                 this.readyListeners.push({
942                     fn: fn,
943                     scope: scope
944                 });
945             }
946         },
947
948 <span id='Ext-Loader-method-historyPush'>        /**
949 </span>         * @private
950          * @param {String} className
951          */
952         historyPush: function(className) {
953             if (className &amp;&amp; this.isFileLoaded.hasOwnProperty(className)) {
954                 Ext.Array.include(this.history, className);
955             }
956
957             return this;
958         }
959     };
960
961 <span id='Ext-method-require'>    /**
962 </span>     * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
963      * {@link Ext.Loader} for examples.
964      * @member Ext
965      * @method require
966      */
967     Ext.require = alias(Loader, 'require');
968
969 <span id='Ext-method-syncRequire'>    /**
970 </span>     * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
971      *
972      * @member Ext
973      * @method syncRequire
974      */
975     Ext.syncRequire = alias(Loader, 'syncRequire');
976
977 <span id='Ext-method-exclude'>    /**
978 </span>     * Convenient shortcut to {@link Ext.Loader#exclude}
979      * @member Ext
980      * @method exclude
981      */
982     Ext.exclude = alias(Loader, 'exclude');
983
984 <span id='Ext-method-onReady'>    /**
985 </span>     * @member Ext
986      * @method onReady
987      */
988     Ext.onReady = function(fn, scope, options) {
989         Loader.onReady(fn, scope, true, options);
990     };
991
992     Class.registerPreprocessor('loader', function(cls, data, continueFn) {
993         var me = this,
994             dependencies = [],
995             className = Manager.getName(cls),
996             i, j, ln, subLn, value, propertyName, propertyValue;
997
998         /*
999         Basically loop through the dependencyProperties, look for string class names and push
1000         them into a stack, regardless of whether the property's value is a string, array or object. For example:
1001         {
1002               extend: 'Ext.MyClass',
1003               requires: ['Ext.some.OtherClass'],
1004               mixins: {
1005                   observable: 'Ext.util.Observable';
1006               }
1007         }
1008         which will later be transformed into:
1009         {
1010               extend: Ext.MyClass,
1011               requires: [Ext.some.OtherClass],
1012               mixins: {
1013                   observable: Ext.util.Observable;
1014               }
1015         }
1016         */
1017
1018         for (i = 0, ln = dependencyProperties.length; i &lt; ln; i++) {
1019             propertyName = dependencyProperties[i];
1020
1021             if (data.hasOwnProperty(propertyName)) {
1022                 propertyValue = data[propertyName];
1023
1024                 if (typeof propertyValue === 'string') {
1025                     dependencies.push(propertyValue);
1026                 }
1027                 else if (propertyValue instanceof Array) {
1028                     for (j = 0, subLn = propertyValue.length; j &lt; subLn; j++) {
1029                         value = propertyValue[j];
1030
1031                         if (typeof value === 'string') {
1032                             dependencies.push(value);
1033                         }
1034                     }
1035                 }
1036                 else {
1037                     for (j in propertyValue) {
1038                         if (propertyValue.hasOwnProperty(j)) {
1039                             value = propertyValue[j];
1040
1041                             if (typeof value === 'string') {
1042                                 dependencies.push(value);
1043                             }
1044                         }
1045                     }
1046                 }
1047             }
1048         }
1049
1050         if (dependencies.length === 0) {
1051 //            Loader.historyPush(className);
1052             return;
1053         }
1054
1055         //&lt;debug error&gt;
1056         var deadlockPath = [],
1057             requiresMap = Loader.requiresMap,
1058             detectDeadlock;
1059
1060         /*
1061         Automatically detect deadlocks before-hand,
1062         will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
1063
1064         - A extends B, then B extends A
1065         - A requires B, B requires C, then C requires A
1066
1067         The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
1068         no matter how deep the path is.
1069         */
1070
1071         if (className) {
1072             requiresMap[className] = dependencies;
1073
1074             detectDeadlock = function(cls) {
1075                 deadlockPath.push(cls);
1076
1077                 if (requiresMap[cls]) {
1078                     if (Ext.Array.contains(requiresMap[cls], className)) {
1079                         Ext.Error.raise({
1080                             sourceClass: &quot;Ext.Loader&quot;,
1081                             msg: &quot;Deadlock detected while loading dependencies! '&quot; + className + &quot;' and '&quot; +
1082                                 deadlockPath[1] + &quot;' &quot; + &quot;mutually require each other. Path: &quot; +
1083                                 deadlockPath.join(' -&gt; ') + &quot; -&gt; &quot; + deadlockPath[0]
1084                         });
1085                     }
1086
1087                     for (i = 0, ln = requiresMap[cls].length; i &lt; ln; i++) {
1088                         detectDeadlock(requiresMap[cls][i]);
1089                     }
1090                 }
1091             };
1092
1093             detectDeadlock(className);
1094         }
1095
1096         //&lt;/debug&gt;
1097
1098         Loader.require(dependencies, function() {
1099             for (i = 0, ln = dependencyProperties.length; i &lt; ln; i++) {
1100                 propertyName = dependencyProperties[i];
1101
1102                 if (data.hasOwnProperty(propertyName)) {
1103                     propertyValue = data[propertyName];
1104
1105                     if (typeof propertyValue === 'string') {
1106                         data[propertyName] = Manager.get(propertyValue);
1107                     }
1108                     else if (propertyValue instanceof Array) {
1109                         for (j = 0, subLn = propertyValue.length; j &lt; subLn; j++) {
1110                             value = propertyValue[j];
1111
1112                             if (typeof value === 'string') {
1113                                 data[propertyName][j] = Manager.get(value);
1114                             }
1115                         }
1116                     }
1117                     else {
1118                         for (var k in propertyValue) {
1119                             if (propertyValue.hasOwnProperty(k)) {
1120                                 value = propertyValue[k];
1121
1122                                 if (typeof value === 'string') {
1123                                     data[propertyName][k] = Manager.get(value);
1124                                 }
1125                             }
1126                         }
1127                     }
1128                 }
1129             }
1130
1131             continueFn.call(me, cls, data);
1132         });
1133
1134         return false;
1135     }, true);
1136
1137     Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
1138
1139     Manager.registerPostprocessor('uses', function(name, cls, data) {
1140         var uses = Ext.Array.from(data.uses),
1141             items = [],
1142             i, ln, item;
1143
1144         for (i = 0, ln = uses.length; i &lt; ln; i++) {
1145             item = uses[i];
1146
1147             if (typeof item === 'string') {
1148                 items.push(item);
1149             }
1150         }
1151
1152         Loader.addOptionalRequires(items);
1153     });
1154
1155     Manager.setDefaultPostprocessorPosition('uses', 'last');
1156
1157 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
1158 </pre>
1159 </body>
1160 </html>