Path: blob/trunk/third_party/closure/goog/events/imehandler.js
2868 views
// Copyright 2010 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 Input Method Editors (IMEs) are OS-level widgets that make16* it easier to type non-ascii characters on ascii keyboards (in particular,17* characters that require more than one keystroke).18*19* When the user wants to type such a character, a modal menu pops up and20* suggests possible "next" characters in the IME character sequence. After21* typing N characters, the user hits "enter" to commit the IME to the field.22* N differs from language to language.23*24* This class offers high-level events for how the user is interacting with the25* IME in editable regions.26*27* Known Issues:28*29* Firefox always fires an extra pair of compositionstart/compositionend events.30* We do not normalize for this.31*32* Opera does not fire any IME events.33*34* Spurious UPDATE events are common on all browsers.35*36* We currently do a bad job detecting when the IME closes on IE, and37* make a "best effort" guess on when we know it's closed.38*39* @author [email protected] (Nick Santos) (Ported to Closure)40*/4142goog.provide('goog.events.ImeHandler');43goog.provide('goog.events.ImeHandler.Event');44goog.provide('goog.events.ImeHandler.EventType');4546goog.require('goog.events.Event');47goog.require('goog.events.EventHandler');48goog.require('goog.events.EventTarget');49goog.require('goog.events.EventType');50goog.require('goog.events.KeyCodes');51goog.require('goog.userAgent');52535455/**56* Dispatches high-level events for IMEs.57* @param {Element} el The element to listen on.58* @extends {goog.events.EventTarget}59* @constructor60* @final61*/62goog.events.ImeHandler = function(el) {63goog.events.ImeHandler.base(this, 'constructor');6465/**66* The element to listen on.67* @type {Element}68* @private69*/70this.el_ = el;7172/**73* Tracks the keyup event only, because it has a different life-cycle from74* other events.75* @type {goog.events.EventHandler<!goog.events.ImeHandler>}76* @private77*/78this.keyUpHandler_ = new goog.events.EventHandler(this);7980/**81* Tracks all the browser events.82* @type {goog.events.EventHandler<!goog.events.ImeHandler>}83* @private84*/85this.handler_ = new goog.events.EventHandler(this);8687if (goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {88this.handler_89.listen(90el, goog.events.EventType.COMPOSITIONSTART,91this.handleCompositionStart_)92.listen(93el, goog.events.EventType.COMPOSITIONEND,94this.handleCompositionEnd_)95.listen(96el, goog.events.EventType.COMPOSITIONUPDATE,97this.handleTextModifyingInput_);98}99100this.handler_101.listen(el, goog.events.EventType.TEXTINPUT, this.handleTextInput_)102.listen(el, goog.events.EventType.TEXT, this.handleTextModifyingInput_)103.listen(el, goog.events.EventType.KEYDOWN, this.handleKeyDown_);104};105goog.inherits(goog.events.ImeHandler, goog.events.EventTarget);106107108/**109* Event types fired by ImeHandler. These events do not make any guarantees110* about whether they were fired before or after the event in question.111* @enum {string}112*/113goog.events.ImeHandler.EventType = {114// After the IME opens.115START: 'startIme',116117// An update to the state of the IME. An 'update' does not necessarily mean118// that the text contents of the field were modified in any way.119UPDATE: 'updateIme',120121// After the IME closes.122END: 'endIme'123};124125126127/**128* An event fired by ImeHandler.129* @param {goog.events.ImeHandler.EventType} type The type.130* @param {goog.events.BrowserEvent} reason The trigger for this event.131* @constructor132* @extends {goog.events.Event}133* @final134*/135goog.events.ImeHandler.Event = function(type, reason) {136goog.events.ImeHandler.Event.base(this, 'constructor', type);137138/**139* The event that triggered this.140* @type {goog.events.BrowserEvent}141*/142this.reason = reason;143};144goog.inherits(goog.events.ImeHandler.Event, goog.events.Event);145146147/**148* Whether to use the composition events.149* @type {boolean}150*/151goog.events.ImeHandler.USES_COMPOSITION_EVENTS = goog.userAgent.GECKO ||152(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher(532));153154155/**156* Stores whether IME mode is active.157* @type {boolean}158* @private159*/160goog.events.ImeHandler.prototype.imeMode_ = false;161162163/**164* The keyCode value of the last keyDown event. This value is used for165* identiying whether or not a textInput event is sent by an IME.166* @type {number}167* @private168*/169goog.events.ImeHandler.prototype.lastKeyCode_ = 0;170171172/**173* @return {boolean} Whether an IME is active.174*/175goog.events.ImeHandler.prototype.isImeMode = function() {176return this.imeMode_;177};178179180/**181* Handles the compositionstart event.182* @param {goog.events.BrowserEvent} e The event.183* @private184*/185goog.events.ImeHandler.prototype.handleCompositionStart_ = function(e) {186this.handleImeActivate_(e);187};188189190/**191* Handles the compositionend event.192* @param {goog.events.BrowserEvent} e The event.193* @private194*/195goog.events.ImeHandler.prototype.handleCompositionEnd_ = function(e) {196this.handleImeDeactivate_(e);197};198199200/**201* Handles the compositionupdate and text events.202* @param {goog.events.BrowserEvent} e The event.203* @private204*/205goog.events.ImeHandler.prototype.handleTextModifyingInput_ = function(e) {206if (this.isImeMode()) {207this.processImeComposition_(e);208}209};210211212/**213* Handles IME activation.214* @param {goog.events.BrowserEvent} e The event.215* @private216*/217goog.events.ImeHandler.prototype.handleImeActivate_ = function(e) {218if (this.imeMode_) {219return;220}221222// Listens for keyup events to handle unexpected IME keydown events on older223// versions of webkit.224//225// In those versions, we currently use textInput events deactivate IME226// (see handleTextInput_() for the reason). However,227// Safari fires a keydown event (as a result of pressing keys to commit IME228// text) with keyCode == WIN_IME after textInput event. This activates IME229// mode again unnecessarily. To prevent this problem, listens keyup events230// which can use to determine whether IME text has been committed.231if (goog.userAgent.WEBKIT &&232!goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {233this.keyUpHandler_.listen(234this.el_, goog.events.EventType.KEYUP, this.handleKeyUpSafari4_);235}236237this.imeMode_ = true;238this.dispatchEvent(239new goog.events.ImeHandler.Event(240goog.events.ImeHandler.EventType.START, e));241};242243244/**245* Handles the IME compose changes.246* @param {goog.events.BrowserEvent} e The event.247* @private248*/249goog.events.ImeHandler.prototype.processImeComposition_ = function(e) {250this.dispatchEvent(251new goog.events.ImeHandler.Event(252goog.events.ImeHandler.EventType.UPDATE, e));253};254255256/**257* Handles IME deactivation.258* @param {goog.events.BrowserEvent} e The event.259* @private260*/261goog.events.ImeHandler.prototype.handleImeDeactivate_ = function(e) {262this.imeMode_ = false;263this.keyUpHandler_.removeAll();264this.dispatchEvent(265new goog.events.ImeHandler.Event(266goog.events.ImeHandler.EventType.END, e));267};268269270/**271* Handles a key down event.272* @param {!goog.events.BrowserEvent} e The event.273* @private274*/275goog.events.ImeHandler.prototype.handleKeyDown_ = function(e) {276// Firefox and Chrome have a separate event for IME composition ('text'277// and 'compositionupdate', respectively), other browsers do not.278if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {279var imeMode = this.isImeMode();280// If we're in IE and we detect an IME input on keyDown then activate281// the IME, otherwise if the imeMode was previously active, deactivate.282if (!imeMode && e.keyCode == goog.events.KeyCodes.WIN_IME) {283this.handleImeActivate_(e);284} else if (imeMode && e.keyCode != goog.events.KeyCodes.WIN_IME) {285if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {286this.handleImeDeactivate_(e);287}288} else if (imeMode) {289this.processImeComposition_(e);290}291}292293// Safari on Mac doesn't send IME events in the right order so that we must294// ignore some modifier key events to insert IME text correctly.295if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {296this.lastKeyCode_ = e.keyCode;297}298};299300301/**302* Handles a textInput event.303* @param {!goog.events.BrowserEvent} e The event.304* @private305*/306goog.events.ImeHandler.prototype.handleTextInput_ = function(e) {307// Some WebKit-based browsers including Safari 4 don't send composition308// events. So, we turn down IME mode when it's still there.309if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS &&310goog.userAgent.WEBKIT &&311this.lastKeyCode_ == goog.events.KeyCodes.WIN_IME && this.isImeMode()) {312this.handleImeDeactivate_(e);313}314};315316317/**318* Handles the key up event for any IME activity. This handler is just used to319* prevent activating IME unnecessary in Safari at this time.320* @param {!goog.events.BrowserEvent} e The event.321* @private322*/323goog.events.ImeHandler.prototype.handleKeyUpSafari4_ = function(e) {324if (this.isImeMode()) {325switch (e.keyCode) {326// These keyup events indicates that IME text has been committed or327// cancelled. We should turn off IME mode when these keyup events328// received.329case goog.events.KeyCodes.ENTER:330case goog.events.KeyCodes.TAB:331case goog.events.KeyCodes.ESC:332this.handleImeDeactivate_(e);333break;334}335}336};337338339/**340* Returns whether the given event should be treated as an IME341* deactivation trigger.342* @param {!goog.events.Event} e The event.343* @return {boolean} Whether the given event is an IME deactivate trigger.344* @private345*/346goog.events.ImeHandler.isImeDeactivateKeyEvent_ = function(e) {347// Which key events involve IME deactivation depends on the user's348// environment (i.e. browsers, platforms, and IMEs). Usually Shift key349// and Ctrl key does not involve IME deactivation, so we currently assume350// that these keys are not IME deactivation trigger.351switch (e.keyCode) {352case goog.events.KeyCodes.SHIFT:353case goog.events.KeyCodes.CTRL:354return false;355default:356return true;357}358};359360361/** @override */362goog.events.ImeHandler.prototype.disposeInternal = function() {363this.handler_.dispose();364this.keyUpHandler_.dispose();365this.el_ = null;366goog.events.ImeHandler.base(this, 'disposeInternal');367};368369370