Форк 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.

689 lines
23 KiB

/**
* This class manages ready detection and handling. Direct use of this class is not
* recommended. Instead use `Ext.onReady`:
*
* Ext.onReady(function () {
* // DOM and Framework are ready...
* });
*
* ## DOM Ready
*
* The lowest-level of readiness is DOM readiness. This level implies only that the document
* body exists. Many things require the DOM to be ready for manipulation. If that is all
* that is required, the `Ext.onDocumentReady` method can be called to register a callback
* to be called as soon as the DOM is ready:
*
* Ext.onDocumentReady(function () {
* // the document body is ready
* });
*
* ## Framework Ready
*
* In production builds of applications it is common to have all of the code loaded before
* DOM ready, so the need to wait for "onReady" is often confused with only that concern.
* This is easy to understand, at least in part because historically `Ext.onReady` only
* waited for DOM ready.
*
* With the introduction of `Ext.Loader`, however, it became common for DOM ready to occur
* in the middle of dynamically loading code. If application code were executed at that
* time, any use of the yet-to-be-loaded classes would throw errors. As a consequence of
* this, the `Ext.onReady` mechanism was extended to wait for both DOM ready *and* all of
* the required classes to be loaded.
*
* When the framework enters or leaves a state where it is not ready (for example, the
* first dynamic load is requested or last load completes), `Ext.env.Ready` is informed.
* For example:
*
* Ext.env.Ready.block();
*
* //...
*
* Ext.env.Ready.unblock();
*
* When there are no blocks and the DOM is ready, the Framework is ready and the "onReady"
* callbacks are called.
*
* Priority can be used to control the ordering of onReady listeners, for example:
*
* Ext.onReady(function() {
*
* }, null, {
* priority: 100
* });
*
* Ready listeners with higher priorities will run sooner than those with lower priorities,
* the default priority being `0`. Internally the framework reserves priorities of 1000
* or greater, and -1000 or lesser for onReady handlers that must run before or after
* any application code. Applications should stick to using priorities in the -999 - 999
* range. The following priorities are currently in use by the framework:
*
* - Element_scroll rtl override: `1001`
* - Event system initialization: `2000`
* - Ext.dom.Element: `1500`
*
* @class Ext.env.Ready
* @singleton
* @private
* @since 5.0.0
*/
Ext.env.Ready = {
// @define Ext.env.Ready
// @require Ext.env.Browser
// @require Ext.env.OS
// @require Ext.env.Feature
/**
* @property {Number} blocks The number of Framework readiness blocks.
* @private
*/
blocks: (location.search || '').indexOf('ext-pauseReadyFire') > 0 ? 1 : 0,
/**
* @property {Number} bound This property stores the state of event listeners bound
* to the document or window to detect ready state.
* @private
*/
bound: 0,
/**
* @property {Number} [delay=1]
* This allows the DOM listener thread to complete (usually desirable with mobWebkit,
* Gecko) before firing the entire onReady chain (high stack load on Loader). For mobile
* devices when running from Home Screen, the splash screen will not disappear until
* all external resource requests finish. This delay clears the splash screen.
* @private
*/
delay: 1,
//<debug>
/**
* @property {Event[]} events An array of events that have triggered ready state. This
* is for diagnostic purposes only and is only available in debug builds.
* An array
* @private
*/
events: [],
//</debug>
/**
* @property {Boolean} firing This property is `true` when we currently calling the
* listeners.
* @private
*/
firing: false,
/**
* @property {Number} generation A counter of the number of mutations of `listeners`.
* @private
*/
generation: 0,
/**
* @property {Object[]} listeners The set of listeners waiting for ready.
* @private
*/
listeners: [],
/**
* @property {Number} nextId A counter so we can assign listeners an `id` to keep
* them in FIFO order.
* @private
*/
nextId: 0,
/**
* @property {Number} sortGeneration A captured value of `generation` that indicates
* when the `listeners` were last sorted.
* @private
*/
sortGeneration: 0,
/**
* @property {Number} state
* Holds the current ready state as managed by this class. The values possible are:
*
* * 0 - Not ready.
* * 1 - Ready detected but listeners are not yet notified.
* * 2 - Ready detected and listeners are notified. See also `firing`.
*
* @private
*/
state: 0,
/**
* @property {Object} timer The handle from `setTimeout` for the delayed notification
* of ready.
* @private
*/
timer: null,
/**
* Binds the appropriate browser event for checking if the DOM has loaded.
* @private
*/
bind: function () {
var me = Ext.env.Ready,
doc = document;
if (!me.bound) {
// Test scenario where load is dynamic AFTER window.load
if (doc.readyState === 'complete') {
// Firefox4+ got support for this state, others already do.
me.onReadyEvent({
type: doc.readyState || 'body'
});
} else {
me.bound = 1;
if (Ext.browser.is.PhoneGap && !Ext.os.is.Desktop) {
me.bound = 2;
doc.addEventListener('deviceready', me.onReadyEvent, false);
}
doc.addEventListener('DOMContentLoaded', me.onReadyEvent, false);
window.addEventListener('load', me.onReadyEvent, false);
}
}
},
block: function () {
++this.blocks;
Ext.isReady = false;
},
/**
* This method starts the process of firing the ready event. This may be delayed based
* on the `delay` property.
* @private
*/
fireReady: function () {
var me = Ext.env.Ready;
if (!me.state) {
Ext._readyTime = Ext.now();
Ext.isDomReady = true;
me.state = 1;
// As soon as we transition to domready, complete the feature detection:
Ext.feature.detect(true);
if (!me.delay) {
me.handleReady();
} else if (navigator.standalone) {
// When running from Home Screen, the splash screen will not disappear
// until all external resource requests finish.
// The first timeout clears the splash screen
// The second timeout allows inital HTML content to be displayed
me.timer = Ext.defer(function() {
me.timer = null;
me.handleReadySoon();
}, 1);
} else {
me.handleReadySoon();
}
}
},
/**
* This method iterates over the `listeners` and invokes them. This advances the
* `state` from 1 to 2 and ensure the proper subset of `listeners` are invoked.
* @private
*/
handleReady: function () {
var me = this;
if (me.state === 1) {
me.state = 2;
Ext._beforeReadyTime = Ext.now();
me.invokeAll();
Ext._afterReadytime = Ext.now();
}
},
/**
* This method is called to schedule a call to `handleReady` using a `setTimeout`. It
* ensures that only one timer is pending.
* @param {Number} [delay] If passed, this overrides the `delay` property.
* @private
*/
handleReadySoon: function (delay) {
var me = this;
if (!me.timer) {
me.timer = Ext.defer(function () {
me.timer = null;
me.handleReady();
}, delay || me.delay);
}
},
/**
* This method invokes the given `listener` instance based on its options.
* @param {Object} listener
*/
invoke: function (listener) {
var delay = listener.delay;
if (delay) {
Ext.defer(listener.fn, delay, listener.scope);
} else {
if (Ext.elevateFunction) {
Ext.elevateFunction(listener.fn, listener.scope);
} else {
listener.fn.call(listener.scope);
}
}
},
/**
* Invokes as many listeners as are appropriate given the current state. This should
* only be called when DOM ready is achieved. The remaining business of `blocks` is
* handled here.
*/
invokeAll: function() {
if (Ext.elevateFunction) {
Ext.elevateFunction(this.doInvokeAll, this);
} else {
this.doInvokeAll();
}
},
doInvokeAll: function () {
var me = this,
listeners = me.listeners,
listener;
if (!me.blocks) {
// Since DOM is ready and we have no blocks, we mark the framework as ready.
Ext.isReady = true;
}
me.firing = true;
// NOTE: We cannot cache this length because each time through the loop a callback
// may have added new callbacks.
while (listeners.length) {
if (me.sortGeneration !== me.generation) {
me.sortGeneration = me.generation;
// This will happen just once on the first pass... if nothing is being
// added as we call the callbacks. This sorts the listeners such that the
// highest priority listener is at the *end* of the array ... so we can
// use pop (as opposed to shift) to extract it.
listeners.sort(me.sortFn);
}
listener = listeners.pop();
if (me.blocks && !listener.dom) {
// If we are blocked (i.e., only DOM ready) and this listener is not a
// DOM-ready listener we have reached the end of the line. The remaining
// listeners are Framework ready listeners.
listeners.push(listener);
break;
}
me.invoke(listener);
}
me.firing = false;
},
/**
* This method wraps the given listener pieces in a proper object for the `listeners`
* array and `invoke` methods.
* @param {Function} fn The method to call.
* @param {Object} [scope] The scope (`this` reference) in which the `fn` executes.
* Defaults to the browser window.
* @param {Object} [options] An object with extra options.
* @param {Number} [options.delay=0] A number of milliseconds to delay.
* @param {Number} [options.priority=0] Relative priority of this callback. A larger
* number will result in the callback being sorted before the others. Priorities
* 1000 or greater and -1000 or lesser are reserved for internal framework use only.
* @param {Boolean} [options.dom=false] Pass `true` to only wait for DOM ready, `false`
* means full Framework and DOM readiness.
* @return {Object} The listener instance.
* @private
*/
makeListener: function (fn, scope, options) {
var ret = {
fn: fn,
id: ++this.nextId, // so sortFn can respect FIFO
scope: scope,
dom: false,
priority: 0
};
if (options) {
Ext.apply(ret, options);
}
ret.phase = ret.dom ? 0 : 1; // to simplify the sortFn
return ret;
},
/**
* Adds a listener to be notified when the document is ready (before onload and before
* images are loaded).
*
* @param {Function} fn The method to call.
* @param {Object} [scope] The scope (`this` reference) in which the `fn` executes.
* Defaults to the browser window.
* @param {Object} [options] An object with extra options.
* @param {Number} [options.delay=0] A number of milliseconds to delay.
* @param {Number} [options.priority=0] Relative priority of this callback. A larger
* number will result in the callback being sorted before the others. Priorities
* 1000 or greater and -1000 or lesser are reserved for internal framework use only.
* @param {Boolean} [options.dom=false] Pass `true` to only wait for DOM ready, `false`
* means full Framework and DOM readiness.
* @private
*/
on: function (fn, scope, options) {
var me = Ext.env.Ready,
listener = me.makeListener(fn, scope, options);
if (me.state === 2 && !me.firing && (listener.dom || !me.blocks)) {
// If we are DOM ready (state === 2) and not currently in invokeAll (!firing)
// and this listener is ready to call (either a DOM ready listener or there
// are no blocks), then we need to invoke the listener now.
//
// Otherwise we can fall to the else block and push the listener. The eventual
// (or currently executing) call to handleReady or unblock will trigger its
// delivery in proper priority order.
me.invoke(listener);
} else {
me.listeners.push(listener);
++me.generation;
if (!me.bound) {
// If we have never bound then bind the ready event now. If we have unbound
// the event then me.bound == -1 and we don't want to rebind it as the DOM
// is ready.
me.bind();
}
}
},
/**
* This is a generic event handler method attached to all of the various events that
* may indicate ready state. The first call to this method indicates ready state has
* been achieved.
* @param {Event} [ev] The event instance.
* @private
*/
onReadyEvent: function (ev) {
var me = Ext.env.Ready;
if (Ext.elevateFunction) {
Ext.elevateFunction(me.doReadyEvent, me, arguments);
} else {
me.doReadyEvent(ev);
}
},
doReadyEvent: function (ev) {
var me = this;
//<debug>
if (ev && ev.type) {
me.events.push(ev);
}
//</debug>
if (me.bound > 0) {
me.unbind();
me.bound = -1; // NOTE: *not* 0 or false - we never want to rebind!
}
if (!me.state) {
me.fireReady();
}
},
/**
* Sorts the `listeners` array by `phase` and `priority` such that the first listener
* to fire can be determined using `pop` on the `listeners` array.
* @private
*/
sortFn: function (a, b) {
return -((a.phase - b.phase) || (b.priority - a.priority) || (a.id - b.id));
},
unblock: function () {
var me = this;
if (me.blocks) {
if (! --me.blocks) {
if (me.state === 2 && !me.firing) {
// We have already finished handleReady (the DOM ready handler) so
// this trigger just needs to dispatch all the remaining listeners.
me.invokeAll();
}
// if we are currently firing then invokeAll will pick up the Framework
// ready listeners automatically.
//
// If me.state < 2 then we are waiting for DOM ready so it will eventually
// call handleReady and invokeAll when everything is ready.
}
}
},
/**
* This method is called to remove all event listeners that may have been set up to
* detect ready state.
* @private
*/
unbind: function () {
var me = this,
doc = document;
if (me.bound > 1) {
doc.removeEventListener('deviceready', me.onReadyEvent, false);
}
doc.removeEventListener('DOMContentLoaded', me.onReadyEvent, false);
window.removeEventListener('load', me.onReadyEvent, false);
}
};
(function () {
var Ready = Ext.env.Ready;
//<feature legacyBrowser>
/*
* EXTJS-13522
* Although IE 9 has the DOMContentLoaded event available, usage of that causes
* timing issues when attempting to access document.namespaces (VmlCanvas.js).
* Consequently, even in IE 9 we need to use the legacy bind override for ready
* detection. This defers ready firing enough to allow access to the
* document.namespaces property.
*
* NOTE: this issue is very timing sensitive, and typically only displays itself
* when there is a large amount of latency between the browser and the server, and
* when testing against a built page (ext-all.js) and not a dev mode page.
*/
if (Ext.isIE9m) {
/* Customized implementation for Legacy IE. The default implementation is
* configured for use with all other 'standards compliant' agents.
* References: http://javascript.nwbox.com/IEContentLoaded/
* licensed courtesy of http://developer.yahoo.com/yui/license.html
*/
Ext.apply(Ready, {
/**
* Timer for doScroll polling
* @private
*/
scrollTimer: null,
/**
* @private
*/
readyStatesRe : /complete/i,
/**
* This strategy has minimal benefits for Sencha solutions that build
* themselves (ie. minimal initial page markup). However, progressively-enhanced
* pages (with image content and/or embedded frames) will benefit the most
* from it. Browser timer resolution is too poor to ensure a doScroll check
* more than once on a page loaded with minimal assets (the readystatechange
* event 'complete' usually beats the doScroll timer on a 'lightly-loaded'
* initial document).
* @private
*/
pollScroll : function() {
var scrollable = true;
try {
document.documentElement.doScroll('left');
} catch(e) {
scrollable = false;
}
// on IE8, when running within an iFrame, document.body is not immediately
// available
if (scrollable && document.body) {
Ready.onReadyEvent({
type: 'doScroll'
});
} else {
// Minimize thrashing --
// adjusted for setTimeout's close-to-minimums (not too low),
// as this method SHOULD always be called once initially
Ready.scrollTimer = Ext.defer(Ready.pollScroll, 20);
}
return scrollable;
},
bind: function () {
if (Ready.bound) {
return;
}
var doc = document,
topContext;
// See if we are in an IFRAME? (doScroll ineffective here)
try {
topContext = window.frameElement === undefined;
} catch(e) {
// If we throw an exception, it means we're probably getting access
// denied, which means we're in an iframe cross domain.
}
if (!topContext || !doc.documentElement.doScroll) {
Ready.pollScroll = Ext.emptyFn; //then noop this test altogether
}
else if (Ready.pollScroll()) { // starts scroll polling if necessary
return;
}
if (doc.readyState === 'complete') {
// Loaded AFTER initial document write/load...
Ready.onReadyEvent({
type: 'already ' + (doc.readyState || 'body')
});
} else {
doc.attachEvent('onreadystatechange', Ready.onReadyStateChange);
window.attachEvent('onload', Ready.onReadyEvent);
Ready.bound = 1;
}
},
unbind : function () {
document.detachEvent('onreadystatechange', Ready.onReadyStateChange);
window.detachEvent('onload', Ready.onReadyEvent);
if (Ext.isNumber(Ready.scrollTimer)) {
clearTimeout(Ready.scrollTimer);
Ready.scrollTimer = null;
}
},
/**
* This event handler is called when the readyState changes.
* @private
*/
onReadyStateChange: function() {
var state = document.readyState;
if (Ready.readyStatesRe.test(state)) {
Ready.onReadyEvent({
type: state
});
}
}
});
}
//</feature>
/**
* @property {Boolean} isDomReady
* `true` when the document body is ready for use.
* @member Ext
* @readonly
*/
/**
* @property {Boolean} isReady
* `true` when `isDomReady` is true and the Framework is ready for use.
* @member Ext
* @readonly
*/
/**
* @method onDocumentReady
* @member Ext
* Adds a listener to be notified when the document is ready (before onload and before
* images are loaded).
*
* @param {Function} fn The method to call.
* @param {Object} [scope] The scope (`this` reference) in which the handler function
* executes. Defaults to the browser window.
* @param {Object} [options] An object with extra options.
* @param {Number} [options.delay=0] A number of milliseconds to delay.
* @param {Number} [options.priority=0] Relative priority of this callback. A larger
* number will result in the callback being sorted before the others. Priorities
* 1000 or greater and -1000 or lesser are reserved for internal framework use only.
* @private
*/
Ext.onDocumentReady = function (fn, scope, options) {
var opt = {
dom: true
};
if (options) {
Ext.apply(opt, options);
}
Ready.on(fn, scope, opt);
};
/**
* @method onReady
* @member Ext
* Adds a listener to be notified when the document is ready (before onload and before
* images are loaded).
*
* @param {Function} fn The method to call.
* @param {Object} [scope] The scope (`this` reference) in which the handler function
* executes. Defaults to the browser window.
* @param {Object} [options] An object with extra options.
* @param {Number} [options.delay=0] A number of milliseconds to delay.
* @param {Number} [options.priority=0] Relative priority of this callback. A larger
* number will result in the callback being sorted before the others. Priorities
* 1000 or greater and -1000 or lesser are reserved for internal framework use only.
* @param {Boolean} [options.dom=false] Pass `true` to only wait for DOM ready, `false`
* means full Framework and DOM readiness.
* numbers are reserved.
*/
Ext.onReady = function (fn, scope, options) {
Ready.on(fn, scope, options);
};
// A shortcut method for onReady with a high priority
Ext.onInternalReady = function(fn, scope, options) {
Ready.on(fn, scope, Ext.apply({
priority: 1000
}, options));
}
Ready.bind();
}());