UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

650 lines (508 loc) 18.6 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * This class provides unified key event handler for Internet Explorer, * Firefox, Opera and Safari. * * NOTE: Instances of this class must be disposed of after use * * @require(qx.event.handler.UserAction) */ qx.Class.define("qx.event.handler.Keyboard", { extend : qx.core.Object, implement : [ qx.event.IEventHandler, qx.core.IDisposable ], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * Create a new instance * * @param manager {qx.event.Manager} Event manager for the window to use */ construct : function(manager) { this.base(arguments); // Define shorthands this.__manager = manager; this.__window = manager.getWindow(); // Gecko ignores key events when not explicitly clicked in the document. if ((qx.core.Environment.get("engine.name") == "gecko")) { this.__root = this.__window; } else { this.__root = this.__window.document.documentElement; } // Internal sequence cache this.__lastUpDownType = {}; // Initialize observer this._initKeyObserver(); }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** @type {Integer} Priority of this handler */ PRIORITY : qx.event.Registration.PRIORITY_NORMAL, /** @type {Map} Supported event types */ SUPPORTED_TYPES : { keyup : 1, keydown : 1, keypress : 1, keyinput : 1 }, /** @type {Integer} Which target check to use */ TARGET_CHECK : qx.event.IEventHandler.TARGET_DOMNODE, /** @type {Integer} Whether the method "canHandleEvent" must be called */ IGNORE_CAN_HANDLE : true }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __onKeyUpDownWrapper : null, __manager : null, __window : null, __root : null, __lastUpDownType : null, __lastKeyCode : null, __inputListeners : null, __onKeyPressWrapper : null, /* --------------------------------------------------------------------------- EVENT HANDLER INTERFACE --------------------------------------------------------------------------- */ // interface implementation canHandleEvent : function(target, type) {}, // interface implementation registerEvent : function(target, type, capture) { // Nothing needs to be done here }, // interface implementation unregisterEvent : function(target, type, capture) { // Nothing needs to be done here }, /* --------------------------------------------------------------------------- HELPER --------------------------------------------------------------------------- */ /** * Fire a key input event with the given parameters * * @param domEvent {Event} DOM event * @param charCode {Integer} character code * @return {qx.Promise?} a promise if the event handlers created one */ _fireInputEvent : function(domEvent, charCode) { var target = this.__getEventTarget(); var tracker = {}; var self = this; // Only fire when target is defined and visible if (target && target.offsetWidth != 0) { var event = qx.event.Registration.createEvent("keyinput", qx.event.type.KeyInput, [domEvent, target, charCode]); qx.event.Utils.then(tracker, function() { self.__manager.dispatchEvent(target, event); }); } // Fire user action event // Needs to check if still alive first if (this.__window) { var self = this; qx.event.Utils.then(tracker, function() { return qx.event.Registration.fireEvent(self.__window, "useraction", qx.event.type.Data, ["keyinput"]); }); } return tracker.promise; }, /** * Fire a key up/down/press event with the given parameters * * @param domEvent {Event} DOM event * @param type {String} type og the event * @param keyIdentifier {String} key identifier * @return {qx.Promise?} a promise, if any of the event handlers returned a promise */ _fireSequenceEvent : function(domEvent, type, keyIdentifier) { var target = this.__getEventTarget(); var keyCode = domEvent.keyCode; var tracker = {}; var self = this; // Fire key event var event = qx.event.Registration.createEvent(type, qx.event.type.KeySequence, [domEvent, target, keyIdentifier]); qx.event.Utils.then(tracker, function() { return self.__manager.dispatchEvent(target, event); }); // IE and Safari suppress a "keypress" event if the "keydown" event's // default action was prevented. In this case we emulate the "keypress" // // FireFox suppresses "keypress" when "keydown" default action is prevented. // from version 29: https://bugzilla.mozilla.org/show_bug.cgi?id=935876. if (event.getDefaultPrevented() && type == "keydown") { if (qx.core.Environment.get("engine.name") == "mshtml" || qx.core.Environment.get("engine.name") == "webkit" || (qx.core.Environment.get("engine.name") == "gecko" && qx.core.Environment.get("browser.version") >= 29)) { // some key press events are already emulated. Ignore these events. if (!qx.event.util.Keyboard.isNonPrintableKeyCode(keyCode) && !this._emulateKeyPress[keyCode]) { qx.event.Utils.then(tracker, function() { return self._fireSequenceEvent(domEvent, "keypress", keyIdentifier); }); } } } // Fire user action event // Needs to check if still alive first if (this.__window) { qx.event.Utils.then(tracker, function() { return qx.event.Registration.fireEvent(self.__window, "useraction", qx.event.type.Data, [type]); }); } return tracker.promise; }, /** * Get the target element for key events * * @return {Element} the event target element */ __getEventTarget : function() { var focusHandler = this.__manager.getHandler(qx.event.handler.Focus); var target = focusHandler.getActive(); // Fallback to focused element when active is null or invisible if (!target || target.offsetWidth == 0) { target = focusHandler.getFocus(); } // Fallback to body when focused is null or invisible if (!target || target.offsetWidth == 0) { target = this.__manager.getWindow().document.body; } return target; }, /* --------------------------------------------------------------------------- OBSERVER INIT/STOP --------------------------------------------------------------------------- */ /** * Initializes the native key event listeners. * * @signature function() */ _initKeyObserver : function() { this.__onKeyUpDownWrapper = qx.lang.Function.listener(this.__onKeyUpDown, this); this.__onKeyPressWrapper = qx.lang.Function.listener(this.__onKeyPress, this); var Event = qx.bom.Event; Event.addNativeListener(this.__root, "keyup", this.__onKeyUpDownWrapper); Event.addNativeListener(this.__root, "keydown", this.__onKeyUpDownWrapper); Event.addNativeListener(this.__root, "keypress", this.__onKeyPressWrapper); }, /** * Stops the native key event listeners. * * @signature function() */ _stopKeyObserver : function() { var Event = qx.bom.Event; Event.removeNativeListener(this.__root, "keyup", this.__onKeyUpDownWrapper); Event.removeNativeListener(this.__root, "keydown", this.__onKeyUpDownWrapper); Event.removeNativeListener(this.__root, "keypress", this.__onKeyPressWrapper); for (var key in (this.__inputListeners || {})) { var listener = this.__inputListeners[key]; Event.removeNativeListener(listener.target, "keypress", listener.callback); } delete(this.__inputListeners); }, /* --------------------------------------------------------------------------- NATIVE EVENT OBSERVERS --------------------------------------------------------------------------- */ /** * Low level handler for "keyup" and "keydown" events * * @internal * @signature function(domEvent) * @param domEvent {Event} DOM event object */ __onKeyUpDown : qx.event.GlobalError.observeMethod(qx.core.Environment.select("engine.name", { "gecko|webkit|mshtml" : function(domEvent) { var keyCode = 0; var charCode = 0; var type = domEvent.type; keyCode = domEvent.keyCode; var tracker = {}; var self = this; qx.event.Utils.track(tracker, this._idealKeyHandler(keyCode, charCode, type, domEvent)); // On non print-able character be sure to add a keypress event if (type == "keydown") { /* * We need an artificial keypress event for every keydown event. * Newer browsers do not fire keypress for a regular charachter key (e.g when typing 'a') * if it was typed with the CTRL, ALT or META Key pressed during typing, like * doing it when typing the combination CTRL+A */ var isModifierDown = domEvent.ctrlKey || domEvent.altKey || domEvent.metaKey; // non-printable, backspace, tab or the modfier keys are down if (qx.event.util.Keyboard.isNonPrintableKeyCode(keyCode) || this._emulateKeyPress[keyCode] || isModifierDown) { qx.event.Utils.then(tracker, function() { return self._idealKeyHandler(keyCode, charCode, "keypress", domEvent); }); } } // Store last type this.__lastUpDownType[keyCode] = type; return tracker.promise; }, "opera" : function(domEvent) { this.__lastKeyCode = domEvent.keyCode; return this._idealKeyHandler(domEvent.keyCode, 0, domEvent.type, domEvent); } })), /** * some keys like "up", "down", "pageup", "pagedown" do not bubble a * "keypress" event in Firefox. To work around this bug we attach keypress * listeners directly to the input events. * * https://bugzilla.mozilla.org/show_bug.cgi?id=467513 * * @signature function(target, type, keyCode) * @param target {Element} The event target * @param type {String} The event type * @param keyCode {Integer} the key code */ __firefoxInputFix : qx.core.Environment.select("engine.name", { "gecko" : function(target, type, keyCode) { if ( type === "keydown" && (keyCode == 33 || keyCode == 34 || keyCode == 38 || keyCode == 40) && target.type == "text" && target.tagName.toLowerCase() === "input" && target.getAttribute("autoComplete") !== "off" ) { if (!this.__inputListeners) { this.__inputListeners = {}; } var hash = qx.core.ObjectRegistry.toHashCode(target); if (this.__inputListeners[hash]) { return; } var self = this; this.__inputListeners[hash] = { target: target, callback : function(domEvent) { qx.bom.Event.stopPropagation(domEvent); self.__onKeyPress(domEvent); } }; var listener = qx.event.GlobalError.observeMethod(this.__inputListeners[hash].callback); qx.bom.Event.addNativeListener(target, "keypress", listener); } }, "default" : null }), /** * Low level key press handler * * @signature function(domEvent) * @param domEvent {Event} DOM event object */ __onKeyPress : qx.event.GlobalError.observeMethod(qx.core.Environment.select("engine.name", { "mshtml" : function(domEvent) { domEvent = window.event || domEvent; if (this._charCode2KeyCode[domEvent.keyCode]) { return this._idealKeyHandler(this._charCode2KeyCode[domEvent.keyCode], 0, domEvent.type, domEvent); } else { return this._idealKeyHandler(0, domEvent.keyCode, domEvent.type, domEvent); } }, "gecko" : function(domEvent) { if(qx.core.Environment.get("engine.version") < 66) { var charCode = domEvent.charCode; var type = domEvent.type; return this._idealKeyHandler(domEvent.keyCode, charCode, type, domEvent); } else { if (this._charCode2KeyCode[domEvent.keyCode]) { return this._idealKeyHandler(this._charCode2KeyCode[domEvent.keyCode], 0, domEvent.type, domEvent); } else { return this._idealKeyHandler(0, domEvent.keyCode, domEvent.type, domEvent); } } }, "webkit" : function(domEvent) { if (this._charCode2KeyCode[domEvent.keyCode]) { return this._idealKeyHandler(this._charCode2KeyCode[domEvent.keyCode], 0, domEvent.type, domEvent); } else { return this._idealKeyHandler(0, domEvent.keyCode, domEvent.type, domEvent); } }, "opera" : function(domEvent) { var keyCode = domEvent.keyCode; var type = domEvent.type; // Some keys are identified differently for key up/down and keypress // (e.g. "v" gets identified as "F7"). // So we store the last key up/down keycode and compare it to the // current keycode. // See http://bugzilla.qooxdoo.org/show_bug.cgi?id=603 if(keyCode != this.__lastKeyCode) { return this._idealKeyHandler(0, this.__lastKeyCode, type, domEvent); } else { if (qx.event.util.Keyboard.keyCodeToIdentifierMap[domEvent.keyCode]) { return this._idealKeyHandler(domEvent.keyCode, 0, domEvent.type, domEvent); } else { return this._idealKeyHandler(0, domEvent.keyCode, domEvent.type, domEvent); } } } })), /* --------------------------------------------------------------------------- IDEAL KEY HANDLER --------------------------------------------------------------------------- */ /** * Key handler for an idealized browser. * Runs after the browser specific key handlers have normalized the key events. * * @param keyCode {String} keyboard code * @param charCode {String} character code * @param eventType {String} type of the event (keydown, keypress, keyup) * @param domEvent {Element} DomEvent * @return {qx.Promise?} a promise, if an event handler created one */ _idealKeyHandler : function(keyCode, charCode, eventType, domEvent) { var keyIdentifier; // Use: keyCode if (keyCode || (!keyCode && !charCode)) { keyIdentifier = qx.event.util.Keyboard.keyCodeToIdentifier(keyCode); return this._fireSequenceEvent(domEvent, eventType, keyIdentifier); } // Use: charCode else { keyIdentifier = qx.event.util.Keyboard.charCodeToIdentifier(charCode); var tracker = {}; var self = this; qx.event.Utils.track(tracker, this._fireSequenceEvent(domEvent, "keypress", keyIdentifier)); return qx.event.Utils.then(tracker, function() { return self._fireInputEvent(domEvent, charCode); }); } }, /* --------------------------------------------------------------------------- KEY MAPS --------------------------------------------------------------------------- */ /** * @type {Map} maps the charcodes of special keys for key press emulation * * @lint ignoreReferenceField(_emulateKeyPress) */ _emulateKeyPress : qx.core.Environment.select("engine.name", { "mshtml" : { 8: true, 9: true }, "webkit" : { 8: true, 9: true, 27: true }, "gecko" : (qx.core.Environment.get("browser.version") >= 65) ? { 8: true, 9: true, 27: true } : { }, "default" : {} }), /* --------------------------------------------------------------------------- HELPER METHODS --------------------------------------------------------------------------- */ /** * converts a key identifier back to a keycode * * @param keyIdentifier {String} The key identifier to convert * @return {Integer} keyboard code */ _identifierToKeyCode : function(keyIdentifier) { return qx.event.util.Keyboard.identifierToKeyCodeMap[keyIdentifier] || keyIdentifier.charCodeAt(0); } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { this._stopKeyObserver(); this.__lastKeyCode = this.__manager = this.__window = this.__root = this.__lastUpDownType = null; }, /* ***************************************************************************** DEFER ***************************************************************************** */ defer : function(statics, members) { // register at the event handler qx.event.Registration.addHandler(statics); if ((qx.core.Environment.get("engine.name") !== "opera")) { members._charCode2KeyCode = { 13 : 13, 27 : 27 }; } } });