@qooxdoo/framework
Version:
The JS Framework for Coders
736 lines (649 loc) • 21.3 kB
JavaScript
/* ************************************************************************
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() {
super();
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(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(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(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(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(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(target) {
qx.bom.Event.addNativeListener(target, "input", this._onInputWrapper);
}
}),
// interface implementation
unregisterEvent(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(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(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(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(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(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(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(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(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(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() {}
})
},
/*
*****************************************************************************
DEFER
*****************************************************************************
*/
defer(statics) {
qx.event.Registration.addHandler(statics);
}
});