/** * @class Ext.EventManager * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides * several useful events directly. * See {@link Ext.EventObject} for more details on normalized event objects. * @singleton */ Ext.EventManager = { optionsRe: /^(?:capture|scope|delay|buffer|single|stopEvent|disableLocking|preventDefault|stopPropagation|normalized|args|delegate|horizontal|vertical|dragThreshold|holdThreshold|doubleTapThreshold|cancelThreshold|singleTapThreshold|fireClickEvent)$/, touchRe: /^(?:pinch|pinchstart|pinchend|tap|singletap|doubletap|swipe|swipeleft|swiperight|drag|dragstart|dragend|touchdown|touchstart|touchmove|touchend|taphold|tapstart|tapcancel)$/i, /** * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version. * @param {String/HTMLElement} el The html element or id to assign the event handler to. * @param {String} eventName The name of the event to listen for. * @param {Function} handler The handler function the event invokes. This function is passed * the following parameters:
this
reference) in which the handler function is executed. Defaults to the Element.
* @param {Object} options (optional) An object containing handler configuration properties.
* This may contain any of the following properties:this
reference) in which the handler function is executed. Defaults to the Element.See {@link Ext.Element#addListener} for examples of how to use these options.
*/ addListener : function(element, eventName, fn, scope, o){ // handle our listener config object syntax if (Ext.isObject(eventName)) { this.handleListenerConfig(element, eventName); return; } var dom = Ext.getDom(element); // if the element doesnt exist throw an error if (!dom){ throw "Error listening for \"" + eventName + '\". Element "' + element + '" doesn\'t exist.'; } if (!fn) { throw 'Error listening for "' + eventName + '". No handler function specified'; } var touch = this.touchRe.test(eventName); // create the wrapper function var wrap = this.createListenerWrap(dom, eventName, fn, scope, o, touch); // add all required data into the event cache this.getEventListenerCache(dom, eventName).push({ fn: fn, wrap: wrap, scope: scope }); if (touch) { Ext.gesture.Manager.addEventListener(dom, eventName, wrap, o); } else { // now add the event listener to the actual element! o = o || {}; dom.addEventListener(eventName, wrap, o.capture || false); } }, /** * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version. * @param {String/HTMLElement} el The id or html element from which to remove the listener. * @param {String} eventName The name of the event. * @param {Function} fn The handler function to remove. This must be a reference to the function passed into the {@link #addListener} call. * @param {Object} scope If a scope (this
reference) was specified when the listener was added,
* then this must refer to the same object.
*/
removeListener : function(element, eventName, fn, scope) {
// handle our listener config object syntax
if (Ext.isObject(eventName)) {
this.handleListenerConfig(element, eventName, true);
return;
}
var dom = Ext.getDom(element),
cache = this.getEventListenerCache(dom, eventName),
i = cache.length, j,
listener, wrap, tasks;
while (i--) {
listener = cache[i];
if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
wrap = listener.wrap;
// clear buffered calls
if (wrap.task) {
clearTimeout(wrap.task);
delete wrap.task;
}
// clear delayed calls
j = wrap.tasks && wrap.tasks.length;
if (j) {
while (j--) {
clearTimeout(wrap.tasks[j]);
}
delete wrap.tasks;
}
if (this.touchRe.test(eventName)) {
Ext.gesture.Manager.removeEventListener(dom, eventName, wrap);
}
else {
// now add the event listener to the actual element!
dom.removeEventListener(eventName, wrap, false);
}
// remove listener from cache
cache.splice(i, 1);
}
}
},
/**
* Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
* directly on an Element in favor of calling this version.
* @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
*/
removeAll : function(element){
var dom = Ext.getDom(element),
cache = this.getElementEventCache(dom),
ev;
for (ev in cache) {
if (!cache.hasOwnProperty(ev)) {
continue;
}
this.removeListener(dom, ev);
}
Ext.cache[dom.id].events = {};
},
purgeElement : function(element, recurse, eventName) {
var dom = Ext.getDom(element),
i = 0, len;
if(eventName) {
this.removeListener(dom, eventName);
}
else {
this.removeAll(dom);
}
if(recurse && dom && dom.childNodes) {
for(len = element.childNodes.length; i < len; i++) {
this.purgeElement(element.childNodes[i], recurse, eventName);
}
}
},
handleListenerConfig : function(element, config, remove) {
var key, value;
// loop over all the keys in the object
for (key in config) {
if (!config.hasOwnProperty(key)) {
continue;
}
// if the key is something else then an event option
if (!this.optionsRe.test(key)) {
value = config[key];
// if the value is a function it must be something like click: function(){}, scope: this
// which means that there might be multiple event listeners with shared options
if (Ext.isFunction(value)) {
// shared options
this[(remove ? 'remove' : 'add') + 'Listener'](element, key, value, config.scope, config);
}
// if its not a function, it must be an object like click: {fn: function(){}, scope: this}
else {
// individual options
this[(remove ? 'remove' : 'add') + 'Listener'](element, key, config.fn, config.scope, config);
}
}
}
},
getId : function(element) {
// if we bind listeners to either the document or the window
// we have to put them in their own id cache since they both
// can't get id's on the actual element
var skip = false,
id;
element = Ext.getDom(element);
if (element === document || element === window) {
skip = true;
}
id = Ext.id(element);
if (!Ext.cache[id]){
Ext.Element.addToCache(new Ext.Element(element), id);
if(skip){
Ext.cache[id].skipGarbageCollection = true;
}
}
return id;
},
// private
createListenerWrap : function(dom, ename, fn, scope, o, touch) {
o = !Ext.isObject(o) ? {} : o;
var f = ["if(!window.Ext) {return;}"];
if (touch) {
f.push('e = new Ext.TouchEventObjectImpl(e, args);');
}
else {
if(o.buffer || o.delay) {
f.push('e = new Ext.EventObjectImpl(e);');
} else {
f.push('e = Ext.EventObject.setEvent(e);');
}
}
if (o.delegate) {
f.push('var t = e.getTarget("' + o.delegate + '", this);');
f.push('if(!t) {return;}');
} else {
f.push('var t = e.target;');
}
if (o.target) {
f.push('if(e.target !== o.target) {return;}');
}
if(o.stopEvent) {
f.push('e.stopEvent();');
} else {
if(o.preventDefault) {
f.push('e.preventDefault();');
}
if(o.stopPropagation) {
f.push('e.stopPropagation();');
}
}
if(o.normalized === false) {
f.push('e = e.browserEvent;');
}
if(o.buffer) {
f.push('(wrap.task && clearTimeout(wrap.task));');
f.push('wrap.task = setTimeout(function(){');
}
if(o.delay) {
f.push('wrap.tasks = wrap.tasks || [];');
f.push('wrap.tasks.push(setTimeout(function(){');
}
// finally call the actual handler fn
f.push('fn.call(scope || dom, e, t, o);');
if(o.single) {
f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
}
if(o.delay) {
f.push('}, ' + o.delay + '));');
}
if(o.buffer) {
f.push('}, ' + o.buffer + ');');
}
var gen = new Function('e', 'o', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join("\n"));
return function(e, args) {
gen.call(dom, e, o, fn, scope, ename, dom, arguments.callee, args);
};
},
getEventListenerCache : function(element, eventName) {
var eventCache = this.getElementEventCache(element);
return eventCache[eventName] || (eventCache[eventName] = []);
},
getElementEventCache : function(element) {
var elementCache = Ext.cache[this.getId(element)];
return elementCache.events || (elementCache.events = {});
},
/**
* Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
* accessed shorthanded as Ext.onReady().
* @param {Function} fn The method the event invokes.
* @param {Object} scope (optional) The scope (this
reference) in which the handler function executes. Defaults to the browser window.
* @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
* {single: true}
be used so that the handler is removed on first invocation.
*/
onDocumentReady : function(fn, scope, options){
var me = this,
readyEvent = me.readyEvent,
intervalId;
if(Ext.isReady){ // if it already fired
readyEvent || (readyEvent = new Ext.util.Event());
readyEvent.addListener(fn, scope, options);
readyEvent.fire();
readyEvent.listeners = []; // clearListeners no longer compatible. Force single: true?
}
else {
if(!readyEvent) {
readyEvent = me.readyEvent = new Ext.util.Event();
// the method that will actually fire the event and clean up any listeners and intervals
var fireReady = function() {
Ext.isReady = true;
//document.removeEventListener('DOMContentLoaded', arguments.callee, false);
window.removeEventListener('load', arguments.callee, false);
// remove interval if there is one
if (intervalId) {
clearInterval(intervalId);
}
// Put this in a timeout to give the browser a chance to hide address
// bars or do other things that would screw up viewport measurements
setTimeout(function() {
Ext.supports.init();
//Ext.TouchEventManager.init();
Ext.gesture.Manager.init();
Ext.orientation = Ext.Element.getOrientation();
// fire the ready event!!
readyEvent.fire({
orientation: Ext.orientation,
width: Ext.Element.getViewportWidth(),
height: Ext.Element.getViewportHeight()
});
readyEvent.listeners = [];
}, 50);
};
// for browsers that support DOMContentLoaded
//document.addEventListener('DOMContentLoaded', fireReady, false);
// // even though newer versions support DOMContentLoaded, we have to be sure
intervalId = setInterval(function(){
if(/loaded|complete/.test(document.readyState)) {
clearInterval(intervalId);
intervalId = null;
fireReady();
}
}, 10);
// final fallback method
window.addEventListener('load', fireReady, false);
}
options = options || {};
options.delay = options.delay || 1;
readyEvent.addListener(fn, scope, options);
}
},
/**
* Adds a listener to be notified when the browser window is resized and provides resize event buffering (50 milliseconds),
* passes new viewport width and height to handlers.
* @param {Function} fn The handler function the window resize event invokes.
* @param {Object} scope The scope (this
reference) in which the handler function executes. Defaults to the browser window.
* @param {boolean} options Options object as passed to {@link Ext.Element#addListener}
*/
onWindowResize : function(fn, scope, options) {
var me = this,
resizeEvent = me.resizeEvent;
if(!resizeEvent){
me.resizeEvent = resizeEvent = new Ext.util.Event();
var onResize = function() {
resizeEvent.fire(Ext.Element.getViewportWidth(), Ext.Element.getViewportHeight());
};
this.addListener(window, 'resize', onResize, this);
}
resizeEvent.addListener(fn, scope, options);
},
onOrientationChange : function(fn, scope, options) {
var me = this,
orientationEvent = me.orientationEvent;
if (!orientationEvent) {
me.orientationEvent = orientationEvent = new Ext.util.Event();
var onOrientationChange = function(viewport, size) {
Ext.orientation = Ext.Viewport.getOrientation();
orientationEvent.fire(Ext.orientation, size.width, size.height);
};
Ext.Viewport.on('resize', onOrientationChange, this);
}
orientationEvent.addListener(fn, scope, options);
},
unOrientationChange : function(fn, scope, options) {
var me = this,
orientationEvent = me.orientationEvent;
if (orientationEvent) {
orientationEvent.removeListener(fn, scope, options);
}
}
};
/**
* Appends an event handler to an element. Shorthand for {@link #addListener}.
* @param {String/HTMLElement} el The html element or id to assign the event handler to
* @param {String} eventName The name of the event to listen for.
* @param {Function} handler The handler function the event invokes.
* @param {Object} scope (optional) (this
reference) in which the handler function executes. Defaults to the Element.
* @param {Object} options (optional) An object containing standard {@link #addListener} options
* @member Ext.EventManager
* @method on
*/
Ext.EventManager.on = Ext.EventManager.addListener;
/**
* Removes an event handler from an element. Shorthand for {@link #removeListener}.
* @param {String/HTMLElement} el The id or html element from which to remove the listener.
* @param {String} eventName The name of the event.
* @param {Function} fn The handler function to remove. This must be a reference to the function passed into the {@link #on} call.
* @param {Object} scope If a scope (this
reference) was specified when the listener was added,
* then this must refer to the same object.
* @member Ext.EventManager
* @method un
*/
Ext.EventManager.un = Ext.EventManager.removeListener;
/**
* Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
* @param {Function} fn The method the event invokes.
* @param {Object} scope (optional) The scope (this
reference) in which the handler function executes. Defaults to the browser window.
* @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
* {single: true}
be used so that the handler is removed on first invocation.
* @member Ext
* @method onReady
*/
Ext.onReady = Ext.EventManager.onDocumentReady;
Ext.EventObjectImpl = Ext.extend(Object, {
constructor : function(e) {
if (e) {
this.setEvent(e.browserEvent || e);
}
},
/** @private */
setEvent : function(e){
var me = this;
if (e == me || (e && e.browserEvent)){ // already wrapped
return e;
}
me.browserEvent = e;
if(e) {
me.type = e.type;
// cache the target for the delayed and or buffered events
var node = e.target;
me.target = node && node.nodeType == 3 ? node.parentNode : node;
// same for XY
me.xy = [e.pageX, e.pageY];
me.timestamp = e.timeStamp;
} else {
me.target = null;
me.xy = [0, 0];
}
return me;
},
/**
* Stop the event (preventDefault and stopPropagation)
*/
stopEvent : function(){
this.stopPropagation();
this.preventDefault();
},
/**
* Prevents the browsers default handling of the event.
*/
preventDefault : function(){
if(this.browserEvent) {
this.browserEvent.preventDefault();
}
},
/**
* Cancels bubbling of the event.
*/
stopPropagation : function() {
if(this.browserEvent) {
this.browserEvent.stopPropagation();
}
},
/**
* Gets the x coordinate of the event.
* @return {Number}
*/
getPageX : function(){
return this.xy[0];
},
/**
* Gets the y coordinate of the event.
* @return {Number}
*/
getPageY : function(){
return this.xy[1];
},
/**
* Gets the page coordinates of the event.
* @return {Array} The xy values like [x, y]
*/
getXY : function(){
return this.xy;
},
/**
* Gets the target for the event.
* @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
* @param {Number/Mixed} maxDepth (optional) The max depth to
search as a number or element (defaults to 10 || document.body)
* @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
* @return {HTMLelement}
*/
getTarget : function(selector, maxDepth, returnEl) {
return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
},
getTime : function() {
return this.timestamp;
}
});
/**
* @class Ext.EventObject
* Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
* wraps the browser's native event-object normalizing cross-browser differences,
* such as which mouse button is clicked, keys pressed, mechanisms to stop
* event-propagation along with a method to prevent default actions from taking place.
* For example:
*
function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
e.preventDefault();
var target = e.getTarget(); // same as t (the target HTMLElement)
...
}
var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
myDiv.on( // 'on' is shorthand for addListener
"click", // perform an action on click of myDiv
handleClick // reference to the action handler
);
// other methods to do the same:
Ext.EventManager.on("myDiv", 'click', handleClick);
Ext.EventManager.addListener("myDiv", 'click', handleClick);
* @singleton
*/
Ext.EventObject = new Ext.EventObjectImpl();