Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Loader.html
index d935c1a..f7e95e3 100644 (file)
+<!DOCTYPE html>
 <html>
 <head>
-  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>The source code</title>
-    <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
-    <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
+  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
+  <style type="text/css">
+    .highlight { display: block; background-color: #ddd; }
+  </style>
+  <script type="text/javascript">
+    function highlight() {
+      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
+    }
+  </script>
 </head>
-<body  onload="prettyPrint();">
-    <pre class="prettyprint lang-js">/*!
- * Ext JS Library 3.2.1
- * Copyright(c) 2006-2010 Ext JS, Inc.
- * licensing@extjs.com
- * http://www.extjs.com/license
- */
-<div id="cls-Ext.Loader"></div>/**
- * @class Ext.Loader
+<body onload="prettyPrint(); highlight();">
+  <pre class="prettyprint lang-js"><span id='Ext-Loader'>/**
+</span> * @class Ext.Loader
  * @singleton
- * Simple class to help load JavaScript files on demand
+ * @author Jacky Nguyen &lt;jacky@sencha.com&gt;
+ * @docauthor Jacky Nguyen &lt;jacky@sencha.com&gt;
+ *
+ * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
+ * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
+ * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
+ * of each approach:
+ *
+ * # Asynchronous Loading
+ *
+ * - Advantages:
+ *      + Cross-domain
+ *      + No web server needed: you can run the application via the file system protocol
+ *     (i.e: `file://path/to/your/index.html`)
+ *      + Best possible debugging experience: error messages come with the exact file name and line number
+ *
+ * - Disadvantages:
+ *      + Dependencies need to be specified before-hand
+ *
+ * ### Method 1: Explicitly include what you need:
+ *
+ *     // Syntax
+ *     Ext.require({String/Array} expressions);
+ *
+ *     // Example: Single alias
+ *     Ext.require('widget.window');
+ *
+ *     // Example: Single class name
+ *     Ext.require('Ext.window.Window');
+ *
+ *     // Example: Multiple aliases / class names mix
+ *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
+ *
+ *     // Wildcards
+ *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
+ *
+ * ### Method 2: Explicitly exclude what you don't need:
+ *
+ *     // Syntax: Note that it must be in this chaining format.
+ *     Ext.exclude({String/Array} expressions)
+ *        .require({String/Array} expressions);
+ *
+ *     // Include everything except Ext.data.*
+ *     Ext.exclude('Ext.data.*').require('*'); 
+ *
+ *     // Include all widgets except widget.checkbox*,
+ *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
+ *     Ext.exclude('widget.checkbox*').require('widget.*');
+ *
+ * # Synchronous Loading on Demand
+ *
+ * - Advantages:
+ *      + There's no need to specify dependencies before-hand, which is always the convenience of including
+ *     ext-all.js before
+ *
+ * - Disadvantages:
+ *      + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
+ *      + Must be from the same domain due to XHR restriction
+ *      + Need a web server, same reason as above
+ *
+ * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
+ *
+ *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
+ *
+ *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
+ *
+ *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
+ *
+ * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
+ * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
+ * the given class and all its dependencies.
+ *
+ * # Hybrid Loading - The Best of Both Worlds
+ *
+ * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
+ *
+ * ### Step 1: Start writing your application using synchronous approach.
+ *
+ * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
+ *
+ *     Ext.onReady(function(){
+ *         var window = Ext.createWidget('window', {
+ *             width: 500,
+ *             height: 300,
+ *             layout: {
+ *                 type: 'border',
+ *                 padding: 5
+ *             },
+ *             title: 'Hello Dialog',
+ *             items: [{
+ *                 title: 'Navigation',
+ *                 collapsible: true,
+ *                 region: 'west',
+ *                 width: 200,
+ *                 html: 'Hello',
+ *                 split: true
+ *             }, {
+ *                 title: 'TabPanel',
+ *                 region: 'center'
+ *             }]
+ *         });
+ *
+ *         window.show();
+ *     })
+ *
+ * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
+ *
+ *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
+ *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
+ *
+ * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
+ *
+ *     Ext.require('Ext.window.Window');
+ *     Ext.require('Ext.layout.container.Border');
+ *
+ *     Ext.onReady(...);
+ *
+ * Everything should now load via asynchronous mode.
+ *
+ * # Deployment
+ *
+ * It's important to note that dynamic loading should only be used during development on your local machines.
+ * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
+ * the whole process of transitioning from / to between development / maintenance and production as easy as
+ * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
+ * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
+ * array into one, then include it on top of your application.
+ *
+ * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
  */
-Ext.Loader = Ext.apply({}, {
-    <div id="method-Ext.Loader-load"></div>/**
-     * Loads a given set of .js files. Calls the callback function when all files have been loaded
-     * Set preserveOrder to true to ensure non-parallel loading of files if load order is important
-     * @param {Array} fileList Array of all files to load
-     * @param {Function} callback Callback to call after all files have been loaded
-     * @param {Object} scope The scope to call the callback in
-     * @param {Boolean} preserveOrder True to make files load in serial, one after the other (defaults to false)
-     */
-    load: function(fileList, callback, scope, preserveOrder) {
-        var scope       = scope || this,
-            head        = document.getElementsByTagName("head")[0],
-            fragment    = document.createDocumentFragment(),
-            numFiles    = fileList.length,
-            loadedFiles = 0,
-            me          = this;
-        
-        <div id="prop-Ext.Loader-var"></div>/**
-         * Loads a particular file from the fileList by index. This is used when preserving order
-         */
-        var loadFileIndex = function(index) {
-            head.appendChild(
-                me.buildScriptTag(fileList[index], onFileLoaded)
-            );
-        };
-        
-        <div id="prop-Ext.Loader-var"></div>/**
-         * Callback function which is called after each file has been loaded. This calls the callback
-         * passed to load once the final file in the fileList has been loaded
-         */
-        var onFileLoaded = function() {
-            loadedFiles ++;
-            
-            //if this was the last file, call the callback, otherwise load the next file
-            if (numFiles == loadedFiles && typeof callback == 'function') {
-                callback.call(scope);
-            } else {
-                if (preserveOrder === true) {
-                    loadFileIndex(loadedFiles);
-                }
-            }
-        };
-        
-        if (preserveOrder === true) {
-            loadFileIndex.call(this, 0);
-        } else {
-            //load each file (most browsers will do this in parallel)
-            Ext.each(fileList, function(file, index) {
-                fragment.appendChild(
-                    this.buildScriptTag(file, onFileLoaded)
-                );  
-            }, this);
-            
-            head.appendChild(fragment);
+(function(Manager, Class, flexSetter, alias) {
+
+    var
+        //&lt;if nonBrowser&gt;
+        isNonBrowser = typeof window === 'undefined',
+        isNodeJS = isNonBrowser &amp;&amp; (typeof require === 'function'),
+        isPhantomJS = (typeof phantom !== 'undefined' &amp;&amp; phantom.fs),
+        //&lt;/if&gt;
+        dependencyProperties = ['extend', 'mixins', 'requires'],
+        Loader;
+
+    Loader = Ext.Loader = {
+<span id='Ext-Loader-property-documentHead'>        /**
+</span>         * @private
+         */
+        documentHead: typeof document !== 'undefined' &amp;&amp; (document.head || document.getElementsByTagName('head')[0]),
+
+<span id='Ext-Loader-property-isLoading'>        /**
+</span>         * Flag indicating whether there are still files being loaded
+         * @private
+         */
+        isLoading: false,
+
+<span id='Ext-Loader-property-queue'>        /**
+</span>         * Maintain the queue for all dependencies. Each item in the array is an object of the format:
+         * {
+         *      requires: [...], // The required classes for this queue item
+         *      callback: function() { ... } // The function to execute when all classes specified in requires exist
+         * }
+         * @private
+         */
+        queue: [],
+
+<span id='Ext-Loader-property-isFileLoaded'>        /**
+</span>         * Maintain the list of files that have already been handled so that they never get double-loaded
+         * @private
+         */
+        isFileLoaded: {},
+
+<span id='Ext-Loader-property-readyListeners'>        /**
+</span>         * Maintain the list of listeners to execute when all required scripts are fully loaded
+         * @private
+         */
+        readyListeners: [],
+
+<span id='Ext-Loader-property-optionalRequires'>        /**
+</span>         * Contains optional dependencies to be loaded last
+         * @private
+         */
+        optionalRequires: [],
+
+<span id='Ext-Loader-property-requiresMap'>        /**
+</span>         * Map of fully qualified class names to an array of dependent classes.
+         * @private
+         */
+        requiresMap: {},
+
+<span id='Ext-Loader-property-numPendingFiles'>        /**
+</span>         * @private
+         */
+        numPendingFiles: 0,
+
+<span id='Ext-Loader-property-numLoadedFiles'>        /**
+</span>         * @private
+         */
+        numLoadedFiles: 0,
+
+<span id='Ext-Loader-property-hasFileLoadError'>        /** @private */
+</span>        hasFileLoadError: false,
+
+<span id='Ext-Loader-property-classNameToFilePathMap'>        /**
+</span>         * @private
+         */
+        classNameToFilePathMap: {},
+
+<span id='Ext-Loader-property-history'>        /**
+</span>         * @property {String[]} history
+         * An array of class names to keep track of the dependency loading order.
+         * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
+         */
+        history: [],
+
+<span id='Ext-Loader-property-config'>        /**
+</span>         * Configuration
+         * @private
+         */
+        config: {
+<span id='Ext-Loader-cfg-enabled'>            /**
+</span>             * @cfg {Boolean} enabled
+             * Whether or not to enable the dynamic dependency loading feature.
+             */
+            enabled: false,
+
+<span id='Ext-Loader-cfg-disableCaching'>            /**
+</span>             * @cfg {Boolean} disableCaching
+             * Appends current timestamp to script files to prevent caching.
+             */
+            disableCaching: true,
+
+<span id='Ext-Loader-cfg-disableCachingParam'>            /**
+</span>             * @cfg {String} disableCachingParam
+             * The get parameter name for the cache buster's timestamp.
+             */
+            disableCachingParam: '_dc',
+
+<span id='Ext-Loader-cfg-paths'>            /**
+</span>             * @cfg {Object} paths
+             * The mapping from namespaces to file paths
+             *
+             *     {
+             *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
+             *                     // loaded from ./layout/Container.js
+             *
+             *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
+             *                                     // ./src/my_own_folder/layout/Container.js
+             *     }
+             *
+             * Note that all relative paths are relative to the current HTML document.
+             * If not being specified, for example, `Other.awesome.Class`
+             * will simply be loaded from `./Other/awesome/Class.js`
+             */
+            paths: {
+                'Ext': '.'
+            }
+        },
+
+<span id='Ext-Loader-method-setConfig'>        /**
+</span>         * Set the configuration for the loader. This should be called right after ext-core.js
+         * (or ext-core-debug.js) is included in the page, e.g.:
+         *
+         *     &lt;script type=&quot;text/javascript&quot; src=&quot;ext-core-debug.js&quot;&gt;&lt;/script&gt;
+         *     &lt;script type=&quot;text/javascript&quot;&gt;
+         *       Ext.Loader.setConfig({
+         *           enabled: true,
+         *           paths: {
+         *               'My': 'my_own_path'
+         *           }
+         *       });
+         *     &lt;script&gt;
+         *     &lt;script type=&quot;text/javascript&quot;&gt;
+         *       Ext.require(...);
+         *
+         *       Ext.onReady(function() {
+         *           // application code here
+         *       });
+         *     &lt;/script&gt;
+         *
+         * Refer to config options of {@link Ext.Loader} for the list of possible properties.
+         *
+         * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
+         * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
+         * @return {Ext.Loader} this
+         */
+        setConfig: function(name, value) {
+            if (Ext.isObject(name) &amp;&amp; arguments.length === 1) {
+                Ext.Object.merge(this.config, name);
+            }
+            else {
+                this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
+            }
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-getConfig'>        /**
+</span>         * Get the config value corresponding to the specified name.
+         * If no name is given, will return the config object.
+         * @param {String} name The config property name
+         * @return {Object}
+         */
+        getConfig: function(name) {
+            if (name) {
+                return this.config[name];
+            }
+
+            return this.config;
+        },
+
+<span id='Ext-Loader-method-setPath'>        /**
+</span>         * Sets the path of a namespace. For Example:
+         *
+         *     Ext.Loader.setPath('Ext', '.');
+         *
+         * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
+         * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
+         * @return {Ext.Loader} this
+         * @method
+         */
+        setPath: flexSetter(function(name, path) {
+            //&lt;if nonBrowser&gt;
+            if (isNonBrowser) {
+                if (isNodeJS) {
+                    path = require('fs').realpathSync(path);
+                }
+            }
+            //&lt;/if&gt;
+            this.config.paths[name] = path;
+
+            return this;
+        }),
+
+<span id='Ext-Loader-method-getPath'>        /**
+</span>         * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
+         * For example:
+         *
+         *     Ext.Loader.setPath('My', '/path/to/My');
+         *
+         *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
+         *
+         * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
+         *
+         *     Ext.Loader.setPath({
+         *         'My': '/path/to/lib',
+         *         'My.awesome': '/other/path/for/awesome/stuff',
+         *         'My.awesome.more': '/more/awesome/path'
+         *     });
+         *
+         *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
+         *
+         *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
+         *
+         *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
+         *
+         *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
+         *
+         * @param {String} className
+         * @return {String} path
+         */
+        getPath: function(className) {
+            var path = '',
+                paths = this.config.paths,
+                prefix = this.getPrefix(className);
+
+            if (prefix.length &gt; 0) {
+                if (prefix === className) {
+                    return paths[prefix];
+                }
+
+                path = paths[prefix];
+                className = className.substring(prefix.length + 1);
+            }
+
+            if (path.length &gt; 0) {
+                path += '/';
+            }
+
+            return path.replace(/\/\.\//g, '/') + className.replace(/\./g, &quot;/&quot;) + '.js';
+        },
+
+<span id='Ext-Loader-method-getPrefix'>        /**
+</span>         * @private
+         * @param {String} className
+         */
+        getPrefix: function(className) {
+            var paths = this.config.paths,
+                prefix, deepestPrefix = '';
+
+            if (paths.hasOwnProperty(className)) {
+                return className;
+            }
+
+            for (prefix in paths) {
+                if (paths.hasOwnProperty(prefix) &amp;&amp; prefix + '.' === className.substring(0, prefix.length + 1)) {
+                    if (prefix.length &gt; deepestPrefix.length) {
+                        deepestPrefix = prefix;
+                    }
+                }
+            }
+
+            return deepestPrefix;
+        },
+
+<span id='Ext-Loader-method-refreshQueue'>        /**
+</span>         * Refresh all items in the queue. If all dependencies for an item exist during looping,
+         * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
+         * empty
+         * @private
+         */
+        refreshQueue: function() {
+            var ln = this.queue.length,
+                i, item, j, requires;
+
+            if (ln === 0) {
+                this.triggerReady();
+                return;
+            }
+
+            for (i = 0; i &lt; ln; i++) {
+                item = this.queue[i];
+
+                if (item) {
+                    requires = item.requires;
+
+                    // Don't bother checking when the number of files loaded
+                    // is still less than the array length
+                    if (requires.length &gt; this.numLoadedFiles) {
+                        continue;
+                    }
+
+                    j = 0;
+
+                    do {
+                        if (Manager.isCreated(requires[j])) {
+                            // Take out from the queue
+                            Ext.Array.erase(requires, j, 1);
+                        }
+                        else {
+                            j++;
+                        }
+                    } while (j &lt; requires.length);
+
+                    if (item.requires.length === 0) {
+                        Ext.Array.erase(this.queue, i, 1);
+                        item.callback.call(item.scope);
+                        this.refreshQueue();
+                        break;
+                    }
+                }
+            }
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-injectScriptElement'>        /**
+</span>         * Inject a script element to document's head, call onLoad and onError accordingly
+         * @private
+         */
+        injectScriptElement: function(url, onLoad, onError, scope) {
+            var script = document.createElement('script'),
+                me = this,
+                onLoadFn = function() {
+                    me.cleanupScriptElement(script);
+                    onLoad.call(scope);
+                },
+                onErrorFn = function() {
+                    me.cleanupScriptElement(script);
+                    onError.call(scope);
+                };
+
+            script.type = 'text/javascript';
+            script.src = url;
+            script.onload = onLoadFn;
+            script.onerror = onErrorFn;
+            script.onreadystatechange = function() {
+                if (this.readyState === 'loaded' || this.readyState === 'complete') {
+                    onLoadFn();
+                }
+            };
+
+            this.documentHead.appendChild(script);
+
+            return script;
+        },
+
+<span id='Ext-Loader-method-cleanupScriptElement'>        /**
+</span>         * @private
+         */
+        cleanupScriptElement: function(script) {
+            script.onload = null;
+            script.onreadystatechange = null;
+            script.onerror = null;
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-loadScriptFile'>        /**
+</span>         * Load a script file, supports both asynchronous and synchronous approaches
+         *
+         * @param {String} url
+         * @param {Function} onLoad
+         * @param {Object} scope
+         * @param {Boolean} synchronous
+         * @private
+         */
+        loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
+            var me = this,
+                noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
+                fileName = url.split('/').pop(),
+                isCrossOriginRestricted = false,
+                xhr, status, onScriptError;
+
+            scope = scope || this;
+
+            this.isLoading = true;
+
+            if (!synchronous) {
+                onScriptError = function() {
+                    onError.call(scope, &quot;Failed loading '&quot; + url + &quot;', please verify that the file exists&quot;, synchronous);
+                };
+
+                if (!Ext.isReady &amp;&amp; Ext.onDocumentReady) {
+                    Ext.onDocumentReady(function() {
+                        me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
+                    });
+                }
+                else {
+                    this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
+                }
+            }
+            else {
+                if (typeof XMLHttpRequest !== 'undefined') {
+                    xhr = new XMLHttpRequest();
+                } else {
+                    xhr = new ActiveXObject('Microsoft.XMLHTTP');
+                }
+
+                try {
+                    xhr.open('GET', noCacheUrl, false);
+                    xhr.send(null);
+                } catch (e) {
+                    isCrossOriginRestricted = true;
+                }
+
+                status = (xhr.status === 1223) ? 204 : xhr.status;
+
+                if (!isCrossOriginRestricted) {
+                    isCrossOriginRestricted = (status === 0);
+                }
+
+                if (isCrossOriginRestricted
+                //&lt;if isNonBrowser&gt;
+                &amp;&amp; !isPhantomJS
+                //&lt;/if&gt;
+                ) {
+                    onError.call(this, &quot;Failed loading synchronously via XHR: '&quot; + url + &quot;'; It's likely that the file is either &quot; +
+                                       &quot;being loaded from a different domain or from the local file system whereby cross origin &quot; +
+                                       &quot;requests are not allowed due to security reasons. Use asynchronous loading with &quot; +
+                                       &quot;Ext.require instead.&quot;, synchronous);
+                }
+                else if (status &gt;= 200 &amp;&amp; status &lt; 300
+                //&lt;if isNonBrowser&gt;
+                || isPhantomJS
+                //&lt;/if&gt;
+                ) {
+                    // Firebug friendly, file names are still shown even though they're eval'ed code
+                    new Function(xhr.responseText + &quot;\n//@ sourceURL=&quot; + fileName)();
+
+                    onLoad.call(scope);
+                }
+                else {
+                    onError.call(this, &quot;Failed loading synchronously via XHR: '&quot; + url + &quot;'; please &quot; +
+                                       &quot;verify that the file exists. &quot; +
+                                       &quot;XHR status code: &quot; + status, synchronous);
+                }
+
+                // Prevent potential IE memory leak
+                xhr = null;
+            }
+        },
+
+<span id='Ext-Loader-method-exclude'>        /**
+</span>         * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
+         * Can be chained with more `require` and `exclude` methods, e.g.:
+         *
+         *     Ext.exclude('Ext.data.*').require('*');
+         *
+         *     Ext.exclude('widget.button*').require('widget.*');
+         *
+         * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
+         *
+         * @param {String/String[]} excludes
+         * @return {Object} object contains `require` method for chaining
+         */
+        exclude: function(excludes) {
+            var me = this;
+
+            return {
+                require: function(expressions, fn, scope) {
+                    return me.require(expressions, fn, scope, excludes);
+                },
+
+                syncRequire: function(expressions, fn, scope) {
+                    return me.syncRequire(expressions, fn, scope, excludes);
+                }
+            };
+        },
+
+<span id='Ext-Loader-method-syncRequire'>        /**
+</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.
+         *
+         * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
+         *
+         * @param {String/String[]} expressions Can either be a string or an array of string
+         * @param {Function} fn (Optional) The callback function
+         * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
+         * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
+         */
+        syncRequire: function() {
+            this.syncModeEnabled = true;
+            this.require.apply(this, arguments);
+            this.refreshQueue();
+            this.syncModeEnabled = false;
+        },
+
+<span id='Ext-Loader-method-require'>        /**
+</span>         * Loads all classes by the given names and all their direct dependencies;
+         * optionally executes the given callback function when finishes, within the optional scope.
+         *
+         * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
+         *
+         * @param {String/String[]} expressions Can either be a string or an array of string
+         * @param {Function} fn (Optional) The callback function
+         * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
+         * @param {String/String[]} excludes (Optional) Classes to be excluded, useful when being used with expressions
+         */
+        require: function(expressions, fn, scope, excludes) {
+            var filePath, expression, exclude, className, excluded = {},
+                excludedClassNames = [],
+                possibleClassNames = [],
+                possibleClassName, classNames = [],
+                i, j, ln, subLn;
+
+            expressions = Ext.Array.from(expressions);
+            excludes = Ext.Array.from(excludes);
+
+            fn = fn || Ext.emptyFn;
+
+            scope = scope || Ext.global;
+
+            for (i = 0, ln = excludes.length; i &lt; ln; i++) {
+                exclude = excludes[i];
+
+                if (typeof exclude === 'string' &amp;&amp; exclude.length &gt; 0) {
+                    excludedClassNames = Manager.getNamesByExpression(exclude);
+
+                    for (j = 0, subLn = excludedClassNames.length; j &lt; subLn; j++) {
+                        excluded[excludedClassNames[j]] = true;
+                    }
+                }
+            }
+
+            for (i = 0, ln = expressions.length; i &lt; ln; i++) {
+                expression = expressions[i];
+
+                if (typeof expression === 'string' &amp;&amp; expression.length &gt; 0) {
+                    possibleClassNames = Manager.getNamesByExpression(expression);
+
+                    for (j = 0, subLn = possibleClassNames.length; j &lt; subLn; j++) {
+                        possibleClassName = possibleClassNames[j];
+
+                        if (!excluded.hasOwnProperty(possibleClassName) &amp;&amp; !Manager.isCreated(possibleClassName)) {
+                            Ext.Array.include(classNames, possibleClassName);
+                        }
+                    }
+                }
+            }
+
+            // If the dynamic dependency feature is not being used, throw an error
+            // if the dependencies are not defined
+            if (!this.config.enabled) {
+                if (classNames.length &gt; 0) {
+                    Ext.Error.raise({
+                        sourceClass: &quot;Ext.Loader&quot;,
+                        sourceMethod: &quot;require&quot;,
+                        msg: &quot;Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. &quot; +
+                             &quot;Missing required class&quot; + ((classNames.length &gt; 1) ? &quot;es&quot; : &quot;&quot;) + &quot;: &quot; + classNames.join(', ')
+                    });
+                }
+            }
+
+            if (classNames.length === 0) {
+                fn.call(scope);
+                return this;
+            }
+
+            this.queue.push({
+                requires: classNames,
+                callback: fn,
+                scope: scope
+            });
+
+            classNames = classNames.slice();
+
+            for (i = 0, ln = classNames.length; i &lt; ln; i++) {
+                className = classNames[i];
+
+                if (!this.isFileLoaded.hasOwnProperty(className)) {
+                    this.isFileLoaded[className] = false;
+
+                    filePath = this.getPath(className);
+
+                    this.classNameToFilePathMap[className] = filePath;
+
+                    this.numPendingFiles++;
+
+                    //&lt;if nonBrowser&gt;
+                    if (isNonBrowser) {
+                        if (isNodeJS) {
+                            require(filePath);
+                        }
+                        // Temporary support for hammerjs
+                        else {
+                            var f = fs.open(filePath),
+                                content = '',
+                                line;
+
+                            while (true) {
+                                line = f.readLine();
+                                if (line.length === 0) {
+                                    break;
+                                }
+                                content += line;
+                            }
+
+                            f.close();
+                            eval(content);
+                        }
+
+                        this.onFileLoaded(className, filePath);
+
+                        if (ln === 1) {
+                            return Manager.get(className);
+                        }
+
+                        continue;
+                    }
+                    //&lt;/if&gt;
+                    this.loadScriptFile(
+                        filePath,
+                        Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
+                        Ext.Function.pass(this.onFileLoadError, [className, filePath]),
+                        this,
+                        this.syncModeEnabled
+                    );
+                }
+            }
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-onFileLoaded'>        /**
+</span>         * @private
+         * @param {String} className
+         * @param {String} filePath
+         */
+        onFileLoaded: function(className, filePath) {
+            this.numLoadedFiles++;
+
+            this.isFileLoaded[className] = true;
+
+            this.numPendingFiles--;
+
+            if (this.numPendingFiles === 0) {
+                this.refreshQueue();
+            }
+
+            //&lt;debug&gt;
+            if (this.numPendingFiles &lt;= 1) {
+                window.status = &quot;Finished loading all dependencies, onReady fired!&quot;;
+            }
+            else {
+                window.status = &quot;Loading dependencies, &quot; + this.numPendingFiles + &quot; files left...&quot;;
+            }
+            //&lt;/debug&gt;
+
+            //&lt;debug&gt;
+            if (!this.syncModeEnabled &amp;&amp; this.numPendingFiles === 0 &amp;&amp; this.isLoading &amp;&amp; !this.hasFileLoadError) {
+                var queue = this.queue,
+                    requires,
+                    i, ln, j, subLn, missingClasses = [], missingPaths = [];
+
+                for (i = 0, ln = queue.length; i &lt; ln; i++) {
+                    requires = queue[i].requires;
+
+                    for (j = 0, subLn = requires.length; j &lt; ln; j++) {
+                        if (this.isFileLoaded[requires[j]]) {
+                            missingClasses.push(requires[j]);
+                        }
+                    }
+                }
+
+                if (missingClasses.length &lt; 1) {
+                    return;
+                }
+
+                missingClasses = Ext.Array.filter(missingClasses, function(item) {
+                    return !this.requiresMap.hasOwnProperty(item);
+                }, this);
+
+                for (i = 0,ln = missingClasses.length; i &lt; ln; i++) {
+                    missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
+                }
+
+                Ext.Error.raise({
+                    sourceClass: &quot;Ext.Loader&quot;,
+                    sourceMethod: &quot;onFileLoaded&quot;,
+                    msg: &quot;The following classes are not declared even if their files have been &quot; +
+                            &quot;loaded: '&quot; + missingClasses.join(&quot;', '&quot;) + &quot;'. Please check the source code of their &quot; +
+                            &quot;corresponding files for possible typos: '&quot; + missingPaths.join(&quot;', '&quot;) + &quot;'&quot;
+                });
+            }
+            //&lt;/debug&gt;
+        },
+
+<span id='Ext-Loader-method-onFileLoadError'>        /**
+</span>         * @private
+         */
+        onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
+            this.numPendingFiles--;
+            this.hasFileLoadError = true;
+
+            //&lt;debug error&gt;
+            Ext.Error.raise({
+                sourceClass: &quot;Ext.Loader&quot;,
+                classToLoad: className,
+                loadPath: filePath,
+                loadingType: isSynchronous ? 'synchronous' : 'async',
+                msg: errorMessage
+            });
+            //&lt;/debug&gt;
+        },
+
+<span id='Ext-Loader-method-addOptionalRequires'>        /**
+</span>         * @private
+         */
+        addOptionalRequires: function(requires) {
+            var optionalRequires = this.optionalRequires,
+                i, ln, require;
+
+            requires = Ext.Array.from(requires);
+
+            for (i = 0, ln = requires.length; i &lt; ln; i++) {
+                require = requires[i];
+
+                Ext.Array.include(optionalRequires, require);
+            }
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-triggerReady'>        /**
+</span>         * @private
+         */
+        triggerReady: function(force) {
+            var readyListeners = this.readyListeners,
+                optionalRequires, listener;
+
+            if (this.isLoading || force) {
+                this.isLoading = false;
+
+                if (this.optionalRequires.length) {
+                    // Clone then empty the array to eliminate potential recursive loop issue
+                    optionalRequires = Ext.Array.clone(this.optionalRequires);
+
+                    // Empty the original array
+                    this.optionalRequires.length = 0;
+
+                    this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
+                    return this;
+                }
+
+                while (readyListeners.length) {
+                    listener = readyListeners.shift();
+                    listener.fn.call(listener.scope);
+
+                    if (this.isLoading) {
+                        return this;
+                    }
+                }
+            }
+
+            return this;
+        },
+
+<span id='Ext-Loader-method-onReady'>        /**
+</span>         * Adds new listener to be executed when all required scripts are fully loaded.
+         *
+         * @param {Function} fn The function callback to be executed
+         * @param {Object} scope The execution scope (`this`) of the callback function
+         * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
+         */
+        onReady: function(fn, scope, withDomReady, options) {
+            var oldFn;
+
+            if (withDomReady !== false &amp;&amp; Ext.onDocumentReady) {
+                oldFn = fn;
+
+                fn = function() {
+                    Ext.onDocumentReady(oldFn, scope, options);
+                };
+            }
+
+            if (!this.isLoading) {
+                fn.call(scope);
+            }
+            else {
+                this.readyListeners.push({
+                    fn: fn,
+                    scope: scope
+                });
+            }
+        },
+
+<span id='Ext-Loader-method-historyPush'>        /**
+</span>         * @private
+         * @param {String} className
+         */
+        historyPush: function(className) {
+            if (className &amp;&amp; this.isFileLoaded.hasOwnProperty(className)) {
+                Ext.Array.include(this.history, className);
+            }
+
+            return this;
         }
-    },
-    
-    /**
-     * @private
-     * Creates and returns a script tag, but does not place it into the document. If a callback function
-     * is passed, this is called when the script has been loaded
-     * @param {String} filename The name of the file to create a script tag for
-     * @param {Function} callback Optional callback, which is called when the script has been loaded
-     * @return {Element} The new script ta
+    };
+
+<span id='Ext-method-require'>    /**
+</span>     * @member Ext
+     * @method require
+     * @alias Ext.Loader#require
      */
-    buildScriptTag: function(filename, callback) {
-        var script  = document.createElement('script');
-        script.type = "text/javascript";
-        script.src  = filename;
-        
-        //IE has a different way of handling <script> loads, so we need to check for it here
-        if (script.readyState) {
-            script.onreadystatechange = function() {
-                if (script.readyState == "loaded" || script.readyState == "complete") {
-                    script.onreadystatechange = null;
-                    callback();
+    Ext.require = alias(Loader, 'require');
+
+<span id='Ext-method-syncRequire'>    /**
+</span>     * @member Ext
+     * @method syncRequire
+     * @alias Ext.Loader#syncRequire
+     */
+    Ext.syncRequire = alias(Loader, 'syncRequire');
+
+<span id='Ext-method-exclude'>    /**
+</span>     * @member Ext
+     * @method exclude
+     * @alias Ext.Loader#exclude
+     */
+    Ext.exclude = alias(Loader, 'exclude');
+
+<span id='Ext-method-onReady'>    /**
+</span>     * @member Ext
+     * @method onReady
+     * @alias Ext.Loader#onReady
+     */
+    Ext.onReady = function(fn, scope, options) {
+        Loader.onReady(fn, scope, true, options);
+    };
+
+<span id='Ext-Class-cfg-requires'>    /**
+</span>     * @cfg {String[]} requires
+     * @member Ext.Class
+     * List of classes that have to be loaded before instantiating this class.
+     * For example:
+     *
+     *     Ext.define('Mother', {
+     *         requires: ['Child'],
+     *         giveBirth: function() {
+     *             // we can be sure that child class is available.
+     *             return new Child();
+     *         }
+     *     });
+     */
+    Class.registerPreprocessor('loader', function(cls, data, continueFn) {
+        var me = this,
+            dependencies = [],
+            className = Manager.getName(cls),
+            i, j, ln, subLn, value, propertyName, propertyValue;
+
+        /*
+        Basically loop through the dependencyProperties, look for string class names and push
+        them into a stack, regardless of whether the property's value is a string, array or object. For example:
+        {
+              extend: 'Ext.MyClass',
+              requires: ['Ext.some.OtherClass'],
+              mixins: {
+                  observable: 'Ext.util.Observable';
+              }
+        }
+        which will later be transformed into:
+        {
+              extend: Ext.MyClass,
+              requires: [Ext.some.OtherClass],
+              mixins: {
+                  observable: Ext.util.Observable;
+              }
+        }
+        */
+
+        for (i = 0, ln = dependencyProperties.length; i &lt; ln; i++) {
+            propertyName = dependencyProperties[i];
+
+            if (data.hasOwnProperty(propertyName)) {
+                propertyValue = data[propertyName];
+
+                if (typeof propertyValue === 'string') {
+                    dependencies.push(propertyValue);
+                }
+                else if (propertyValue instanceof Array) {
+                    for (j = 0, subLn = propertyValue.length; j &lt; subLn; j++) {
+                        value = propertyValue[j];
+
+                        if (typeof value === 'string') {
+                            dependencies.push(value);
+                        }
+                    }
+                }
+                else if (typeof propertyValue != 'function') {
+                    for (j in propertyValue) {
+                        if (propertyValue.hasOwnProperty(j)) {
+                            value = propertyValue[j];
+
+                            if (typeof value === 'string') {
+                                dependencies.push(value);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (dependencies.length === 0) {
+//            Loader.historyPush(className);
+            return;
+        }
+
+        //&lt;debug error&gt;
+        var deadlockPath = [],
+            requiresMap = Loader.requiresMap,
+            detectDeadlock;
+
+        /*
+        Automatically detect deadlocks before-hand,
+        will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
+
+        - A extends B, then B extends A
+        - A requires B, B requires C, then C requires A
+
+        The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
+        no matter how deep the path is.
+        */
+
+        if (className) {
+            requiresMap[className] = dependencies;
+
+            detectDeadlock = function(cls) {
+                deadlockPath.push(cls);
+
+                if (requiresMap[cls]) {
+                    if (Ext.Array.contains(requiresMap[cls], className)) {
+                        Ext.Error.raise({
+                            sourceClass: &quot;Ext.Loader&quot;,
+                            msg: &quot;Deadlock detected while loading dependencies! '&quot; + className + &quot;' and '&quot; +
+                                deadlockPath[1] + &quot;' &quot; + &quot;mutually require each other. Path: &quot; +
+                                deadlockPath.join(' -&gt; ') + &quot; -&gt; &quot; + deadlockPath[0]
+                        });
+                    }
+
+                    for (i = 0, ln = requiresMap[cls].length; i &lt; ln; i++) {
+                        detectDeadlock(requiresMap[cls][i]);
+                    }
                 }
             };
-        } else {
-            script.onload = callback;
-        }    
-        
-        return script;
-    }
-});</pre>    
+
+            detectDeadlock(className);
+        }
+
+        //&lt;/debug&gt;
+
+        Loader.require(dependencies, function() {
+            for (i = 0, ln = dependencyProperties.length; i &lt; ln; i++) {
+                propertyName = dependencyProperties[i];
+
+                if (data.hasOwnProperty(propertyName)) {
+                    propertyValue = data[propertyName];
+
+                    if (typeof propertyValue === 'string') {
+                        data[propertyName] = Manager.get(propertyValue);
+                    }
+                    else if (propertyValue instanceof Array) {
+                        for (j = 0, subLn = propertyValue.length; j &lt; subLn; j++) {
+                            value = propertyValue[j];
+
+                            if (typeof value === 'string') {
+                                data[propertyName][j] = Manager.get(value);
+                            }
+                        }
+                    }
+                    else if (typeof propertyValue != 'function') {
+                        for (var k in propertyValue) {
+                            if (propertyValue.hasOwnProperty(k)) {
+                                value = propertyValue[k];
+
+                                if (typeof value === 'string') {
+                                    data[propertyName][k] = Manager.get(value);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            continueFn.call(me, cls, data);
+        });
+
+        return false;
+    }, true);
+
+    Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
+
+<span id='Ext-Class-cfg-uses'>    /**
+</span>     * @cfg {String[]} uses
+     * @member Ext.Class
+     * List of classes to load together with this class.  These aren't neccessarily loaded before
+     * this class is instantiated. For example:
+     *
+     *     Ext.define('Mother', {
+     *         uses: ['Child'],
+     *         giveBirth: function() {
+     *             // This code might, or might not work:
+     *             // return new Child();
+     *
+     *             // Instead use Ext.create() to load the class at the spot if not loaded already:
+     *             return Ext.create('Child');
+     *         }
+     *     });
+     */
+    Manager.registerPostprocessor('uses', function(name, cls, data) {
+        var uses = Ext.Array.from(data.uses),
+            items = [],
+            i, ln, item;
+
+        for (i = 0, ln = uses.length; i &lt; ln; i++) {
+            item = uses[i];
+
+            if (typeof item === 'string') {
+                items.push(item);
+            }
+        }
+
+        Loader.addOptionalRequires(items);
+    });
+
+    Manager.setDefaultPostprocessorPosition('uses', 'last');
+
+})(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
+</pre>
 </body>
-</html>
\ No newline at end of file
+</html>