X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/core/src/EventObject.js diff --git a/src/core/src/EventObject.js b/src/core/src/EventObject.js new file mode 100644 index 00000000..5905caed --- /dev/null +++ b/src/core/src/EventObject.js @@ -0,0 +1,771 @@ +/** + * @class Ext.EventObject + +Just as {@link Ext.core.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.core.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 + * @markdown + */ +Ext.define('Ext.EventObjectImpl', { + uses: ['Ext.util.Point'], + + /** Key constant @type Number */ + BACKSPACE: 8, + /** Key constant @type Number */ + TAB: 9, + /** Key constant @type Number */ + NUM_CENTER: 12, + /** Key constant @type Number */ + ENTER: 13, + /** Key constant @type Number */ + RETURN: 13, + /** Key constant @type Number */ + SHIFT: 16, + /** Key constant @type Number */ + CTRL: 17, + /** Key constant @type Number */ + ALT: 18, + /** Key constant @type Number */ + PAUSE: 19, + /** Key constant @type Number */ + CAPS_LOCK: 20, + /** Key constant @type Number */ + ESC: 27, + /** Key constant @type Number */ + SPACE: 32, + /** Key constant @type Number */ + PAGE_UP: 33, + /** Key constant @type Number */ + PAGE_DOWN: 34, + /** Key constant @type Number */ + END: 35, + /** Key constant @type Number */ + HOME: 36, + /** Key constant @type Number */ + LEFT: 37, + /** Key constant @type Number */ + UP: 38, + /** Key constant @type Number */ + RIGHT: 39, + /** Key constant @type Number */ + DOWN: 40, + /** Key constant @type Number */ + PRINT_SCREEN: 44, + /** Key constant @type Number */ + INSERT: 45, + /** Key constant @type Number */ + DELETE: 46, + /** Key constant @type Number */ + ZERO: 48, + /** Key constant @type Number */ + ONE: 49, + /** Key constant @type Number */ + TWO: 50, + /** Key constant @type Number */ + THREE: 51, + /** Key constant @type Number */ + FOUR: 52, + /** Key constant @type Number */ + FIVE: 53, + /** Key constant @type Number */ + SIX: 54, + /** Key constant @type Number */ + SEVEN: 55, + /** Key constant @type Number */ + EIGHT: 56, + /** Key constant @type Number */ + NINE: 57, + /** Key constant @type Number */ + A: 65, + /** Key constant @type Number */ + B: 66, + /** Key constant @type Number */ + C: 67, + /** Key constant @type Number */ + D: 68, + /** Key constant @type Number */ + E: 69, + /** Key constant @type Number */ + F: 70, + /** Key constant @type Number */ + G: 71, + /** Key constant @type Number */ + H: 72, + /** Key constant @type Number */ + I: 73, + /** Key constant @type Number */ + J: 74, + /** Key constant @type Number */ + K: 75, + /** Key constant @type Number */ + L: 76, + /** Key constant @type Number */ + M: 77, + /** Key constant @type Number */ + N: 78, + /** Key constant @type Number */ + O: 79, + /** Key constant @type Number */ + P: 80, + /** Key constant @type Number */ + Q: 81, + /** Key constant @type Number */ + R: 82, + /** Key constant @type Number */ + S: 83, + /** Key constant @type Number */ + T: 84, + /** Key constant @type Number */ + U: 85, + /** Key constant @type Number */ + V: 86, + /** Key constant @type Number */ + W: 87, + /** Key constant @type Number */ + X: 88, + /** Key constant @type Number */ + Y: 89, + /** Key constant @type Number */ + Z: 90, + /** Key constant @type Number */ + CONTEXT_MENU: 93, + /** Key constant @type Number */ + NUM_ZERO: 96, + /** Key constant @type Number */ + NUM_ONE: 97, + /** Key constant @type Number */ + NUM_TWO: 98, + /** Key constant @type Number */ + NUM_THREE: 99, + /** Key constant @type Number */ + NUM_FOUR: 100, + /** Key constant @type Number */ + NUM_FIVE: 101, + /** Key constant @type Number */ + NUM_SIX: 102, + /** Key constant @type Number */ + NUM_SEVEN: 103, + /** Key constant @type Number */ + NUM_EIGHT: 104, + /** Key constant @type Number */ + NUM_NINE: 105, + /** Key constant @type Number */ + NUM_MULTIPLY: 106, + /** Key constant @type Number */ + NUM_PLUS: 107, + /** Key constant @type Number */ + NUM_MINUS: 109, + /** Key constant @type Number */ + NUM_PERIOD: 110, + /** Key constant @type Number */ + NUM_DIVISION: 111, + /** Key constant @type Number */ + F1: 112, + /** Key constant @type Number */ + F2: 113, + /** Key constant @type Number */ + F3: 114, + /** Key constant @type Number */ + F4: 115, + /** Key constant @type Number */ + F5: 116, + /** Key constant @type Number */ + F6: 117, + /** Key constant @type Number */ + F7: 118, + /** Key constant @type Number */ + F8: 119, + /** Key constant @type Number */ + F9: 120, + /** Key constant @type Number */ + F10: 121, + /** Key constant @type Number */ + F11: 122, + /** Key constant @type Number */ + F12: 123, + + /** + * Simple click regex + * @private + */ + clickRe: /(dbl)?click/, + // safari keypress events for special keys return bad keycodes + safariKeys: { + 3: 13, // enter + 63234: 37, // left + 63235: 39, // right + 63232: 38, // up + 63233: 40, // down + 63276: 33, // page up + 63277: 34, // page down + 63272: 46, // delete + 63273: 36, // home + 63275: 35 // end + }, + // normalize button clicks, don't see any way to feature detect this. + btnMap: Ext.isIE ? { + 1: 0, + 4: 1, + 2: 2 + } : { + 0: 0, + 1: 1, + 2: 2 + }, + + constructor: function(event, freezeEvent){ + if (event) { + this.setEvent(event.browserEvent || event, freezeEvent); + } + }, + + setEvent: function(event, freezeEvent){ + var me = this, button, options; + + if (event == me || (event && event.browserEvent)) { // already wrapped + return event; + } + me.browserEvent = event; + if (event) { + // normalize buttons + button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1); + if (me.clickRe.test(event.type) && button == -1) { + button = 0; + } + options = { + type: event.type, + button: button, + shiftKey: event.shiftKey, + // mac metaKey behaves like ctrlKey + ctrlKey: event.ctrlKey || event.metaKey || false, + altKey: event.altKey, + // in getKey these will be normalized for the mac + keyCode: event.keyCode, + charCode: event.charCode, + // cache the targets for the delayed and or buffered events + target: Ext.EventManager.getTarget(event), + relatedTarget: Ext.EventManager.getRelatedTarget(event), + currentTarget: event.currentTarget, + xy: (freezeEvent ? me.getXY() : null) + }; + } else { + options = { + button: -1, + shiftKey: false, + ctrlKey: false, + altKey: false, + keyCode: 0, + charCode: 0, + target: null, + xy: [0, 0] + }; + } + Ext.apply(me, options); + 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) { + Ext.EventManager.preventDefault(this.browserEvent); + } + }, + + /** + * Cancels bubbling of the event. + */ + stopPropagation: function(){ + var browserEvent = this.browserEvent; + + if (browserEvent) { + if (browserEvent.type == 'mousedown') { + Ext.EventManager.stoppedMouseDownEvent.fire(this); + } + Ext.EventManager.stopPropagation(browserEvent); + } + }, + + /** + * Gets the character code for the event. + * @return {Number} + */ + getCharCode: function(){ + return this.charCode || this.keyCode; + }, + + /** + * Returns a normalized keyCode for the event. + * @return {Number} The key code + */ + getKey: function(){ + return this.normalizeKey(this.keyCode || this.charCode); + }, + + /** + * Normalize key codes across browsers + * @private + * @param {Number} key The key code + * @return {Number} The normalized code + */ + normalizeKey: function(key){ + // can't feature detect this + return Ext.isWebKit ? (this.safariKeys[key] || key) : key; + }, + + /** + * Gets the x coordinate of the event. + * @return {Number} + * @deprecated 4.0 Replaced by {@link #getX} + */ + getPageX: function(){ + return this.getX(); + }, + + /** + * Gets the y coordinate of the event. + * @return {Number} + * @deprecated 4.0 Replaced by {@link #getY} + */ + getPageY: function(){ + return this.getY(); + }, + + /** + * Gets the x coordinate of the event. + * @return {Number} + */ + getX: function() { + return this.getXY()[0]; + }, + + /** + * Gets the y coordinate of the event. + * @return {Number} + */ + getY: function() { + return this.getXY()[1]; + }, + + /** + * Gets the page coordinates of the event. + * @return {Array} The xy values like [x, y] + */ + getXY: function() { + if (!this.xy) { + // same for XY + this.xy = Ext.EventManager.getPageXY(this.browserEvent); + } + 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.core.Element object instead of DOM node + * @return {HTMLelement} + */ + getTarget : function(selector, maxDepth, returnEl){ + if (selector) { + return Ext.fly(this.target).findParent(selector, maxDepth, returnEl); + } + return returnEl ? Ext.get(this.target) : this.target; + }, + + /** + * Gets the related target. + * @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.core.Element object instead of DOM node + * @return {HTMLElement} + */ + getRelatedTarget : function(selector, maxDepth, returnEl){ + if (selector) { + return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl); + } + return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget; + }, + + /** + * Normalizes mouse wheel delta across browsers + * @return {Number} The delta + */ + getWheelDelta : function(){ + var event = this.browserEvent, + delta = 0; + + if (event.wheelDelta) { /* IE/Opera. */ + delta = event.wheelDelta / 120; + } else if (event.detail){ /* Mozilla case. */ + delta = -event.detail / 3; + } + return delta; + }, + + /** + * Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el. + * Example usage:

+// Handle click on any child of an element
+Ext.getBody().on('click', function(e){
+    if(e.within('some-el')){
+        alert('Clicked on a child of some-el!');
+    }
+});
+
+// Handle click directly on an element, ignoring clicks on child nodes
+Ext.getBody().on('click', function(e,t){
+    if((t.id == 'some-el') && !e.within(t, true)){
+        alert('Clicked directly on some-el!');
+    }
+});
+
+ * @param {Mixed} el The id, DOM element or Ext.core.Element to check + * @param {Boolean} related (optional) true to test if the related target is within el instead of the target + * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target + * @return {Boolean} + */ + within : function(el, related, allowEl){ + if(el){ + var t = related ? this.getRelatedTarget() : this.getTarget(), + result; + + if (t) { + result = Ext.fly(el).contains(t); + if (!result && allowEl) { + result = t == Ext.getDom(el); + } + return result; + } + } + return false; + }, + + /** + * Checks if the key pressed was a "navigation" key + * @return {Boolean} True if the press is a navigation keypress + */ + isNavKeyPress : function(){ + var me = this, + k = this.normalizeKey(me.keyCode); + + return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down + k == me.RETURN || + k == me.TAB || + k == me.ESC; + }, + + /** + * Checks if the key pressed was a "special" key + * @return {Boolean} True if the press is a special keypress + */ + isSpecialKey : function(){ + var k = this.normalizeKey(this.keyCode); + return (this.type == 'keypress' && this.ctrlKey) || + this.isNavKeyPress() || + (k == this.BACKSPACE) || // Backspace + (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock + (k >= 44 && k <= 46); // Print Screen, Insert, Delete + }, + + /** + * Returns a point object that consists of the object coordinates. + * @return {Ext.util.Point} point + */ + getPoint : function(){ + var xy = this.getXY(); + return Ext.create('Ext.util.Point', xy[0], xy[1]); + }, + + /** + * Returns true if the control, meta, shift or alt key was pressed during this event. + * @return {Boolean} + */ + hasModifier : function(){ + return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey; + }, + + /** + * Injects a DOM event using the data in this object and (optionally) a new target. + * This is a low-level technique and not likely to be used by application code. The + * currently supported event types are: + *

HTMLEvents

+ * + *

MouseEvents

+ * + *

UIEvents

+ * + * @param {Element/HTMLElement} target If specified, the target for the event. This + * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget} + * is used to determine the target. + */ + injectEvent: function () { + var API, + dispatchers = {}; // keyed by event type (e.g., 'mousedown') + + // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html + + // IE9 has createEvent, but this code causes major problems with htmleditor (it + // blocks all mouse events and maybe more). TODO + + if (!Ext.isIE && document.createEvent) { // if (DOM compliant) + API = { + createHtmlEvent: function (doc, type, bubbles, cancelable) { + var event = doc.createEvent('HTMLEvents'); + + event.initEvent(type, bubbles, cancelable); + return event; + }, + + createMouseEvent: function (doc, type, bubbles, cancelable, detail, + clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, + button, relatedTarget) { + var event = doc.createEvent('MouseEvents'), + view = doc.defaultView || window; + + if (event.initMouseEvent) { + event.initMouseEvent(type, bubbles, cancelable, view, detail, + clientX, clientY, clientX, clientY, ctrlKey, altKey, + shiftKey, metaKey, button, relatedTarget); + } else { // old Safari + event = doc.createEvent('UIEvents'); + event.initEvent(type, bubbles, cancelable); + event.view = view; + event.detail = detail; + event.screenX = clientX; + event.screenY = clientY; + event.clientX = clientX; + event.clientY = clientY; + event.ctrlKey = ctrlKey; + event.altKey = altKey; + event.metaKey = metaKey; + event.shiftKey = shiftKey; + event.button = button; + event.relatedTarget = relatedTarget; + } + + return event; + }, + + createUIEvent: function (doc, type, bubbles, cancelable, detail) { + var event = doc.createEvent('UIEvents'), + view = doc.defaultView || window; + + event.initUIEvent(type, bubbles, cancelable, view, detail); + return event; + }, + + fireEvent: function (target, type, event) { + target.dispatchEvent(event); + }, + + fixTarget: function (target) { + // Safari3 doesn't have window.dispatchEvent() + if (target == window && !target.dispatchEvent) { + return document; + } + + return target; + } + } + } else if (document.createEventObject) { // else if (IE) + var crazyIEButtons = { 0: 1, 1: 4, 2: 2 }; + + API = { + createHtmlEvent: function (doc, type, bubbles, cancelable) { + var event = doc.createEventObject(); + event.bubbles = bubbles; + event.cancelable = cancelable; + return event; + }, + + createMouseEvent: function (doc, type, bubbles, cancelable, detail, + clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, + button, relatedTarget) { + var event = doc.createEventObject(); + event.bubbles = bubbles; + event.cancelable = cancelable; + event.detail = detail; + event.screenX = clientX; + event.screenY = clientY; + event.clientX = clientX; + event.clientY = clientY; + event.ctrlKey = ctrlKey; + event.altKey = altKey; + event.shiftKey = shiftKey; + event.metaKey = metaKey; + event.button = crazyIEButtons[button] || button; + event.relatedTarget = relatedTarget; // cannot assign to/fromElement + return event; + }, + + createUIEvent: function (doc, type, bubbles, cancelable, detail) { + var event = doc.createEventObject(); + event.bubbles = bubbles; + event.cancelable = cancelable; + return event; + }, + + fireEvent: function (target, type, event) { + target.fireEvent('on' + type, event); + }, + + fixTarget: function (target) { + if (target == document) { + // IE6,IE7 thinks window==document and doesn't have window.fireEvent() + // IE6,IE7 cannot properly call document.fireEvent() + return document.documentElement; + } + + return target; + } + }; + } + + //---------------- + // HTMLEvents + + Ext.Object.each({ + load: [false, false], + unload: [false, false], + select: [true, false], + change: [true, false], + submit: [true, true], + reset: [true, false], + resize: [true, false], + scroll: [true, false] + }, + function (name, value) { + var bubbles = value[0], cancelable = value[1]; + dispatchers[name] = function (targetEl, srcEvent) { + var e = API.createHtmlEvent(name, bubbles, cancelable); + API.fireEvent(targetEl, name, e); + }; + }); + + //---------------- + // MouseEvents + + function createMouseEventDispatcher (type, detail) { + var cancelable = (type != 'mousemove'); + return function (targetEl, srcEvent) { + var xy = srcEvent.getXY(), + e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable, + detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey, + srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button, + srcEvent.relatedTarget); + API.fireEvent(targetEl, type, e); + }; + } + + Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'], + function (eventName) { + dispatchers[eventName] = createMouseEventDispatcher(eventName, 1); + }); + + //---------------- + // UIEvents + + Ext.Object.each({ + focusin: [true, false], + focusout: [true, false], + activate: [true, true], + focus: [false, false], + blur: [false, false] + }, + function (name, value) { + var bubbles = value[0], cancelable = value[1]; + dispatchers[name] = function (targetEl, srcEvent) { + var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1); + API.fireEvent(targetEl, name, e); + }; + }); + + //--------- + if (!API) { + // not even sure what ancient browsers fall into this category... + + dispatchers = {}; // never mind all those we just built :P + + API = { + fixTarget: function (t) { + return t; + } + }; + } + + function cannotInject (target, srcEvent) { + // + // TODO log something + // + } + + return function (target) { + var me = this, + dispatcher = dispatchers[me.type] || cannotInject, + t = target ? (target.dom || target) : me.getTarget(); + + t = API.fixTarget(t); + dispatcher(t, me); + }; + }() // call to produce method + +}, function() { + +Ext.EventObject = new Ext.EventObjectImpl(); + +}); +