]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/class/Loader.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / class / Loader.js
CommitLineData
6527f429
DM
1// @tag class\r
2/**\r
3 * This class provides dynamic loading support for JavaScript classes. Application code\r
4 * does not typically need to call `Ext.Loader` except perhaps to configure path mappings\r
5 * when not using [Sencha Cmd](http://www.sencha.com/products/sencha-cmd/).\r
6 *\r
7 * Ext.Loader.setPath('MyApp', 'app');\r
8 *\r
9 * When using Sencha Cmd, this is handled by the "bootstrap" provided by the application\r
10 * build script and such configuration is not necessary.\r
11 *\r
12 * # Typical Usage\r
13 *\r
14 * The `Ext.Loader` is most often used behind the scenes to satisfy class references in\r
15 * class declarations. Like so:\r
16 *\r
17 * Ext.define('MyApp.view.Main', {\r
18 * extend: 'Ext.panel.Panel',\r
19 *\r
20 * mixins: [\r
21 * 'MyApp.util.Mixin'\r
22 * ],\r
23 *\r
24 * requires: [\r
25 * 'Ext.grid.Panel'\r
26 * ],\r
27 *\r
28 * uses: [\r
29 * 'MyApp.util.Stuff'\r
30 * ]\r
31 * });\r
32 *\r
33 * In all of these cases, `Ext.Loader` is used internally to resolve these class names\r
34 * and ensure that the necessary class files are loaded.\r
35 *\r
36 * During development, these files are loaded individually for optimal debugging. For a\r
37 * production use, [Sencha Cmd](http://www.sencha.com/products/sencha-cmd/) will replace\r
38 * all of these strings with the actual resolved class references because it ensures that\r
39 * the classes are all contained in the build in the correct order. In development, these\r
40 * files will not be loaded until the `MyApp.view.Main` class indicates they are needed\r
41 * as shown above.\r
42 *\r
43 * # Loading Classes\r
44 *\r
45 * You can also use `Ext.Loader` directly to load classes or files. The simplest form of\r
46 * use is `{@link Ext#require}`.\r
47 *\r
48 * For example:\r
49 *\r
50 * Ext.require('MyApp.view.Main', function () {\r
51 * // On callback, the MyApp.view.Main class is now loaded\r
52 *\r
53 * var view = new MyApp.view.Main();\r
54 * });\r
55 *\r
56 * You can alternatively require classes by alias or wildcard.\r
57 *\r
58 * Ext.require('widget.window');\r
59 *\r
60 * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);\r
61 *\r
62 * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);\r
63 *\r
64 * The callback function is optional.\r
65 *\r
66 * **Note** Using `Ext.require` at global scope will cause `{@link Ext#onReady}` and\r
67 * `{@link Ext.app.Application#launch}` methods to be deferred until the required classes\r
68 * are loaded. It is these cases where the callback function is most often unnecessary.\r
69 *\r
70 * ## Using Excludes\r
71 *\r
72 * Alternatively, you can exclude what you don't need:\r
73 *\r
74 * // Include everything except Ext.tree.*\r
75 * Ext.exclude('Ext.tree.*').require('*');\r
76 *\r
77 * // Include all widgets except widget.checkbox* (this will exclude\r
78 * // widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.)\r
79 * Ext.exclude('widget.checkbox*').require('widget.*');\r
80 *\r
81 * # Dynamic Instantiation\r
82 *\r
83 * Another feature enabled by `Ext.Loader` is instantiation using class names or aliases.\r
84 *\r
85 * For example:\r
86 *\r
87 * var win = Ext.create({\r
88 * xtype: 'window',\r
89 *\r
90 * // or\r
91 * // xclass: 'Ext.window.Window'\r
92 *\r
93 * title: 'Hello'\r
94 * });\r
95 *\r
96 * This form of creation can be useful if the type to create (`window` in the above) is\r
97 * not known statically. Internally, `{@link Ext#create}` may need to *synchronously*\r
98 * load the desired class and its requirements. Doing this will generate a warning in\r
99 * the console:\r
100 * \r
101 * [Ext.Loader] Synchronously loading 'Ext.window.Window'...\r
102 *\r
103 * If you see these in your debug console, you should add the indicated class(es) to the\r
104 * appropriate `requires` array (as above) or make an `{@link Ext#require}` call.\r
105 * \r
106 * \r
107 * **Note** Using `{@link Ext#create}` has some performance overhead and is best reserved\r
108 * for cases where the target class is not known until run-time.\r
109 * \r
110 * @class Ext.Loader\r
111 * @singleton\r
112 */\r
113Ext.Loader = (new function() { // jshint ignore:line\r
114// @define Ext.Loader\r
115// @require Ext.Base\r
116// @require Ext.Class\r
117// @require Ext.ClassManager\r
118// @require Ext.Function\r
119// @require Ext.Array\r
120// @require Ext.env.Ready\r
121\r
122 var Loader = this,\r
123 Manager = Ext.ClassManager, // this is an instance of Ext.Inventory\r
124 Boot = Ext.Boot,\r
125 Class = Ext.Class,\r
126 Ready = Ext.env.Ready,\r
127 alias = Ext.Function.alias,\r
128 dependencyProperties = ['extend', 'mixins', 'requires'],\r
129 isInHistory = {},\r
130 history = [],\r
131 readyListeners = [],\r
132 usedClasses = [],\r
133 _requiresMap = {},\r
134 _missingQueue = {},\r
135 _config = {\r
136 /**\r
137 * @cfg {Boolean} [enabled=true]\r
138 * Whether or not to enable the dynamic dependency loading feature.\r
139 */\r
140 enabled: true,\r
141\r
142 /**\r
143 * @cfg {Boolean} [scriptChainDelay=false]\r
144 * millisecond delay between asynchronous script injection (prevents stack\r
145 * overflow on some user agents) 'false' disables delay but potentially\r
146 * increases stack load.\r
147 */\r
148 scriptChainDelay: false,\r
149\r
150 /**\r
151 * @cfg {Boolean} [disableCaching=true]\r
152 * Appends current timestamp to script files to prevent caching.\r
153 */\r
154 disableCaching: true,\r
155\r
156 /**\r
157 * @cfg {String} [disableCachingParam="_dc"]\r
158 * The get parameter name for the cache buster's timestamp.\r
159 */\r
160 disableCachingParam: '_dc',\r
161\r
162 /**\r
163 * @cfg {Object} paths\r
164 * The mapping from namespaces to file paths\r
165 *\r
166 * {\r
167 * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be\r
168 * // loaded from ./layout/Container.js\r
169 *\r
170 * 'My': './src/my_own_folder' // My.layout.Container will be loaded from\r
171 * // ./src/my_own_folder/layout/Container.js\r
172 * }\r
173 *\r
174 * Note that all relative paths are relative to the current HTML document.\r
175 * If not being specified, for example, `Other.awesome.Class` will simply be\r
176 * loaded from `"./Other/awesome/Class.js"`.\r
177 */\r
178 paths: Manager.paths,\r
179\r
180 /**\r
181 * @cfg {Boolean} preserveScripts\r
182 * `false` to remove asynchronously loaded scripts, `true` to retain script\r
183 * element for browser debugger compatibility and improved load performance.\r
184 */\r
185 preserveScripts: true,\r
186\r
187 /**\r
188 * @cfg {String} scriptCharset\r
189 * Optional charset to specify encoding of dynamic script content.\r
190 */\r
191 scriptCharset: undefined\r
192 },\r
193 // These configs are delegated to Ext.Script and may need different names:\r
194 delegatedConfigs = {\r
195 disableCaching: true,\r
196 disableCachingParam: true,\r
197 preserveScripts: true,\r
198 scriptChainDelay: 'loadDelay'\r
199 };\r
200\r
201 Ext.apply(Loader, {\r
202 /**\r
203 * @private\r
204 */\r
205 isInHistory: isInHistory,\r
206\r
207 /**\r
208 * Flag indicating whether there are still files being loaded\r
209 * @private\r
210 */\r
211 isLoading: false,\r
212\r
213 /**\r
214 * An array of class names to keep track of the dependency loading order.\r
215 * This is not guaranteed to be the same everytime due to the asynchronous\r
216 * nature of the Loader.\r
217 *\r
218 * @property {Array} history\r
219 */\r
220 history: history,\r
221\r
222 /**\r
223 * Configuration\r
224 * @private\r
225 */\r
226 config: _config,\r
227\r
228 /**\r
229 * Maintain the list of listeners to execute when all required scripts are fully loaded\r
230 * @private\r
231 */\r
232 readyListeners: readyListeners,\r
233\r
234 /**\r
235 * Contains classes referenced in `uses` properties.\r
236 * @private\r
237 */\r
238 optionalRequires: usedClasses,\r
239\r
240 /**\r
241 * Map of fully qualified class names to an array of dependent classes.\r
242 * @private\r
243 */\r
244 requiresMap: _requiresMap,\r
245\r
246 /** @private */\r
247 hasFileLoadError: false,\r
248\r
249 /**\r
250 * The number of scripts loading via loadScript.\r
251 * @private\r
252 */\r
253 scriptsLoading: 0,\r
254\r
255 //<debug>\r
256 /**\r
257 * @private\r
258 */\r
259 classesLoading: [],\r
260 //</debug>\r
261\r
262 /**\r
263 * @private\r
264 */\r
265 syncModeEnabled: false,\r
266\r
267 \r
268 /**\r
269 * @private\r
270 */\r
271 missingQueue: _missingQueue,\r
272 \r
273 init: function () {\r
274 // initalize the default path of the framework\r
275 var scripts = document.getElementsByTagName('script'),\r
276 src = scripts[scripts.length - 1].src,\r
277 path = src.substring(0, src.lastIndexOf('/') + 1),\r
278 meta = Ext._classPathMetadata,\r
279 microloader = Ext.Microloader,\r
280 manifest = Ext.manifest,\r
281 loadOrder, baseUrl, loadlen, l, loadItem;\r
282\r
283 //<debug>\r
284 if (src.indexOf("packages/core/src/") !== -1) {\r
285 path = path + "../../";\r
286 } else if (src.indexOf("/core/src/class/") !== -1) {\r
287 path = path + "../../../";\r
288 }\r
289 //</debug>\r
290\r
291 \r
292 if(!Manager.getPath("Ext")) {\r
293 Manager.setPath('Ext', path + 'src');\r
294 }\r
295\r
296 // Pull in Cmd generated metadata if available.\r
297 if (meta) {\r
298 Ext._classPathMetadata = null;\r
299 Loader.addClassPathMappings(meta);\r
300 }\r
301 \r
302 if(manifest) {\r
303 loadOrder = manifest.loadOrder;\r
304 // if the manifest paths were calculated as relative to the \r
305 // bootstrap file, then we need to prepend Boot.baseUrl to the\r
306 // paths before processing\r
307 baseUrl = Ext.Boot.baseUrl;\r
308 if(loadOrder && manifest.bootRelative) {\r
309 for(loadlen = loadOrder.length, l = 0; l < loadlen; l++) {\r
310 loadItem = loadOrder[l];\r
311 loadItem.path = baseUrl + loadItem.path;\r
312 } \r
313 }\r
314 }\r
315 \r
316 if(microloader) {\r
317 Ready.block();\r
318 microloader.onMicroloaderReady(function(){\r
319 Ready.unblock();\r
320 });\r
321 }\r
322 },\r
323\r
324 /**\r
325 * Set the configuration for the loader. This should be called right after ext-(debug).js\r
326 * is included in the page, and before Ext.onReady. i.e:\r
327 *\r
328 * <script type="text/javascript" src="ext-core-debug.js"></script>\r
329 * <script type="text/javascript">\r
330 * Ext.Loader.setConfig({\r
331 * enabled: true,\r
332 * paths: {\r
333 * 'My': 'my_own_path'\r
334 * }\r
335 * });\r
336 * </script>\r
337 * <script type="text/javascript">\r
338 * Ext.require(...);\r
339 *\r
340 * Ext.onReady(function() {\r
341 * // application code here\r
342 * });\r
343 * </script>\r
344 *\r
345 * Refer to config options of {@link Ext.Loader} for the list of possible properties\r
346 *\r
347 * @param {Object} config The config object to override the default values\r
348 * @return {Ext.Loader} this\r
349 */\r
350 setConfig: Ext.Function.flexSetter(function (name, value) {\r
351 if (name === 'paths') {\r
352 Loader.setPath(value);\r
353 } else {\r
354 _config[name] = value;\r
355\r
356 var delegated = delegatedConfigs[name];\r
357 if (delegated) {\r
358 Boot.setConfig((delegated === true) ? name : delegated, value);\r
359 }\r
360 }\r
361\r
362 return Loader;\r
363 }),\r
364\r
365 /**\r
366 * Get the config value corresponding to the specified name. If no name is given, will return the config object\r
367 * @param {String} name The config property name\r
368 * @return {Object}\r
369 */\r
370 getConfig: function(name) {\r
371 return name ? _config[name] : _config;\r
372 },\r
373\r
374 /**\r
375 * Sets the path of a namespace.\r
376 * For Example:\r
377 *\r
378 * Ext.Loader.setPath('Ext', '.');\r
379 *\r
380 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}\r
381 * @param {String} [path] See {@link Ext.Function#flexSetter flexSetter}\r
382 * @return {Ext.Loader} this\r
383 * @method\r
384 */\r
385 setPath: function () {\r
386 // Paths are an Ext.Inventory thing and ClassManager is an instance of that:\r
387 Manager.setPath.apply(Manager, arguments);\r
388 return Loader;\r
389 },\r
390\r
391 /**\r
392 * Sets a batch of path entries\r
393 *\r
394 * @param {Object } paths a set of className: path mappings\r
395 * @return {Ext.Loader} this\r
396 */\r
397 addClassPathMappings: function(paths) {\r
398 // Paths are an Ext.Inventory thing and ClassManager is an instance of that:\r
399 Manager.setPath(paths);\r
400 return Loader;\r
401 },\r
402\r
403 /**\r
404 * fixes up loader path configs by prepending Ext.Boot#baseUrl to the beginning\r
405 * of the path, then delegates to Ext.Loader#addClassPathMappings\r
406 * @param pathConfig\r
407 */\r
408\r
409 addBaseUrlClassPathMappings: function(pathConfig) {\r
410 for(var name in pathConfig) {\r
411 pathConfig[name] = Boot.baseUrl + pathConfig[name];\r
412 }\r
413 Ext.Loader.addClassPathMappings(pathConfig);\r
414 },\r
415\r
416\r
417 /**\r
418 * Translates a className to a file path by adding the\r
419 * the proper prefix and converting the .'s to /'s. For example:\r
420 *\r
421 * Ext.Loader.setPath('My', '/path/to/My');\r
422 *\r
423 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'\r
424 *\r
425 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:\r
426 *\r
427 * Ext.Loader.setPath({\r
428 * 'My': '/path/to/lib',\r
429 * 'My.awesome': '/other/path/for/awesome/stuff',\r
430 * 'My.awesome.more': '/more/awesome/path'\r
431 * });\r
432 *\r
433 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'\r
434 *\r
435 * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'\r
436 *\r
437 * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'\r
438 *\r
439 * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'\r
440 *\r
441 * @param {String} className\r
442 * @return {String} path\r
443 */\r
444 getPath: function(className) {\r
445 // Paths are an Ext.Inventory thing and ClassManager is an instance of that:\r
446 return Manager.getPath(className);\r
447 },\r
448\r
449 require: function (expressions, fn, scope, excludes) {\r
450 if (excludes) {\r
451 return Loader.exclude(excludes).require(expressions, fn, scope);\r
452 }\r
453\r
454 var classNames = Manager.getNamesByExpression(expressions);\r
455\r
456 return Loader.load(classNames, fn, scope);\r
457 },\r
458\r
459 syncRequire: function () {\r
460 var wasEnabled = Loader.syncModeEnabled;\r
461\r
462 Loader.syncModeEnabled = true;\r
463\r
464 var ret = Loader.require.apply(Loader, arguments);\r
465\r
466 Loader.syncModeEnabled = wasEnabled;\r
467\r
468 return ret;\r
469 },\r
470\r
471 exclude: function (excludes) {\r
472 var selector = Manager.select({\r
473 require: function (classNames, fn, scope) {\r
474 return Loader.load(classNames, fn, scope);\r
475 },\r
476\r
477 syncRequire: function (classNames, fn, scope) {\r
478 var wasEnabled = Loader.syncModeEnabled;\r
479\r
480 Loader.syncModeEnabled = true;\r
481\r
482 var ret = Loader.load(classNames, fn, scope);\r
483\r
484 Loader.syncModeEnabled = wasEnabled;\r
485\r
486 return ret;\r
487 }\r
488 });\r
489\r
490 selector.exclude(excludes);\r
491 return selector;\r
492 },\r
493\r
494 load: function (classNames, callback, scope) {\r
495 if (callback) {\r
496 if (callback.length) {\r
497 // If callback expects arguments, shim it with a function that will map\r
498 // the requires class(es) from the names we are given.\r
499 callback = Loader.makeLoadCallback(classNames, callback);\r
500 }\r
501 callback = callback.bind(scope || Ext.global);\r
502 }\r
503\r
504 var missingClassNames = [],\r
505 numClasses = classNames.length,\r
506 className, i, numMissing, urls = [],\r
507 state = Manager.classState;\r
508 \r
509 for (i = 0; i < numClasses; ++i) {\r
510 className = Manager.resolveName(classNames[i]);\r
511 if (!Manager.isCreated(className)) {\r
512 missingClassNames.push(className);\r
513 _missingQueue[className] = Loader.getPath(className);\r
514 if(!state[className]) {\r
515 urls.push(_missingQueue[className]);\r
516 }\r
517 }\r
518 }\r
519\r
520 // If the dynamic dependency feature is not being used, throw an error\r
521 // if the dependencies are not defined\r
522 numMissing = missingClassNames.length;\r
523 if (numMissing) {\r
524 Loader.missingCount += numMissing;\r
525 //<debug>\r
526 Ext.Array.push(Loader.classesLoading, missingClassNames);\r
527 //</debug>\r
528\r
529 Manager.onCreated(function () {\r
530 //<debug>\r
531 Ext.Array.remove(Loader.classesLoading, missingClassNames);\r
532 Ext.each(missingClassNames, function(name){\r
533 Ext.Array.remove(Loader.classesLoading, name);\r
534 });\r
535 //</debug>\r
536 if (callback) {\r
537 Ext.callback(callback, scope, arguments);\r
538 }\r
539 Loader.checkReady();\r
540 }, Loader, missingClassNames);\r
541\r
542 if (!_config.enabled) {\r
543 Ext.raise("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +\r
544 "Missing required class" + ((missingClassNames.length > 1) ? "es" : "") + \r
545 ": " + missingClassNames.join(', '));\r
546 }\r
547\r
548 if(urls.length) {\r
549 Loader.loadScripts({\r
550 url: urls,\r
551 // scope: this options object so we can pass these along:\r
552 _classNames: missingClassNames\r
553 });\r
554 } else {\r
555 // need to call checkReady here, as the _missingCoun\r
556 // may have transitioned from 0 to > 0, meaning we\r
557 // need to block ready\r
558 Loader.checkReady();\r
559 }\r
560 } else {\r
561 if (callback) {\r
562 callback.call(scope);\r
563 }\r
564 // need to call checkReady here, as the _missingCoun\r
565 // may have transitioned from 0 to > 0, meaning we\r
566 // need to block ready\r
567 Loader.checkReady();\r
568 }\r
569 \r
570 if (Loader.syncModeEnabled) {\r
571 // Class may have been just loaded or was already loaded\r
572 if (numClasses === 1) {\r
573 return Manager.get(classNames[0]);\r
574 }\r
575 }\r
576\r
577 return Loader;\r
578 },\r
579\r
580 makeLoadCallback: function (classNames, callback) {\r
581 return function () {\r
582 var classes = [],\r
583 i = classNames.length;\r
584\r
585 while (i-- > 0) {\r
586 classes[i] = Manager.get(classNames[i]);\r
587 }\r
588\r
589 return callback.apply(this, classes);\r
590 };\r
591 },\r
592 \r
593 onLoadFailure: function () {\r
594 var options = this,\r
595 onError = options.onError;\r
596\r
597 Loader.hasFileLoadError = true;\r
598 --Loader.scriptsLoading;\r
599\r
600 if (onError) {\r
601 //TODO: need an adapter to convert to v4 onError signatures\r
602 onError.call(options.userScope, options);\r
603 }\r
604 //<debug>\r
605 else {\r
606 Ext.log.error("[Ext.Loader] Some requested files failed to load.");\r
607 }\r
608 //</debug>\r
609\r
610 Loader.checkReady();\r
611 },\r
612\r
613 onLoadSuccess: function () {\r
614 var options = this,\r
615 onLoad = options.onLoad;\r
616\r
617 --Loader.scriptsLoading;\r
618 if (onLoad) {\r
619 //TODO: need an adapter to convert to v4 onLoad signatures\r
620 onLoad.call(options.userScope, options);\r
621 // onLoad can cause more loads to start, so it must run first\r
622 }\r
623\r
624 Loader.checkReady();\r
625 },\r
626\r
627// TODO: this timing of this needs to be deferred until all classes have had a chance to be created\r
628 //<debug>\r
629 reportMissingClasses: function () {\r
630 if (!Loader.syncModeEnabled && !Loader.scriptsLoading && Loader.isLoading &&\r
631 !Loader.hasFileLoadError) {\r
632 var missingClasses = [],\r
633 missingPaths = [];\r
634\r
635 for (var missingClassName in _missingQueue) {\r
636 missingClasses.push(missingClassName);\r
637 missingPaths.push(_missingQueue[missingClassName]);\r
638 }\r
639\r
640 if (missingClasses.length) {\r
641 throw new Error("The following classes are not declared even if their files have been " +\r
642 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +\r
643 "corresponding files for possible typos: '" + missingPaths.join("', '"));\r
644 }\r
645 }\r
646 },\r
647 //</debug>\r
648\r
649 /**\r
650 * Add a new listener to be executed when all required scripts are fully loaded\r
651 *\r
652 * @param {Function} fn The function callback to be executed\r
653 * @param {Object} scope The execution scope (`this`) of the callback function.\r
654 * @param {Boolean} [withDomReady=true] Pass `false` to not also wait for document\r
655 * dom ready.\r
656 * @param {Object} [options] Additional callback options.\r
657 * @param {Number} [options.delay=0] A number of milliseconds to delay.\r
658 * @param {Number} [options.priority=0] Relative priority of this callback. Negative\r
659 * numbers are reserved.\r
660 */\r
661 onReady: function(fn, scope, withDomReady, options) {\r
662 if (withDomReady) {\r
663 Ready.on(fn, scope, options);\r
664 } else {\r
665 var listener = Ready.makeListener(fn, scope, options);\r
666\r
667 if (Loader.isLoading) {\r
668 readyListeners.push(listener);\r
669 } else {\r
670 Ready.invoke(listener);\r
671 }\r
672 }\r
673 },\r
674\r
675 /**\r
676 * @private\r
677 * Ensure that any classes referenced in the `uses` property are loaded.\r
678 */\r
679 addUsedClasses: function (classes) {\r
680 var cls, i, ln;\r
681\r
682 if (classes) {\r
683 classes = (typeof classes === 'string') ? [classes] : classes;\r
684 for (i = 0, ln = classes.length; i < ln; i++) {\r
685 cls = classes[i];\r
686 if (typeof cls === 'string' && !Ext.Array.contains(usedClasses, cls)) {\r
687 usedClasses.push(cls);\r
688 }\r
689 }\r
690 }\r
691\r
692 return Loader;\r
693 },\r
694\r
695 /**\r
696 * @private\r
697 */\r
698 triggerReady: function() {\r
699 var listener,\r
700 refClasses = usedClasses;\r
701\r
702 if (Loader.isLoading && refClasses.length) {\r
703 // Empty the array to eliminate potential recursive loop issue\r
704 usedClasses = [];\r
705\r
706 // this may immediately call us back if all 'uses' classes\r
707 // have been loaded\r
708 Loader.require(refClasses);\r
709 } else {\r
710 // Must clear this before calling callbacks. This will cause any new loads\r
711 // to call Ready.block() again. See below for more on this.\r
712 Loader.isLoading = false;\r
713\r
714 // These listeners are just those attached directly to Loader to wait for\r
715 // class loading only.\r
716 readyListeners.sort(Ready.sortFn);\r
717\r
718 // this method can be called with Loader.isLoading either true or false\r
719 // (can be called with false when all 'uses' classes are already loaded)\r
720 // this may bypass the above if condition\r
721 while (readyListeners.length && !Loader.isLoading) {\r
722 // we may re-enter triggerReady so we cannot necessarily iterate the\r
723 // readyListeners array\r
724 listener = readyListeners.pop();\r
725 Ready.invoke(listener);\r
726 }\r
727\r
728 // If the DOM is also ready, this will fire the normal onReady listeners.\r
729 // An astute observer would note that we may now be back to isLoading and\r
730 // so ask "Why you call unblock?". The reason is that we must match the\r
731 // calls to block and since we transitioned from isLoading to !isLoading\r
732 // here we must call unblock. If we have transitioned back to isLoading in\r
733 // the above loop it will have called block again so the counter will be\r
734 // increased and this call will not reduce the block count to 0. This is\r
735 // done by loadScripts.\r
736 Ready.unblock();\r
737 }\r
738 },\r
739\r
740 /**\r
741 * @private\r
742 * @param {String} className\r
743 */\r
744 historyPush: function(className) {\r
745 if (className && !isInHistory[className] && !Manager.overrideMap[className]) {\r
746 isInHistory[className] = true;\r
747 history.push(className);\r
748 }\r
749 return Loader;\r
750 },\r
751\r
752 /**\r
753 * This is an internal method that delegate content loading to the \r
754 * bootstrap layer.\r
755 * @private\r
756 * @param params\r
757 */\r
758 loadScripts: function(params) {\r
759 var manifest = Ext.manifest,\r
760 loadOrder = manifest && manifest.loadOrder,\r
761 loadOrderMap = manifest && manifest.loadOrderMap,\r
762 options;\r
763 \r
764 ++Loader.scriptsLoading;\r
765 \r
766 // if the load order map hasn't been created, create it now \r
767 // and cache on the manifest\r
768 if (loadOrder && !loadOrderMap) {\r
769 manifest.loadOrderMap = loadOrderMap = Boot.createLoadOrderMap(loadOrder);\r
770 }\r
771\r
772 // verify the loading state, as this may have transitioned us from\r
773 // not loading to loading\r
774 Loader.checkReady();\r
775\r
776 options = Ext.apply({\r
777 loadOrder: loadOrder,\r
778 loadOrderMap: loadOrderMap,\r
779 charset: _config.scriptCharset,\r
780 success: Loader.onLoadSuccess,\r
781 failure: Loader.onLoadFailure,\r
782 sync: Loader.syncModeEnabled,\r
783 _classNames: []\r
784 }, params);\r
785\r
786 options.userScope = options.scope;\r
787 options.scope = options;\r
788\r
789 Boot.load(options);\r
790 },\r
791\r
792 /**\r
793 * This method is provide for use by the bootstrap layer.\r
794 * @private\r
795 * @param {String[]} urls\r
796 */\r
797 loadScriptsSync: function(urls) {\r
798 var syncwas = Loader.syncModeEnabled;\r
799 Loader.syncModeEnabled = true;\r
800 Loader.loadScripts({url: urls});\r
801 Loader.syncModeEnabled = syncwas;\r
802 },\r
803\r
804 /**\r
805 * This method is provide for use by the bootstrap layer.\r
806 * @private\r
807 * @param {String[]} urls\r
808 */\r
809 loadScriptsSyncBasePrefix: function(urls) {\r
810 var syncwas = Loader.syncModeEnabled;\r
811 Loader.syncModeEnabled = true;\r
812 Loader.loadScripts({url: urls, prependBaseUrl: true});\r
813 Loader.syncModeEnabled = syncwas;\r
814 },\r
815 \r
816 /**\r
817 * Loads the specified script URL and calls the supplied callbacks. If this method\r
818 * is called before {@link Ext#isReady}, the script's load will delay the transition\r
819 * to ready. This can be used to load arbitrary scripts that may contain further\r
820 * {@link Ext#require Ext.require} calls.\r
821 *\r
822 * @param {Object/String/String[]} options The options object or simply the URL(s) to load.\r
823 * @param {String} options.url The URL from which to load the script.\r
824 * @param {Function} [options.onLoad] The callback to call on successful load.\r
825 * @param {Function} [options.onError] The callback to call on failure to load.\r
826 * @param {Object} [options.scope] The scope (`this`) for the supplied callbacks.\r
827 */\r
828 loadScript: function(options) {\r
829 var isString = typeof options === 'string',\r
830 isArray = options instanceof Array,\r
831 isObject = !isArray && !isString,\r
832 url = isObject ? options.url : options,\r
833 onError = isObject && options.onError,\r
834 onLoad = isObject && options.onLoad,\r
835 scope = isObject && options.scope,\r
836 request = {\r
837 url: url,\r
838 scope: scope,\r
839 onLoad: onLoad,\r
840 onError: onError,\r
841 _classNames: []\r
842 };\r
843\r
844 Loader.loadScripts(request);\r
845 },\r
846\r
847 /**\r
848 * @private\r
849 */\r
850 flushMissingQueue: function() {\r
851 var name, val, missingwas = 0, missing = 0;\r
852 \r
853 for(name in _missingQueue) {\r
854 missingwas++;\r
855 val = _missingQueue[name];\r
856 if(Manager.isCreated(name)) {\r
857 delete _missingQueue[name];\r
858 } else if (Manager.existCache[name] === 2) {\r
859 delete _missingQueue[name];\r
860 } else {\r
861 ++missing;\r
862 }\r
863 }\r
864 this.missingCount = missing;\r
865 },\r
866\r
867 /**\r
868 * @private\r
869 */\r
870 checkReady: function() {\r
871 var wasLoading = Loader.isLoading,\r
872 isLoading;\r
873\r
874 Loader.flushMissingQueue();\r
875 isLoading = Loader.missingCount + Loader.scriptsLoading;\r
876 \r
877 if (isLoading && !wasLoading) {\r
878 Ready.block();\r
879 Loader.isLoading = !!isLoading;\r
880 } else if (!isLoading && wasLoading) {\r
881 Loader.triggerReady();\r
882 }\r
883\r
884 //<debug>\r
885 if (!Loader.scriptsLoading && Loader.missingCount) {\r
886 // Things look bad, but since load requests may come later, defer this\r
887 // for a bit then check if things are still stuck.\r
888 Ext.defer(function () {\r
889 if (!Loader.scriptsLoading && Loader.missingCount) {\r
890 Ext.log.error('[Loader] The following classes failed to load:');\r
891 for (var name in Loader.missingQueue) {\r
892 Ext.log.error('[Loader] ' + name + ' from ' +\r
893 Loader.missingQueue[name]);\r
894 }\r
895 }\r
896 }, 1000);\r
897 }\r
898 //</debug>\r
899 }\r
900 });\r
901\r
902 /**\r
903 * Loads all classes by the given names and all their direct dependencies; optionally\r
904 * executes the given callback function when finishes, within the optional scope.\r
905 *\r
906 * @param {String/String[]} expressions The class, classes or wildcards to load.\r
907 * @param {Function} [fn] The callback function.\r
908 * @param {Object} [scope] The execution scope (`this`) of the callback function.\r
909 * @member Ext\r
910 * @method require\r
911 */\r
912 Ext.require = alias(Loader, 'require');\r
913\r
914 /**\r
915 * Synchronously loads all classes by the given names and all their direct dependencies; optionally\r
916 * executes the given callback function when finishes, within the optional scope.\r
917 *\r
918 * @param {String/String[]} expressions The class, classes or wildcards to load.\r
919 * @param {Function} [fn] The callback function.\r
920 * @param {Object} [scope] The execution scope (`this`) of the callback function.\r
921 * @member Ext\r
922 * @method syncRequire\r
923 */\r
924 Ext.syncRequire = alias(Loader, 'syncRequire');\r
925\r
926 /**\r
927 * Explicitly exclude files from being loaded. Useful when used in conjunction with a\r
928 * broad include expression. Can be chained with more `require` and `exclude` methods,\r
929 * for example:\r
930 *\r
931 * Ext.exclude('Ext.data.*').require('*');\r
932 *\r
933 * Ext.exclude('widget.button*').require('widget.*');\r
934 *\r
935 * @param {String/String[]} excludes\r
936 * @return {Object} Contains `exclude`, `require` and `syncRequire` methods for chaining.\r
937 * @member Ext\r
938 * @method exclude\r
939 */\r
940 Ext.exclude = alias(Loader, 'exclude');\r
941\r
942//<feature classSystem.loader>\r
943 /**\r
944 * @cfg {String[]} requires\r
945 * @member Ext.Class\r
946 * List of classes that have to be loaded before instantiating this class.\r
947 * For example:\r
948 *\r
949 * Ext.define('Mother', {\r
950 * requires: ['Child'],\r
951 * giveBirth: function() {\r
952 * // we can be sure that child class is available.\r
953 * return new Child();\r
954 * }\r
955 * });\r
956 */\r
957 Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {\r
958 //<debug>\r
959 Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#loaderPreprocessor', arguments); // jshint ignore:line\r
960 //</debug>\r
961 \r
962 var me = this,\r
963 dependencies = [],\r
964 dependency,\r
965 className = Manager.getName(cls),\r
966 i, j, ln, subLn, value, propertyName, propertyValue,\r
967 requiredMap;\r
968\r
969 /*\r
970 Loop through the dependencyProperties, look for string class names and push\r
971 them into a stack, regardless of whether the property's value is a string, array or object. For example:\r
972 {\r
973 extend: 'Ext.MyClass',\r
974 requires: ['Ext.some.OtherClass'],\r
975 mixins: {\r
976 thing: 'Foo.bar.Thing';\r
977 }\r
978 }\r
979 which will later be transformed into:\r
980 {\r
981 extend: Ext.MyClass,\r
982 requires: [Ext.some.OtherClass],\r
983 mixins: {\r
984 thing: Foo.bar.Thing;\r
985 }\r
986 }\r
987 */\r
988\r
989 for (i = 0,ln = dependencyProperties.length; i < ln; i++) {\r
990 propertyName = dependencyProperties[i];\r
991\r
992 if (data.hasOwnProperty(propertyName)) {\r
993 propertyValue = data[propertyName];\r
994\r
995 if (typeof propertyValue === 'string') {\r
996 dependencies.push(propertyValue);\r
997 }\r
998 else if (propertyValue instanceof Array) {\r
999 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {\r
1000 value = propertyValue[j];\r
1001\r
1002 if (typeof value === 'string') {\r
1003 dependencies.push(value);\r
1004 }\r
1005 }\r
1006 }\r
1007 else if (typeof propertyValue !== 'function') {\r
1008 for (j in propertyValue) {\r
1009 if (propertyValue.hasOwnProperty(j)) {\r
1010 value = propertyValue[j];\r
1011\r
1012 if (typeof value === 'string') {\r
1013 dependencies.push(value);\r
1014 }\r
1015 }\r
1016 }\r
1017 }\r
1018 }\r
1019 }\r
1020\r
1021 if (dependencies.length === 0) {\r
1022 return;\r
1023 }\r
1024 if (className) {\r
1025 _requiresMap[className] = dependencies;\r
1026 }\r
1027\r
1028 //<debug>\r
1029 var deadlockPath = [],\r
1030 detectDeadlock;\r
1031\r
1032 /*\r
1033 Automatically detect deadlocks before-hand,\r
1034 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:\r
1035\r
1036 - A extends B, then B extends A\r
1037 - A requires B, B requires C, then C requires A\r
1038\r
1039 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks\r
1040 no matter how deep the path is.\r
1041 */\r
1042\r
1043 if (className) {\r
1044 requiredMap = Loader.requiredByMap || (Loader.requiredByMap = {});\r
1045\r
1046 for (i = 0,ln = dependencies.length; i < ln; i++) {\r
1047 dependency = dependencies[i];\r
1048 (requiredMap[dependency] || (requiredMap[dependency] = [])).push(className);\r
1049 }\r
1050\r
1051 detectDeadlock = function(cls) {\r
1052 deadlockPath.push(cls);\r
1053\r
1054 if (_requiresMap[cls]) {\r
1055 if (Ext.Array.contains(_requiresMap[cls], className)) {\r
1056 Ext.raise("Circular requirement detected! '" + className +\r
1057 "' and '" + deadlockPath[1] + "' mutually require each other. Path: " +\r
1058 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);\r
1059 }\r
1060\r
1061 for (i = 0,ln = _requiresMap[cls].length; i < ln; i++) {\r
1062 detectDeadlock(_requiresMap[cls][i]);\r
1063 }\r
1064 }\r
1065 };\r
1066\r
1067 detectDeadlock(className);\r
1068 }\r
1069\r
1070 //</debug>\r
1071\r
1072 (className ? Loader.exclude(className) : Loader).require(dependencies, function() {\r
1073 for (i = 0,ln = dependencyProperties.length; i < ln; i++) {\r
1074 propertyName = dependencyProperties[i];\r
1075\r
1076 if (data.hasOwnProperty(propertyName)) {\r
1077 propertyValue = data[propertyName];\r
1078\r
1079 if (typeof propertyValue === 'string') {\r
1080 data[propertyName] = Manager.get(propertyValue);\r
1081 }\r
1082 else if (propertyValue instanceof Array) {\r
1083 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {\r
1084 value = propertyValue[j];\r
1085\r
1086 if (typeof value === 'string') {\r
1087 data[propertyName][j] = Manager.get(value);\r
1088 }\r
1089 }\r
1090 }\r
1091 else if (typeof propertyValue !== 'function') {\r
1092 for (var k in propertyValue) {\r
1093 if (propertyValue.hasOwnProperty(k)) {\r
1094 value = propertyValue[k];\r
1095\r
1096 if (typeof value === 'string') {\r
1097 data[propertyName][k] = Manager.get(value);\r
1098 }\r
1099 }\r
1100 }\r
1101 }\r
1102 }\r
1103 }\r
1104\r
1105 continueFn.call(me, cls, data, hooks);\r
1106 });\r
1107\r
1108 return false;\r
1109 }, true, 'after', 'className');\r
1110\r
1111 /**\r
1112 * @cfg {String[]} uses\r
1113 * @member Ext.Class\r
1114 * List of optional classes to load together with this class. These aren't neccessarily loaded before\r
1115 * this class is created, but are guaranteed to be available before Ext.onReady listeners are\r
1116 * invoked. For example:\r
1117 *\r
1118 * Ext.define('Mother', {\r
1119 * uses: ['Child'],\r
1120 * giveBirth: function() {\r
1121 * // This code might, or might not work:\r
1122 * // return new Child();\r
1123 *\r
1124 * // Instead use Ext.create() to load the class at the spot if not loaded already:\r
1125 * return Ext.create('Child');\r
1126 * }\r
1127 * });\r
1128 */\r
1129 Manager.registerPostprocessor('uses', function(name, cls, data) {\r
1130 //<debug>\r
1131 Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.Loader#usesPostprocessor', arguments); // jshint ignore:line\r
1132 //</debug>\r
1133 \r
1134 var manifest = Ext.manifest,\r
1135 loadOrder = manifest && manifest.loadOrder,\r
1136 classes = manifest && manifest.classes,\r
1137 uses, clazz, item, len, i, indexMap;\r
1138\r
1139 if (loadOrder) {\r
1140 clazz = classes[name];\r
1141 if (clazz && !isNaN(i = clazz.idx)) {\r
1142 item = loadOrder[i];\r
1143 uses = item.uses;\r
1144 indexMap = {};\r
1145 for (len = uses.length, i = 0; i < len; i++) {\r
1146 indexMap[uses[i]] = true;\r
1147 }\r
1148 uses = Ext.Boot.getPathsFromIndexes(indexMap, loadOrder, true);\r
1149 if (uses.length > 0) {\r
1150 Loader.loadScripts({\r
1151 url: uses,\r
1152 sequential: true\r
1153 });\r
1154 }\r
1155 }\r
1156 }\r
1157\r
1158 if (data.uses) {\r
1159 uses = data.uses;\r
1160 Loader.addUsedClasses(uses);\r
1161 }\r
1162 });\r
1163\r
1164 Manager.onCreated(Loader.historyPush);\r
1165//</feature>\r
1166\r
1167 Loader.init();\r
1168 \r
1169}());\r
1170\r
1171//-----------------------------------------------------------------------------\r
1172\r
1173// Use performance.now when available to keep timestamps consistent.\r
1174Ext._endTime = Ext.ticks();\r
1175\r
1176// This hook is to allow tools like DynaTrace to deterministically detect the availability\r
1177// of Ext.onReady. Since Loader takes over Ext.onReady this must be done here and not in\r
1178// Ext.env.Ready.\r
1179if (Ext._beforereadyhandler){\r
1180 Ext._beforereadyhandler();\r
1181}\r