// @tag class/** * This class provides dynamic loading support for JavaScript classes. Application code * does not typically need to call `Ext.Loader` except perhaps to configure path mappings * when not using [Sencha Cmd](http://www.sencha.com/products/sencha-cmd/). * * Ext.Loader.setPath('MyApp', 'app'); * * When using Sencha Cmd, this is handled by the "bootstrap" provided by the application * build script and such configuration is not necessary. * * # Typical Usage * * The `Ext.Loader` is most often used behind the scenes to satisfy class references in * class declarations. Like so: * * Ext.define('MyApp.view.Main', { * extend: 'Ext.panel.Panel', * * mixins: [ * 'MyApp.util.Mixin' * ], * * requires: [ * 'Ext.grid.Panel' * ], * * uses: [ * 'MyApp.util.Stuff' * ] * }); * * In all of these cases, `Ext.Loader` is used internally to resolve these class names * and ensure that the necessary class files are loaded. * * During development, these files are loaded individually for optimal debugging. For a * production use, [Sencha Cmd](http://www.sencha.com/products/sencha-cmd/) will replace * all of these strings with the actual resolved class references because it ensures that * the classes are all contained in the build in the correct order. In development, these * files will not be loaded until the `MyApp.view.Main` class indicates they are needed * as shown above. * * # Loading Classes * * You can also use `Ext.Loader` directly to load classes or files. The simplest form of * use is `{@link Ext#require}`. * * For example: * * Ext.require('MyApp.view.Main', function () { * // On callback, the MyApp.view.Main class is now loaded * * var view = new MyApp.view.Main(); * }); * * You can alternatively require classes by alias or wildcard. * * Ext.require('widget.window'); * * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']); * * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']); * * The callback function is optional. * * **Note** Using `Ext.require` at global scope will cause `{@link Ext#onReady}` and * `{@link Ext.app.Application#launch}` methods to be deferred until the required classes * are loaded. It is these cases where the callback function is most often unnecessary. * * ## Using Excludes * * Alternatively, you can exclude what you don't need: * * // Include everything except Ext.tree.* * Ext.exclude('Ext.tree.*').require('*'); * * // Include all widgets except widget.checkbox* (this will exclude * // widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.) * Ext.exclude('widget.checkbox*').require('widget.*'); * * # Dynamic Instantiation * * Another feature enabled by `Ext.Loader` is instantiation using class names or aliases. * * For example: * * var win = Ext.create({ * xtype: 'window', * * // or * // xclass: 'Ext.window.Window' * * title: 'Hello' * }); * * This form of creation can be useful if the type to create (`window` in the above) is * not known statically. Internally, `{@link Ext#method!create}` may need to *synchronously* * load the desired class and its requirements. Doing this will generate a warning in * the console: * * [Ext.Loader] Synchronously loading 'Ext.window.Window'... * * If you see these in your debug console, you should add the indicated class(es) to the * appropriate `requires` array (as above) or make an `{@link Ext#require}` call. * * * **Note** Using `{@link Ext#method!create}` has some performance overhead and is best reserved * for cases where the target class is not known until run-time. * * @class Ext.Loader * @singleton */Ext.Loader = (new function() {// @define Ext.Loader// @require Ext.Base// @require Ext.Class// @require Ext.ClassManager// @require Ext.mixin.Watchable// @require Ext.Function// @require Ext.Array// @require Ext.env.Ready var Loader = this, Manager = Ext.ClassManager, // this is an instance of Ext.Inventory Boot = Ext.Boot, Class = Ext.Class, Ready = Ext.env.Ready, alias = Ext.Function.alias, dependencyProperties = ['extend', 'mixins', 'requires'], isInHistory = {}, history = [], readyListeners = [], usedClasses = [], _requiresMap = {}, _config = { /** * @cfg {Boolean} [enabled=true] * Whether or not to enable the dynamic dependency loading feature. */ enabled: true, /** * @cfg {Boolean} [scriptChainDelay=false] * millisecond delay between asynchronous script injection (prevents stack * overflow on some user agents) 'false' disables delay but potentially * increases stack load. */ scriptChainDelay: false, /** * @cfg {Boolean} [disableCaching=true] * Appends current timestamp to script files to prevent caching. */ disableCaching: true, /** * @cfg {String} [disableCachingParam="_dc"] * The get parameter name for the cache buster's timestamp. */ 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: Manager.paths, /** * @cfg {Boolean} preserveScripts * `false` to remove asynchronously loaded scripts, `true` to retain script * element for browser debugger compatibility and improved load performance. */ preserveScripts: true, /** * @cfg {String} scriptCharset * Optional charset to specify encoding of dynamic script content. */ scriptCharset: undefined }, // These configs are delegated to Ext.Script and may need different names: delegatedConfigs = { disableCaching: true, disableCachingParam: true, preserveScripts: true, scriptChainDelay: 'loadDelay' }; Ext.apply(Loader, { /** * @private */ isInHistory: isInHistory, /** * Flag indicating whether there are still files being loaded * @private */ isLoading: false, /** * 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. * * @property {Array} history */ history: history, /** * Configuration * @private */ config: _config, /** * Maintain the list of listeners to execute when all required scripts are fully loaded * @private */ readyListeners: readyListeners, /** * Contains classes referenced in `uses` properties. * @private */ optionalRequires: usedClasses, /** * Map of fully qualified class names to an array of dependent classes. * @private */ requiresMap: _requiresMap, /** @private */ hasFileLoadError: false, /** * The number of scripts loading via loadScript. * @private */ scriptsLoading: 0, /** * @private */ classesLoading: {}, missingCount: 0, missingQueue: {}, /** * @private */ syncModeEnabled: false, init: function() { // initalize the default path of the framework var scripts = document.getElementsByTagName('script'), src = scripts[scripts.length - 1].src, path = src.substring(0, src.lastIndexOf('/') + 1), meta = Ext._classPathMetadata, microloader = Ext.Microloader, manifest = Ext.manifest, loadOrder, baseUrl, loadlen, l, loadItem; //<debug> if (src.indexOf("packages/core/src/") !== -1) { path = path + "../../"; } else if (src.indexOf("/core/src/class/") !== -1) { path = path + "../../../"; } //</debug> if (!Manager.getPath("Ext")) { Manager.setPath('Ext', path + 'src'); } // Pull in Cmd generated metadata if available. if (meta) { Ext._classPathMetadata = null; Loader.addClassPathMappings(meta); } if (manifest) { loadOrder = manifest.loadOrder; // if the manifest paths were calculated as relative to the // bootstrap file, then we need to prepend Boot.baseUrl to the // paths before processing baseUrl = Ext.Boot.baseUrl; if (loadOrder && manifest.bootRelative) { for (loadlen = loadOrder.length, l = 0; l < loadlen; l++) { loadItem = loadOrder[l]; loadItem.path = baseUrl + loadItem.path; loadItem.canonicalPath = true; } } } if (microloader) { Ready.block(); microloader.onMicroloaderReady(function() { Ready.unblock(); }); } }, /** * @method setConfig * Set the configuration for the loader. This should be called right after ext-(debug).js * is included in the page, and before Ext.onReady. i.e: * * <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 {Object} config The config object to override the default values * @return {Ext.Loader} this */ setConfig: Ext.Function.flexSetter(function(name, value) { var delegated = delegatedConfigs[name]; if (name === 'paths') { Loader.setPath(value); } else { _config[name] = value; if (delegated) { Boot.setConfig((delegated === true) ? name : delegated, value); } } return Loader; }), /** * 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) { return name ? _config[name] : _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: function() { // Paths are an Ext.Inventory thing and ClassManager is an instance of that: Manager.setPath.apply(Manager, arguments); return Loader; }, /** * Sets a batch of path entries * * @param {Object} paths a set of className: path mappings * @return {Ext.Loader} this */ addClassPathMappings: function(paths) { // Paths are an Ext.Inventory thing and ClassManager is an instance of that: Manager.setPath(paths); return Loader; }, /** * fixes up loader path configs by prepending Ext.Boot#baseUrl to the beginning * of the path, then delegates to Ext.Loader#addClassPathMappings * @param pathConfig */ addBaseUrlClassPathMappings: function(pathConfig) { var name; for (name in pathConfig) { pathConfig[name] = Boot.baseUrl + pathConfig[name]; } Ext.Loader.addClassPathMappings(pathConfig); }, /** * 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'); * * // alerts '/path/to/My/awesome/Class.js' * alert(Ext.Loader.getPath('My.awesome.Class')); * * 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' * }); * * // alerts '/other/path/for/awesome/stuff/Class.js' * alert(Ext.Loader.getPath('My.awesome.Class')); * * // alerts '/more/awesome/path/Class.js' * alert(Ext.Loader.getPath('My.awesome.more.Class')); * * // alerts '/path/to/lib/cool/Class.js' * alert(Ext.Loader.getPath('My.cool.Class')); * * // alerts 'Unknown/strange/Stuff.js' * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); * * @param {String} className * @return {String} path */ getPath: function(className) { // Paths are an Ext.Inventory thing and ClassManager is an instance of that: return Manager.getPath(className); }, require: function(expressions, fn, scope, excludes) { var classNames; if (excludes) { return Loader.exclude(excludes).require(expressions, fn, scope); } classNames = Manager.getNamesByExpression(expressions); return Loader.load(classNames, fn, scope); }, syncRequire: function() { var wasEnabled = Loader.syncModeEnabled, ret; Loader.syncModeEnabled = true; ret = Loader.require.apply(Loader, arguments); Loader.syncModeEnabled = wasEnabled; return ret; }, exclude: function(excludes) { var selector = Manager.select({ require: function(classNames, fn, scope) { return Loader.load(classNames, fn, scope); }, syncRequire: function(classNames, fn, scope) { var wasEnabled = Loader.syncModeEnabled, ret; Loader.syncModeEnabled = true; ret = Loader.load(classNames, fn, scope); Loader.syncModeEnabled = wasEnabled; return ret; } }); selector.exclude(excludes); return selector; }, load: function(classNames, callback, scope) { if (callback) { if (callback.length) { // If callback expects arguments, shim it with a function that will map // the requires class(es) from the names we are given. callback = Loader.makeLoadCallback(classNames, callback); } callback = callback.bind(scope || Ext.global); } /* eslint-disable-next-line vars-on-top */ var state = Manager.classState, missingClassNames = [], urls = [], urlByClass = {}, numClasses = classNames.length, className, i, numMissing; for (i = 0; i < numClasses; ++i) { className = Manager.resolveName(classNames[i]); if (!Manager.isCreated(className)) { missingClassNames.push(className); if (!state[className]) { urlByClass[className] = Loader.getPath(className); urls.push(urlByClass[className]); } } } // If the dynamic dependency feature is not being used, throw an error // if the dependencies are not defined numMissing = missingClassNames.length; if (numMissing) { Loader.missingCount += numMissing; Manager.onCreated(function() { if (callback) { Ext.callback(callback, scope, arguments); } Loader.checkReady(); }, Loader, missingClassNames); if (!_config.enabled) { Ext.raise("Ext.Loader is not enabled, so dependencies cannot be resolved " + "dynamically. Missing required class" + ((missingClassNames.length > 1) ? "es" : "") + ": " + missingClassNames.join(', ')); } if (urls.length) { Loader.loadScripts({ url: urls, // scope will be this options object so we can pass these along: _classNames: missingClassNames, _urlByClass: urlByClass }); } else { // need to call checkReady here, as the _missingCoun // may have transitioned from 0 to > 0, meaning we // need to block ready Loader.checkReady(); } } else { if (callback) { callback.call(scope); } // need to call checkReady here, as the _missingCoun // may have transitioned from 0 to > 0, meaning we // need to block ready Loader.checkReady(); } if (Loader.syncModeEnabled) { // Class may have been just loaded or was already loaded if (numClasses === 1) { return Manager.get(classNames[0]); } } return Loader; }, makeLoadCallback: function(classNames, callback) { return function() { var classes = [], i = classNames.length; while (i-- > 0) { classes[i] = Manager.get(classNames[i]); } return callback.apply(this, classes); }; }, onLoadFailure: function(request) { var options = this, entries = request.entries || [], onError = options.onError, error, entry, i; Loader.hasFileLoadError = true; --Loader.scriptsLoading; if (onError) { for (i = 0; i < entries.length; i++) { entry = entries[i]; if (entry.error) { error = new Error('Failed to load: ' + entry.url); break; } } error = error || new Error('Failed to load'); onError.call(options.userScope, options, error, request); } //<debug> else { Ext.log.error("[Ext.Loader] Some requested files failed to load."); } //</debug> Loader.checkReady(); }, onLoadSuccess: function() { var options = this, onLoad = options.onLoad, classNames = options._classNames, urlByClass = options._urlByClass, state = Manager.classState, missingQueue = Loader.missingQueue, className, i, len; --Loader.scriptsLoading; if (onLoad) { // TODO: need an adapter to convert to v4 onLoad signatures onLoad.call(options.userScope, options); // onLoad can cause more loads to start, so it must run first } // classNames is the array of *all* classes that load() was asked to load, // including those that might have been already loaded but not yet created. // urlByClass is a map of only those classes that we asked Boot to load. for (i = 0, len = classNames.length; i < len; i++) { className = classNames[i]; // When a script is loaded and executed, we should have Ext.define() called // for at least one of the classes in the list, which will set the state // for that class. That by itself does not mean that the class is available // *now* but it means that ClassManager is tracking it and will fire the // onCreated callback that we set back in load(). // However if there is no state for the class, that may mean two things: // either it is not a Ext class, or it is truly missing. In any case we need // to watch for that thing ourselves, which we will do every checkReady(). if (!state[className]) { missingQueue[className] = urlByClass[className]; } } Loader.checkReady(); }, // TODO: this timing of this needs to be deferred until all classes have had // a chance to be created //<debug> reportMissingClasses: function() { var missingQueue = Loader.missingQueue, missingClasses = [], missingPaths = [], missingClassName; if (!Loader.syncModeEnabled && !Loader.scriptsLoading && Loader.isLoading && !Loader.hasFileLoadError) { for (missingClassName in missingQueue) { missingClasses.push(missingClassName); missingPaths.push(missingQueue[missingClassName]); } if (missingClasses.length) { throw new Error("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> /** * Add a 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=true] Pass `false` to not also wait for document * dom ready. * @param {Object} [options] Additional callback options. * @param {Number} [options.delay=0] A number of milliseconds to delay. * @param {Number} [options.priority=0] Relative priority of this callback. Negative * numbers are reserved. */ onReady: function(fn, scope, withDomReady, options) { var listener; if (withDomReady) { Ready.on(fn, scope, options); } else { listener = Ready.makeListener(fn, scope, options); if (Loader.isLoading) { readyListeners.push(listener); } else { Ready.invoke(listener); } } }, /** * @private * Ensure that any classes referenced in the `uses` property are loaded. */ addUsedClasses: function(classes) { var cls, i, ln; if (classes) { classes = (typeof classes === 'string') ? [classes] : classes; for (i = 0, ln = classes.length; i < ln; i++) { cls = classes[i]; if (typeof cls === 'string' && !Ext.Array.contains(usedClasses, cls)) { usedClasses.push(cls); } } } return Loader; }, /** * @private */ triggerReady: function() { var listener, refClasses = usedClasses; if (Loader.isLoading && refClasses.length) { // Empty the array to eliminate potential recursive loop issue usedClasses = []; // this may immediately call us back if all 'uses' classes // have been loaded Loader.require(refClasses); } else { // Must clear this before calling callbacks. This will cause any new loads // to call Ready.block() again. See below for more on this. Loader.isLoading = false; // These listeners are just those attached directly to Loader to wait for // class loading only. readyListeners.sort(Ready.sortFn); // this method can be called with Loader.isLoading either true or false // (can be called with false when all 'uses' classes are already loaded) // this may bypass the above if condition while (readyListeners.length && !Loader.isLoading) { // we may re-enter triggerReady so we cannot necessarily iterate the // readyListeners array listener = readyListeners.pop(); Ready.invoke(listener); } // If the DOM is also ready, this will fire the normal onReady listeners. // An astute observer would note that we may now be back to isLoading and // so ask "Why you call unblock?". The reason is that we must match the // calls to block and since we transitioned from isLoading to !isLoading // here we must call unblock. If we have transitioned back to isLoading in // the above loop it will have called block again so the counter will be // increased and this call will not reduce the block count to 0. This is // done by loadScripts. Ready.unblock(); } }, /** * @private * @param {String} className */ historyPush: function(className) { if (className && !isInHistory[className] && !Manager.overrideMap[className]) { isInHistory[className] = true; history.push(className); } return Loader; }, /** * This is an internal method that delegate content loading to the * bootstrap layer. * @private * @param params */ loadScripts: function(params) { var manifest = Ext.manifest, loadOrder = manifest && manifest.loadOrder, loadOrderMap = manifest && manifest.loadOrderMap, options; ++Loader.scriptsLoading; // if the load order map hasn't been created, create it now // and cache on the manifest if (loadOrder && !loadOrderMap) { manifest.loadOrderMap = loadOrderMap = Boot.createLoadOrderMap(loadOrder); } // verify the loading state, as this may have transitioned us from // not loading to loading Loader.checkReady(); options = Ext.apply({ loadOrder: loadOrder, loadOrderMap: loadOrderMap, charset: _config.scriptCharset, success: Loader.onLoadSuccess, failure: Loader.onLoadFailure, sync: Loader.syncModeEnabled, _classNames: [] }, params); options.userScope = options.scope; options.scope = options; Boot.load(options); }, /** * This method is provide for use by the bootstrap layer. * @private * @param {String[]} urls */ loadScriptsSync: function(urls) { var syncwas = Loader.syncModeEnabled; Loader.syncModeEnabled = true; Loader.loadScripts({ url: urls }); Loader.syncModeEnabled = syncwas; }, /** * This method is provide for use by the bootstrap layer. * @private * @param {String[]} urls */ loadScriptsSyncBasePrefix: function(urls) { var syncwas = Loader.syncModeEnabled; Loader.syncModeEnabled = true; Loader.loadScripts({ url: urls, prependBaseUrl: true }); Loader.syncModeEnabled = syncwas; }, /** * Loads the specified script URL and calls the supplied callbacks. If this method * is called before {@link Ext#isReady}, the script's load will delay the transition * to ready. This can be used to load arbitrary scripts that may contain further * {@link Ext#require Ext.require} calls. * * @param {Object/String/String[]} options The options object or simply the URL(s) to load. * @param {String} options.url The URL from which to load the script. * @param {Function} [options.onLoad] The callback to call on successful load. * @param {Function} [options.onError] The callback to call on failure to load. * @param {Object} [options.scope] The scope (`this`) for the supplied callbacks. */ loadScript: function(options) { var isString = typeof options === 'string', isArray = options instanceof Array, isObject = !isArray && !isString, url = isObject ? options.url : options, onError = isObject && options.onError, onLoad = isObject && options.onLoad, scope = isObject && options.scope, request = { url: url, scope: scope, onLoad: onLoad, onError: onError, _classNames: [] }; Loader.loadScripts(request); }, /** * @private */ checkMissingQueue: function() { var missingQueue = Loader.missingQueue, newQueue = {}, missing = 0, name; for (name in missingQueue) { // If class state is available for the name, that means ClassManager // is tracking it and will fire callback when it is created. // We only need to track non-class things in the Loader. if (!(Manager.classState[name] || Manager.isCreated(name))) { newQueue[name] = missingQueue[name]; missing++; } } Loader.missingCount = missing; Loader.missingQueue = newQueue; }, /** * @private */ checkReady: function() { var wasLoading = Loader.isLoading, isLoading; Loader.checkMissingQueue(); isLoading = Loader.missingCount + Loader.scriptsLoading; if (isLoading && !wasLoading) { Ready.block(); Loader.isLoading = !!isLoading; } else if (!isLoading && wasLoading) { Loader.triggerReady(); } //<debug> if (!Loader.scriptsLoading && Loader.missingCount) { // Things look bad, but since load requests may come later, defer this // for a bit then check if things are still stuck. Ext.defer(function() { var name; if (!Loader.scriptsLoading && Loader.missingCount) { Ext.log.error('[Loader] The following classes failed to load:'); for (name in Loader.missingQueue) { Ext.log.error('[Loader] ' + name + ' from ' + Loader.missingQueue[name]); } } }, 1000); } //</debug> } }); /** * Loads all classes by the given names and all their direct dependencies; optionally * executes the given callback function when finishes, within the optional scope. * * @param {String/String[]} expressions The class, classes or wildcards to load. * @param {Function} [fn] The callback function. * @param {Object} [scope] The execution scope (`this`) of the callback function. * @member Ext * @method require */ Ext.require = alias(Loader, 'require'); /** * 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. * * @param {String/String[]} expressions The class, classes or wildcards to load. * @param {Function} [fn] The callback function. * @param {Object} [scope] The execution scope (`this`) of the callback function. * @member Ext * @method syncRequire */ Ext.syncRequire = alias(Loader, 'syncRequire'); /** * 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, * for example: * * Ext.exclude('Ext.data.*').require('*'); * * Ext.exclude('widget.button*').require('widget.*'); * * @param {String/String[]} excludes * @return {Object} Contains `exclude`, `require` and `syncRequire` methods for chaining. * @member Ext * @method exclude */ Ext.exclude = alias(Loader, 'exclude'); //<feature classSystem.loader> /** * @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, hooks, continueFn) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#loaderPreprocessor', arguments); //</debug> /* eslint-disable-next-line vars-on-top */ var me = this, dependencies = [], dependency, className = Manager.getName(cls), i, j, ln, subLn, value, propertyName, propertyValue, requiredMap; /* 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: { thing: 'Foo.bar.Thing'; } } which will later be transformed into: { extend: Ext.MyClass, requires: [Ext.some.OtherClass], mixins: { thing: Foo.bar.Thing; } } */ 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 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) { return; } if (className) { _requiresMap[className] = dependencies; } //<debug> /* eslint-disable-next-line vars-on-top */ var manifestClasses = Ext.manifest && Ext.manifest.classes, deadlockPath = [], 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. However we don't need * to run this check if the class name is in the manifest: that means Cmd has * already resolved all dependencies for this class with no deadlocks. */ if (className && (!manifestClasses || !manifestClasses[className])) { requiredMap = Loader.requiredByMap || (Loader.requiredByMap = {}); for (i = 0, ln = dependencies.length; i < ln; i++) { dependency = dependencies[i]; (requiredMap[dependency] || (requiredMap[dependency] = [])).push(className); } detectDeadlock = function(cls) { var requires = _requiresMap[cls], dep, i, ln; deadlockPath.push(cls); if (requires) { if (Ext.Array.contains(requires, className)) { Ext.Error.raise("Circular requirement detected! '" + className + "' and '" + deadlockPath[1] + "' mutually require each other. " + "Path: " + deadlockPath.join(' -> ') + " -> " + deadlockPath[0]); } for (i = 0, ln = requires.length; i < ln; i++) { dep = requires[i]; if (!isInHistory[dep]) { detectDeadlock(requires[i]); } } } }; detectDeadlock(className); } //</debug> (className ? Loader.exclude(className) : Loader).require(dependencies, function() { var i, ln, j, subLn, k; 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 if (typeof propertyValue !== 'function') { for (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, hooks); }); return false; }, true, 'after', 'className'); /** * @cfg {String[]} uses * @member Ext.Class * List of optional classes to load together with this class. These aren't neccessarily loaded * before this class is created, but are guaranteed to be available before Ext.onReady * listeners are invoked. 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 = data.uses, classNames; //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#usesPostprocessor', arguments); //</debug> if (uses) { classNames = Manager.getNamesByExpression(data.uses); Loader.addUsedClasses(classNames); } }); Manager.onCreated(Loader.historyPush); //</feature> Loader.init();}()); //----------------------------------------------------------------------------- // Use performance.now when available to keep timestamps consistent.Ext._endTime = Ext.ticks(); // This hook is to allow tools like DynaTrace to deterministically detect the availability// of Ext.onReady. Since Loader takes over Ext.onReady this must be done here and not in// Ext.env.Ready.if (Ext._beforereadyhandler) { Ext._beforereadyhandler();}