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