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