]> git.proxmox.com Git - extjs.git/blame - extjs/examples/classic/desktop/bootstrap.js
add extjs 6.0.1 sources
[extjs.git] / extjs / examples / classic / desktop / bootstrap.js
CommitLineData
6527f429
DM
1var Ext = Ext || {};
2Ext.manifest = Ext.manifest || "bootstrap.json";
3// @tag core
4// @define Ext.Boot
5
6var Ext = Ext || {};
7
8//<editor-fold desc="Boot">
9/*
10 * @class Ext.Boot
11 * @singleton
12 */
13Ext.Boot = Ext.Boot || (function (emptyFn) {
14
15 var doc = document,
16 _emptyArray = [],
17 _config = {
18 /*
19 * @cfg {Boolean} [disableCaching=true]
20 * If `true` current timestamp is added to script URL's to prevent caching.
21 * In debug builds, adding a "cache" or "disableCacheBuster" query parameter
22 * to the page's URL will set this to `false`.
23 */
24 disableCaching: (/[?&](?:cache|disableCacheBuster)\b/i.test(location.search) ||
25 !(/http[s]?\:/i.test(location.href)) ||
26 /(^|[ ;])ext-cache=1/.test(doc.cookie)) ? false :
27 true,
28
29 /*
30 * @cfg {String} [disableCachingParam="_dc"]
31 * The query parameter name for the cache buster's timestamp.
32 */
33 disableCachingParam: '_dc',
34
35 /*
36 * @cfg {Boolean} loadDelay
37 * Millisecond delay between asynchronous script injection (prevents stack
38 * overflow on some user agents) 'false' disables delay but potentially
39 * increases stack load.
40 */
41 loadDelay: false,
42
43 /*
44 * @cfg {Boolean} preserveScripts
45 * `false` to remove asynchronously loaded scripts, `true` to retain script
46 * element for browser debugger compatibility and improved load performance.
47 */
48 preserveScripts: true,
49
50 /*
51 * @cfg {String} [charset=UTF-8]
52 * Optional charset to specify encoding of dynamic content.
53 */
54 charset: 'UTF-8'
55 },
56
57 _assetConfig= {},
58
59 cssRe = /\.css(?:\?|$)/i,
60 resolverEl = doc.createElement('a'),
61 isBrowser = typeof window !== 'undefined',
62 _environment = {
63 browser: isBrowser,
64 node: !isBrowser && (typeof require === 'function'),
65 phantom: (window && (window._phantom || window.callPhantom)) || /PhantomJS/.test(window.navigator.userAgent)
66 },
67 _tags = (Ext.platformTags = {}),
68
69 _apply = function (object, config, defaults) {
70 if (defaults) {
71 _apply(object, defaults);
72 }
73 if (object && config && typeof config === 'object') {
74 for (var i in config) {
75 object[i] = config[i];
76 }
77 }
78 return object;
79 },
80 _merge = function() {
81 var lowerCase = false,
82 obj = Array.prototype.shift.call(arguments),
83 index, i, len, value;
84
85 if (typeof arguments[arguments.length - 1] === 'boolean') {
86 lowerCase = Array.prototype.pop.call(arguments);
87 }
88
89 len = arguments.length;
90 for (index = 0; index < len; index++) {
91 value = arguments[index];
92 if (typeof value === 'object') {
93 for (i in value) {
94 obj[lowerCase ? i.toLowerCase() : i] = value[i];
95 }
96 }
97 }
98
99 return obj;
100 },
101 _getKeys = (typeof Object.keys == 'function') ?
102 function(object){
103 if (!object) {
104 return [];
105 }
106 return Object.keys(object);
107 } :
108 function(object) {
109 var keys = [],
110 property;
111
112 for (property in object) {
113 if (object.hasOwnProperty(property)) {
114 keys.push(property);
115 }
116 }
117
118 return keys;
119 },
120 /*
121 * The Boot loader class manages Request objects that contain one or
122 * more individual urls that need to be loaded. Requests can be performed
123 * synchronously or asynchronously, but will always evaluate urls in the
124 * order specified on the request object.
125 */
126 Boot = {
127 loading: 0,
128 loaded: 0,
129 apply: _apply,
130 env: _environment,
131 config: _config,
132
133 /**
134 * @cfg {Object} assetConfig
135 * A map (url->assetConfig) that contains information about assets loaded by the Microlaoder.
136 */
137 assetConfig: _assetConfig,
138
139 // Keyed by absolute URL this object holds "true" if that URL is already loaded
140 // or an array of callbacks to call once it loads.
141 scripts: {
142 /*
143 Entry objects
144
145 'http://foo.com/bar/baz/Thing.js': {
146 done: true,
147 el: scriptEl || linkEl,
148 preserve: true,
149 requests: [ request1, ... ]
150 }
151 */
152 },
153
154 /*
155 * contains the current script name being loaded
156 * (loadSync or sequential load only)
157 */
158 currentFile: null,
159 suspendedQueue: [],
160 currentRequest: null,
161
162 // when loadSync is called, need to cause subsequent load requests to also be loadSync,
163 // eg, when Ext.require(...) is called
164 syncMode: false,
165
166 /*
167 * simple helper method for debugging
168 */
169
170 /*
171 * enables / disables loading scripts via script / link elements rather
172 * than using ajax / eval
173 */
174 useElements: true,
175
176 listeners: [],
177
178 Request: Request,
179
180 Entry: Entry,
181
182 allowMultipleBrowsers: false,
183
184 browserNames: {
185 ie: 'IE',
186 firefox: 'Firefox',
187 safari: 'Safari',
188 chrome: 'Chrome',
189 opera: 'Opera',
190 dolfin: 'Dolfin',
191 edge: 'Edge',
192 webosbrowser: 'webOSBrowser',
193 chromeMobile: 'ChromeMobile',
194 chromeiOS: 'ChromeiOS',
195 silk: 'Silk',
196 other: 'Other'
197 },
198
199 osNames: {
200 ios: 'iOS',
201 android: 'Android',
202 windowsPhone: 'WindowsPhone',
203 webos: 'webOS',
204 blackberry: 'BlackBerry',
205 rimTablet: 'RIMTablet',
206 mac: 'MacOS',
207 win: 'Windows',
208 tizen: 'Tizen',
209 linux: 'Linux',
210 bada: 'Bada',
211 chromeOS: 'ChromeOS',
212 other: 'Other'
213 },
214
215 browserPrefixes: {
216 ie: 'MSIE ',
217 edge: 'Edge/',
218 firefox: 'Firefox/',
219 chrome: 'Chrome/',
220 safari: 'Version/',
221 opera: 'OPR/',
222 dolfin: 'Dolfin/',
223 webosbrowser: 'wOSBrowser/',
224 chromeMobile: 'CrMo/',
225 chromeiOS: 'CriOS/',
226 silk: 'Silk/'
227 },
228
229 // When a UA reports multiple browsers this list is used to prioritize the 'real' browser
230 // lower index number will win
231 browserPriority: [
232 'edge',
233 'opera',
234 'dolfin',
235 'webosbrowser',
236 'silk',
237 'chromeiOS',
238 'chromeMobile',
239 'ie',
240 'firefox',
241 'safari',
242 'chrome'
243 ],
244
245 osPrefixes: {
246 tizen: '(Tizen )',
247 ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ',
248 android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default,
249 // so we need to add a direct check for HTC_
250 windowsPhone: 'Windows Phone ',
251 blackberry: '(?:BlackBerry|BB)(?:.*)Version\/',
252 rimTablet: 'RIM Tablet OS ',
253 webos: '(?:webOS|hpwOS)\/',
254 bada: 'Bada\/',
255 chromeOS: 'CrOS '
256 },
257
258 fallbackOSPrefixes: {
259 windows: 'win',
260 mac: 'mac',
261 linux: 'linux'
262 },
263
264 devicePrefixes: {
265 iPhone: 'iPhone',
266 iPod: 'iPod',
267 iPad: 'iPad'
268 },
269
270 maxIEVersion: 12,
271
272
273 /**
274 * The default function that detects various platforms and sets tags
275 * in the platform map accordingly. Examples are iOS, android, tablet, etc.
276 * @param tags the set of tags to populate
277 */
278 detectPlatformTags: function () {
279 var me = this,
280 ua = navigator.userAgent,
281 isMobile = /Mobile(\/|\s)/.test(ua),
282 element = document.createElement('div'),
283 isEventSupported = function (name, tag) {
284 if (tag === undefined) {
285 tag = window;
286 }
287
288 var eventName = 'on' + name.toLowerCase(),
289 isSupported = (eventName in element);
290
291 if (!isSupported) {
292 if (element.setAttribute && element.removeAttribute) {
293 element.setAttribute(eventName, '');
294 isSupported = typeof element[eventName] === 'function';
295
296 if (typeof element[eventName] !== 'undefined') {
297 element[eventName] = undefined;
298 }
299
300 element.removeAttribute(eventName);
301 }
302 }
303
304 return isSupported;
305 },
306
307 // Browser Detection
308 getBrowsers = function () {
309 var browsers = {},
310 maxIEVersion, prefix,
311 value, key, index, len, match, version, matched;
312
313 // MS Edge browser (and possibly others) can report multiple browsers in the UserAgent
314 // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
315 // we use this to prioritize the actual browser in this situation
316 len = me.browserPriority.length;
317 for (index = 0; index < len; index++) {
318 key = me.browserPriority[index];
319 if (!matched) {
320 value = me.browserPrefixes[key];
321 match = ua.match(new RegExp('(' + value + ')([\\w\\._]+)'));
322 version = match && match.length > 1 ? parseInt(match[2]) : 0;
323 if (version) {
324 matched = true;
325 }
326 } else {
327 version = 0;
328 }
329 browsers[key] = version;
330 }
331
332 //Deal with IE document mode
333 if (browsers.ie) {
334 var mode = document.documentMode;
335
336 if (mode >= 8) {
337 browsers.ie = mode;
338 }
339 }
340
341 // Fancy IE greater than and less then quick tags
342 version = browsers.ie || false;
343 maxIEVersion = Math.max(version, me.maxIEVersion);
344
345 for (index = 8; index <= maxIEVersion; ++index) {
346 prefix = 'ie' + index;
347 browsers[prefix + 'm'] = version ? version <= index : 0;
348 browsers[prefix] = version ? version === index : 0;
349 browsers[prefix + 'p'] = version ? version >= index : 0;
350 }
351
352 return browsers;
353 },
354
355 //OS Detection
356 getOperatingSystems = function () {
357 var systems = {},
358 value, key, keys, index, len, match, matched, version, activeCount;
359
360 keys = _getKeys(me.osPrefixes);
361 len = keys.length;
362 for (index = 0, activeCount = 0; index < len; index++) {
363 key = keys[index];
364 value = me.osPrefixes[key];
365 match = ua.match(new RegExp('(' + value + ')([^\\s;]+)'));
366 matched = match ? match[1] : null;
367
368 // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default.
369 // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent
370 if (matched && (matched === 'HTC_' || matched === 'Silk/')) {
371 version = 2.3;
372 } else {
373 version = match && match.length > 1 ? parseFloat(match[match.length - 1]) : 0;
374 }
375
376 if (version) {
377 activeCount++;
378 }
379 systems[key] = version;
380 }
381
382 keys = _getKeys(me.fallbackOSPrefixes);
383
384 // If no OS could be found we resort to the fallbacks, otherwise we just
385 // falsify the fallbacks
386 len = keys.length;
387 for (index = 0; index < len; index++) {
388 key = keys[index];
389
390 // No OS was detected from osPrefixes
391 if (activeCount === 0) {
392 value = me.fallbackOSPrefixes[key];
393 match = ua.toLowerCase().match(new RegExp(value));
394 systems[key] = match ? true : 0;
395 } else {
396 systems[key] = 0;
397 }
398 }
399
400 return systems;
401 },
402
403 // Device Detection
404 getDevices = function () {
405 var devices = {},
406 value, key, keys, index, len, match;
407
408 keys = _getKeys(me.devicePrefixes);
409 len = keys.length;
410 for (index = 0; index < len; index++) {
411 key = keys[index];
412 value = me.devicePrefixes[key];
413 match = ua.match(new RegExp(value));
414 devices[key] = match ? true : 0;
415 }
416
417 return devices;
418 },
419 browsers = getBrowsers(),
420 systems = getOperatingSystems(),
421 devices = getDevices(),
422 platformParams = Boot.loadPlatformsParam();
423
424 // We apply platformParams from the query here first to allow for forced user valued
425 // to be used in calculation of generated tags
426 _merge(_tags, browsers, systems, devices, platformParams, true);
427
428 _tags.phone = (_tags.iphone || _tags.ipod) ||
429 (!_tags.silk && (_tags.android && (_tags.android < 3 || isMobile))) ||
430 (_tags.blackberry && isMobile) ||
431 (_tags.windowsphone);
432
433 _tags.tablet = !_tags.phone && (
434 _tags.ipad ||
435 _tags.android ||
436 _tags.silk ||
437 _tags.rimtablet ||
438 (_tags.ie10 && /; Touch/.test(ua))
439 );
440
441 _tags.touch =
442 // if the browser has touch events we can be reasonably sure the device has
443 // a touch screen
444 isEventSupported('touchend') ||
445 // browsers that use pointer event have maxTouchPoints > 0 if the
446 // device supports touch input
447 // http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints
448 navigator.maxTouchPoints ||
449 // IE10 uses a vendor-prefixed maxTouchPoints property
450 navigator.msMaxTouchPoints;
451
452 _tags.desktop = !_tags.phone && !_tags.tablet;
453 _tags.cordova = _tags.phonegap = !!(window.PhoneGap || window.Cordova || window.cordova);
454 _tags.webview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(ua);
455 _tags.androidstock = (_tags.android <= 4.3) && (_tags.safari || _tags.silk);
456
457 // Re-apply any query params here to allow for user override of generated tags (desktop, touch, tablet, etc)
458 _merge(_tags, platformParams, true);
459 },
460
461 /**
462 * Extracts user supplied platform tags from the "platformTags" query parameter
463 * of the form:
464 *
465 * ?platformTags=name:state,name:state,...
466 *
467 * (each tag defaults to true when state is unspecified)
468 *
469 * Example:
470 * ?platformTags=isTablet,isPhone:false,isDesktop:0,iOS:1,Safari:true, ...
471 *
472 * @returns {Object} the platform tags supplied by the query string
473 */
474 loadPlatformsParam: function () {
475 // Check if the ?platform parameter is set in the URL
476 var paramsString = window.location.search.substr(1),
477 paramsArray = paramsString.split("&"),
478 params = {}, i,
479 platforms = {},
480 tmpArray, tmplen, platform, name, enabled;
481
482 for (i = 0; i < paramsArray.length; i++) {
483 tmpArray = paramsArray[i].split("=");
484 params[tmpArray[0]] = tmpArray[1];
485 }
486
487 if (params.platformTags) {
488 tmpArray = params.platformTags.split(",");
489 for (tmplen = tmpArray.length, i = 0; i < tmplen; i++) {
490 platform = tmpArray[i].split(":");
491 name = platform[0];
492 enabled=true;
493 if (platform.length > 1) {
494 enabled = platform[1];
495 if (enabled === 'false' || enabled === '0') {
496 enabled = false;
497 }
498 }
499 platforms[name] = enabled;
500 }
501 }
502 return platforms;
503 },
504
505 filterPlatform: function (platform, excludes) {
506 platform = _emptyArray.concat(platform || _emptyArray);
507 excludes = _emptyArray.concat(excludes || _emptyArray);
508
509 var plen = platform.length,
510 elen = excludes.length,
511 include = (!plen && elen), // default true if only excludes specified
512 i, tag;
513
514 for (i = 0; i < plen && !include; i++) {
515 tag = platform[i];
516 include = !!_tags[tag];
517 }
518
519 for (i = 0; i < elen && include; i++) {
520 tag = excludes[i];
521 include = !_tags[tag];
522 }
523
524 return include;
525 },
526
527 init: function () {
528 var scriptEls = doc.getElementsByTagName('script'),
529 len = scriptEls.length,
530 re = /\/ext(\-[a-z\-]+)?\.js$/,
531 entry, script, src, state, baseUrl, key, n, origin;
532
533 // Since we are loading after other scripts, and we needed to gather them
534 // anyway, we track them in _scripts so we don't have to ask for them all
535 // repeatedly.
536 for (n = 0; n < len; n++) {
537 src = (script = scriptEls[n]).src;
538 if (!src) {
539 continue;
540 }
541 state = script.readyState || null;
542
543 // If we find a script file called "ext-*.js", then the base path is that file's base path.
544 if (!baseUrl) {
545 if (re.test(src)) {
546 Boot.hasReadyState = ("readyState" in script);
547 Boot.hasAsync = ("async" in script) || !Boot.hasReadyState;
548 baseUrl = src;
549 }
550 }
551
552 if (!Boot.scripts[key = Boot.canonicalUrl(src)]) {
553 entry = new Entry({
554 key: key,
555 url: src,
556 done: state === null || // non-IE
557 state === 'loaded' || state === 'complete', // IE only
558 el: script,
559 prop: 'src'
560 });
561 }
562 }
563
564 if (!baseUrl) {
565 script = scriptEls[scriptEls.length - 1];
566 baseUrl = script.src;
567 Boot.hasReadyState = ('readyState' in script);
568 Boot.hasAsync = ("async" in script) || !Boot.hasReadyState;
569 }
570
571 Boot.baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1);
572 origin = window.location.origin ||
573 window.location.protocol +
574 "//" +
575 window.location.hostname +
576 (window.location.port ? ':' + window.location.port: '');
577 Boot.origin = origin;
578
579 Boot.detectPlatformTags();
580 Ext.filterPlatform = Boot.filterPlatform;
581 },
582
583 /*
584 * This method returns a canonical URL for the given URL.
585 *
586 * For example, the following all produce the same canonical URL (which is the
587 * last one):
588 *
589 * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js?_dc=12345
590 * http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js
591 * http://foo.com/bar/baz/zoo/derp/../jazz/../../goo/Thing.js
592 * http://foo.com/bar/baz/zoo/../goo/Thing.js
593 * http://foo.com/bar/baz/goo/Thing.js
594 *
595 * @private
596 */
597 canonicalUrl: function (url) {
598 // @TODO - see if we need this fallback logic
599 // http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
600 resolverEl.href = url;
601
602 var ret = resolverEl.href,
603 dc = _config.disableCachingParam,
604 pos = dc ? ret.indexOf(dc + '=') : -1,
605 c, end;
606
607 // If we have a _dc query parameter we need to remove it from the canonical
608 // URL.
609 if (pos > 0 && ((c = ret.charAt(pos - 1)) === '?' || c === '&')) {
610 end = ret.indexOf('&', pos);
611 end = (end < 0) ? '' : ret.substring(end);
612 if (end && c === '?') {
613 ++pos; // keep the '?'
614 end = end.substring(1); // remove the '&'
615 }
616 ret = ret.substring(0, pos - 1) + end;
617 }
618
619 return ret;
620 },
621
622 /*
623 * Get the config value corresponding to the specified name. If no name is given, will return the config object
624 * @param {String} name The config property name
625 * @return {Object}
626 */
627 getConfig: function (name) {
628 return name ? Boot.config[name] : Boot.config;
629 },
630
631 /*
632 * Set the configuration.
633 * @param {Object} config The config object to override the default values.
634 * @return {Ext.Boot} this
635 */
636 setConfig: function (name, value) {
637 if (typeof name === 'string') {
638 Boot.config[name] = value;
639 } else {
640 for (var s in name) {
641 Boot.setConfig(s, name[s]);
642 }
643 }
644 return Boot;
645 },
646
647 getHead: function () {
648 return Boot.docHead ||
649 (Boot.docHead = doc.head ||
650 doc.getElementsByTagName('head')[0]);
651 },
652
653 create: function (url, key, cfg) {
654 var config = cfg || {};
655 config.url = url;
656 config.key = key;
657 return Boot.scripts[key] = new Entry(config);
658 },
659
660 getEntry: function (url, cfg) {
661 var key = Boot.canonicalUrl(url),
662 entry = Boot.scripts[key];
663 if (!entry) {
664 entry = Boot.create(url, key, cfg);
665 }
666 return entry;
667 },
668
669 registerContent: function (url, type, content) {
670 var cfg = {
671 content: content,
672 loaded: true,
673 css: type === 'css'
674 };
675
676 return Boot.getEntry(url, cfg);
677 },
678
679 processRequest: function(request, sync) {
680 request.loadEntries(sync);
681 },
682
683 load: function (request) {
684 var request = new Request(request);
685
686 if (request.sync || Boot.syncMode) {
687 return Boot.loadSync(request);
688 }
689
690 // If there is a request in progress, we must
691 // queue this new request to be fired when the current request completes.
692 if (Boot.currentRequest) {
693 // trigger assignment of entries now to ensure that overlapping
694 // entries with currently running requests will synchronize state
695 // with this pending one as they complete
696 request.getEntries();
697 Boot.suspendedQueue.push(request);
698 } else {
699 Boot.currentRequest = request;
700 Boot.processRequest(request, false);
701 }
702 return Boot;
703 },
704
705 loadSync: function (request) {
706 var request = new Request(request);
707
708 Boot.syncMode++;
709 Boot.processRequest(request, true);
710 Boot.syncMode--;
711 return Boot;
712 },
713
714 loadBasePrefix: function(request) {
715 request = new Request(request);
716 request.prependBaseUrl = true;
717 return Boot.load(request);
718 },
719
720 loadSyncBasePrefix: function(request) {
721 request = new Request(request);
722 request.prependBaseUrl = true;
723 return Boot.loadSync(request);
724 },
725
726 requestComplete: function(request) {
727 var next;
728
729 if (Boot.currentRequest === request) {
730 Boot.currentRequest = null;
731 while(Boot.suspendedQueue.length > 0) {
732 next = Boot.suspendedQueue.shift();
733 if(!next.done) {
734 Boot.load(next);
735 break;
736 }
737 }
738 }
739 if (!Boot.currentRequest && Boot.suspendedQueue.length == 0) {
740 Boot.fireListeners();
741 }
742 },
743
744 isLoading: function () {
745 return !Boot.currentRequest && Boot.suspendedQueue.length == 0;
746 },
747
748 fireListeners: function () {
749 var listener;
750 while (Boot.isLoading() && (listener = Boot.listeners.shift())) {
751 listener();
752 }
753 },
754
755 onBootReady: function (listener) {
756 if (!Boot.isLoading()) {
757 listener();
758 } else {
759 Boot.listeners.push(listener);
760 }
761 },
762
763 /*
764 * this is a helper function used by Ext.Loader to flush out
765 * 'uses' arrays for classes
766 */
767 getPathsFromIndexes: function (indexMap, loadOrder) {
768 return Request.prototype.getPathsFromIndexes(indexMap, loadOrder);
769 },
770
771 createLoadOrderMap: function(loadOrder) {
772 return Request.prototype.createLoadOrderMap(loadOrder);
773 },
774
775 fetch: function(url, complete, scope, async) {
776 async = (async === undefined) ? !!complete : async;
777
778 var xhr = new XMLHttpRequest(),
779 result, status, content, exception = false,
780 readyStateChange = function () {
781 if (xhr && xhr.readyState == 4) {
782 status = (xhr.status === 1223) ? 204 :
783 (xhr.status === 0 && ((self.location || {}).protocol === 'file:' ||
784 (self.location || {}).protocol === 'ionp:')) ? 200 : xhr.status;
785 content = xhr.responseText;
786 result = {
787 content: content,
788 status: status,
789 exception: exception
790 };
791 if (complete) {
792 complete.call(scope, result);
793 }
794 xhr = null;
795 }
796 };
797
798 if (async) {
799 xhr.onreadystatechange = readyStateChange;
800 }
801
802 try {
803 xhr.open('GET', url, async);
804 xhr.send(null);
805 } catch (err) {
806 exception = err;
807 readyStateChange();
808 return result;
809 }
810
811 if (!async) {
812 readyStateChange();
813 }
814
815 return result;
816 },
817
818 notifyAll: function(entry) {
819 entry.notifyRequests();
820 }
821 };
822
823 /*
824 * The request class encapsulates a series of Entry objects
825 * and provides notification around the completion of all Entries
826 * in this request.
827 */
828 function Request(cfg) {
829 if(cfg.$isRequest) {
830 return cfg;
831 }
832
833 var cfg = cfg.url ? cfg : {url: cfg},
834 url = cfg.url,
835 urls = url.charAt ? [ url ] : url,
836 charset = cfg.charset || Boot.config.charset;
837
838 _apply(cfg, {
839 urls: urls,
840 charset: charset
841 });
842 _apply(this, cfg);
843 };
844 Request.prototype = {
845 $isRequest: true,
846
847 /*
848 * @private
849 * @param manifest
850 * @returns {*}
851 */
852 createLoadOrderMap: function (loadOrder) {
853 var len = loadOrder.length,
854 loadOrderMap = {},
855 i, element;
856
857 for (i = 0; i < len; i++) {
858 element = loadOrder[i];
859 loadOrderMap[element.path] = element;
860 }
861
862 return loadOrderMap;
863 },
864
865 /*
866 * @private
867 * @param index
868 * @param indexMap
869 * @returns {{}}
870 */
871 getLoadIndexes: function (index, indexMap, loadOrder, includeUses, skipLoaded) {
872 var item = loadOrder[index],
873 len, i, reqs, entry, stop, added, idx, ridx, url;
874
875 if (indexMap[index]) {
876 // prevent cycles
877 return indexMap;
878 }
879
880 indexMap[index] = true;
881
882 stop = false;
883 while (!stop) {
884 added = false;
885
886 // iterate the requirements for each index and
887 // accumulate in the index map
888 for (idx in indexMap) {
889 if (indexMap.hasOwnProperty(idx)) {
890 item = loadOrder[idx];
891 if (!item) {
892 continue;
893 }
894 url = this.prepareUrl(item.path);
895 entry = Boot.getEntry(url);
896 if (!skipLoaded || !entry || !entry.done) {
897 reqs = item.requires;
898 if (includeUses && item.uses) {
899 reqs = reqs.concat(item.uses);
900 }
901 for (len = reqs.length, i = 0; i < len; i++) {
902 ridx = reqs[i];
903 // if we find a requirement that wasn't
904 // already in the index map,
905 // set the added flag to indicate we need to
906 // reprocess
907 if (!indexMap[ridx]) {
908 indexMap[ridx] = true;
909 added = true;
910 }
911 }
912 }
913 }
914 }
915
916 // if we made a pass through the index map and didn't add anything
917 // then we can stop
918 if (!added) {
919 stop = true;
920 }
921 }
922
923 return indexMap;
924 },
925
926 getPathsFromIndexes: function (indexMap, loadOrder) {
927 var indexes = [],
928 paths = [],
929 index, len, i;
930
931 for (index in indexMap) {
932 if (indexMap.hasOwnProperty(index) && indexMap[index]) {
933 indexes.push(index);
934 }
935 }
936
937 indexes.sort(function (a, b) {
938 return a - b;
939 });
940
941 // convert indexes back into load paths
942 for (len = indexes.length, i = 0; i < len; i++) {
943 paths.push(loadOrder[indexes[i]].path);
944 }
945
946 return paths;
947 },
948
949 expandUrl: function (url, indexMap, includeUses, skipLoaded) {
950 if (typeof url == 'string') {
951 url = [url];
952 }
953
954 var me = this,
955 loadOrder = me.loadOrder,
956 loadOrderMap = me.loadOrderMap;
957
958 if (loadOrder) {
959 loadOrderMap = loadOrderMap || me.createLoadOrderMap(loadOrder);
960 me.loadOrderMap = loadOrderMap;
961 indexMap = indexMap || {};
962 var len = url.length,
963 unmapped = [],
964 i, item;
965
966 for (i = 0; i < len; i++) {
967 item = loadOrderMap[url[i]];
968 if (item) {
969 me.getLoadIndexes(item.idx, indexMap, loadOrder, includeUses, skipLoaded);
970 } else {
971 unmapped.push(url[i]);
972 }
973 }
974
975
976 return me.getPathsFromIndexes(indexMap, loadOrder).concat(unmapped);
977 }
978 return url;
979 },
980
981 expandUrls: function (urls, includeUses) {
982 if (typeof urls == "string") {
983 urls = [urls];
984 }
985
986 var expanded = [],
987 expandMap = {},
988 tmpExpanded,
989 len = urls.length,
990 i, t, tlen, tUrl;
991
992 for (i = 0; i < len; i++) {
993 tmpExpanded = this.expandUrl(urls[i], {}, includeUses, true);
994 for (t = 0, tlen = tmpExpanded.length; t < tlen; t++) {
995 tUrl = tmpExpanded[t];
996 if (!expandMap[tUrl]) {
997 expandMap[tUrl] = true;
998 expanded.push(tUrl);
999 }
1000 }
1001 }
1002
1003 if (expanded.length == 0) {
1004 expanded = urls;
1005 }
1006
1007 return expanded;
1008 },
1009
1010 expandLoadOrder: function () {
1011 var me = this,
1012 urls = me.urls,
1013 expanded;
1014
1015 if (!me.expanded) {
1016 expanded = this.expandUrls(urls, true);
1017 me.expanded = true;
1018 } else {
1019 expanded = urls;
1020 }
1021
1022 me.urls = expanded;
1023
1024 // if we added some urls to the request to honor the indicated
1025 // load order, the request needs to be sequential
1026 if (urls.length != expanded.length) {
1027 me.sequential = true;
1028 }
1029
1030 return me;
1031 },
1032
1033 getUrls: function () {
1034 this.expandLoadOrder();
1035 return this.urls;
1036 },
1037
1038 prepareUrl: function(url) {
1039 if(this.prependBaseUrl) {
1040 return Boot.baseUrl + url;
1041 }
1042 return url;
1043 },
1044
1045 getEntries: function () {
1046 var me = this,
1047 entries = me.entries,
1048 i, entry, urls, url;
1049 if (!entries) {
1050 entries = [];
1051 urls = me.getUrls();
1052 for (i = 0; i < urls.length; i++) {
1053 url = me.prepareUrl(urls[i]);
1054 entry = Boot.getEntry(url, {
1055 buster: me.buster,
1056 charset: me.charset
1057 });
1058 entry.requests.push(me);
1059 entries.push(entry);
1060 }
1061 me.entries = entries;
1062 }
1063 return entries;
1064 },
1065
1066 loadEntries: function(sync) {
1067 var me = this,
1068 entries = me.getEntries(),
1069 len = entries.length,
1070 start = me.loadStart || 0,
1071 continueLoad, entry, i;
1072
1073 if(sync !== undefined) {
1074 me.sync = sync;
1075 }
1076
1077 me.loaded = me.loaded || 0;
1078 me.loading = me.loading || len;
1079
1080 for(i = start; i < len; i++) {
1081 entry = entries[i];
1082 if(!entry.loaded) {
1083 continueLoad = entries[i].load(me.sync);
1084 } else {
1085 continueLoad = true;
1086 }
1087 if(!continueLoad) {
1088 me.loadStart = i;
1089 entry.onDone(function(){
1090 me.loadEntries(sync);
1091 });
1092 break;
1093 }
1094 }
1095 me.processLoadedEntries();
1096 },
1097
1098 processLoadedEntries: function () {
1099 var me = this,
1100 entries = me.getEntries(),
1101 len = entries.length,
1102 start = me.startIndex || 0,
1103 i, entry;
1104
1105 if (!me.done) {
1106 for (i = start; i < len; i++) {
1107 entry = entries[i];
1108
1109 if (!entry.loaded) {
1110 me.startIndex = i;
1111 return;
1112 }
1113
1114 if (!entry.evaluated) {
1115 entry.evaluate();
1116 }
1117
1118 if (entry.error) {
1119 me.error = true;
1120 }
1121 }
1122 me.notify();
1123 }
1124 },
1125
1126 notify: function () {
1127 var me = this;
1128 if (!me.done) {
1129 var error = me.error,
1130 fn = me[error ? 'failure' : 'success'],
1131 delay = ('delay' in me)
1132 ? me.delay
1133 : (error ? 1 : Boot.config.chainDelay),
1134 scope = me.scope || me;
1135 me.done = true;
1136 if (fn) {
1137 if (delay === 0 || delay > 0) {
1138 // Free the stack (and defer the next script)
1139 setTimeout(function () {
1140 fn.call(scope, me);
1141 }, delay);
1142 } else {
1143 fn.call(scope, me);
1144 }
1145 }
1146 me.fireListeners();
1147 Boot.requestComplete(me);
1148 }
1149 },
1150
1151 onDone: function(listener) {
1152 var me = this,
1153 listeners = me.listeners || (me.listeners = []);
1154 if(me.done) {
1155 listener(me);
1156 } else {
1157 listeners.push(listener);
1158 }
1159 },
1160
1161 fireListeners: function() {
1162 var listeners = this.listeners,
1163 listener;
1164 if(listeners) {
1165 while((listener = listeners.shift())) {
1166 listener(this);
1167 }
1168 }
1169 }
1170 };
1171
1172 /*
1173 * The Entry class is a token to manage the load and evaluation
1174 * state of a particular url. It is used to notify all Requests
1175 * interested in this url that the content is available.
1176 */
1177 function Entry(cfg) {
1178 if(cfg.$isEntry) {
1179 return cfg;
1180 }
1181
1182
1183 var charset = cfg.charset || Boot.config.charset,
1184 manifest = Ext.manifest,
1185 loader = manifest && manifest.loader,
1186 cache = (cfg.cache !== undefined) ? cfg.cache : (loader && loader.cache),
1187 buster, busterParam;
1188
1189 if (Boot.config.disableCaching) {
1190 if (cache === undefined) {
1191 cache = !Boot.config.disableCaching;
1192 }
1193
1194 if (cache === false) {
1195 buster = +new Date();
1196 } else if (cache !== true) {
1197 buster = cache;
1198 }
1199
1200 if (buster) {
1201 busterParam = (loader && loader.cacheParam) || Boot.config.disableCachingParam;
1202 buster = busterParam + "=" + buster;
1203 }
1204 }
1205
1206 _apply(cfg, {
1207 charset: charset,
1208 buster: buster,
1209 requests: []
1210 });
1211 _apply(this, cfg);
1212 };
1213 Entry.prototype = {
1214 $isEntry: true,
1215 done: false,
1216 evaluated: false,
1217 loaded: false,
1218
1219 isCrossDomain: function() {
1220 var me = this;
1221 if(me.crossDomain === undefined) {
1222 me.crossDomain = (me.getLoadUrl().indexOf(Boot.origin) !== 0);
1223 }
1224 return me.crossDomain;
1225 },
1226
1227 isCss: function () {
1228 var me = this;
1229 if (me.css === undefined) {
1230 if (me.url) {
1231 var assetConfig = Boot.assetConfig[me.url];
1232 me.css = assetConfig ? assetConfig.type === "css" : cssRe.test(me.url);
1233 } else {
1234 me.css = false;
1235 }
1236 }
1237 return this.css;
1238 },
1239
1240 getElement: function (tag) {
1241 var me = this,
1242 el = me.el;
1243 if (!el) {
1244 if (me.isCss()) {
1245 tag = tag || "link";
1246 el = doc.createElement(tag);
1247 if(tag == "link") {
1248 el.rel = 'stylesheet';
1249 me.prop = 'href';
1250 } else {
1251 me.prop="textContent";
1252 }
1253 el.type = "text/css";
1254 } else {
1255 tag = tag || "script";
1256 el = doc.createElement(tag);
1257 el.type = 'text/javascript';
1258 me.prop = 'src';
1259
1260 if (me.charset) {
1261 el.charset = me.charset;
1262 }
1263
1264 if (Boot.hasAsync) {
1265 el.async = false;
1266 }
1267 }
1268 me.el = el;
1269 }
1270 return el;
1271 },
1272
1273 getLoadUrl: function () {
1274 var me = this,
1275 url = Boot.canonicalUrl(me.url);
1276 if (!me.loadUrl) {
1277 me.loadUrl = !!me.buster
1278 ? (url + (url.indexOf('?') === -1 ? '?' : '&') + me.buster)
1279 : url;
1280 }
1281 return me.loadUrl;
1282 },
1283
1284 fetch: function (req) {
1285 var url = this.getLoadUrl(),
1286 async = !!req.async,
1287 complete = req.complete;
1288
1289 Boot.fetch(url, complete, this, async);
1290 },
1291
1292 onContentLoaded: function (response) {
1293 var me = this,
1294 status = response.status,
1295 content = response.content,
1296 exception = response.exception,
1297 url = this.getLoadUrl();
1298 me.loaded = true;
1299 if ((exception || status === 0) && !_environment.phantom) {
1300 me.error =
1301 true;
1302 me.evaluated = true;
1303 }
1304 else if ((status >= 200 && status < 300) || status === 304
1305 || _environment.phantom
1306 || (status === 0 && content.length > 0)
1307 ) {
1308 me.content = content;
1309 }
1310 else {
1311 me.error =
1312 true;
1313 me.evaluated = true;
1314 }
1315 },
1316
1317 createLoadElement: function(callback) {
1318 var me = this,
1319 el = me.getElement(),
1320 readyStateChange = function(){
1321 if (this.readyState === 'loaded' || this.readyState === 'complete') {
1322 if(callback) {
1323 callback();
1324 }
1325 }
1326 },
1327 errorFn = function() {
1328 me.error = true;
1329 if(callback) {
1330 callback();
1331 }
1332 };
1333 me.preserve = true;
1334 el.onerror = errorFn;
1335 if(Boot.hasReadyState) {
1336 el.onreadystatechange = readyStateChange;
1337 } else {
1338 el.onload = callback;
1339 }
1340 // IE starts loading here
1341 el[me.prop] = me.getLoadUrl();
1342 },
1343
1344 onLoadElementReady: function() {
1345 Boot.getHead().appendChild(this.getElement());
1346 this.evaluated = true;
1347 },
1348
1349 inject: function (content, asset) {
1350 var me = this,
1351 head = Boot.getHead(),
1352 url = me.url,
1353 key = me.key,
1354 base, el, ieMode, basePath;
1355
1356 if (me.isCss()) {
1357 me.preserve = true;
1358 basePath = key.substring(0, key.lastIndexOf("/") + 1);
1359 base = doc.createElement('base');
1360 base.href = basePath;
1361 if(head.firstChild) {
1362 head.insertBefore(base, head.firstChild);
1363 } else {
1364 head.appendChild(base);
1365 }
1366 // reset the href attribute to cuase IE to pick up the change
1367 base.href = base.href;
1368
1369 if (url) {
1370 content += "\n/*# sourceURL=" + key + " */";
1371 }
1372
1373 // create element after setting base
1374 el = me.getElement("style");
1375
1376 ieMode = ('styleSheet' in el);
1377
1378 head.appendChild(base);
1379 if(ieMode) {
1380 head.appendChild(el);
1381 el.styleSheet.cssText = content;
1382 } else {
1383 el.textContent = content;
1384 head.appendChild(el);
1385 }
1386 head.removeChild(base);
1387
1388 } else {
1389 // Debugger friendly, file names are still shown even though they're
1390 // eval'ed code. Breakpoints work on both Firebug and Chrome's Web
1391 // Inspector.
1392 if (url) {
1393 content += "\n//# sourceURL=" + key;
1394 }
1395 Ext.globalEval(content);
1396 }
1397 return me;
1398 },
1399
1400 loadCrossDomain: function() {
1401 var me = this,
1402 complete = function(){
1403 me.loaded = me.evaluated = me.done = true;
1404 me.notifyRequests();
1405 };
1406 me.createLoadElement(function(){
1407 complete();
1408 });
1409 me.evaluateLoadElement();
1410 // at this point, we need sequential evaluation,
1411 // which means we can't advance the load until
1412 // this entry has fully completed
1413 return false;
1414 },
1415
1416 loadElement: function() {
1417 var me = this,
1418 complete = function(){
1419 me.loaded = me.evaluated = me.done = true;
1420 me.notifyRequests();
1421 };
1422 me.createLoadElement(function(){
1423 complete();
1424 });
1425 me.evaluateLoadElement();
1426 return true;
1427 },
1428
1429 loadSync: function() {
1430 var me = this;
1431 me.fetch({
1432 async: false,
1433 complete: function (response) {
1434 me.onContentLoaded(response);
1435 }
1436 });
1437 me.evaluate();
1438 me.notifyRequests();
1439 },
1440
1441 load: function (sync) {
1442 var me = this;
1443 if (!me.loaded) {
1444 if(me.loading) {
1445 // if we're calling back through load and we're loading but haven't
1446 // yet loaded, then we should be in a sequential, cross domain
1447 // load scenario which means we can't continue the load on the
1448 // request until this entry has fully evaluated, which will mean
1449 // loaded = evaluated = done = true in one step. For css files, this
1450 // will happen immediately upon <link> element creation / insertion,
1451 // but <script> elements will set this upon load notification
1452 return false;
1453 }
1454 me.loading = true;
1455
1456 // for async modes, we have some options
1457 if (!sync) {
1458 // if cross domain, just inject the script tag and let the onload
1459 // events drive the progression
1460 if(me.isCrossDomain()) {
1461 return me.loadCrossDomain();
1462 }
1463 // for IE, use the readyStateChange allows us to load scripts in parallel
1464 // but serialize the evaluation by appending the script node to the
1465 // document
1466 else if(!me.isCss() && Boot.hasReadyState) {
1467 me.createLoadElement(function () {
1468 me.loaded = true;
1469 me.notifyRequests();
1470 });
1471 }
1472
1473 else if(Boot.useElements &&
1474 // older webkit, phantomjs included, won't fire load for link elements
1475 !(me.isCss() && _environment.phantom)) {
1476 return me.loadElement();
1477 }
1478 // for other browsers, just ajax the content down in parallel, and use
1479 // globalEval to serialize evaluation
1480 else {
1481 me.fetch({
1482 async: !sync,
1483 complete: function (response) {
1484 me.onContentLoaded(response);
1485 me.notifyRequests();
1486 }
1487 });
1488 }
1489 }
1490
1491 // for sync mode in js, global eval FTW. IE won't honor the comment
1492 // paths in the debugger, so eventually we need a sync mode for IE that
1493 // uses the readyStateChange mechanism
1494 else {
1495 me.loadSync();
1496 }
1497 }
1498 // signal that the load process can continue
1499 return true;
1500 },
1501
1502 evaluateContent: function () {
1503 this.inject(this.content);
1504 this.content = null;
1505 },
1506
1507 evaluateLoadElement: function() {
1508 Boot.getHead().appendChild(this.getElement());
1509 },
1510
1511 evaluate: function () {
1512 var me = this;
1513 if(!me.evaluated) {
1514 if(me.evaluating) {
1515 return;
1516 }
1517 me.evaluating = true;
1518 if(me.content !== undefined) {
1519 me.evaluateContent();
1520 } else if(!me.error) {
1521 me.evaluateLoadElement();
1522 }
1523 me.evaluated = me.done = true;
1524 me.cleanup();
1525 }
1526 },
1527
1528 /*
1529 * @private
1530 */
1531 cleanup: function () {
1532 var me = this,
1533 el = me.el,
1534 prop;
1535
1536 if (!el) {
1537 return;
1538 }
1539
1540 if (!me.preserve) {
1541 me.el = null;
1542
1543 el.parentNode.removeChild(el); // Remove, since its useless now
1544
1545 for (prop in el) {
1546 try {
1547 if (prop !== me.prop) {
1548 // If we set the src property to null IE
1549 // will try and request a script at './null'
1550 el[prop] = null;
1551 }
1552 delete el[prop]; // and prepare for GC
1553 } catch (cleanEx) {
1554 //ignore
1555 }
1556 }
1557 }
1558
1559 // Setting to null can cause exceptions if IE ever needs to call these
1560 // again (like onreadystatechange). This emptyFn has nothing locked in
1561 // closure scope so it is about as safe as null for memory leaks.
1562 el.onload = el.onerror = el.onreadystatechange = emptyFn;
1563 },
1564
1565 notifyRequests: function () {
1566 var requests = this.requests,
1567 len = requests.length,
1568 i, request;
1569 for (i = 0; i < len; i++) {
1570 request = requests[i];
1571 request.processLoadedEntries();
1572 }
1573 if(this.done) {
1574 this.fireListeners();
1575 }
1576 },
1577
1578 onDone: function(listener) {
1579 var me = this,
1580 listeners = me.listeners || (me.listeners = []);
1581 if(me.done) {
1582 listener(me);
1583 } else {
1584 listeners.push(listener);
1585 }
1586 },
1587
1588 fireListeners: function() {
1589 var listeners = this.listeners,
1590 listener;
1591 if(listeners && listeners.length > 0) {
1592 while((listener = listeners.shift())) {
1593 listener(this);
1594 }
1595 }
1596 }
1597 };
1598
1599 /*
1600 * Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally
1601 * dynamically loaded scripts have an extra query parameter appended to avoid stale
1602 * cached scripts. This method can be used to disable this mechanism, and is primarily
1603 * useful for testing. This is done using a cookie.
1604 * @param {Boolean} disable True to disable the cache buster.
1605 * @param {String} [path="/"] An optional path to scope the cookie.
1606 */
1607 Ext.disableCacheBuster = function (disable, path) {
1608 var date = new Date();
1609 date.setTime(date.getTime() + (disable ? 10 * 365 : -1) * 24 * 60 * 60 * 1000);
1610 date = date.toGMTString();
1611 doc.cookie = 'ext-cache=1; expires=' + date + '; path=' + (path || '/');
1612 };
1613
1614
1615 Boot.init();
1616 return Boot;
1617
1618// NOTE: We run the eval at global scope to protect the body of the function and allow
1619// compressors to still process it.
1620}(function () {
1621}));//(eval("/*@cc_on!@*/!1"));
1622
1623/*
1624 * This method evaluates the given code free of any local variable. This
1625 * will be at global scope, in others it will be in a function.
1626 * @parma {String} code The code to evaluate.
1627 * @private
1628 * @method
1629 */
1630Ext.globalEval = Ext.globalEval || (this.execScript
1631 ? function (code) { execScript(code); }
1632 : function ($$code) { eval.call(window, $$code); });
1633
1634//<feature legacyBrowser>
1635/*
1636 * Only IE8 & IE/Quirks lack Function.prototype.bind so we polyfill that here.
1637 */
1638if (!Function.prototype.bind) {
1639 (function () {
1640 var slice = Array.prototype.slice,
1641 // To reduce overhead on call of the bound fn we have two flavors based on
1642 // whether we have args to prepend or not:
1643 bind = function (me) {
1644 var args = slice.call(arguments, 1),
1645 method = this;
1646
1647 if (args.length) {
1648 return function () {
1649 var t = arguments;
1650 // avoid the slice/concat if the caller does not supply args
1651 return method.apply(me, t.length ? args.concat(slice.call(t)) : args);
1652 };
1653 }
1654 // this is the majority use case - just fn.bind(this) and no args
1655
1656 args = null;
1657 return function () {
1658 return method.apply(me, arguments);
1659 };
1660 };
1661 Function.prototype.bind = bind;
1662 bind.$extjs = true; // to detect this polyfill if one want to improve it
1663 }());
1664}
1665//</feature>
1666
1667//</editor-fold>
1668
1669Ext.setResourcePath = function (poolName, path) {
1670 var manifest = Ext.manifest || (Ext.manifest = {}),
1671 paths = manifest.resources || (manifest.resources = {});
1672
1673 if (manifest) {
1674 if (typeof poolName !== 'string') {
1675 Ext.apply(paths, poolName);
1676 } else {
1677 paths[poolName] = path;
1678 }
1679 manifest.resources = paths;
1680 }
1681};
1682
1683Ext.getResourcePath = function (path, poolName, packageName) {
1684 if (typeof path !== 'string') {
1685 poolName = path.pool;
1686 packageName = path.packageName;
1687 path = path.path;
1688 }
1689 var manifest = Ext.manifest,
1690 paths = manifest && manifest.resources,
1691 poolPath = paths[poolName],
1692 output = [];
1693
1694 if (poolPath == null) {
1695 poolPath = paths.path;
1696 if (poolPath == null) {
1697 poolPath = 'resources';
1698 }
1699 }
1700
1701 if (poolPath) {
1702 output.push(poolPath);
1703 }
1704
1705 if (packageName) {
1706 output.push(packageName);
1707 }
1708
1709 output.push(path);
1710 return output.join('/');
1711};
1712// here, the extra check for window['Ext'] is needed for use with cmd-test
1713// code injection. we need to make that this file will sync up with page global
1714// scope to avoid duplicate Ext.Boot state. That check is after the initial Ext check
1715// to allow the sandboxing template to inject an appropriate Ext var and prevent the
1716// global detection.
1717var Ext = Ext || window['Ext'] || {};
1718
1719
1720//<editor-fold desc="Microloader">
1721/**
1722 * @Class Ext.Microloader
1723 * @singleton
1724 */
1725Ext.Microloader = Ext.Microloader || (function () {
1726 var Boot = Ext.Boot,
1727 _warn = function (message) {
1728 console.log("[WARN] " + message);
1729 },
1730 _privatePrefix = '_ext:' + location.pathname,
1731
1732 /**
1733 * The Following combination is used to create isolated local storage keys
1734 * '_ext' is used to scope all the local storage keys that we internally by Ext
1735 * 'location.pathname' is used to force each assets to cache by an absolute URL (/build/MyApp) (dev vs prod)
1736 * 'url' is used to force each asset to cache relative to the page (app.json vs resources/app.css)
1737 * 'profileId' is used to differentiate the builds of an application (neptune vs crisp)
1738 * 'Microloader.appId' is unique to the application and will differentiate apps on the same host (dev mode running app watch against multiple apps)
1739 */
1740 getStorageKey = function(url, profileId) {
1741 return _privatePrefix + url + '-' + (profileId ? profileId + '-' : '') + Microloader.appId;
1742 },
1743 postProcessor, _storage;
1744
1745 try {
1746 _storage = window['localStorage'];
1747 } catch(ex) {
1748 // ignore
1749 }
1750
1751 var _cache = window['applicationCache'],
1752 // Local Storage Controller
1753 LocalStorage = {
1754 clearAllPrivate: function(manifest) {
1755 if(_storage) {
1756
1757 //Remove the entry for the manifest first
1758 _storage.removeItem(manifest.key);
1759
1760 var i, key,
1761 removeKeys = [],
1762 suffix = manifest.profile + '-' + Microloader.appId,
1763 ln = _storage.length;
1764 for (i = 0; i < ln; i++) {
1765 key = _storage.key(i);
1766 // If key starts with the private key and the suffix is present we can clear this entry
1767 if (key.indexOf(_privatePrefix) === 0 && key.indexOf(suffix) !== -1) {
1768 removeKeys.push(key);
1769 }
1770 }
1771
1772 for(i in removeKeys) {
1773 _storage.removeItem(removeKeys[i]);
1774 }
1775 }
1776 },
1777 /**
1778 * private
1779 */
1780 retrieveAsset: function (key) {
1781 try {
1782 return _storage.getItem(key);
1783 }
1784 catch (e) {
1785 // Private browsing mode
1786 return null;
1787 }
1788 },
1789
1790 setAsset: function(key, content) {
1791 try {
1792 if (content === null || content == '') {
1793 _storage.removeItem(key);
1794 } else {
1795 _storage.setItem(key, content);
1796 }
1797 }
1798 catch (e) {
1799 if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {
1800 }
1801 }
1802 }
1803 };
1804
1805 var Asset = function (cfg) {
1806 if (typeof cfg.assetConfig === 'string') {
1807 this.assetConfig = {
1808 path: cfg.assetConfig
1809 };
1810 } else {
1811 this.assetConfig = cfg.assetConfig;
1812 }
1813
1814 this.type = cfg.type;
1815 this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile);
1816
1817 if (cfg.loadFromCache) {
1818 this.loadFromCache();
1819 }
1820 };
1821
1822 Asset.prototype = {
1823 shouldCache: function() {
1824 return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote;
1825 },
1826
1827 is: function (asset) {
1828 return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))
1829 },
1830
1831 cache: function(content) {
1832 if (this.shouldCache()) {
1833 LocalStorage.setAsset(this.key, content || this.content);
1834 }
1835 },
1836
1837 uncache: function() {
1838 LocalStorage.setAsset(this.key, null);
1839 },
1840
1841 updateContent: function (content) {
1842 this.content = content;
1843 },
1844
1845 getSize: function () {
1846 return this.content ? this.content.length : 0;
1847 },
1848
1849 loadFromCache: function() {
1850 if (this.shouldCache()) {
1851 this.content = LocalStorage.retrieveAsset(this.key);
1852 }
1853 }
1854 };
1855
1856 var Manifest = function (cfg) {
1857 if (typeof cfg.content === "string") {
1858 this.content = JSON.parse(cfg.content);
1859 } else {
1860 this.content = cfg.content;
1861 }
1862 this.assetMap = {};
1863
1864 this.url = cfg.url;
1865 this.fromCache = !!cfg.cached;
1866 this.assetCache = !(cfg.assetCache === false);
1867 this.key = getStorageKey(this.url);
1868
1869 // Pull out select properties for repetitive use
1870 this.profile = this.content.profile;
1871 this.hash = this.content.hash;
1872 this.loadOrder = this.content.loadOrder;
1873 this.deltas = this.content.cache ? this.content.cache.deltas : null;
1874 this.cacheEnabled = this.content.cache ? this.content.cache.enable : false;
1875
1876 this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null;
1877
1878 var tags = this.content.tags,
1879 platformTags = Ext.platformTags;
1880
1881 if (tags) {
1882 if (tags instanceof Array) {
1883 for (var i = 0; i < tags.length; i++) {
1884 platformTags[tags[i]] = true;
1885 }
1886 } else {
1887 Boot.apply(platformTags, tags);
1888 }
1889
1890 // re-apply the query parameters, so that the params as specified
1891 // in the url always has highest priority
1892 Boot.apply(platformTags, Boot.loadPlatformsParam());
1893 }
1894
1895 // Convert all assets into Assets
1896 this.js = this.processAssets(this.content.js, 'js');
1897 this.css = this.processAssets(this.content.css, 'css');
1898 };
1899
1900 Manifest.prototype = {
1901 processAsset: function(assetConfig, type) {
1902 var processedAsset = new Asset({
1903 manifest: this,
1904 assetConfig: assetConfig,
1905 type: type,
1906 loadFromCache: this.assetCache
1907 });
1908 this.assetMap[assetConfig.path] = processedAsset;
1909 return processedAsset;
1910 },
1911
1912 processAssets: function(assets, type) {
1913 var results = [],
1914 ln = assets.length,
1915 i, assetConfig;
1916
1917 for (i = 0; i < ln; i++) {
1918 assetConfig = assets[i];
1919 results.push(this.processAsset(assetConfig, type));
1920 }
1921
1922 return results;
1923 },
1924
1925 useAppCache: function() {
1926 return true;
1927 },
1928
1929 // Concatenate all assets for easy access
1930 getAssets: function () {
1931 return this.css.concat(this.js);
1932 },
1933
1934 getAsset: function (path) {
1935 return this.assetMap[path];
1936 },
1937
1938 shouldCache: function() {
1939 return this.hash && this.cacheEnabled;
1940 },
1941
1942 cache: function(content) {
1943 if (this.shouldCache()) {
1944 LocalStorage.setAsset(this.key, JSON.stringify(content || this.content));
1945 }
1946 },
1947
1948 is: function(manifest) {
1949 return this.hash === manifest.hash;
1950 },
1951
1952 // Clear the manifest from local storage
1953 uncache: function() {
1954 LocalStorage.setAsset(this.key, null);
1955 },
1956
1957 exportContent: function() {
1958 return Boot.apply({
1959 loadOrderMap: this.loadOrderMap
1960 }, this.content);
1961 }
1962 };
1963
1964 /**
1965 * Microloader
1966 * @type {Array}
1967 * @private
1968 */
1969 var _listeners = [],
1970 _loaded = false,
1971 Microloader = {
1972 init: function () {
1973 Ext.microloaded = true;
1974
1975 // data-app is in the dev template for an application and is also
1976 // injected into the app my CMD for production
1977 // We use this to prefix localStorage cache to prevent collisions
1978 var microloaderElement = document.getElementById('microloader');
1979 Microloader.appId = microloaderElement ? microloaderElement.getAttribute('data-app') : '';
1980
1981 if (Ext.beforeLoad) {
1982 postProcessor = Ext.beforeLoad(Ext.platformTags);
1983 }
1984
1985 var readyHandler = Ext._beforereadyhandler;
1986
1987 Ext._beforereadyhandler = function () {
1988 if (Ext.Boot !== Boot) {
1989 Ext.apply(Ext.Boot, Boot);
1990 Ext.Boot = Boot;
1991 }
1992 if (readyHandler) {
1993 readyHandler();
1994 }
1995 };
1996 },
1997
1998 applyCacheBuster: function(url) {
1999 var tstamp = new Date().getTime(),
2000 sep = url.indexOf('?') === -1 ? '?' : '&';
2001 url = url + sep + "_dc=" + tstamp;
2002 return url;
2003 },
2004
2005 run: function() {
2006 Microloader.init();
2007 var manifest = Ext.manifest;
2008
2009 if (typeof manifest === "string") {
2010 var extension = ".json",
2011 url = manifest.indexOf(extension) === manifest.length - extension.length
2012 ? manifest
2013 : manifest + ".json",
2014 key = getStorageKey(url),
2015 content = LocalStorage.retrieveAsset(key);
2016
2017 // Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building.
2018 if (content) {
2019 manifest = new Manifest({
2020 url: url,
2021 content: content,
2022 cached: true
2023 });
2024 if (postProcessor) {
2025 postProcessor(manifest);
2026 }
2027 Microloader.load(manifest);
2028
2029
2030 // Manifest is not in local storage. Fetch it from the server
2031 } else {
2032 Boot.fetch(Microloader.applyCacheBuster(url), function (result) {
2033 manifest = new Manifest({
2034 url: url,
2035 content: result.content
2036 });
2037
2038 manifest.cache();
2039 if (postProcessor) {
2040 postProcessor(manifest);
2041 }
2042 Microloader.load(manifest);
2043 });
2044 }
2045
2046 // Embedded Manifest into JS file
2047 } else {
2048 manifest = new Manifest({
2049 content: manifest
2050 });
2051 Microloader.load(manifest);
2052 }
2053 },
2054
2055 /**
2056 *
2057 * @param {Manifest} manifest
2058 */
2059 load: function (manifest) {
2060 Microloader.urls = [];
2061 Microloader.manifest = manifest;
2062 Ext.manifest = Microloader.manifest.exportContent();
2063
2064 var assets = manifest.getAssets(),
2065 cachedAssets = [],
2066 asset, i, len, include, entry;
2067
2068 for (len = assets.length, i = 0; i < len; i++) {
2069 asset = assets[i];
2070 include = Microloader.filterAsset(asset);
2071 if (include) {
2072 // Asset is using the localStorage caching system
2073 if (manifest.shouldCache() && asset.shouldCache()) {
2074 // Asset already has content from localStorage, instantly seed that into boot
2075 if (asset.content) {
2076 entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content);
2077 if (entry.evaluated) {
2078 _warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted.");
2079 }
2080 //load via AJAX and seed content into Boot
2081 } else {
2082 cachedAssets.push(asset);
2083 }
2084 }
2085 Microloader.urls.push(asset.assetConfig.path);
2086 Boot.assetConfig[asset.assetConfig.path] = Boot.apply({type: asset.type}, asset.assetConfig);
2087 }
2088 }
2089
2090 // If any assets are using the caching system and do not have local versions load them first via AJAX
2091 if (cachedAssets.length > 0) {
2092 Microloader.remainingCachedAssets = cachedAssets.length;
2093 while (cachedAssets.length > 0) {
2094 asset = cachedAssets.pop();
2095 Boot.fetch(asset.assetConfig.path, (function(asset) {
2096 return function(result) {
2097 Microloader.onCachedAssetLoaded(asset, result);
2098 }
2099 })(asset));
2100 }
2101 } else {
2102 Microloader.onCachedAssetsReady();
2103 }
2104 },
2105
2106 // Load the asset and seed its content into Boot to be evaluated in sequence
2107 onCachedAssetLoaded: function (asset, result) {
2108 var checksum;
2109 result = Microloader.parseResult(result);
2110 Microloader.remainingCachedAssets--;
2111
2112 if (!result.error) {
2113 checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
2114 if (!checksum) {
2115 _warn("Cached Asset '" + asset.assetConfig.path + "' has failed checksum. This asset will be uncached for future loading");
2116
2117 // Un cache this asset so it is loaded next time
2118 asset.uncache();
2119 }
2120
2121 Boot.registerContent(asset.assetConfig.path, asset.type, result.content);
2122 asset.updateContent(result.content);
2123 asset.cache();
2124 } else {
2125 _warn("There was an error pre-loading the asset '" + asset.assetConfig.path + "'. This asset will be uncached for future loading");
2126
2127 // Un cache this asset so it is loaded next time
2128 asset.uncache();
2129 }
2130
2131 if (Microloader.remainingCachedAssets === 0) {
2132 Microloader.onCachedAssetsReady();
2133 }
2134 },
2135
2136 onCachedAssetsReady: function(){
2137 Boot.load({
2138 url: Microloader.urls,
2139 loadOrder: Microloader.manifest.loadOrder,
2140 loadOrderMap: Microloader.manifest.loadOrderMap,
2141 sequential: true,
2142 success: Microloader.onAllAssetsReady,
2143 failure: Microloader.onAllAssetsReady
2144 });
2145 },
2146
2147 onAllAssetsReady: function() {
2148 _loaded = true;
2149 Microloader.notify();
2150
2151 if (navigator.onLine !== false) {
2152 Microloader.checkAllUpdates();
2153 }
2154 else {
2155 if(window['addEventListener']) {
2156 window.addEventListener('online', Microloader.checkAllUpdates, false);
2157 }
2158 }
2159 },
2160
2161 onMicroloaderReady: function (listener) {
2162 if (_loaded) {
2163 listener();
2164 } else {
2165 _listeners.push(listener);
2166 }
2167 },
2168
2169 /**
2170 * @private
2171 */
2172 notify: function () {
2173 var listener;
2174 while((listener = _listeners.shift())) {
2175 listener();
2176 }
2177 },
2178
2179 // Delta patches content
2180 patch: function (content, delta) {
2181 var output = [],
2182 chunk, i, ln;
2183
2184 if (delta.length === 0) {
2185 return content;
2186 }
2187
2188 for (i = 0,ln = delta.length; i < ln; i++) {
2189 chunk = delta[i];
2190
2191 if (typeof chunk === 'number') {
2192 output.push(content.substring(chunk, chunk + delta[++i]));
2193 }
2194 else {
2195 output.push(chunk);
2196 }
2197 }
2198
2199 return output.join('');
2200 },
2201
2202 checkAllUpdates: function() {
2203 if(window['removeEventListener']) {
2204 window.removeEventListener('online', Microloader.checkAllUpdates, false);
2205 }
2206
2207 if(_cache) {
2208 Microloader.checkForAppCacheUpdate();
2209 }
2210
2211 // Manifest came from a cached instance, check for updates
2212 if (Microloader.manifest.fromCache) {
2213 Microloader.checkForUpdates();
2214 }
2215 },
2216
2217 checkForAppCacheUpdate: function() {
2218 if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) {
2219 Microloader.appCacheState = 'updated';
2220 } else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) {
2221 Microloader.appCacheState = 'checking';
2222 _cache.addEventListener('error', Microloader.onAppCacheError);
2223 _cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated);
2224 _cache.addEventListener('cached', Microloader.onAppCacheNotUpdated);
2225 _cache.addEventListener('updateready', Microloader.onAppCacheReady);
2226 _cache.addEventListener('obsolete', Microloader.onAppCacheObsolete);
2227 } else {
2228 Microloader.appCacheState = 'current';
2229 }
2230 },
2231
2232 checkForUpdates: function() {
2233 // Fetch the Latest Manifest from the server
2234 Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url), Microloader.onUpdatedManifestLoaded);
2235 },
2236
2237 onAppCacheError: function(e) {
2238 _warn(e.message);
2239
2240 Microloader.appCacheState = 'error';
2241 Microloader.notifyUpdateReady();
2242 },
2243
2244 onAppCacheReady: function() {
2245 _cache.swapCache();
2246 Microloader.appCacheUpdated();
2247 },
2248
2249 onAppCacheObsolete: function() {
2250 Microloader.appCacheUpdated();
2251 },
2252
2253 appCacheUpdated: function() {
2254 Microloader.appCacheState = 'updated';
2255 Microloader.notifyUpdateReady();
2256 },
2257
2258 onAppCacheNotUpdated: function() {
2259 Microloader.appCacheState = 'current';
2260 Microloader.notifyUpdateReady();
2261 },
2262
2263
2264 filterAsset: function(asset) {
2265 var cfg = (asset && asset.assetConfig) || {};
2266 if(cfg.platform || cfg.exclude) {
2267 return Boot.filterPlatform(cfg.platform, cfg.exclude);
2268 }
2269 return true;
2270 },
2271
2272 onUpdatedManifestLoaded: function (result) {
2273 result = Microloader.parseResult(result);
2274
2275 if (!result.error) {
2276 var currentAssets, newAssets, currentAsset, newAsset, prop,
2277 assets, deltas, deltaPath, include,
2278 updatingAssets = [],
2279 manifest = new Manifest({
2280 url: Microloader.manifest.url,
2281 content: result.content,
2282 assetCache: false
2283 });
2284
2285 Microloader.remainingUpdatingAssets = 0;
2286 Microloader.updatedAssets = [];
2287 Microloader.removedAssets = [];
2288 Microloader.updatedManifest = null;
2289 Microloader.updatedAssetsReady = false;
2290
2291 // If the updated manifest has turned off caching we need to clear out all local storage
2292 // and trigger a appupdate as all content is now uncached
2293 if (!manifest.shouldCache()) {
2294
2295 Microloader.updatedManifest = manifest;
2296 LocalStorage.clearAllPrivate(manifest);
2297 Microloader.onAllUpdatedAssetsReady();
2298 return;
2299 }
2300
2301 // Manifest itself has changed
2302 if (!Microloader.manifest.is(manifest)) {
2303 Microloader.updatedManifest = manifest;
2304
2305 currentAssets = Microloader.manifest.getAssets();
2306 newAssets = manifest.getAssets();
2307
2308 // Look through new assets for assets that do not exist or assets that have different versions
2309 for (prop in newAssets) {
2310 newAsset = newAssets[prop];
2311 currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path);
2312 include = Microloader.filterAsset(newAsset);
2313
2314 if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) {
2315 updatingAssets.push({_new: newAsset, _current: currentAsset});
2316 }
2317 }
2318
2319 // Look through current assets for stale/old assets that have been removed
2320 for (prop in currentAssets) {
2321 currentAsset = currentAssets[prop];
2322 newAsset = manifest.getAsset(currentAsset.assetConfig.path);
2323
2324 //New version of this asset has been filtered out
2325 include = !Microloader.filterAsset(newAsset);
2326
2327 if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) {
2328 Microloader.removedAssets.push(currentAsset);
2329 }
2330 }
2331
2332 // Loop through all assets that need updating
2333 if (updatingAssets.length > 0) {
2334 Microloader.remainingUpdatingAssets = updatingAssets.length;
2335 while (updatingAssets.length > 0) {
2336 assets = updatingAssets.pop();
2337 newAsset = assets._new;
2338 currentAsset = assets._current;
2339
2340 // Full Updates will simply download the file and replace its current content
2341 if (newAsset.assetConfig.update === "full" || !currentAsset) {
2342
2343
2344 // Load the asset and cache its its content into Boot to be evaluated in sequence
2345 Boot.fetch(newAsset.assetConfig.path, (function (asset) {
2346 return function (result) {
2347 Microloader.onFullAssetUpdateLoaded(asset, result)
2348 };
2349 }(newAsset))
2350 );
2351
2352 // Delta updates will be given a delta patch
2353 } else if (newAsset.assetConfig.update === "delta") {
2354 deltas = manifest.deltas;
2355 deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json";
2356 // Fetch the Delta Patch and update the contents of the asset
2357 Boot.fetch(deltaPath,
2358 (function (asset, oldAsset) {
2359 return function (result) {
2360 Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result)
2361 };
2362 }(newAsset, currentAsset))
2363 );
2364 }
2365 }
2366 } else {
2367 Microloader.onAllUpdatedAssetsReady();
2368 }
2369 } else {
2370 Microloader.onAllUpdatedAssetsReady();
2371 }
2372 } else {
2373 _warn("Error loading manifest file to check for updates");
2374 Microloader.onAllUpdatedAssetsReady();
2375 }
2376 },
2377
2378 onFullAssetUpdateLoaded: function(asset, result) {
2379 var checksum;
2380 result = Microloader.parseResult(result);
2381 Microloader.remainingUpdatingAssets--;
2382
2383 if (!result.error) {
2384 checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
2385 if (!checksum) {
2386
2387 // uncache this asset as there is a new version somewhere that has not been loaded.
2388 asset.uncache();
2389 } else {
2390 asset.updateContent(result.content);
2391 Microloader.updatedAssets.push(asset);
2392 }
2393 } else {
2394
2395 // uncache this asset as there is a new version somewhere that has not been loaded.
2396 asset.uncache();
2397 }
2398
2399 if (Microloader.remainingUpdatingAssets === 0) {
2400 Microloader.onAllUpdatedAssetsReady();
2401 }
2402 },
2403
2404 onDeltaAssetUpdateLoaded: function(asset, oldAsset, result) {
2405 var json, checksum, content;
2406 result = Microloader.parseResult(result);
2407 Microloader.remainingUpdatingAssets--;
2408
2409 if (!result.error) {
2410 try {
2411 json = JSON.parse(result.content);
2412 content = Microloader.patch(oldAsset.content, json);
2413 checksum = Microloader.checksum(content, asset.assetConfig.hash);
2414 if (!checksum) {
2415
2416 // uncache this asset as there is a new version somewhere that has not been loaded.
2417 asset.uncache();
2418 } else {
2419 asset.updateContent(content);
2420 Microloader.updatedAssets.push(asset);
2421 }
2422 } catch (e) {
2423 _warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");
2424 // uncache this asset as there is a new version somewhere that has not been loaded.
2425 asset.uncache();
2426 }
2427 } else {
2428 _warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");
2429
2430 // uncache this asset as there is a new version somewhere that has not been loaded.
2431 asset.uncache();
2432 }
2433 if (Microloader.remainingUpdatingAssets === 0) {
2434 Microloader.onAllUpdatedAssetsReady();
2435 }
2436 },
2437
2438 //TODO: Make this all transaction based to allow for reverting if quota is exceeded
2439 onAllUpdatedAssetsReady: function() {
2440 var asset;
2441 Microloader.updatedAssetsReady = true;
2442
2443 if (Microloader.updatedManifest) {
2444 while (Microloader.removedAssets.length > 0) {
2445 asset = Microloader.removedAssets.pop();
2446 asset.uncache();
2447 }
2448
2449 if (Microloader.updatedManifest) {
2450 Microloader.updatedManifest.cache();
2451 }
2452
2453 while (Microloader.updatedAssets.length > 0) {
2454 asset = Microloader.updatedAssets.pop();
2455 asset.cache();
2456 }
2457
2458 }
2459
2460 Microloader.notifyUpdateReady();
2461 },
2462
2463 notifyUpdateReady: function () {
2464 if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) {
2465 if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) {
2466 Microloader.appUpdate = {
2467 updated: true,
2468 app: Microloader.appCacheState === 'updated',
2469 manifest: Microloader.updatedManifest && Microloader.updatedManifest.exportContent()
2470 };
2471
2472 Microloader.fireAppUpdate();
2473 }
2474 }
2475 },
2476
2477 fireAppUpdate: function() {
2478 if (Ext.GlobalEvents) {
2479 // We defer dispatching this event slightly in order to let the application finish loading
2480 // as we are still very early in the lifecycle
2481 Ext.defer(function() {
2482 Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate);
2483 }, 100);
2484 }
2485 },
2486
2487 checksum: function(content, hash) {
2488 if(!content || !hash) {
2489 return false;
2490 }
2491
2492 var passed = true,
2493 hashLn = hash.length,
2494 checksumType = content.substring(0, 1);
2495
2496 if (checksumType == '/') {
2497 if (content.substring(2, hashLn + 2) !== hash) {
2498 passed = false;
2499 }
2500 } else if (checksumType == 'f') {
2501 if (content.substring(10, hashLn + 10) !== hash) {
2502 passed = false;
2503 }
2504 } else if (checksumType == '.') {
2505 if (content.substring(1, hashLn + 1) !== hash) {
2506 passed = false;
2507 }
2508 }
2509 return passed;
2510 },
2511 parseResult: function(result) {
2512 var rst = {};
2513 if ((result.exception || result.status === 0) && !Boot.env.phantom) {
2514 rst.error = true;
2515 } else if ((result.status >= 200 && result.status < 300) || result.status === 304
2516 || Boot.env.phantom
2517 || (result.status === 0 && result.content.length > 0)
2518 ) {
2519 rst.content = result.content;
2520 } else {
2521 rst.error = true;
2522 }
2523 return rst;
2524 }
2525 };
2526
2527 return Microloader;
2528}());
2529
2530/**
2531 * @type {String/Object}
2532 */
2533Ext.manifest = Ext.manifest || "bootstrap";
2534
2535Ext.Microloader.run();