Path: blob/trunk/third_party/closure/goog/events/inputhandler.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview An object that encapsulates text changed events for textareas16* and input element of type text and password. The event occurs after the value17* has been changed. The event does not occur if value was changed18* programmatically.<br>19* <br>20* Note: this does not guarantee the correctness of {@code keyCode} or21* {@code charCode}, or attempt to unify them across browsers. See22* {@code goog.events.KeyHandler} for that functionality<br>23* <br>24* Known issues:25* <ul>26* <li>IE doesn't have native support for input event. WebKit before version 53127* doesn't have support for textareas. For those browsers an emulation mode28* based on key, clipboard and drop events is used. Thus this event won't29* trigger in emulation mode if text was modified by context menu commands30* such as 'Undo' and 'Delete'.31* </ul>32* @author [email protected] (Erik Arvidsson)33* @see ../demos/inputhandler.html34*/3536goog.provide('goog.events.InputHandler');37goog.provide('goog.events.InputHandler.EventType');3839goog.require('goog.Timer');40goog.require('goog.dom.TagName');41goog.require('goog.events.BrowserEvent');42goog.require('goog.events.EventHandler');43goog.require('goog.events.EventTarget');44goog.require('goog.events.KeyCodes');45goog.require('goog.userAgent');46474849/**50* This event handler will dispatch events when the user types into a text51* input, password input or a textarea52* @param {Element} element The element that you want to listen for input53* events on.54* @constructor55* @extends {goog.events.EventTarget}56*/57goog.events.InputHandler = function(element) {58goog.events.InputHandler.base(this, 'constructor');5960/**61* Id of a timer used to postpone firing input event in emulation mode.62* @type {?number}63* @private64*/65this.timer_ = null;6667/**68* The element that you want to listen for input events on.69* @type {Element}70* @private71*/72this.element_ = element;7374// Determine whether input event should be emulated.75// IE8 doesn't support input events. We could use property change events but76// they are broken in many ways:77// - Fire even if value was changed programmatically.78// - Aren't always delivered. For example, if you change value or even width79// of input programmatically, next value change made by user won't fire an80// event.81// IE9 supports input events when characters are inserted, but not deleted.82// WebKit before version 531 did not support input events for textareas.83var emulateInputEvents = goog.userAgent.IE || goog.userAgent.EDGE ||84(goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('531') &&85element.tagName == goog.dom.TagName.TEXTAREA);8687/**88* @type {goog.events.EventHandler<!goog.events.InputHandler>}89* @private90*/91this.eventHandler_ = new goog.events.EventHandler(this);9293// Even if input event emulation is enabled, still listen for input events94// since they may be partially supported by the browser (such as IE9).95// If the input event does fire, we will be able to dispatch synchronously.96// (InputHandler events being asynchronous for IE is a common issue for97// cases like auto-grow textareas where they result in a quick flash of98// scrollbars between the textarea content growing and it being resized to99// fit.)100this.eventHandler_.listen(101this.element_,102emulateInputEvents ? ['keydown', 'paste', 'cut', 'drop', 'input'] :103'input',104this);105};106goog.inherits(goog.events.InputHandler, goog.events.EventTarget);107108109/**110* Enum type for the events fired by the input handler111* @enum {string}112*/113goog.events.InputHandler.EventType = {114INPUT: 'input'115};116117118/**119* This handles the underlying events and dispatches a new event as needed.120* @param {goog.events.BrowserEvent} e The underlying browser event.121*/122goog.events.InputHandler.prototype.handleEvent = function(e) {123if (e.type == 'input') {124// http://stackoverflow.com/questions/18389732/changing-placeholder-triggers-input-event-in-ie-10125// IE 10+ fires an input event when there are inputs with placeholders.126// It fires the event with keycode 0, so if we detect it we don't127// propagate the input event.128if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher(10) &&129e.keyCode == 0 && e.charCode == 0) {130return;131}132// This event happens after all the other events we listen to, so cancel133// an asynchronous event dispatch if we have it queued up. Otherwise, we134// will end up firing an extra event.135this.cancelTimerIfSet_();136137this.dispatchEvent(this.createInputEvent_(e));138} else {139// Filter out key events that don't modify text.140if (e.type == 'keydown' &&141!goog.events.KeyCodes.isTextModifyingKeyEvent(e)) {142return;143}144145// It is still possible that pressed key won't modify the value of an146// element. Storing old value will help us to detect modification but is147// also a little bit dangerous. If value is changed programmatically in148// another key down handler, we will detect it as user-initiated change.149var valueBeforeKey = e.type == 'keydown' ? this.element_.value : null;150151// In IE on XP, IME the element's value has already changed when we get152// keydown events when the user is using an IME. In this case, we can't153// check the current value normally, so we assume that it's a modifying key154// event. This means that ENTER when used to commit will fire a spurious155// input event, but it's better to have a false positive than let some input156// slip through the cracks.157if (goog.userAgent.IE && e.keyCode == goog.events.KeyCodes.WIN_IME) {158valueBeforeKey = null;159}160161// Create an input event now, because when we fire it on timer, the162// underlying event will already be disposed.163var inputEvent = this.createInputEvent_(e);164165// Since key down, paste, cut and drop events are fired before actual value166// of the element has changed, we need to postpone dispatching input event167// until value is updated.168this.cancelTimerIfSet_();169this.timer_ = goog.Timer.callOnce(function() {170this.timer_ = null;171if (this.element_.value != valueBeforeKey) {172this.dispatchEvent(inputEvent);173}174}, 0, this);175}176};177178179/**180* Cancels timer if it is set, does nothing otherwise.181* @private182*/183goog.events.InputHandler.prototype.cancelTimerIfSet_ = function() {184if (this.timer_ != null) {185goog.Timer.clear(this.timer_);186this.timer_ = null;187}188};189190191/**192* Creates an input event from the browser event.193* @param {goog.events.BrowserEvent} be A browser event.194* @return {!goog.events.BrowserEvent} An input event.195* @private196*/197goog.events.InputHandler.prototype.createInputEvent_ = function(be) {198var e = new goog.events.BrowserEvent(be.getBrowserEvent());199e.type = goog.events.InputHandler.EventType.INPUT;200return e;201};202203204/** @override */205goog.events.InputHandler.prototype.disposeInternal = function() {206goog.events.InputHandler.base(this, 'disposeInternal');207this.eventHandler_.dispose();208this.cancelTimerIfSet_();209delete this.element_;210};211212213