UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

613 lines (506 loc) 19.5 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) * Fabian Jakobs (fjakobs) * Christian Hagendorn (chris_schmidt) ************************************************************************ */ // Original behavior: // ================================================================ // Normally a "change" event should occur on blur of the element // (http://www.w3.org/TR/DOM-Level-2-Events/events.html) // However this is not true for "file" upload fields // And this is also not true for checkboxes and radiofields (all non mshtml) // And this is also not true for select boxes where the selections // happens in the opened popup (Gecko + Webkit) // Normalized behavior: // ================================================================ // Change on blur for textfields, textareas and file // Instant change event on checkboxes, radiobuttons // Select field fires on select (when using popup or size>1) // but differs when using keyboard: // mshtml+opera=keypress; mozilla+safari=blur // Input event for textareas does not work in Safari 3 beta (WIN) // Safari 3 beta (WIN) repeats change event for select box on blur when selected using popup // Opera fires "change" on radio buttons two times for each change /** * This handler provides an "change" event for all form fields and an * "input" event for form fields of type "text" and "textarea". * * To let these events work it is needed to create the elements using * {@link qx.bom.Input} */ qx.Class.define("qx.event.handler.Input", { extend : qx.core.Object, implement : qx.event.IEventHandler, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { this.base(arguments); this._onChangeCheckedWrapper = qx.lang.Function.listener(this._onChangeChecked, this); this._onChangeValueWrapper = qx.lang.Function.listener(this._onChangeValue, this); this._onInputWrapper = qx.lang.Function.listener(this._onInput, this); this._onPropertyWrapper = qx.lang.Function.listener(this._onProperty, this); // special event handler for opera if ((qx.core.Environment.get("engine.name") == "opera")) { this._onKeyDownWrapper = qx.lang.Function.listener(this._onKeyDown, this); this._onKeyUpWrapper = qx.lang.Function.listener(this._onKeyUp, this); } }, /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** @type {Integer} Priority of this handler */ PRIORITY : qx.event.Registration.PRIORITY_NORMAL, /** @type {Map} Supported event types */ SUPPORTED_TYPES : { input : 1, change : 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 : false }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { // special handling for opera __enter : false, __onInputTimeoutId : null, // stores the former set value for opera and IE __oldValue : null, // stores the former set value for IE __oldInputValue : null, /* --------------------------------------------------------------------------- EVENT HANDLER INTERFACE --------------------------------------------------------------------------- */ // interface implementation canHandleEvent : function(target, type) { var lower = target.tagName.toLowerCase(); if (type === "input" && (lower === "input" || lower === "textarea")) { return true; } if (type === "change" && (lower === "input" || lower === "textarea" || lower === "select")) { return true; } return false; }, // interface implementation registerEvent : function(target, type, capture) { if ( qx.core.Environment.get("engine.name") == "mshtml" && (qx.core.Environment.get("engine.version") < 9 || (qx.core.Environment.get("engine.version") >= 9 && qx.core.Environment.get("browser.documentmode") < 9)) ) { if (!target.__inputHandlerAttached) { var tag = target.tagName.toLowerCase(); var elementType = target.type; if (elementType === "text" || elementType === "password" || tag === "textarea" || elementType === "checkbox" || elementType === "radio") { qx.bom.Event.addNativeListener(target, "propertychange", this._onPropertyWrapper); } if (elementType !== "checkbox" && elementType !== "radio") { qx.bom.Event.addNativeListener(target, "change", this._onChangeValueWrapper); } if (elementType === "text" || elementType === "password") { this._onKeyPressWrapped = qx.lang.Function.listener(this._onKeyPress, this, target); qx.bom.Event.addNativeListener(target, "keypress", this._onKeyPressWrapped); } target.__inputHandlerAttached = true; } } else { if (type === "input") { this.__registerInputListener(target); } else if (type === "change") { if (target.type === "radio" || target.type === "checkbox") { qx.bom.Event.addNativeListener(target, "change", this._onChangeCheckedWrapper); } else { qx.bom.Event.addNativeListener(target, "change", this._onChangeValueWrapper); } // special enter bugfix for opera if ((qx.core.Environment.get("engine.name") == "opera") || (qx.core.Environment.get("engine.name") == "mshtml")) { if (target.type === "text" || target.type === "password") { this._onKeyPressWrapped = qx.lang.Function.listener(this._onKeyPress, this, target); qx.bom.Event.addNativeListener(target, "keypress", this._onKeyPressWrapped); } } } } }, __registerInputListener : qx.core.Environment.select("engine.name", { "mshtml" : function(target) { if ( qx.core.Environment.get("engine.version") >= 9 && qx.core.Environment.get("browser.documentmode") >= 9 ) { qx.bom.Event.addNativeListener(target, "input", this._onInputWrapper); if (target.type === "text" || target.type === "password" || target.type === "textarea") { // Fixed input for delete and backspace key this._inputFixWrapper = qx.lang.Function.listener(this._inputFix, this, target); qx.bom.Event.addNativeListener(target, "keyup", this._inputFixWrapper); } } }, "webkit" : function(target) { var tag = target.tagName.toLowerCase(); // the change event is not fired while typing // this has been fixed in the latest nightlies if (parseFloat(qx.core.Environment.get("engine.version")) < 532 && tag == "textarea") { qx.bom.Event.addNativeListener(target, "keypress", this._onInputWrapper); } qx.bom.Event.addNativeListener(target, "input", this._onInputWrapper); }, "opera" : function(target) { // register key events for filtering "enter" on input events qx.bom.Event.addNativeListener(target, "keyup", this._onKeyUpWrapper); qx.bom.Event.addNativeListener(target, "keydown", this._onKeyDownWrapper); // register an blur event for preventing the input event on blur qx.bom.Event.addNativeListener(target, "input", this._onInputWrapper); }, "default" : function(target) { qx.bom.Event.addNativeListener(target, "input", this._onInputWrapper); } }), // interface implementation unregisterEvent : function(target, type) { if ( qx.core.Environment.get("engine.name") == "mshtml" && qx.core.Environment.get("engine.version") < 9 && qx.core.Environment.get("browser.documentmode") < 9 ) { if (target.__inputHandlerAttached) { var tag = target.tagName.toLowerCase(); var elementType = target.type; if (elementType === "text" || elementType === "password" || tag === "textarea" || elementType === "checkbox" || elementType === "radio") { qx.bom.Event.removeNativeListener(target, "propertychange", this._onPropertyWrapper); } if (elementType !== "checkbox" && elementType !== "radio") { qx.bom.Event.removeNativeListener(target, "change", this._onChangeValueWrapper); } if (elementType === "text" || elementType === "password") { qx.bom.Event.removeNativeListener(target, "keypress", this._onKeyPressWrapped); } try { delete target.__inputHandlerAttached; } catch(ex) { target.__inputHandlerAttached = null; } } } else { if (type === "input") { this.__unregisterInputListener(target); } else if (type === "change") { if (target.type === "radio" || target.type === "checkbox") { qx.bom.Event.removeNativeListener(target, "change", this._onChangeCheckedWrapper); } else { qx.bom.Event.removeNativeListener(target, "change", this._onChangeValueWrapper); } } if ((qx.core.Environment.get("engine.name") == "opera") || (qx.core.Environment.get("engine.name") == "mshtml")) { if (target.type === "text" || target.type === "password") { qx.bom.Event.removeNativeListener(target, "keypress", this._onKeyPressWrapped); } } } }, __unregisterInputListener : qx.core.Environment.select("engine.name", { "mshtml" : function(target) { if ( qx.core.Environment.get("engine.version") >= 9 && qx.core.Environment.get("browser.documentmode") >= 9 ) { qx.bom.Event.removeNativeListener(target, "input", this._onInputWrapper); if (target.type === "text" || target.type === "password" || target.type === "textarea") { // Fixed input for delete and backspace key qx.bom.Event.removeNativeListener(target, "keyup", this._inputFixWrapper); } } }, "webkit" : function(target) { var tag = target.tagName.toLowerCase(); // the change event is not fired while typing // this has been fixed in the latest nightlies if (parseFloat(qx.core.Environment.get("engine.version")) < 532 && tag == "textarea") { qx.bom.Event.removeNativeListener(target, "keypress", this._onInputWrapper); } qx.bom.Event.removeNativeListener(target, "input", this._onInputWrapper); }, "opera" : function(target) { // unregister key events for filtering "enter" on input events qx.bom.Event.removeNativeListener(target, "keyup", this._onKeyUpWrapper); qx.bom.Event.removeNativeListener(target, "keydown", this._onKeyDownWrapper); qx.bom.Event.removeNativeListener(target, "input", this._onInputWrapper); }, "default" : function(target) { qx.bom.Event.removeNativeListener(target, "input", this._onInputWrapper); } }), /* --------------------------------------------------------------------------- FOR OPERA AND IE (KEYPRESS TO SIMULATE CHANGE EVENT) --------------------------------------------------------------------------- */ /** * Handler for fixing the different behavior when pressing the enter key. * * FF and Safari fire a "change" event if the user presses the enter key. * IE and Opera fire the event only if the focus is changed. * * @signature function(e, target) * @param e {Event} DOM event object * @param target {Element} The event target */ _onKeyPress : qx.core.Environment.select("engine.name", { "mshtml" : function(e, target) { if (e.keyCode === 13) { if (target.value !== this.__oldValue) { this.__oldValue = target.value; qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.value]); } } }, "opera" : function(e, target) { if (e.keyCode === 13) { if (target.value !== this.__oldValue) { this.__oldValue = target.value; qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.value]); } } }, "default" : null }), /* --------------------------------------------------------------------------- FOR IE (KEYUP TO SIMULATE INPUT EVENT) --------------------------------------------------------------------------- */ /** * Handler for fixing the different behavior when pressing the backspace or * delete key. * * The other browsers fire a "input" event if the user presses the backspace * or delete key. * IE fire the event only for other keys. * * @signature function(e, target) * @param e {Event} DOM event object * @param target {Element} The event target */ _inputFix : qx.core.Environment.select("engine.name", { "mshtml" : function(e, target) { if (e.keyCode === 46 || e.keyCode === 8) { if (target.value !== this.__oldInputValue) { this.__oldInputValue = target.value; qx.event.Registration.fireEvent(target, "input", qx.event.type.Data, [target.value]); } } }, "default" : null }), /* --------------------------------------------------------------------------- FOR OPERA ONLY LISTENER (KEY AND BLUR) --------------------------------------------------------------------------- */ /** * Key event listener for opera which recognizes if the enter key has been * pressed. * * @signature function(e) * @param e {Event} DOM event object */ _onKeyDown : qx.core.Environment.select("engine.name", { "opera" : function(e) { // enter is pressed if (e.keyCode === 13) { this.__enter = true; } }, "default" : null }), /** * Key event listener for opera which recognizes if the enter key has been * pressed. * * @signature function(e) * @param e {Event} DOM event object */ _onKeyUp : qx.core.Environment.select("engine.name", { "opera" : function(e) { // enter is pressed if (e.keyCode === 13) { this.__enter = false; } }, "default" : null }), /* --------------------------------------------------------------------------- NATIVE EVENT HANDLERS --------------------------------------------------------------------------- */ /** * Internal function called by input elements created using {@link qx.bom.Input}. * * @signature function(e) * @param e {Event} Native DOM event */ _onInput : qx.event.GlobalError.observeMethod(function(e) { var target = qx.bom.Event.getTarget(e); var tag = target.tagName.toLowerCase(); // ignore native input event when triggered by return in input element if (!this.__enter || tag !== "input") { // opera lower 10.6 needs a special treatment for input events because // they are also fired on blur if ((qx.core.Environment.get("engine.name") == "opera") && qx.core.Environment.get("browser.version") < 10.6) { this.__onInputTimeoutId = window.setTimeout(function() { qx.event.Registration.fireEvent(target, "input", qx.event.type.Data, [target.value]); }, 0); } else { qx.event.Registration.fireEvent(target, "input", qx.event.type.Data, [target.value]); } } }), /** * Internal function called by input elements created using {@link qx.bom.Input}. * * @signature function(e) * @param e {Event} Native DOM event */ _onChangeValue : qx.event.GlobalError.observeMethod(function(e) { var target = qx.bom.Event.getTarget(e); var data = target.value; if (target.type === "select-multiple") { var data = []; for (var i=0, o=target.options, l=o.length; i<l; i++) { if (o[i].selected) { data.push(o[i].value); } } } qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [data]); }), /** * Internal function called by input elements created using {@link qx.bom.Input}. * * @signature function(e) * @param e {Event} Native DOM event */ _onChangeChecked : qx.event.GlobalError.observeMethod(function(e) { var target = qx.bom.Event.getTarget(e); if (target.type === "radio") { if (target.checked) { qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.value]); } } else { qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.checked]); } }), /** * Internal function called by input elements created using {@link qx.bom.Input}. * * @signature function(e) * @param e {Event} Native DOM event */ _onProperty : qx.core.Environment.select("engine.name", { "mshtml" : qx.event.GlobalError.observeMethod(function(e) { var target = qx.bom.Event.getTarget(e); var prop = e.propertyName; if (prop === "value" && (target.type === "text" || target.type === "password" || target.tagName.toLowerCase() === "textarea")) { if (!target.$$inValueSet) { qx.event.Registration.fireEvent(target, "input", qx.event.type.Data, [target.value]); } } else if (prop === "checked") { if (target.type === "checkbox") { qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.checked]); } else if (target.checked) { qx.event.Registration.fireEvent(target, "change", qx.event.type.Data, [target.value]); } } }), "default" : function() {} }) }, /* ***************************************************************************** DEFER ***************************************************************************** */ defer : function(statics) { qx.event.Registration.addHandler(statics); } });