Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1499 lines
51 KiB

// @tag core
// @define Ext.Boot
var Ext = Ext || {};
//<editor-fold desc="Boot">
/*
* @class Ext.Boot
* @singleton
*/
Ext.Boot = Ext.Boot || (function (emptyFn) {
var doc = document,
apply = function (dest, src, defaults) {
if (defaults) {
apply(dest, defaults);
}
if (dest && src && typeof src == 'object') {
for (var key in src) {
dest[key] = src[key];
}
}
return dest;
},
_config = {
/*
* @cfg {Boolean} [disableCaching=true]
* If `true` current timestamp is added to script URL's to prevent caching.
* In debug builds, adding a "cache" or "disableCacheBuster" query parameter
* to the page's URL will set this to `false`.
*/
disableCaching: (/[?&](?:cache|disableCacheBuster)\b/i.test(location.search) ||
!(/http[s]?\:/i.test(location.href)) ||
/(^|[ ;])ext-cache=1/.test(doc.cookie)) ? false :
true,
/*
* @cfg {String} [disableCachingParam="_dc"]
* The query parameter name for the cache buster's timestamp.
*/
disableCachingParam: '_dc',
/*
* @cfg {Boolean} loadDelay
* Millisecond delay between asynchronous script injection (prevents stack
* overflow on some user agents) 'false' disables delay but potentially
* increases stack load.
*/
loadDelay: false,
/*
* @cfg {Boolean} preserveScripts
* `false` to remove asynchronously loaded scripts, `true` to retain script
* element for browser debugger compatibility and improved load performance.
*/
preserveScripts: true,
/*
* @cfg {String} charset
* Optional charset to specify encoding of dynamic content.
*/
charset: undefined
},
cssRe = /\.css(?:\?|$)/i,
resolverEl = doc.createElement('a'),
isBrowser = typeof window !== 'undefined',
_environment = {
browser: isBrowser,
node: !isBrowser && (typeof require === 'function'),
phantom: (typeof phantom !== 'undefined' && phantom.fs)
},
_tags = (Ext.platformTags = {}),
//<debug>
_debug = function (message) {
//console.log(message);
},
//</debug>
_apply = function (object, config, defaults) {
if (defaults) {
_apply(object, defaults);
}
if (object && config && typeof config === 'object') {
for (var i in config) {
object[i] = config[i];
}
}
return object;
},
/*
* The Boot loader class manages Request objects that contain one or
* more individual urls that need to be loaded. Requests can be performed
* synchronously or asynchronously, but will always evaluate urls in the
* order specified on the request object.
*/
Boot = {
loading: 0,
loaded: 0,
env: _environment,
config: _config,
// Keyed by absolute URL this object holds "true" if that URL is already loaded
// or an array of callbacks to call once it loads.
scripts: {
/*
Entry objects
'http://foo.com/bar/baz/Thing.js': {
done: true,
el: scriptEl || linkEl,
preserve: true,
requests: [ request1, ... ]
}
*/
},
/*
* contains the current script name being loaded
* (loadSync or sequential load only)
*/
currentFile: null,
suspendedQueue: [],
currentRequest: null,
// when loadSync is called, need to cause subsequent load requests to also be loadSync,
// eg, when Ext.require(...) is called
syncMode: false,
/*
* simple helper method for debugging
*/
//<debug>
debug: _debug,
//</debug>
/*
* enables / disables loading scripts via script / link elements rather
* than using ajax / eval
*/
useElements: true,
listeners: [],
Request: Request,
Entry: Entry,
/**
* The defult function that detects various platforms and sets tags
* in the platform map accrodingly. Examples are iOS, android, tablet, etc.
* @param tags the set of tags to populate
*/
detectPlatformTags: function () {
var ua = navigator.userAgent,
isMobile = _tags.isMobile = /Mobile(\/|\s)/.test(ua),
isPhone, isDesktop, isTablet, touchSupported, isIE10, isBlackberry,
element = document.createElement('div'),
uaTagChecks = [
'iPhone',
'iPod',
'Android',
'Silk',
'Android 2',
'BlackBerry',
'BB',
'iPad',
'RIM Tablet OS',
'MSIE 10',
'Trident',
'Chrome',
'Tizen',
'Firefox',
'Safari',
'Windows Phone'
],
isEventSupported = function(name, tag) {
if (tag === undefined) {
tag = window;
}
var eventName = 'on' + name.toLowerCase(),
isSupported = (eventName in element);
if (!isSupported) {
if (element.setAttribute && element.removeAttribute) {
element.setAttribute(eventName, '');
isSupported = typeof element[eventName] === 'function';
if (typeof element[eventName] !== 'undefined') {
element[eventName] = undefined;
}
element.removeAttribute(eventName);
}
}
return isSupported;
},
uaTags = {},
len = uaTagChecks.length, check, c;
for (c = 0; c < len; c++) {
check = uaTagChecks[c];
uaTags[check] = new RegExp(check).test(ua);
}
isPhone =
(uaTags.iPhone || uaTags.iPod) ||
(!uaTags.Silk && (uaTags.Android && (uaTags['Android 2'] || isMobile))) ||
((uaTags.BlackBerry || uaTags.BB) && uaTags.isMobile) ||
(uaTags['Windows Phone']);
isTablet =
(!_tags.isPhone) && (
uaTags.iPad ||
uaTags.Android ||
uaTags.Silk ||
uaTags['RIM Tablet OS'] ||
(uaTags['MSIE 10'] && /; Touch/.test(ua))
);
touchSupported =
// if the browser has touch events we can be reasonably sure the device has
// a touch screen
isEventSupported('touchend') ||
// browsers that use pointer event have maxTouchPoints > 0 if the
// device supports touch input
// http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints
navigator.maxTouchPoints ||
// IE10 uses a vendor-prefixed maxTouchPoints property
navigator.msMaxTouchPoints;
isDesktop = !isPhone && !isTablet;
isIE10 = uaTags['MSIE 10'];
isBlackberry = uaTags.Blackberry || uaTags.BB;
apply(_tags, Boot.loadPlatformsParam(), {
phone: isPhone,
tablet: isTablet,
desktop: isDesktop,
touch: touchSupported,
ios: (uaTags.iPad || uaTags.iPhone || uaTags.iPod),
android: uaTags.Android || uaTags.Silk,
blackberry: isBlackberry,
safari: uaTags.Safari && !isBlackberry,
chrome: uaTags.Chrome,
ie10: isIE10,
windows: isIE10 || uaTags.Trident,
tizen: uaTags.Tizen,
firefox: uaTags.Firefox
});
},
/**
* Extracts user supplied platform tags from the "platformTags" query parameter
* of the form:
*
* ?platformTags=name:state,name:state,...
*
* (each tag defaults to true when state is unspecified)
*
* Example:
* ?platformTags=isTablet,isPhone:false,isDesktop:0,iOS:1,Safari:true, ...
*
* @returns {Object} the platform tags supplied by the query string
*/
loadPlatformsParam: function () {
// Check if the ?platform parameter is set in the URL
var paramsString = window.location.search.substr(1),
paramsArray = paramsString.split("&"),
params = {}, i,
platforms = {},
tmpArray, tmplen, platform, name, enabled;
for (i = 0; i < paramsArray.length; i++) {
tmpArray = paramsArray[i].split("=");
params[tmpArray[0]] = tmpArray[1];
}
if (params.platformTags) {
tmpArray = params.platform.split(/\W/);
for (tmplen = tmpArray.length, i = 0; i < tmplen; i++) {
platform = tmpArray[i].split(":");
name = platform[0];
if (platform.length > 1) {
enabled = platform[1];
if (enabled === 'false' || enabled === '0') {
enabled = false;
} else {
enabled = true;
}
}
platforms[name] = enabled;
}
}
return platform;
},
filterPlatform: function (platform) {
platform = [].concat(platform);
var len, p, tag;
for (len = platform.length, p = 0; p < len; p++) {
tag = platform[p];
if (_tags.hasOwnProperty(tag)) {
return !!_tags[tag];
}
}
return false;
},
init: function () {
var scriptEls = doc.getElementsByTagName('script'),
len = scriptEls.length,
re = /\/ext(\-[a-z\-]+)?\.js$/,
entry, script, src, state, baseUrl, key, n, origin;
// Since we are loading after other scripts, and we needed to gather them
// anyway, we track them in _scripts so we don't have to ask for them all
// repeatedly.
for (n = 0; n < len; n++) {
src = (script = scriptEls[n]).src;
if (!src) {
continue;
}
state = script.readyState || null;
// If we find a script file called "ext-*.js", then the base path is that file's base path.
if (!baseUrl) {
if (re.test(src)) {
Boot.hasReadyState = ("readyState" in script);
Boot.hasAsync = ("async" in script) || !Boot.hasReadyState;
baseUrl = src;
}
}
if (!Boot.scripts[key = Boot.canonicalUrl(src)]) {
//<debug>
_debug("creating entry " + key + " in Boot.init");
//</debug>
entry = new Entry({
key: key,
url: src,
done: state === null || // non-IE
state === 'loaded' || state === 'complete', // IE only
el: script,
prop: 'src'
});
}
}
if (!baseUrl) {
script = scriptEls[scriptEls.length - 1];
baseUrl = script.src;
Boot.hasReadyState = ('readyState' in script);
Boot.hasAsync = ("async" in script) || !Boot.hasReadyState;
}
Boot.baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1);
origin = window.location.origin ||
window.location.protocol +
"//" +
window.location.hostname +
(window.location.port ? ':' + window.location.port: '');
Boot.origin = origin;
Boot.detectPlatformTags();
Ext.filterPlatform = Boot.filterPlatform;
},
/*
* This method returns a canonical URL for the given URL.
*
* For example, the following all produce the same canonical URL (which is the
* last one):
*
* http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js?_dc=12345
* http://foo.com/bar/baz/zoo/derp/../../goo/Thing.js
* http://foo.com/bar/baz/zoo/derp/../jazz/../../goo/Thing.js
* http://foo.com/bar/baz/zoo/../goo/Thing.js
* http://foo.com/bar/baz/goo/Thing.js
*
* @private
*/
canonicalUrl: function (url) {
// @TODO - see if we need this fallback logic
// http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
resolverEl.href = url;
var ret = resolverEl.href,
dc = _config.disableCachingParam,
pos = dc ? ret.indexOf(dc + '=') : -1,
c, end;
// If we have a _dc query parameter we need to remove it from the canonical
// URL.
if (pos > 0 && ((c = ret.charAt(pos - 1)) === '?' || c === '&')) {
end = ret.indexOf('&', pos);
end = (end < 0) ? '' : ret.substring(end);
if (end && c === '?') {
++pos; // keep the '?'
end = end.substring(1); // remove the '&'
}
ret = ret.substring(0, pos - 1) + end;
}
return ret;
},
/*
* Get the config value corresponding to the specified name. If no name is given, will return the config object
* @param {String} name The config property name
* @return {Object}
*/
getConfig: function (name) {
return name ? Boot.config[name] : Boot.config;
},
/*
* Set the configuration.
* @param {Object} config The config object to override the default values.
* @return {Ext.Boot} this
*/
setConfig: function (name, value) {
if (typeof name === 'string') {
Boot.config[name] = value;
} else {
for (var s in name) {
Boot.setConfig(s, name[s]);
}
}
return Boot;
},
getHead: function () {
return Boot.docHead ||
(Boot.docHead = doc.head ||
doc.getElementsByTagName('head')[0]);
},
create: function (url, key, cfg) {
var config = cfg || {};
config.url = url;
config.key = key;
return Boot.scripts[key] = new Entry(config);
},
getEntry: function (url, cfg) {
var key = Boot.canonicalUrl(url),
entry = Boot.scripts[key];
if (!entry) {
entry = Boot.create(url, key, cfg);
}
return entry;
},
processRequest: function(request, sync) {
request.loadEntries(sync);
},
load: function (request) {
//<debug>
_debug("Boot.load called");
//</debug>
var request = new Request(request);
if (request.sync || Boot.syncMode) {
return Boot.loadSync(request);
}
// If there is a request in progress, we must
// queue this new request to be fired when the current request completes.
if (Boot.currentRequest) {
//<debug>
_debug("current active request, suspending this request");
//</debug>
// trigger assignment of entries now to ensure that overlapping
// entries with currently running requests will synchronize state
// with this pending one as they complete
request.getEntries();
Boot.suspendedQueue.push(request);
} else {
Boot.currentRequest = request;
Boot.processRequest(request, false);
}
return Boot;
},
loadSync: function (request) {
//<debug>
_debug("Boot.loadSync called");
//</debug>
var request = new Request(request);
Boot.syncMode++;
Boot.processRequest(request, true);
Boot.syncMode--;
return Boot;
},
loadBasePrefix: function(request) {
request = new Request(request);
request.prependBaseUrl = true;
return Boot.load(request);
},
loadSyncBasePrefix: function(request) {
request = new Request(request);
request.prependBaseUrl = true;
return Boot.loadSync(request);
},
requestComplete: function(request) {
var next;
if (Boot.currentRequest === request) {
Boot.currentRequest = null;
while(Boot.suspendedQueue.length > 0) {
next = Boot.suspendedQueue.shift();
if(!next.done) {
//<debug>
_debug("resuming suspended request");
//</debug>
Boot.load(next);
break;
}
}
}
if (!Boot.currentRequest && Boot.suspendedQueue.length == 0) {
Boot.fireListeners();
}
},
isLoading: function () {
return !Boot.currentRequest && Boot.suspendedQueue.length == 0;
},
fireListeners: function () {
var listener;
while (Boot.isLoading() && (listener = Boot.listeners.shift())) {
listener();
}
},
onBootReady: function (listener) {
if (!Boot.isLoading()) {
listener();
} else {
Boot.listeners.push(listener);
}
},
/*
* this is a helper function used by Ext.Loader to flush out
* 'uses' arrays for classes
*/
getPathsFromIndexes: function (indexMap, loadOrder) {
return Request.prototype.getPathsFromIndexes(indexMap, loadOrder);
},
createLoadOrderMap: function(loadOrder) {
return Request.prototype.createLoadOrderMap(loadOrder);
},
fetch: function(url, complete, scope, async) {
async = (async === undefined) ? !!complete : async;
var xhr = new XMLHttpRequest(),
result, status, content, exception = false,
readyStateChange = function () {
if (xhr && xhr.readyState == 4) {
status = (xhr.status === 1223) ? 204 :
(xhr.status === 0 && ((self.location || {}).protocol === 'file:' ||
(self.location || {}).protocol === 'ionp:')) ? 200 : xhr.status;
content = xhr.responseText;
result = {
content: content,
status: status,
exception: exception
};
if (complete) {
complete.call(scope, result);
}
xhr = null;
}
};
if (async) {
xhr.onreadystatechange = readyStateChange;
}
try {
//<debug>
_debug("fetching " + url + " " + (async ? "async" : "sync"));
//</debug>
xhr.open('GET', url, async);
xhr.send(null);
} catch (err) {
exception = err;
readyStateChange();
return result;
}
if (!async) {
readyStateChange();
}
return result;
},
notifyAll: function(entry) {
entry.notifyRequests();
}
};
/*
* The request class encapsulates a series of Entry objects
* and provides notification around the completion of all Entries
* in this request.
*/
function Request(cfg) {
if(cfg.$isRequest) {
return cfg;
}
var cfg = cfg.url ? cfg : {url: cfg},
url = cfg.url,
urls = url.charAt ? [ url ] : url,
charset = cfg.charset || Boot.config.charset;
_apply(cfg, {
urls: urls,
charset: charset
});
_apply(this, cfg);
};
Request.prototype = {
$isRequest: true,
/*
* @private
* @param manifest
* @returns {*}
*/
createLoadOrderMap: function (loadOrder) {
var len = loadOrder.length,
loadOrderMap = {},
i, element;
for (i = 0; i < len; i++) {
element = loadOrder[i];
loadOrderMap[element.path] = element;
}
return loadOrderMap;
},
/*
* @private
* @param index
* @param indexMap
* @returns {{}}
*/
getLoadIndexes: function (index, indexMap, loadOrder, includeUses, skipLoaded) {
var item = loadOrder[index],
len, i, reqs, entry, stop, added, idx, ridx, url;
if (indexMap[index]) {
// prevent cycles
return indexMap;
}
indexMap[index] = true;
stop = false;
while (!stop) {
added = false;
// iterate the requirements for each index and
// accumulate in the index map
for (idx in indexMap) {
if (indexMap.hasOwnProperty(idx)) {
item = loadOrder[idx];
if (!item) {
continue;
}
url = this.prepareUrl(item.path);
entry = Boot.getEntry(url);
if (!skipLoaded || !entry || !entry.done) {
reqs = item.requires;
if (includeUses && item.uses) {
reqs = reqs.concat(item.uses);
}
for (len = reqs.length, i = 0; i < len; i++) {
ridx = reqs[i];
// if we find a requirement that wasn't
// already in the index map,
// set the added flag to indicate we need to
// reprocess
if (!indexMap[ridx]) {
indexMap[ridx] = true;
added = true;
}
}
}
}
}
// if we made a pass through the index map and didn't add anything
// then we can stop
if (!added) {
stop = true;
}
}
return indexMap;
},
getPathsFromIndexes: function (indexMap, loadOrder) {
var indexes = [],
paths = [],
index, len, i;
for (index in indexMap) {
if (indexMap.hasOwnProperty(index) && indexMap[index]) {
indexes.push(index);
}
}
indexes.sort(function (a, b) {
return a - b;
});
// convert indexes back into load paths
for (len = indexes.length, i = 0; i < len; i++) {
paths.push(loadOrder[indexes[i]].path);
}
return paths;
},
expandUrl: function (url, indexMap, includeUses, skipLoaded) {
if (typeof url == 'string') {
url = [url];
}
var me = this,
loadOrder = me.loadOrder,
loadOrderMap = me.loadOrderMap;
if (loadOrder) {
loadOrderMap = loadOrderMap || me.createLoadOrderMap(loadOrder);
me.loadOrderMap = loadOrderMap;
indexMap = indexMap || {};
var len = url.length,
unmapped = [],
i, item;
for (i = 0; i < len; i++) {
item = loadOrderMap[url[i]];
if (item) {
me.getLoadIndexes(item.idx, indexMap, loadOrder, includeUses, skipLoaded);
} else {
unmapped.push(url[i]);
}
}
return me.getPathsFromIndexes(indexMap, loadOrder).concat(unmapped);
}
return url;
},
expandUrls: function (urls, includeUses) {
if (typeof urls == "string") {
urls = [urls];
}
var expanded = [],
expandMap = {},
tmpExpanded,
len = urls.length,
i, t, tlen, tUrl;
for (i = 0; i < len; i++) {
tmpExpanded = this.expandUrl(urls[i], {}, includeUses, true);
for (t = 0, tlen = tmpExpanded.length; t < tlen; t++) {
tUrl = tmpExpanded[t];
if (!expandMap[tUrl]) {
expandMap[tUrl] = true;
expanded.push(tUrl);
}
}
}
if (expanded.length == 0) {
expanded = urls;
}
return expanded;
},
expandLoadOrder: function () {
var me = this,
urls = me.urls,
expanded;
if (!me.expanded) {
expanded = this.expandUrls(urls, true);
me.expanded = true;
} else {
expanded = urls;
}
me.urls = expanded;
// if we added some urls to the request to honor the indicated
// load order, the request needs to be sequential
if (urls.length != expanded.length) {
me.sequential = true;
}
return me;
},
getUrls: function () {
this.expandLoadOrder();
return this.urls;
},
prepareUrl: function(url) {
if(this.prependBaseUrl) {
return Boot.baseUrl + url;
}
return url;
},
getEntries: function () {
var me = this,
entries = me.entries,
i, entry, urls, url;
if (!entries) {
entries = [];
urls = me.getUrls();
for (i = 0; i < urls.length; i++) {
url = me.prepareUrl(urls[i]);
entry = Boot.getEntry(url, {
buster: me.buster,
charset: me.charset
});
entry.requests.push(me);
entries.push(entry);
}
me.entries = entries;
}
return entries;
},
loadEntries: function(sync) {
var me = this,
entries = me.getEntries(),
len = entries.length,
start = me.loadStart || 0,
continueLoad, entry, i;
if(sync !== undefined) {
me.sync = sync;
}
me.loaded = me.loaded || 0;
me.loading = me.loading || len;
for(i = start; i < len; i++) {
entry = entries[i];
if(!entry.loaded) {
continueLoad = entries[i].load(me.sync);
} else {
continueLoad = true;
}
if(!continueLoad) {
me.loadStart = i;
entry.onDone(function(){
me.loadEntries(sync);
});
break;
}
}
me.processLoadedEntries();
},
processLoadedEntries: function () {
var me = this,
entries = me.getEntries(),
len = entries.length,
start = me.startIndex || 0,
i, entry;
if (!me.done) {
for (i = start; i < len; i++) {
entry = entries[i];
if (!entry.loaded) {
me.startIndex = i;
return;
}
if (!entry.evaluated) {
entry.evaluate();
}
if (entry.error) {
me.error = true;
}
}
me.notify();
}
},
notify: function () {
var me = this;
if (!me.done) {
var error = me.error,
fn = me[error ? 'failure' : 'success'],
delay = ('delay' in me)
? me.delay
: (error ? 1 : Boot.config.chainDelay),
scope = me.scope || me;
me.done = true;
if (fn) {
if (delay === 0 || delay > 0) {
// Free the stack (and defer the next script)
setTimeout(function () {
fn.call(scope, me);
}, delay);
} else {
fn.call(scope, me);
}
}
me.fireListeners();
Boot.requestComplete(me);
}
},
onDone: function(listener) {
var me = this,
listeners = me.listeners || (me.listeners = []);
if(me.done) {
listener(me);
} else {
listeners.push(listener);
}
},
fireListeners: function() {
var listeners = this.listeners,
listener;
if(listeners) {
//<debug>
_debug("firing request listeners");
//</debug>
while((listener = listeners.shift())) {
listener(this);
}
}
}
};
/*
* The Entry class is a token to manage the load and evaluation
* state of a particular url. It is used to notify all Requests
* interested in this url that the content is available.
*/
function Entry(cfg) {
if(cfg.$isEntry) {
return cfg;
}
//<debug>
_debug("creating entry for " + cfg.url);
//</debug>
var charset = cfg.charset || Boot.config.charset,
manifest = Ext.manifest,
loader = manifest && manifest.loader,
cache = (cfg.cache !== undefined) ? cfg.cache : (loader && loader.cache),
buster, busterParam;
if(cache === undefined) {
cache = !Boot.config.disableCaching;
}
if(cache === false) {
buster = +new Date();
} else if(cache !== true) {
buster = cache;
}
if(buster) {
busterParam = (loader && loader.cacheParam) || Boot.config.disableCachingParam;
buster = busterParam + "=" + buster;
};
_apply(cfg, {
charset: charset,
buster: buster,
requests: []
});
_apply(this, cfg);
};
Entry.prototype = {
$isEntry: true,
done: false,
evaluated: false,
loaded: false,
isCrossDomain: function() {
var me = this;
if(me.crossDomain === undefined) {
//<debug>
_debug("checking " + me.getLoadUrl() + " for prefix " + Boot.origin);
//</debug>
me.crossDomain = (me.getLoadUrl().indexOf(Boot.origin) !== 0);
}
return me.crossDomain;
},
isCss: function () {
var me = this;
if (me.css === undefined) {
me.css = me.url && cssRe.test(me.url);
}
return this.css;
},
getElement: function (tag) {
var me = this,
el = me.el;
if (!el) {
//<debug>
_debug("creating element for " + me.url);
//</debug>
if (me.isCss()) {
tag = tag || "link";
el = doc.createElement(tag);
if(tag == "link") {
el.rel = 'stylesheet';
me.prop = 'href';
} else {
me.prop="textContent";
}
el.type = "text/css";
} else {
tag = tag || "script";
el = doc.createElement(tag);
el.type = 'text/javascript';
me.prop = 'src';
if (Boot.hasAsync) {
el.async = false;
}
}
me.el = el;
}
return el;
},
getLoadUrl: function () {
var me = this,
url = Boot.canonicalUrl(me.url);
if (!me.loadUrl) {
me.loadUrl = !!me.buster
? (url + (url.indexOf('?') === -1 ? '?' : '&') + me.buster)
: url;
}
return me.loadUrl;
},
fetch: function (req) {
var url = this.getLoadUrl(),
async = !!req.async,
complete = req.complete;
Boot.fetch(url, complete, this, async);
},
onContentLoaded: function (response) {
var me = this,
status = response.status,
content = response.content,
exception = response.exception,
url = this.getLoadUrl();
me.loaded = true;
if ((exception || status === 0) && !_environment.phantom) {
me.error =
//<debug>
("Failed loading synchronously via XHR: '" + url +
"'. It's likely that the file is either being loaded from a " +
"different domain or from the local file system where cross " +
"origin requests are not allowed for security reasons. Try " +
"asynchronous loading instead.") ||
//</debug>
true;
me.evaluated = true;
}
else if ((status >= 200 && status < 300) || status === 304
|| _environment.phantom
|| (status === 0 && content.length > 0)
) {
me.content = content;
}
else {
me.error =
//<debug>
("Failed loading synchronously via XHR: '" + url +
"'. Please verify that the file exists. XHR status code: " +
status) ||
//</debug>
true;
me.evaluated = true;
}
},
createLoadElement: function(callback) {
var me = this,
el = me.getElement(),
readyStateChange = function(){
if (this.readyState === 'loaded' || this.readyState === 'complete') {
if(callback) {
callback();
}
}
},
errorFn = function() {
me.error = true;
if(callback) {
callback();
}
};
me.preserve = true;
el.onerror = errorFn;
if(Boot.hasReadyState) {
el.onreadystatechange = readyStateChange;
} else {
el.onload = callback;
}
// IE starts loading here
el[me.prop] = me.getLoadUrl();
},
onLoadElementReady: function() {
Boot.getHead().appendChild(this.getElement());
this.evaluated = true;
},
inject: function (content, asset) {
//<debug>
_debug("injecting content for " + this.url);
//</debug>
var me = this,
head = Boot.getHead(),
url = me.url,
key = me.key,
base, el, ieMode, basePath;
if (me.isCss()) {
me.preserve = true;
basePath = key.substring(0, key.lastIndexOf("/") + 1);
base = doc.createElement('base');
base.href = basePath;
if(head.firstChild) {
head.insertBefore(base, head.firstChild);
} else {
head.appendChild(base);
}
// reset the href attribute to cuase IE to pick up the change
base.href = base.href;
if (url) {
content += "\n/*# sourceURL=" + key + " */";
}
// create element after setting base
el = me.getElement("style");
ieMode = ('styleSheet' in el);
head.appendChild(base);
if(ieMode) {
head.appendChild(el);
el.styleSheet.cssText = content;
} else {
el.textContent = content;
head.appendChild(el);
}
head.removeChild(base);
} else {
// Debugger friendly, file names are still shown even though they're
// eval'ed code. Breakpoints work on both Firebug and Chrome's Web
// Inspector.
if (url) {
content += "\n//# sourceURL=" + key;
}
Ext.globalEval(content);
}
return me;
},
loadCrossDomain: function() {
var me = this,
complete = function(){
me.loaded = me.evaluated = me.done = true;
me.notifyRequests();
};
if(me.isCss()) {
me.createLoadElement();
me.evaluateLoadElement();
complete();
} else {
me.createLoadElement(function(){
complete();
});
me.evaluateLoadElement();
// at this point, we need sequential evaluation,
// which means we can't advance the load until
// this entry has fully completed
return false;
}
return true;
},
loadElement: function() {
var me = this,
complete = function(){
me.loaded = me.evaluated = me.done = true;
me.notifyRequests();
};
if(me.isCss()) {
return me.loadCrossDomain();
} else {
me.createLoadElement(function(){
complete();
});
me.evaluateLoadElement();
}
return true;
},
loadSync: function() {
var me = this;
me.fetch({
async: false,
complete: function (response) {
me.onContentLoaded(response);
}
});
me.evaluate();
me.notifyRequests();
},
load: function (sync) {
var me = this;
if (!me.loaded) {
if(me.loading) {
// if we're calling back through load and we're loading but haven't
// yet loaded, then we should be in a sequential, cross domain
// load scenario which means we can't continue the load on the
// request until this entry has fully evaluated, which will mean
// loaded = evaluated = done = true in one step. For css files, this
// will happen immediately upon <link> element creation / insertion,
// but <script> elements will set this upon load notification
return false;
}
me.loading = true;
// for async modes, we have some options
if (!sync) {
// if cross domain, just inject the script tag and let the onload
// events drive the progression
if(me.isCrossDomain()) {
return me.loadCrossDomain();
}
// for IE, use the readyStateChange allows us to load scripts in parallel
// but serialize the evaluation by appending the script node to the
// document
else if(!me.isCss() && Boot.hasReadyState) {
me.createLoadElement(function () {
me.loaded = true;
me.notifyRequests();
});
}
else if(Boot.useElements) {
return me.loadElement();
}
// for other browsers, just ajax the content down in parallel, and use
// globalEval to serialize evaluation
else {
me.fetch({
async: !sync,
complete: function (response) {
me.onContentLoaded(response);
me.notifyRequests();
}
});
}
}
// for sync mode in js, global eval FTW. IE won't honor the comment
// paths in the debugger, so eventually we need a sync mode for IE that
// uses the readyStateChange mechanism
else {
me.loadSync();
}
}
// signal that the load process can continue
return true;
},
evaluateContent: function () {
this.inject(this.content);
this.content = null;
},
evaluateLoadElement: function() {
Boot.getHead().appendChild(this.getElement());
},
evaluate: function () {
var me = this;
if(!me.evaluated) {
if(me.evaluating) {
return;
}
me.evaluating = true;
if(me.content !== undefined) {
me.evaluateContent();
} else if(!me.error) {
me.evaluateLoadElement();
}
me.evaluated = me.done = true;
me.cleanup();
}
},
/*
* @private
*/
cleanup: function () {
var me = this,
el = me.el,
prop;
if (!el) {
return;
}
if (!me.preserve) {
me.el = null;
el.parentNode.removeChild(el); // Remove, since its useless now
for (prop in el) {
try {
if (prop !== me.prop) {
// If we set the src property to null IE
// will try and request a script at './null'
el[prop] = null;
}
delete el[prop]; // and prepare for GC
} catch (cleanEx) {
//ignore
}
}
}
// Setting to null can cause exceptions if IE ever needs to call these
// again (like onreadystatechange). This emptyFn has nothing locked in
// closure scope so it is about as safe as null for memory leaks.
el.onload = el.onerror = el.onreadystatechange = emptyFn;
},
notifyRequests: function () {
var requests = this.requests,
len = requests.length,
i, request;
for (i = 0; i < len; i++) {
request = requests[i];
request.processLoadedEntries();
}
if(this.done) {
this.fireListeners();
}
},
onDone: function(listener) {
var me = this,
listeners = me.listeners || (me.listeners = []);
if(me.done) {
listener(me);
} else {
listeners.push(listener);
}
},
fireListeners: function() {
var listeners = this.listeners,
listener;
if(listeners && listeners.length > 0) {
//<debug>
_debug("firing event listeners for url " + this.url);
//</debug>
while((listener = listeners.shift())) {
listener(this);
}
}
}
};
/*
* Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally
* dynamically loaded scripts have an extra query parameter appended to avoid stale
* cached scripts. This method can be used to disable this mechanism, and is primarily
* useful for testing. This is done using a cookie.
* @param {Boolean} disable True to disable the cache buster.
* @param {String} [path="/"] An optional path to scope the cookie.
*/
Ext.disableCacheBuster = function (disable, path) {
var date = new Date();
date.setTime(date.getTime() + (disable ? 10 * 365 : -1) * 24 * 60 * 60 * 1000);
date = date.toGMTString();
doc.cookie = 'ext-cache=1; expires=' + date + '; path=' + (path || '/');
};
//<if nonBrowser>
if (_environment.node) {
Boot.prototype.load = Boot.prototype.loadSync = function (request) {
// @TODO
require(filePath);
onLoad.call(scope);
};
Boot.prototype.init = emptyFn;
}
//</if>
Boot.init();
return Boot;
// NOTE: We run the eval at global scope to protect the body of the function and allow
// compressors to still process it.
}(function () {
}));//(eval("/*@cc_on!@*/!1"));
/*
* This method evaluates the given code free of any local variable. This
* will be at global scope, in others it will be in a function.
* @parma {String} code The code to evaluate.
* @private
* @method
*/
Ext.globalEval = Ext.globalEval || (this.execScript
? function (code) { execScript(code); }
: function ($$code) { eval.call(window, $$code); });
//<feature legacyBrowser>
/*
* Only IE8 & IE/Quirks lack Function.prototype.bind so we polyfill that here.
*/
if (!Function.prototype.bind) {
(function () {
var slice = Array.prototype.slice,
// To reduce overhead on call of the bound fn we have two flavors based on
// whether we have args to prepend or not:
bind = function (me) {
var args = slice.call(arguments, 1),
method = this;
if (args.length) {
return function () {
var t = arguments;
// avoid the slice/concat if the caller does not supply args
return method.apply(me, t.length ? args.concat(slice.call(t)) : args);
};
}
// this is the majority use case - just fn.bind(this) and no args
args = null;
return function () {
return method.apply(me, arguments);
};
};
Function.prototype.bind = bind;
bind.$extjs = true; // to detect this polyfill if one want to improve it
}());
}
//</feature>
//</editor-fold>