/** * @class Ext.Loader * @singleton * @author Jacky Nguyen <jacky@sencha.com> * @docauthor Jacky Nguyen <jacky@sencha.com> * * 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. */ (function(Manager, Class, flexSetter, alias) { var //<if nonBrowser> isNonBrowser = typeof window === 'undefined', isNodeJS = isNonBrowser && (typeof require === 'function'), isPhantomJS = (typeof phantom !== 'undefined' && phantom.fs), //</if> dependencyProperties = ['extend', 'mixins', 'requires'], Loader; Loader = Ext.Loader = { /** * @private */ documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]), /** * Flag indicating whether there are still files being loaded * @private */ isLoading: false, /** * 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: [], /** * Maintain the list of files that have already been handled so that they never get double-loaded * @private */ isFileLoaded: {}, /** * Maintain the list of listeners to execute when all required scripts are fully loaded * @private */ readyListeners: [], /** * Contains optional dependencies to be loaded last * @private */ optionalRequires: [], /** * Map of fully qualified class names to an array of dependent classes. * @private */ requiresMap: {}, /** * @private */ numPendingFiles: 0, /** * @private */ numLoadedFiles: 0, /** @private */ hasFileLoadError: false, /** * @private */ classNameToFilePathMap: {}, /** * @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: [], /** * Configuration * @private */ config: { /** * @cfg {Boolean} enabled * Whether or not to enable the dynamic dependency loading feature Defaults to false */ enabled: false, /** * @cfg {Boolean} disableCaching * Appends current timestamp to script files to prevent caching Defaults to true */ disableCaching: true, /** * @cfg {String} disableCachingParam * The get parameter name for the cache buster's timestamp. Defaults to '_dc' */ disableCachingParam: '_dc', /** * @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': '.' } }, /** * 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.: * * <script type="text/javascript" src="ext-core-debug.js"></script> * <script type="text/javascript"> * Ext.Loader.setConfig({ * enabled: true, * paths: { * 'My': 'my_own_path' * } * }); * <script> * <script type="text/javascript"> * Ext.require(...); * * Ext.onReady(function() { * // application code here * }); * </script> * * 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) && 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; }, /** * 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/Mixed} */ getConfig: function(name) { if (name) { return this.config[name]; } return this.config; }, /** * 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) { //<if nonBrowser> if (isNonBrowser) { if (isNodeJS) { path = require('fs').realpathSync(path); } } //</if> this.config.paths[name] = path; return this; }), /** * 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 > 0) { if (prefix === className) { return paths[prefix]; } path = paths[prefix]; className = className.substring(prefix.length + 1); } if (path.length > 0) { path += '/'; } return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js'; }, /** * @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) && prefix + '.' === className.substring(0, prefix.length + 1)) { if (prefix.length > deepestPrefix.length) { deepestPrefix = prefix; } } } return deepestPrefix; }, /** * 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 < 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 > 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 < requires.length); if (item.requires.length === 0) { Ext.Array.erase(this.queue, i, 1); item.callback.call(item.scope); this.refreshQueue(); break; } } } return this; }, /** * 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; }, /** * @private */ cleanupScriptElement: function(script) { script.onload = null; script.onreadystatechange = null; script.onerror = null; return this; }, /** * Load a script file, supports both asynchronous and synchronous approaches * * @param {String} url * @param {Function} onLoad * @param {Scope} 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, "Failed loading '" + url + "', please verify that the file exists", synchronous); }; if (!Ext.isReady && 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 //<if isNonBrowser> && !isPhantomJS //</if> ) { onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " + "being loaded from a different domain or from the local file system whereby cross origin " + "requests are not allowed due to security reasons. Use asynchronous loading with " + "Ext.require instead.", synchronous); } else if (status >= 200 && status < 300 //<if isNonBrowser> || isPhantomJS //</if> ) { // Firebug friendly, file names are still shown even though they're eval'ed code new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)(); onLoad.call(scope); } else { onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " + "verify that the file exists. " + "XHR status code: " + status, synchronous); } // Prevent potential IE memory leak xhr = null; } }, /** * 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); } }; }, /** * 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; }, /** * 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 < ln; i++) { exclude = excludes[i]; if (typeof exclude === 'string' && exclude.length > 0) { excludedClassNames = Manager.getNamesByExpression(exclude); for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) { excluded[excludedClassNames[j]] = true; } } } for (i = 0, ln = expressions.length; i < ln; i++) { expression = expressions[i]; if (typeof expression === 'string' && expression.length > 0) { possibleClassNames = Manager.getNamesByExpression(expression); for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) { possibleClassName = possibleClassNames[j]; if (!excluded.hasOwnProperty(possibleClassName) && !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 > 0) { Ext.Error.raise({ sourceClass: "Ext.Loader", sourceMethod: "require", msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " + "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + 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 < ln; i++) { className = classNames[i]; if (!this.isFileLoaded.hasOwnProperty(className)) { this.isFileLoaded[className] = false; filePath = this.getPath(className); this.classNameToFilePathMap[className] = filePath; this.numPendingFiles++; //<if nonBrowser> 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; } //</if> this.loadScriptFile( filePath, Ext.Function.pass(this.onFileLoaded, [className, filePath], this), Ext.Function.pass(this.onFileLoadError, [className, filePath]), this, this.syncModeEnabled ); } } return this; }, /** * @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(); } //<debug> if (this.numPendingFiles <= 1) { window.status = "Finished loading all dependencies, onReady fired!"; } else { window.status = "Loading dependencies, " + this.numPendingFiles + " files left..."; } //</debug> //<debug> if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) { var queue = this.queue, requires, i, ln, j, subLn, missingClasses = [], missingPaths = []; for (i = 0, ln = queue.length; i < ln; i++) { requires = queue[i].requires; for (j = 0, subLn = requires.length; j < ln; j++) { if (this.isFileLoaded[requires[j]]) { missingClasses.push(requires[j]); } } } if (missingClasses.length < 1) { return; } missingClasses = Ext.Array.filter(missingClasses, function(item) { return !this.requiresMap.hasOwnProperty(item); }, this); for (i = 0,ln = missingClasses.length; i < ln; i++) { missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]); } Ext.Error.raise({ sourceClass: "Ext.Loader", sourceMethod: "onFileLoaded", msg: "The following classes are not declared even if their files have been " + "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " + "corresponding files for possible typos: '" + missingPaths.join("', '") + "'" }); } //</debug> }, /** * @private */ onFileLoadError: function(className, filePath, errorMessage, isSynchronous) { this.numPendingFiles--; this.hasFileLoadError = true; //<debug error> Ext.Error.raise({ sourceClass: "Ext.Loader", classToLoad: className, loadPath: filePath, loadingType: isSynchronous ? 'synchronous' : 'async', msg: errorMessage }); //</debug> }, /** * @private */ addOptionalRequires: function(requires) { var optionalRequires = this.optionalRequires, i, ln, require; requires = Ext.Array.from(requires); for (i = 0, ln = requires.length; i < ln; i++) { require = requires[i]; Ext.Array.include(optionalRequires, require); } return this; }, /** * @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; }, /** * 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 && 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 }); } }, /** * @private * @param {String} className */ historyPush: function(className) { if (className && this.isFileLoaded.hasOwnProperty(className)) { Ext.Array.include(this.history, className); } return this; } }; /** * @member Ext * @method require * @alias Ext.Loader#require */ Ext.require = alias(Loader, 'require'); /** * @member Ext * @method syncRequire * @alias Ext.Loader#syncRequire */ Ext.syncRequire = alias(Loader, 'syncRequire'); /** * @member Ext * @method exclude * @alias Ext.Loader#exclude */ Ext.exclude = alias(Loader, 'exclude'); /** * @member Ext * @method onReady * @alias Ext.Loader#onReady */ Ext.onReady = function(fn, scope, options) { Loader.onReady(fn, scope, true, options); }; /** * @cfg {[String]} requires * @member Ext.Class * List of classes that have to be loaded before instanciating 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 < 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 < subLn; j++) { value = propertyValue[j]; if (typeof value === 'string') { dependencies.push(value); } } } else { 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; } //<debug error> 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: "Ext.Loader", msg: "Deadlock detected while loading dependencies! '" + className + "' and '" + deadlockPath[1] + "' " + "mutually require each other. Path: " + deadlockPath.join(' -> ') + " -> " + deadlockPath[0] }); } for (i = 0, ln = requiresMap[cls].length; i < ln; i++) { detectDeadlock(requiresMap[cls][i]); } } }; detectDeadlock(className); } //</debug> Loader.require(dependencies, function() { for (i = 0, ln = dependencyProperties.length; i < 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 < subLn; j++) { value = propertyValue[j]; if (typeof value === 'string') { data[propertyName][j] = Manager.get(value); } } } else { 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'); /** * @cfg {[String]} uses * @member Ext.Class * List of classes to load together with this class. These aren't neccessarily loaded before * this class is instanciated. 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 < 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);