Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,136 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Action event wrapper implementation.
* @author eae@google.com (Emil A Eklund)
*/
goog.provide('goog.events.actionEventWrapper');
goog.require('goog.events');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.events.EventWrapper');
goog.require('goog.events.KeyCodes');
/**
* Event wrapper for action handling. Fires when an element is activated either
* by clicking it or by focusing it and pressing Enter.
*
* @constructor
* @implements {goog.events.EventWrapper}
* @private
*/
goog.events.ActionEventWrapper_ = function() {
};
/**
* Singleton instance of ActionEventWrapper_.
* @type {goog.events.ActionEventWrapper_}
*/
goog.events.actionEventWrapper = new goog.events.ActionEventWrapper_();
/**
* Event types used by the wrapper.
*
* @type {Array.<goog.events.EventType>}
* @private
*/
goog.events.ActionEventWrapper_.EVENT_TYPES_ = [
goog.events.EventType.CLICK,
goog.userAgent.GECKO ?
goog.events.EventType.KEYPRESS :
goog.events.EventType.KEYDOWN
];
/**
* Adds an event listener using the wrapper on a DOM Node or an object that has
* implemented {@link goog.events.EventTarget}. A listener can only be added
* once to an object.
*
* @param {goog.events.ListenableType} target The target to listen to events on.
* @param {Function|Object} listener Callback method, or an object with a
* handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_scope Element in whose scope to call the listener.
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
* listener to.
* @override
*/
goog.events.ActionEventWrapper_.prototype.listen = function(target, listener,
opt_capt, opt_scope, opt_eventHandler) {
var callback = function(e) {
if (e.type == goog.events.EventType.CLICK && e.isMouseActionButton()) {
listener.call(opt_scope, e);
} else if (e.keyCode == goog.events.KeyCodes.ENTER ||
e.keyCode == goog.events.KeyCodes.MAC_ENTER) {
// convert keydown to keypress for backward compatibility.
e.type = goog.events.EventType.KEYPRESS;
listener.call(opt_scope, e);
}
};
callback.listener_ = listener;
callback.scope_ = opt_scope;
if (opt_eventHandler) {
opt_eventHandler.listen(target,
goog.events.ActionEventWrapper_.EVENT_TYPES_,
callback, opt_capt);
} else {
goog.events.listen(target,
goog.events.ActionEventWrapper_.EVENT_TYPES_,
callback, opt_capt);
}
};
/**
* Removes an event listener added using goog.events.EventWrapper.listen.
*
* @param {goog.events.ListenableType} target The node to remove listener from.
* @param {Function|Object} listener Callback method, or an object with a
* handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_scope Element in whose scope to call the listener.
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
* listener from.
* @override
*/
goog.events.ActionEventWrapper_.prototype.unlisten = function(target, listener,
opt_capt, opt_scope, opt_eventHandler) {
for (var type, j = 0; type = goog.events.ActionEventWrapper_.EVENT_TYPES_[j];
j++) {
var listeners = goog.events.getListeners(target, type, !!opt_capt);
for (var obj, i = 0; obj = listeners[i]; i++) {
if (obj.listener.listener_ == listener &&
obj.listener.scope_ == opt_scope) {
if (opt_eventHandler) {
opt_eventHandler.unlisten(target, type, obj.listener, opt_capt,
opt_scope);
} else {
goog.events.unlisten(target, type, obj.listener, opt_capt, opt_scope);
}
break;
}
}
}
};

View File

@@ -0,0 +1,181 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This file contains a class to provide a unified mechanism for
* CLICK and enter KEYDOWN events. This provides better accessibility by
* providing the given functionality to a keyboard user which is otherwise
* would be available only via a mouse click.
*
* If there is an existing CLICK listener or planning to be added as below -
*
* <code>this.eventHandler_.listen(el, CLICK, this.onClick_);<code>
*
* it can be replaced with an ACTION listener as follows:
*
* <code>this.eventHandler_.listen(
* new goog.events.ActionHandler(el),
* ACTION,
* this.onAction_);<code>
*
*/
goog.provide('goog.events.ActionEvent');
goog.provide('goog.events.ActionHandler');
goog.provide('goog.events.ActionHandler.EventType');
goog.provide('goog.events.BeforeActionEvent');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.userAgent');
/**
* A wrapper around an element that you want to listen to ACTION events on.
* @param {Element|Document} element The element or document to listen on.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.ActionHandler = function(element) {
goog.events.EventTarget.call(this);
/**
* This is the element that we will listen to events on.
* @type {Element|Document}
* @private
*/
this.element_ = element;
goog.events.listen(element, goog.events.ActionHandler.KEY_EVENT_TYPE_,
this.handleKeyDown_, false, this);
goog.events.listen(element, goog.events.EventType.CLICK,
this.handleClick_, false, this);
};
goog.inherits(goog.events.ActionHandler, goog.events.EventTarget);
/**
* Enum type for the events fired by the action handler
* @enum {string}
*/
goog.events.ActionHandler.EventType = {
ACTION: 'action',
BEFOREACTION: 'beforeaction'
};
/**
* Key event type to listen for.
* @type {string}
* @private
*/
goog.events.ActionHandler.KEY_EVENT_TYPE_ = goog.userAgent.GECKO ?
goog.events.EventType.KEYPRESS :
goog.events.EventType.KEYDOWN;
/**
* Handles key press events.
* @param {!goog.events.BrowserEvent} e The key press event.
* @private
*/
goog.events.ActionHandler.prototype.handleKeyDown_ = function(e) {
if (e.keyCode == goog.events.KeyCodes.ENTER ||
goog.userAgent.WEBKIT && e.keyCode == goog.events.KeyCodes.MAC_ENTER) {
this.dispatchEvents_(e);
}
};
/**
* Handles mouse events.
* @param {!goog.events.BrowserEvent} e The click event.
* @private
*/
goog.events.ActionHandler.prototype.handleClick_ = function(e) {
this.dispatchEvents_(e);
};
/**
* Dispatches BeforeAction and Action events to the element
* @param {!goog.events.BrowserEvent} e The event causing dispatches.
* @private
*/
goog.events.ActionHandler.prototype.dispatchEvents_ = function(e) {
var beforeActionEvent = new goog.events.BeforeActionEvent(e);
// Allow application specific logic here before the ACTION event.
// For example, Gmail uses this event to restore keyboard focus
if (!this.dispatchEvent(beforeActionEvent)) {
// If the listener swallowed the BEFOREACTION event, don't dispatch the
// ACTION event.
return;
}
// Wrap up original event and send it off
var actionEvent = new goog.events.ActionEvent(e);
try {
this.dispatchEvent(actionEvent);
} finally {
// Stop propagating the event
e.stopPropagation();
}
};
/** @override */
goog.events.ActionHandler.prototype.disposeInternal = function() {
goog.events.ActionHandler.superClass_.disposeInternal.call(this);
goog.events.unlisten(this.element_, goog.events.ActionHandler.KEY_EVENT_TYPE_,
this.handleKeyDown_, false, this);
goog.events.unlisten(this.element_, goog.events.EventType.CLICK,
this.handleClick_, false, this);
delete this.element_;
};
/**
* This class is used for the goog.events.ActionHandler.EventType.ACTION event.
* @param {!goog.events.BrowserEvent} browserEvent Browser event object.
* @constructor
* @extends {goog.events.BrowserEvent}
*/
goog.events.ActionEvent = function(browserEvent) {
goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
this.type = goog.events.ActionHandler.EventType.ACTION;
};
goog.inherits(goog.events.ActionEvent, goog.events.BrowserEvent);
/**
* This class is used for the goog.events.ActionHandler.EventType.BEFOREACTION
* event. BEFOREACTION gives a chance to the application so the keyboard focus
* can be restored back, if required.
* @param {!goog.events.BrowserEvent} browserEvent Browser event object.
* @constructor
* @extends {goog.events.BrowserEvent}
*/
goog.events.BeforeActionEvent = function(browserEvent) {
goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
this.type = goog.events.ActionHandler.EventType.BEFOREACTION;
};
goog.inherits(goog.events.BeforeActionEvent, goog.events.BrowserEvent);

View File

@@ -0,0 +1,411 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A patched, standardized event object for browser events.
*
* <pre>
* The patched event object contains the following members:
* - type {string} Event type, e.g. 'click'
* - timestamp {Date} A date object for when the event was fired
* - target {Object} The element that actually triggered the event
* - currentTarget {Object} The element the listener is attached to
* - relatedTarget {Object} For mouseover and mouseout, the previous object
* - offsetX {number} X-coordinate relative to target
* - offsetY {number} Y-coordinate relative to target
* - clientX {number} X-coordinate relative to viewport
* - clientY {number} Y-coordinate relative to viewport
* - screenX {number} X-coordinate relative to the edge of the screen
* - screenY {number} Y-coordinate relative to the edge of the screen
* - button {number} Mouse button. Use isButton() to test.
* - keyCode {number} Key-code
* - ctrlKey {boolean} Was ctrl key depressed
* - altKey {boolean} Was alt key depressed
* - shiftKey {boolean} Was shift key depressed
* - metaKey {boolean} Was meta key depressed
* - defaultPrevented {boolean} Whether the default action has been prevented
* - state {Object} History state object
*
* NOTE: The keyCode member contains the raw browser keyCode. For normalized
* key and character code use {@link goog.events.KeyHandler}.
* </pre>
*
*/
goog.provide('goog.events.BrowserEvent');
goog.provide('goog.events.BrowserEvent.MouseButton');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.reflect');
goog.require('goog.userAgent');
/**
* Accepts a browser event object and creates a patched, cross browser event
* object.
* The content of this object will not be initialized if no event object is
* provided. If this is the case, init() needs to be invoked separately.
* @param {Event=} opt_e Browser event object.
* @param {EventTarget=} opt_currentTarget Current target for event.
* @constructor
* @extends {goog.events.Event}
*/
goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
if (opt_e) {
this.init(opt_e, opt_currentTarget);
}
};
goog.inherits(goog.events.BrowserEvent, goog.events.Event);
/**
* Normalized button constants for the mouse.
* @enum {number}
*/
goog.events.BrowserEvent.MouseButton = {
LEFT: 0,
MIDDLE: 1,
RIGHT: 2
};
/**
* Static data for mapping mouse buttons.
* @type {Array.<number>}
*/
goog.events.BrowserEvent.IEButtonMap = [
1, // LEFT
4, // MIDDLE
2 // RIGHT
];
/**
* Target that fired the event.
* @override
* @type {Node}
*/
goog.events.BrowserEvent.prototype.target = null;
/**
* Node that had the listener attached.
* @override
* @type {Node|undefined}
*/
goog.events.BrowserEvent.prototype.currentTarget;
/**
* For mouseover and mouseout events, the related object for the event.
* @type {Node}
*/
goog.events.BrowserEvent.prototype.relatedTarget = null;
/**
* X-coordinate relative to target.
* @type {number}
*/
goog.events.BrowserEvent.prototype.offsetX = 0;
/**
* Y-coordinate relative to target.
* @type {number}
*/
goog.events.BrowserEvent.prototype.offsetY = 0;
/**
* X-coordinate relative to the window.
* @type {number}
*/
goog.events.BrowserEvent.prototype.clientX = 0;
/**
* Y-coordinate relative to the window.
* @type {number}
*/
goog.events.BrowserEvent.prototype.clientY = 0;
/**
* X-coordinate relative to the monitor.
* @type {number}
*/
goog.events.BrowserEvent.prototype.screenX = 0;
/**
* Y-coordinate relative to the monitor.
* @type {number}
*/
goog.events.BrowserEvent.prototype.screenY = 0;
/**
* Which mouse button was pressed.
* @type {number}
*/
goog.events.BrowserEvent.prototype.button = 0;
/**
* Keycode of key press.
* @type {number}
*/
goog.events.BrowserEvent.prototype.keyCode = 0;
/**
* Keycode of key press.
* @type {number}
*/
goog.events.BrowserEvent.prototype.charCode = 0;
/**
* Whether control was pressed at time of event.
* @type {boolean}
*/
goog.events.BrowserEvent.prototype.ctrlKey = false;
/**
* Whether alt was pressed at time of event.
* @type {boolean}
*/
goog.events.BrowserEvent.prototype.altKey = false;
/**
* Whether shift was pressed at time of event.
* @type {boolean}
*/
goog.events.BrowserEvent.prototype.shiftKey = false;
/**
* Whether the meta key was pressed at time of event.
* @type {boolean}
*/
goog.events.BrowserEvent.prototype.metaKey = false;
/**
* History state object, only set for PopState events where it's a copy of the
* state object provided to pushState or replaceState.
* @type {Object}
*/
goog.events.BrowserEvent.prototype.state;
/**
* Whether the default platform modifier key was pressed at time of event.
* (This is control for all platforms except Mac, where it's Meta.
* @type {boolean}
*/
goog.events.BrowserEvent.prototype.platformModifierKey = false;
/**
* The browser event object.
* @type {Event}
* @private
*/
goog.events.BrowserEvent.prototype.event_ = null;
/**
* Accepts a browser event object and creates a patched, cross browser event
* object.
* @param {Event} e Browser event object.
* @param {EventTarget=} opt_currentTarget Current target for event.
*/
goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
var type = this.type = e.type;
goog.events.Event.call(this, type);
// TODO(nicksantos): Change this.target to type EventTarget.
this.target = /** @type {Node} */ (e.target) || e.srcElement;
// TODO(nicksantos): Change this.currentTarget to type EventTarget.
this.currentTarget = /** @type {Node} */ (opt_currentTarget);
var relatedTarget = /** @type {Node} */ (e.relatedTarget);
if (relatedTarget) {
// There's a bug in FireFox where sometimes, relatedTarget will be a
// chrome element, and accessing any property of it will get a permission
// denied exception. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=497780
if (goog.userAgent.GECKO) {
if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
relatedTarget = null;
}
}
// TODO(arv): Use goog.events.EventType when it has been refactored into its
// own file.
} else if (type == goog.events.EventType.MOUSEOVER) {
relatedTarget = e.fromElement;
} else if (type == goog.events.EventType.MOUSEOUT) {
relatedTarget = e.toElement;
}
this.relatedTarget = relatedTarget;
// Webkit emits a lame warning whenever layerX/layerY is accessed.
// http://code.google.com/p/chromium/issues/detail?id=101733
this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
e.offsetX : e.layerX;
this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
e.offsetY : e.layerY;
this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
this.screenX = e.screenX || 0;
this.screenY = e.screenY || 0;
this.button = e.button;
this.keyCode = e.keyCode || 0;
this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
this.ctrlKey = e.ctrlKey;
this.altKey = e.altKey;
this.shiftKey = e.shiftKey;
this.metaKey = e.metaKey;
this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
this.state = e.state;
this.event_ = e;
if (e.defaultPrevented) {
this.preventDefault();
}
delete this.propagationStopped_;
};
/**
* Tests to see which button was pressed during the event. This is really only
* useful in IE and Gecko browsers. And in IE, it's only useful for
* mousedown/mouseup events, because click only fires for the left mouse button.
*
* Safari 2 only reports the left button being clicked, and uses the value '1'
* instead of 0. Opera only reports a mousedown event for the middle button, and
* no mouse events for the right button. Opera has default behavior for left and
* middle click that can only be overridden via a configuration setting.
*
* There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
*
* @param {goog.events.BrowserEvent.MouseButton} button The button
* to test for.
* @return {boolean} True if button was pressed.
*/
goog.events.BrowserEvent.prototype.isButton = function(button) {
if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
if (this.type == 'click') {
return button == goog.events.BrowserEvent.MouseButton.LEFT;
} else {
return !!(this.event_.button &
goog.events.BrowserEvent.IEButtonMap[button]);
}
} else {
return this.event_.button == button;
}
};
/**
* Whether this has an "action"-producing mouse button.
*
* By definition, this includes left-click on windows/linux, and left-click
* without the ctrl key on Macs.
*
* @return {boolean} The result.
*/
goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
// Webkit does not ctrl+click to be a right-click, so we
// normalize it to behave like Gecko and Opera.
return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
!(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
};
/**
* @override
*/
goog.events.BrowserEvent.prototype.stopPropagation = function() {
goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
if (this.event_.stopPropagation) {
this.event_.stopPropagation();
} else {
this.event_.cancelBubble = true;
}
};
/**
* @override
*/
goog.events.BrowserEvent.prototype.preventDefault = function() {
goog.events.BrowserEvent.superClass_.preventDefault.call(this);
var be = this.event_;
if (!be.preventDefault) {
be.returnValue = false;
if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
/** @preserveTry */
try {
// Most keys can be prevented using returnValue. Some special keys
// require setting the keyCode to -1 as well:
//
// In IE7:
// F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
//
// In IE8:
// Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
//
// We therefore do this for all function keys as well as when Ctrl key
// is pressed.
var VK_F1 = 112;
var VK_F12 = 123;
if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
be.keyCode = -1;
}
} catch (ex) {
// IE throws an 'access denied' exception when trying to change
// keyCode in some situations (e.g. srcElement is input[type=file],
// or srcElement is an anchor tag rewritten by parent's innerHTML).
// Do nothing in this case.
}
}
} else {
be.preventDefault();
}
};
/**
* @return {Event} The underlying browser event object.
*/
goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
return this.event_;
};
/** @override */
goog.events.BrowserEvent.prototype.disposeInternal = function() {
};

View File

@@ -0,0 +1,85 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the events package.
*
*/
goog.provide('goog.events.BrowserFeature');
goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.events.BrowserFeature = {
/**
* Whether the button attribute of the event is W3C compliant. False in
* Internet Explorer prior to version 9; document-version dependent.
*/
HAS_W3C_BUTTON: !goog.userAgent.IE ||
goog.userAgent.isDocumentModeOrHigher(9),
/**
* Whether the browser supports full W3C event model.
*/
HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
goog.userAgent.isDocumentModeOrHigher(9),
/**
* To prevent default in IE7-8 for certain keydown events we need set the
* keyCode to -1.
*/
SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
!goog.userAgent.isVersionOrHigher('9'),
/**
* Whether the {@code navigator.onLine} property is supported.
*/
HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network online/offline events are supported.
*/
HAS_HTML5_NETWORK_EVENT_SUPPORT:
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network events fire on document.body, or otherwise the
* window.
*/
HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
/**
* Whether touch is enabled in the browser.
*/
TOUCH_ENABLED:
('ontouchstart' in goog.global ||
!!(goog.global['document'] &&
document.documentElement &&
'ontouchstart' in document.documentElement) ||
// IE10 uses non-standard touch events, so it has a different check.
!!(goog.global['navigator'] &&
goog.global['navigator']['msMaxTouchPoints']))
};

View File

@@ -0,0 +1,154 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A base class for event objects.
*
*/
goog.provide('goog.events.Event');
goog.provide('goog.events.EventLike');
// goog.events.Event no longer depends on goog.Disposable. Keep requiring
// goog.Disposable here to not break projects which assume this dependency.
goog.require('goog.Disposable');
/**
* A typedef for event like objects that are dispatchable via the
* goog.events.dispatchEvent function. strings are treated as the type for a
* goog.events.Event. Objects are treated as an extension of a new
* goog.events.Event with the type property of the object being used as the type
* of the Event.
* @typedef {string|Object|goog.events.Event}
*/
goog.events.EventLike;
/**
* A base class for event objects, so that they can support preventDefault and
* stopPropagation.
*
* @param {string} type Event Type.
* @param {Object=} opt_target Reference to the object that is the target of
* this event. It has to implement the {@code EventTarget} interface
* declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
* @constructor
*/
goog.events.Event = function(type, opt_target) {
/**
* Event type.
* @type {string}
*/
this.type = type;
/**
* Target of the event.
* @type {Object|undefined}
*/
this.target = opt_target;
/**
* Object that had the listener attached.
* @type {Object|undefined}
*/
this.currentTarget = this.target;
};
/**
* For backwards compatibility (goog.events.Event used to inherit
* goog.Disposable).
* @deprecated Events don't need to be disposed.
*/
goog.events.Event.prototype.disposeInternal = function() {
};
/**
* For backwards compatibility (goog.events.Event used to inherit
* goog.Disposable).
* @deprecated Events don't need to be disposed.
*/
goog.events.Event.prototype.dispose = function() {
};
/**
* Whether to cancel the event in internal capture/bubble processing for IE.
* @type {boolean}
* @suppress {underscore} Technically public, but referencing this outside
* this package is strongly discouraged.
*/
goog.events.Event.prototype.propagationStopped_ = false;
/**
* Whether the default action has been prevented.
* This is a property to match the W3C specification at {@link
* http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-defaultPrevented}.
* Must be treated as read-only outside the class.
* @type {boolean}
*/
goog.events.Event.prototype.defaultPrevented = false;
/**
* Return value for in internal capture/bubble processing for IE.
* @type {boolean}
* @suppress {underscore} Technically public, but referencing this outside
* this package is strongly discouraged.
*/
goog.events.Event.prototype.returnValue_ = true;
/**
* Stops event propagation.
*/
goog.events.Event.prototype.stopPropagation = function() {
this.propagationStopped_ = true;
};
/**
* Prevents the default action, for example a link redirecting to a url.
*/
goog.events.Event.prototype.preventDefault = function() {
this.defaultPrevented = true;
this.returnValue_ = false;
};
/**
* Stops the propagation of the event. It is equivalent to
* {@code e.stopPropagation()}, but can be used as the callback argument of
* {@link goog.events.listen} without declaring another function.
* @param {!goog.events.Event} e An event.
*/
goog.events.Event.stopPropagation = function(e) {
e.stopPropagation();
};
/**
* Prevents the default action. It is equivalent to
* {@code e.preventDefault()}, but can be used as the callback argument of
* {@link goog.events.listen} without declaring another function.
* @param {!goog.events.Event} e An event.
*/
goog.events.Event.preventDefault = function(e) {
e.preventDefault();
};

View File

@@ -0,0 +1,291 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Class to create objects which want to handle multiple events
* and have their listeners easily cleaned up via a dispose method.
*
* Example:
* <pre>
* function Something() {
* goog.base(this);
*
* ... set up object ...
*
* // Add event listeners
* this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
* this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
* this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
* this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
* this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
* }
* goog.inherits(Something, goog.events.EventHandler);
*
* Something.prototype.disposeInternal = function() {
* goog.base(this, 'disposeInternal');
* goog.dom.removeNode(this.container);
* };
*
*
* // Then elsewhere:
*
* var activeSomething = null;
* function openSomething() {
* activeSomething = new Something();
* }
*
* function closeSomething() {
* if (activeSomething) {
* activeSomething.dispose(); // Remove event listeners
* activeSomething = null;
* }
* }
* </pre>
*
*/
goog.provide('goog.events.EventHandler');
goog.require('goog.Disposable');
goog.require('goog.events');
goog.require('goog.object');
/**
* Super class for objects that want to easily manage a number of event
* listeners. It allows a short cut to listen and also provides a quick way
* to remove all events listeners belonging to this object.
* @param {Object=} opt_handler Object in whose scope to call the listeners.
* @constructor
* @extends {goog.Disposable}
*/
goog.events.EventHandler = function(opt_handler) {
goog.Disposable.call(this);
this.handler_ = opt_handler;
/**
* Keys for events that are being listened to.
* @type {!Object.<!goog.events.Key>}
* @private
*/
this.keys_ = {};
};
goog.inherits(goog.events.EventHandler, goog.Disposable);
/**
* Utility array used to unify the cases of listening for an array of types
* and listening for a single event, without using recursion or allocating
* an array each time.
* @type {Array.<string>}
* @private
*/
goog.events.EventHandler.typeArray_ = [];
/**
* Listen to an event on a Listenable. If the function is omitted then the
* EventHandler's handleEvent method will be used.
* @param {goog.events.ListenableType} src Event source.
* @param {string|Array.<string>} type Event type to listen for or array of
* event types.
* @param {Function|Object=} opt_fn Optional callback function to be used as the
* listener or an object with handleEvent function.
* @param {boolean=} opt_capture Optional whether to use capture phase.
* @param {Object=} opt_handler Object in whose scope to call the listener.
* @return {goog.events.EventHandler} This object, allowing for chaining of
* calls.
*/
goog.events.EventHandler.prototype.listen = function(src, type, opt_fn,
opt_capture,
opt_handler) {
if (!goog.isArray(type)) {
goog.events.EventHandler.typeArray_[0] = /** @type {string} */(type);
type = goog.events.EventHandler.typeArray_;
}
for (var i = 0; i < type.length; i++) {
var listenerObj = goog.events.listen(
src, type[i], opt_fn || this,
opt_capture || false,
opt_handler || this.handler_ || this);
if (goog.DEBUG && !listenerObj) {
// Some tests mock goog.events.listen, thus ensuring that
// they are never testing the real thing anyway, hence this is safe
// (except that #getListenerCount() will return the wrong value).
return this;
}
var key = listenerObj.key;
this.keys_[key] = listenerObj;
}
return this;
};
/**
* Listen to an event on a Listenable. If the function is omitted, then the
* EventHandler's handleEvent method will be used. After the event has fired the
* event listener is removed from the target. If an array of event types is
* provided, each event type will be listened to once.
* @param {goog.events.ListenableType} src Event source.
* @param {string|Array.<string>} type Event type to listen for or array of
* event types.
* @param {Function|Object=} opt_fn Optional callback function to be used as the
* listener or an object with handleEvent function.
* @param {boolean=} opt_capture Optional whether to use capture phase.
* @param {Object=} opt_handler Object in whose scope to call the listener.
* @return {goog.events.EventHandler} This object, allowing for chaining of
* calls.
*/
goog.events.EventHandler.prototype.listenOnce = function(src, type, opt_fn,
opt_capture,
opt_handler) {
if (goog.isArray(type)) {
for (var i = 0; i < type.length; i++) {
this.listenOnce(src, type[i], opt_fn, opt_capture, opt_handler);
}
} else {
var listenerObj = goog.events.listenOnce(
src, type, opt_fn || this, opt_capture,
opt_handler || this.handler_ || this);
var key = listenerObj.key;
this.keys_[key] = listenerObj;
}
return this;
};
/**
* Adds an event listener with a specific event wrapper on a DOM Node or an
* object that has implemented {@link goog.events.EventTarget}. A listener can
* only be added once to an object.
*
* @param {EventTarget|goog.events.EventTarget} src The node to listen to
* events on.
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
* @param {Function|Object} listener Callback method, or an object with a
* handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.EventHandler} This object, allowing for chaining of
* calls.
*/
goog.events.EventHandler.prototype.listenWithWrapper = function(src, wrapper,
listener, opt_capt, opt_handler) {
wrapper.listen(src, listener, opt_capt, opt_handler || this.handler_ || this,
this);
return this;
};
/**
* @return {number} Number of listeners registered by this handler.
*/
goog.events.EventHandler.prototype.getListenerCount = function() {
var count = 0;
for (var key in this.keys_) {
if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
count++;
}
}
return count;
};
/**
* Unlistens on an event.
* @param {goog.events.ListenableType} src Event source.
* @param {string|Array.<string>} type Event type to listen for.
* @param {Function|Object=} opt_fn Optional callback function to be used as the
* listener or an object with handleEvent function.
* @param {boolean=} opt_capture Optional whether to use capture phase.
* @param {Object=} opt_handler Object in whose scope to call the listener.
* @return {goog.events.EventHandler} This object, allowing for chaining of
* calls.
*/
goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
opt_capture,
opt_handler) {
if (goog.isArray(type)) {
for (var i = 0; i < type.length; i++) {
this.unlisten(src, type[i], opt_fn, opt_capture, opt_handler);
}
} else {
var listener = goog.events.getListener(src, type, opt_fn || this,
opt_capture, opt_handler || this.handler_ || this);
if (listener) {
goog.events.unlistenByKey(listener);
delete this.keys_[listener.key];
}
}
return this;
};
/**
* Removes an event listener which was added with listenWithWrapper().
*
* @param {EventTarget|goog.events.EventTarget} src The target to stop
* listening to events on.
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
* @param {Function|Object} listener The listener function to remove.
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
* whether the listener is fired during the capture or bubble phase of the
* event.
* @param {Object=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.EventHandler} This object, allowing for chaining of
* calls.
*/
goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper,
listener, opt_capt, opt_handler) {
wrapper.unlisten(src, listener, opt_capt,
opt_handler || this.handler_ || this, this);
return this;
};
/**
* Unlistens to all events.
*/
goog.events.EventHandler.prototype.removeAll = function() {
goog.object.forEach(this.keys_, goog.events.unlistenByKey);
this.keys_ = {};
};
/**
* Disposes of this EventHandler and removes all listeners that it registered.
* @override
* @protected
*/
goog.events.EventHandler.prototype.disposeInternal = function() {
goog.events.EventHandler.superClass_.disposeInternal.call(this);
this.removeAll();
};
/**
* Default event handler
* @param {goog.events.Event} e Event object.
*/
goog.events.EventHandler.prototype.handleEvent = function(e) {
throw Error('EventHandler.handleEvent not implemented');
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,401 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A disposable implementation of a custom
* listenable/event target. See also: documentation for
* {@code goog.events.Listenable}.
*
* @author arv@google.com (Erik Arvidsson) [Original implementation]
* @author pupius@google.com (Daniel Pupius) [Port to use goog.events]
* @see ../demos/eventtarget.html
* @see goog.events.Listenable
*/
goog.provide('goog.events.EventTarget');
goog.require('goog.Disposable');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.Listenable');
goog.require('goog.events.ListenerMap');
goog.require('goog.object');
/**
* An implementation of {@code goog.events.Listenable} with full W3C
* EventTarget-like support (capture/bubble mechanism, stopping event
* propagation, preventing default actions).
*
* You may subclass this class to turn your class into a Listenable.
*
* Unless propagation is stopped, an event dispatched by an
* EventTarget will bubble to the parent returned by
* {@code getParentEventTarget}. To set the parent, call
* {@code setParentEventTarget}. Subclasses that don't support
* changing the parent can override the setter to throw an error.
*
* Example usage:
* <pre>
* var source = new goog.events.EventTarget();
* function handleEvent(e) {
* alert('Type: ' + e.type + '; Target: ' + e.target);
* }
* source.listen('foo', handleEvent);
* // Or: goog.events.listen(source, 'foo', handleEvent);
* ...
* source.dispatchEvent('foo'); // will call handleEvent
* ...
* source.unlisten('foo', handleEvent);
* // Or: goog.events.unlisten(source, 'foo', handleEvent);
* </pre>
*
* TODO(user): Consider writing a parallel class to this that
* does not implement goog.Disposable.
*
* @constructor
* @extends {goog.Disposable}
* @implements {goog.events.Listenable}
*/
goog.events.EventTarget = function() {
goog.Disposable.call(this);
/**
* Maps of event type to an array of listeners.
* @private {!goog.events.ListenerMap}
*/
this.eventTargetListeners_ = new goog.events.ListenerMap(this);
/**
* The object to use for event.target. Useful when mixing in an
* EventTarget to another object.
* @private {!Object}
*/
this.actualEventTarget_ = this;
};
goog.inherits(goog.events.EventTarget, goog.Disposable);
goog.events.Listenable.addImplementation(goog.events.EventTarget);
/**
* An artificial cap on the number of ancestors you can have. This is mainly
* for loop detection.
* @const {number}
* @private
*/
goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
/**
* Parent event target, used during event bubbling.
*
* TODO(user): Change this to goog.events.Listenable. This
* currently breaks people who expect getParentEventTarget to return
* goog.events.EventTarget.
*
* @type {goog.events.EventTarget}
* @private
*/
goog.events.EventTarget.prototype.parentEventTarget_ = null;
/**
* Returns the parent of this event target to use for bubbling.
*
* @return {goog.events.EventTarget} The parent EventTarget or null if
* there is no parent.
* @override
*/
goog.events.EventTarget.prototype.getParentEventTarget = function() {
return this.parentEventTarget_;
};
/**
* Sets the parent of this event target to use for capture/bubble
* mechanism.
* @param {goog.events.EventTarget} parent Parent listenable (null if none).
*/
goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
this.parentEventTarget_ = parent;
};
/**
* Adds an event listener to the event target. The same handler can only be
* added once per the type. Even if you add the same handler multiple times
* using the same type then it will only be called once when the event is
* dispatched.
*
* Supported for legacy but use goog.events.listen(src, type, handler) instead.
*
* TODO(user): Deprecate this.
*
* @param {string} type The type of the event to listen for.
* @param {Function|Object} handler The function to handle the event. The
* handler can also be an object that implements the handleEvent method
* which takes the event object as argument.
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
* whether the listener is fired during the capture or bubble phase
* of the event.
* @param {Object=} opt_handlerScope Object in whose scope to call
* the listener.
*/
goog.events.EventTarget.prototype.addEventListener = function(
type, handler, opt_capture, opt_handlerScope) {
goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
};
/**
* Removes an event listener from the event target. The handler must be the
* same object as the one added. If the handler has not been added then
* nothing is done.
*
* TODO(user): Deprecate this.
*
* @param {string} type The type of the event to listen for.
* @param {Function|Object} handler The function to handle the event. The
* handler can also be an object that implements the handleEvent method
* which takes the event object as argument.
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
* whether the listener is fired during the capture or bubble phase
* of the event.
* @param {Object=} opt_handlerScope Object in whose scope to call
* the listener.
*/
goog.events.EventTarget.prototype.removeEventListener = function(
type, handler, opt_capture, opt_handlerScope) {
goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
};
/** @override */
goog.events.EventTarget.prototype.dispatchEvent = function(e) {
this.assertInitialized_();
var ancestorsTree, ancestor = this.getParentEventTarget();
if (ancestor) {
ancestorsTree = [];
var ancestorCount = 1;
for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
ancestorsTree.push(ancestor);
goog.asserts.assert(
(++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
'infinite loop');
}
}
return goog.events.EventTarget.dispatchEventInternal_(
this.actualEventTarget_, e, ancestorsTree);
};
/**
* Removes listeners from this object. Classes that extend EventTarget may
* need to override this method in order to remove references to DOM Elements
* and additional listeners.
* @override
*/
goog.events.EventTarget.prototype.disposeInternal = function() {
goog.events.EventTarget.superClass_.disposeInternal.call(this);
this.removeAllListeners();
this.parentEventTarget_ = null;
};
/** @override */
goog.events.EventTarget.prototype.listen = function(
type, listener, opt_useCapture, opt_listenerScope) {
this.assertInitialized_();
return this.eventTargetListeners_.add(
type, listener, false /* callOnce */, opt_useCapture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.listenOnce = function(
type, listener, opt_useCapture, opt_listenerScope) {
return this.eventTargetListeners_.add(
type, listener, true /* callOnce */, opt_useCapture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.unlisten = function(
type, listener, opt_useCapture, opt_listenerScope) {
return this.eventTargetListeners_.remove(
type, listener, opt_useCapture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.unlistenByKey = function(key) {
return this.eventTargetListeners_.removeByKey(key);
};
/** @override */
goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
// TODO(user): Previously, removeAllListeners can be called on
// uninitialized EventTarget, so we preserve that behavior. We
// should remove this when usages that rely on that fact are purged.
if (!this.eventTargetListeners_) {
return 0;
}
return this.eventTargetListeners_.removeAll(opt_type);
};
/** @override */
goog.events.EventTarget.prototype.fireListeners = function(
type, capture, eventObject) {
// TODO(user): Original code avoids array creation when there
// is no listener, so we do the same. If this optimization turns
// out to be not required, we can replace this with
// getListeners(type, capture) instead, which is simpler.
var listenerArray = this.eventTargetListeners_.listeners[type];
if (!listenerArray) {
return true;
}
listenerArray = goog.array.clone(listenerArray);
var rv = true;
for (var i = 0; i < listenerArray.length; ++i) {
var listener = listenerArray[i];
// We might not have a listener if the listener was removed.
if (listener && !listener.removed && listener.capture == capture) {
var listenerFn = listener.listener;
var listenerHandler = listener.handler || listener.src;
if (listener.callOnce) {
this.unlistenByKey(listener);
}
rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
}
}
return rv && eventObject.returnValue_ != false;
};
/** @override */
goog.events.EventTarget.prototype.getListeners = function(type, capture) {
return this.eventTargetListeners_.getListeners(type, capture);
};
/** @override */
goog.events.EventTarget.prototype.getListener = function(
type, listener, capture, opt_listenerScope) {
return this.eventTargetListeners_.getListener(
type, listener, capture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.hasListener = function(
opt_type, opt_capture) {
return this.eventTargetListeners_.hasListener(opt_type, opt_capture);
};
/**
* Sets the target to be used for {@code event.target} when firing
* event. Mainly used for testing. For example, see
* {@code goog.testing.events.mixinListenable}.
* @param {!Object} target The target.
*/
goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
this.actualEventTarget_ = target;
};
/**
* Asserts that the event target instance is initialized properly.
* @private
*/
goog.events.EventTarget.prototype.assertInitialized_ = function() {
goog.asserts.assert(
this.eventTargetListeners_,
'Event target is not initialized. Did you call the superclass ' +
'(goog.events.EventTarget) constructor?');
};
/**
* Dispatches the given event on the ancestorsTree.
*
* TODO(user): Look for a way to reuse this logic in
* goog.events, if possible.
*
* @param {!Object} target The target to dispatch on.
* @param {goog.events.Event|Object|string} e The event object.
* @param {Array.<goog.events.Listenable>=} opt_ancestorsTree The ancestors
* tree of the target, in reverse order from the closest ancestor
* to the root event target. May be null if the target has no ancestor.
* @return {boolean} If anyone called preventDefault on the event object (or
* if any of the listeners returns false) this will also return false.
* @private
*/
goog.events.EventTarget.dispatchEventInternal_ = function(
target, e, opt_ancestorsTree) {
var type = e.type || /** @type {string} */ (e);
// If accepting a string or object, create a custom event object so that
// preventDefault and stopPropagation work with the event.
if (goog.isString(e)) {
e = new goog.events.Event(e, target);
} else if (!(e instanceof goog.events.Event)) {
var oldEvent = e;
e = new goog.events.Event(type, target);
goog.object.extend(e, oldEvent);
} else {
e.target = e.target || target;
}
var rv = true, currentTarget;
// Executes all capture listeners on the ancestors, if any.
if (opt_ancestorsTree) {
for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
i--) {
currentTarget = e.currentTarget = opt_ancestorsTree[i];
rv = currentTarget.fireListeners(type, true, e) && rv;
}
}
// Executes capture and bubble listeners on the target.
if (!e.propagationStopped_) {
currentTarget = e.currentTarget = target;
rv = currentTarget.fireListeners(type, true, e) && rv;
if (!e.propagationStopped_) {
rv = currentTarget.fireListeners(type, false, e) && rv;
}
}
// Executes all bubble listeners on the ancestors, if any.
if (opt_ancestorsTree) {
for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
currentTarget = e.currentTarget = opt_ancestorsTree[i];
rv = currentTarget.fireListeners(type, false, e) && rv;
}
}
return rv;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Event Types.
*
* @author arv@google.com (Erik Arvidsson)
* @author mirkov@google.com (Mirko Visontai)
*/
goog.provide('goog.events.EventType');
goog.require('goog.userAgent');
/**
* Constants for event names.
* @enum {string}
*/
goog.events.EventType = {
// Mouse events
CLICK: 'click',
DBLCLICK: 'dblclick',
MOUSEDOWN: 'mousedown',
MOUSEUP: 'mouseup',
MOUSEOVER: 'mouseover',
MOUSEOUT: 'mouseout',
MOUSEMOVE: 'mousemove',
SELECTSTART: 'selectstart', // IE, Safari, Chrome
// Key events
KEYPRESS: 'keypress',
KEYDOWN: 'keydown',
KEYUP: 'keyup',
// Focus
BLUR: 'blur',
FOCUS: 'focus',
DEACTIVATE: 'deactivate', // IE only
// NOTE: The following two events are not stable in cross-browser usage.
// WebKit and Opera implement DOMFocusIn/Out.
// IE implements focusin/out.
// Gecko implements neither see bug at
// https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
// The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
// http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
// You can use FOCUS in Capture phase until implementations converge.
FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
// Forms
CHANGE: 'change',
SELECT: 'select',
SUBMIT: 'submit',
INPUT: 'input',
PROPERTYCHANGE: 'propertychange', // IE only
// Drag and drop
DRAGSTART: 'dragstart',
DRAG: 'drag',
DRAGENTER: 'dragenter',
DRAGOVER: 'dragover',
DRAGLEAVE: 'dragleave',
DROP: 'drop',
DRAGEND: 'dragend',
// WebKit touch events.
TOUCHSTART: 'touchstart',
TOUCHMOVE: 'touchmove',
TOUCHEND: 'touchend',
TOUCHCANCEL: 'touchcancel',
// Misc
BEFOREUNLOAD: 'beforeunload',
CONSOLEMESSAGE: 'consolemessage',
CONTEXTMENU: 'contextmenu',
DOMCONTENTLOADED: 'DOMContentLoaded',
ERROR: 'error',
HELP: 'help',
LOAD: 'load',
LOSECAPTURE: 'losecapture',
READYSTATECHANGE: 'readystatechange',
RESIZE: 'resize',
SCROLL: 'scroll',
UNLOAD: 'unload',
// HTML 5 History events
// See http://www.w3.org/TR/html5/history.html#event-definitions
HASHCHANGE: 'hashchange',
PAGEHIDE: 'pagehide',
PAGESHOW: 'pageshow',
POPSTATE: 'popstate',
// Copy and Paste
// Support is limited. Make sure it works on your favorite browser
// before using.
// http://www.quirksmode.org/dom/events/cutcopypaste.html
COPY: 'copy',
PASTE: 'paste',
CUT: 'cut',
BEFORECOPY: 'beforecopy',
BEFORECUT: 'beforecut',
BEFOREPASTE: 'beforepaste',
// HTML5 online/offline events.
// http://www.w3.org/TR/offline-webapps/#related
ONLINE: 'online',
OFFLINE: 'offline',
// HTML 5 worker events
MESSAGE: 'message',
CONNECT: 'connect',
// CSS transition events. Based on the browser support described at:
// https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
TRANSITIONEND: goog.userAgent.WEBKIT ? 'webkitTransitionEnd' :
(goog.userAgent.OPERA ? 'oTransitionEnd' : 'transitionend'),
// IE specific events.
// See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx
MSGESTURECHANGE: 'MSGestureChange',
MSGESTUREEND: 'MSGestureEnd',
MSGESTUREHOLD: 'MSGestureHold',
MSGESTURESTART: 'MSGestureStart',
MSGESTURETAP: 'MSGestureTap',
MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
MSINERTIASTART: 'MSInertiaStart',
MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
MSPOINTERCANCEL: 'MSPointerCancel',
MSPOINTERDOWN: 'MSPointerDown',
MSPOINTERMOVE: 'MSPointerMove',
MSPOINTEROVER: 'MSPointerOver',
MSPOINTEROUT: 'MSPointerOut',
MSPOINTERUP: 'MSPointerUp',
// Native IMEs/input tools events.
TEXTINPUT: 'textinput',
COMPOSITIONSTART: 'compositionstart',
COMPOSITIONUPDATE: 'compositionupdate',
COMPOSITIONEND: 'compositionend',
// Webview tag events
// See http://developer.chrome.com/dev/apps/webview_tag.html
EXIT: 'exit',
LOADABORT: 'loadabort',
LOADCOMMIT: 'loadcommit',
LOADREDIRECT: 'loadredirect',
LOADSTART: 'loadstart',
LOADSTOP: 'loadstop',
RESPONSIVE: 'responsive',
SIZECHANGED: 'sizechanged',
UNRESPONSIVE: 'unresponsive'
};

View File

@@ -0,0 +1,66 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the goog.events.EventWrapper interface.
*
* @author eae@google.com (Emil A Eklund)
*/
goog.provide('goog.events.EventWrapper');
/**
* Interface for event wrappers.
* @interface
*/
goog.events.EventWrapper = function() {
};
/**
* Adds an event listener using the wrapper on a DOM Node or an object that has
* implemented {@link goog.events.EventTarget}. A listener can only be added
* once to an object.
*
* @param {goog.events.ListenableType} src The node to listen to events on.
* @param {Function|Object} listener Callback method, or an object with a
* handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_scope Element in whose scope to call the listener.
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
* listener to.
*/
goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt,
opt_scope, opt_eventHandler) {
};
/**
* Removes an event listener added using goog.events.EventWrapper.listen.
*
* @param {goog.events.ListenableType} src The node to remove listener from.
* @param {Function|Object} listener Callback method, or an object with a
* handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_scope Element in whose scope to call the listener.
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
* listener from.
*/
goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt,
opt_scope, opt_eventHandler) {
};

View File

@@ -0,0 +1,221 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides a files drag and drop event detector. It works on
* HTML5 browsers.
*
* @see ../demos/filedrophandler.html
*/
goog.provide('goog.events.FileDropHandler');
goog.provide('goog.events.FileDropHandler.EventType');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.log');
/**
* A files drag and drop event detector. Gets an {@code element} as parameter
* and fires {@code goog.events.FileDropHandler.EventType.DROP} event when files
* are dropped in the {@code element}.
*
* @param {Element|Document} element The element or document to listen on.
* @param {boolean=} opt_preventDropOutside Whether to prevent a drop on the
* area outside the {@code element}. Default false.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.FileDropHandler = function(element, opt_preventDropOutside) {
goog.events.EventTarget.call(this);
/**
* Handler for drag/drop events.
* @type {!goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
var doc = element;
if (opt_preventDropOutside) {
doc = goog.dom.getOwnerDocument(element);
}
// Add dragenter listener to the owner document of the element.
this.eventHandler_.listen(doc,
goog.events.EventType.DRAGENTER,
this.onDocDragEnter_);
// Add dragover listener to the owner document of the element only if the
// document is not the element itself.
if (doc != element) {
this.eventHandler_.listen(doc,
goog.events.EventType.DRAGOVER,
this.onDocDragOver_);
}
// Add dragover and drop listeners to the element.
this.eventHandler_.listen(element,
goog.events.EventType.DRAGOVER,
this.onElemDragOver_);
this.eventHandler_.listen(element,
goog.events.EventType.DROP,
this.onElemDrop_);
};
goog.inherits(goog.events.FileDropHandler, goog.events.EventTarget);
/**
* Whether the drag event contains files. It is initialized only in the
* dragenter event. It is used in all the drag events to prevent default actions
* only if the drag contains files. Preventing default actions is necessary to
* go from dragenter to dragover and from dragover to drop. However we do not
* always want to prevent default actions, e.g. when the user drags text or
* links on a text area we should not prevent the browser default action that
* inserts the text in the text area. It is also necessary to stop propagation
* when handling drag events on the element to prevent them from propagating
* to the document.
* @private
* @type {boolean}
*/
goog.events.FileDropHandler.prototype.dndContainsFiles_ = false;
/**
* A logger, used to help us debug the algorithm.
* @type {goog.log.Logger}
* @private
*/
goog.events.FileDropHandler.prototype.logger_ =
goog.log.getLogger('goog.events.FileDropHandler');
/**
* The types of events fired by this class.
* @enum {string}
*/
goog.events.FileDropHandler.EventType = {
DROP: goog.events.EventType.DROP
};
/** @override */
goog.events.FileDropHandler.prototype.disposeInternal = function() {
goog.events.FileDropHandler.superClass_.disposeInternal.call(this);
this.eventHandler_.dispose();
};
/**
* Dispatches the DROP event.
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.FileDropHandler.prototype.dispatch_ = function(e) {
goog.log.fine(this.logger_, 'Firing DROP event...');
var event = new goog.events.BrowserEvent(e.getBrowserEvent());
event.type = goog.events.FileDropHandler.EventType.DROP;
this.dispatchEvent(event);
};
/**
* Handles dragenter on the document.
* @param {goog.events.BrowserEvent} e The dragenter event.
* @private
*/
goog.events.FileDropHandler.prototype.onDocDragEnter_ = function(e) {
goog.log.log(this.logger_, goog.log.Level.FINER,
'"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
var dt = e.getBrowserEvent().dataTransfer;
// Check whether the drag event contains files.
this.dndContainsFiles_ = !!(dt &&
((dt.types &&
(goog.array.contains(dt.types, 'Files') ||
goog.array.contains(dt.types, 'public.file-url'))) ||
(dt.files && dt.files.length > 0)));
// If it does
if (this.dndContainsFiles_) {
// Prevent default actions.
e.preventDefault();
}
goog.log.log(this.logger_, goog.log.Level.FINER,
'dndContainsFiles_: ' + this.dndContainsFiles_);
};
/**
* Handles dragging something over the document.
* @param {goog.events.BrowserEvent} e The dragover event.
* @private
*/
goog.events.FileDropHandler.prototype.onDocDragOver_ = function(e) {
goog.log.log(this.logger_, goog.log.Level.FINEST,
'"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
if (this.dndContainsFiles_) {
// Prevent default actions.
e.preventDefault();
// Disable the drop on the document outside the drop zone.
var dt = e.getBrowserEvent().dataTransfer;
dt.dropEffect = 'none';
}
};
/**
* Handles dragging something over the element (drop zone).
* @param {goog.events.BrowserEvent} e The dragover event.
* @private
*/
goog.events.FileDropHandler.prototype.onElemDragOver_ = function(e) {
goog.log.log(this.logger_, goog.log.Level.FINEST,
'"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
if (this.dndContainsFiles_) {
// Prevent default actions and stop the event from propagating further to
// the document. Both lines are needed! (See comment above).
e.preventDefault();
e.stopPropagation();
// Allow the drop on the drop zone.
var dt = e.getBrowserEvent().dataTransfer;
dt.effectAllowed = 'all';
dt.dropEffect = 'copy';
}
};
/**
* Handles dropping something onto the element (drop zone).
* @param {goog.events.BrowserEvent} e The drop event.
* @private
*/
goog.events.FileDropHandler.prototype.onElemDrop_ = function(e) {
goog.log.log(this.logger_, goog.log.Level.FINER,
'"' + e.target.id + '" (' + e.target + ') dispatched: ' + e.type);
// If the drag and drop event contains files.
if (this.dndContainsFiles_) {
// Prevent default actions and stop the event from propagating further to
// the document. Both lines are needed! (See comment above).
e.preventDefault();
e.stopPropagation();
// Dispatch DROP event.
this.dispatch_(e);
}
};

View File

@@ -0,0 +1,106 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This event handler allows you to catch focusin and focusout
* events on descendants. Unlike the "focus" and "blur" events which do not
* propagate consistently, and therefore must be added to the element that is
* focused, this allows you to attach one listener to an ancester and you will
* be notified when the focus state changes of ony of its descendants.
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/focushandler.html
*/
goog.provide('goog.events.FocusHandler');
goog.provide('goog.events.FocusHandler.EventType');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.userAgent');
/**
* This event handler allows you to catch focus events when descendants gain or
* loses focus.
* @param {Element|Document} element The node to listen on.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.FocusHandler = function(element) {
goog.events.EventTarget.call(this);
/**
* This is the element that we will listen to the real focus events on.
* @type {Element|Document}
* @private
*/
this.element_ = element;
// In IE we use focusin/focusout and in other browsers we use a capturing
// listner for focus/blur
var typeIn = goog.userAgent.IE ? 'focusin' : 'focus';
var typeOut = goog.userAgent.IE ? 'focusout' : 'blur';
/**
* Store the listen key so it easier to unlisten in dispose.
* @private
* @type {goog.events.Key}
*/
this.listenKeyIn_ =
goog.events.listen(this.element_, typeIn, this, !goog.userAgent.IE);
/**
* Store the listen key so it easier to unlisten in dispose.
* @private
* @type {goog.events.Key}
*/
this.listenKeyOut_ =
goog.events.listen(this.element_, typeOut, this, !goog.userAgent.IE);
};
goog.inherits(goog.events.FocusHandler, goog.events.EventTarget);
/**
* Enum type for the events fired by the focus handler
* @enum {string}
*/
goog.events.FocusHandler.EventType = {
FOCUSIN: 'focusin',
FOCUSOUT: 'focusout'
};
/**
* This handles the underlying events and dispatches a new event.
* @param {goog.events.BrowserEvent} e The underlying browser event.
*/
goog.events.FocusHandler.prototype.handleEvent = function(e) {
var be = e.getBrowserEvent();
var event = new goog.events.BrowserEvent(be);
event.type = e.type == 'focusin' || e.type == 'focus' ?
goog.events.FocusHandler.EventType.FOCUSIN :
goog.events.FocusHandler.EventType.FOCUSOUT;
this.dispatchEvent(event);
};
/** @override */
goog.events.FocusHandler.prototype.disposeInternal = function() {
goog.events.FocusHandler.superClass_.disposeInternal.call(this);
goog.events.unlistenByKey(this.listenKeyIn_);
goog.events.unlistenByKey(this.listenKeyOut_);
delete this.element_;
};

View File

@@ -0,0 +1,363 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Input Method Editors (IMEs) are OS-level widgets that make
* it easier to type non-ascii characters on ascii keyboards (in particular,
* characters that require more than one keystroke).
*
* When the user wants to type such a character, a modal menu pops up and
* suggests possible "next" characters in the IME character sequence. After
* typing N characters, the user hits "enter" to commit the IME to the field.
* N differs from language to language.
*
* This class offers high-level events for how the user is interacting with the
* IME in editable regions.
*
* Known Issues:
*
* Firefox always fires an extra pair of compositionstart/compositionend events.
* We do not normalize for this.
*
* Opera does not fire any IME events.
*
* Spurious UPDATE events are common on all browsers.
*
* We currently do a bad job detecting when the IME closes on IE, and
* make a "best effort" guess on when we know it's closed.
*
*/
goog.provide('goog.events.ImeHandler');
goog.provide('goog.events.ImeHandler.Event');
goog.provide('goog.events.ImeHandler.EventType');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.userAgent');
/**
* Dispatches high-level events for IMEs.
* @param {Element} el The element to listen on.
* @extends {goog.events.EventTarget}
* @constructor
*/
goog.events.ImeHandler = function(el) {
goog.base(this);
/**
* The element to listen on.
* @type {Element}
* @private
*/
this.el_ = el;
/**
* Tracks the keyup event only, because it has a different life-cycle from
* other events.
* @type {goog.events.EventHandler}
* @private
*/
this.keyUpHandler_ = new goog.events.EventHandler(this);
/**
* Tracks all the browser events.
* @type {goog.events.EventHandler}
* @private
*/
this.handler_ = new goog.events.EventHandler(this);
if (goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
this.handler_.
listen(el, 'compositionstart', this.handleCompositionStart_).
listen(el, 'compositionend', this.handleCompositionEnd_).
listen(el, 'compositionupdate', this.handleTextModifyingInput_);
}
this.handler_.
listen(el, 'textInput', this.handleTextInput_).
listen(el, 'text', this.handleTextModifyingInput_).
listen(el, goog.events.EventType.KEYDOWN, this.handleKeyDown_);
};
goog.inherits(goog.events.ImeHandler, goog.events.EventTarget);
/**
* Event types fired by ImeHandler. These events do not make any guarantees
* about whether they were fired before or after the event in question.
* @enum {string}
*/
goog.events.ImeHandler.EventType = {
// After the IME opens.
START: 'startIme',
// An update to the state of the IME. An 'update' does not necessarily mean
// that the text contents of the field were modified in any way.
UPDATE: 'updateIme',
// After the IME closes.
END: 'endIme'
};
/**
* An event fired by ImeHandler.
* @param {goog.events.ImeHandler.EventType} type The type.
* @param {goog.events.BrowserEvent} reason The trigger for this event.
* @constructor
* @extends {goog.events.Event}
*/
goog.events.ImeHandler.Event = function(type, reason) {
goog.base(this, type);
/**
* The event that triggered this.
* @type {goog.events.BrowserEvent}
*/
this.reason = reason;
};
goog.inherits(goog.events.ImeHandler.Event, goog.events.Event);
/**
* Whether to use the composition events.
* @type {boolean}
*/
goog.events.ImeHandler.USES_COMPOSITION_EVENTS =
goog.userAgent.GECKO ||
(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher(532));
/**
* Stores whether IME mode is active.
* @type {boolean}
* @private
*/
goog.events.ImeHandler.prototype.imeMode_ = false;
/**
* The keyCode value of the last keyDown event. This value is used for
* identiying whether or not a textInput event is sent by an IME.
* @type {number}
* @private
*/
goog.events.ImeHandler.prototype.lastKeyCode_ = 0;
/**
* @return {boolean} Whether an IME is active.
*/
goog.events.ImeHandler.prototype.isImeMode = function() {
return this.imeMode_;
};
/**
* Handles the compositionstart event.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleCompositionStart_ =
function(e) {
this.handleImeActivate_(e);
};
/**
* Handles the compositionend event.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleCompositionEnd_ = function(e) {
this.handleImeDeactivate_(e);
};
/**
* Handles the compositionupdate and text events.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleTextModifyingInput_ =
function(e) {
if (this.isImeMode()) {
this.processImeComposition_(e);
}
};
/**
* Handles IME activation.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleImeActivate_ = function(e) {
if (this.imeMode_) {
return;
}
// Listens for keyup events to handle unexpected IME keydown events on older
// versions of webkit.
//
// In those versions, we currently use textInput events deactivate IME
// (see handleTextInput_() for the reason). However,
// Safari fires a keydown event (as a result of pressing keys to commit IME
// text) with keyCode == WIN_IME after textInput event. This activates IME
// mode again unnecessarily. To prevent this problem, listens keyup events
// which can use to determine whether IME text has been committed.
if (goog.userAgent.WEBKIT &&
!goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
this.keyUpHandler_.listen(this.el_,
goog.events.EventType.KEYUP, this.handleKeyUpSafari4_);
}
this.imeMode_ = true;
this.dispatchEvent(
new goog.events.ImeHandler.Event(
goog.events.ImeHandler.EventType.START, e));
};
/**
* Handles the IME compose changes.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.processImeComposition_ = function(e) {
this.dispatchEvent(
new goog.events.ImeHandler.Event(
goog.events.ImeHandler.EventType.UPDATE, e));
};
/**
* Handles IME deactivation.
* @param {goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleImeDeactivate_ = function(e) {
this.imeMode_ = false;
this.keyUpHandler_.removeAll();
this.dispatchEvent(
new goog.events.ImeHandler.Event(
goog.events.ImeHandler.EventType.END, e));
};
/**
* Handles a key down event.
* @param {!goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleKeyDown_ = function(e) {
// Firefox and Chrome have a separate event for IME composition ('text'
// and 'compositionupdate', respectively), other browsers do not.
if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS) {
var imeMode = this.isImeMode();
// If we're in IE and we detect an IME input on keyDown then activate
// the IME, otherwise if the imeMode was previously active, deactivate.
if (!imeMode && e.keyCode == goog.events.KeyCodes.WIN_IME) {
this.handleImeActivate_(e);
} else if (imeMode && e.keyCode != goog.events.KeyCodes.WIN_IME) {
if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {
this.handleImeDeactivate_(e);
}
} else if (imeMode) {
this.processImeComposition_(e);
}
}
// Safari on Mac doesn't send IME events in the right order so that we must
// ignore some modifier key events to insert IME text correctly.
if (goog.events.ImeHandler.isImeDeactivateKeyEvent_(e)) {
this.lastKeyCode_ = e.keyCode;
}
};
/**
* Handles a textInput event.
* @param {!goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleTextInput_ = function(e) {
// Some WebKit-based browsers including Safari 4 don't send composition
// events. So, we turn down IME mode when it's still there.
if (!goog.events.ImeHandler.USES_COMPOSITION_EVENTS &&
goog.userAgent.WEBKIT &&
this.lastKeyCode_ == goog.events.KeyCodes.WIN_IME &&
this.isImeMode()) {
this.handleImeDeactivate_(e);
}
};
/**
* Handles the key up event for any IME activity. This handler is just used to
* prevent activating IME unnecessary in Safari at this time.
* @param {!goog.events.BrowserEvent} e The event.
* @private
*/
goog.events.ImeHandler.prototype.handleKeyUpSafari4_ = function(e) {
if (this.isImeMode()) {
switch (e.keyCode) {
// These keyup events indicates that IME text has been committed or
// cancelled. We should turn off IME mode when these keyup events
// received.
case goog.events.KeyCodes.ENTER:
case goog.events.KeyCodes.TAB:
case goog.events.KeyCodes.ESC:
this.handleImeDeactivate_(e);
break;
}
}
};
/**
* Returns whether the given event should be treated as an IME
* deactivation trigger.
* @param {!goog.events.Event} e The event.
* @return {boolean} Whether the given event is an IME deactivate trigger.
* @private
*/
goog.events.ImeHandler.isImeDeactivateKeyEvent_ = function(e) {
// Which key events involve IME deactivation depends on the user's
// environment (i.e. browsers, platforms, and IMEs). Usually Shift key
// and Ctrl key does not involve IME deactivation, so we currently assume
// that these keys are not IME deactivation trigger.
switch (e.keyCode) {
case goog.events.KeyCodes.SHIFT:
case goog.events.KeyCodes.CTRL:
return false;
default:
return true;
}
};
/** @override */
goog.events.ImeHandler.prototype.disposeInternal = function() {
this.handler_.dispose();
this.keyUpHandler_.dispose();
this.el_ = null;
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,213 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An object that encapsulates text changed events for textareas
* and input element of type text and password. The event occurs after the value
* has been changed. The event does not occur if value was changed
* programmatically.<br>
* <br>
* Note: this does not guarantee the correctness of {@code keyCode} or
* {@code charCode}, or attempt to unify them across browsers. See
* {@code goog.events.KeyHandler} for that functionality.<br>
* <br>
* Known issues:
* <ul>
* <li>Does not trigger for drop events on Opera due to browser bug.
* <li>IE doesn't have native support for input event. WebKit before version 531
* doesn't have support for textareas. For those browsers an emulation mode
* based on key, clipboard and drop events is used. Thus this event won't
* trigger in emulation mode if text was modified by context menu commands
* such as 'Undo' and 'Delete'.
* </ul>
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/inputhandler.html
*/
goog.provide('goog.events.InputHandler');
goog.provide('goog.events.InputHandler.EventType');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.KeyCodes');
goog.require('goog.userAgent');
/**
* This event handler will dispatch events when the user types into a text
* input, password input or a textarea
* @param {Element} element The element that you want to listen for input
* events on.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.InputHandler = function(element) {
goog.base(this);
/**
* The element that you want to listen for input events on.
* @type {Element}
* @private
*/
this.element_ = element;
// Determine whether input event should be emulated.
// IE8 doesn't support input events. We could use property change events but
// they are broken in many ways:
// - Fire even if value was changed programmatically.
// - Aren't always delivered. For example, if you change value or even width
// of input programmatically, next value change made by user won't fire an
// event.
// IE9 supports input events when characters are inserted, but not deleted.
// WebKit before version 531 did not support input events for textareas.
var emulateInputEvents = goog.userAgent.IE ||
(goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('531') &&
element.tagName == 'TEXTAREA');
/**
* @type {goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
// Even if input event emulation is enabled, still listen for input events
// since they may be partially supported by the browser (such as IE9).
// If the input event does fire, we will be able to dispatch synchronously.
// (InputHandler events being asynchronous for IE is a common issue for
// cases like auto-grow textareas where they result in a quick flash of
// scrollbars between the textarea content growing and it being resized to
// fit.)
this.eventHandler_.listen(
this.element_,
emulateInputEvents ?
['keydown', 'paste', 'cut', 'drop', 'input'] :
'input',
this);
};
goog.inherits(goog.events.InputHandler, goog.events.EventTarget);
/**
* Enum type for the events fired by the input handler
* @enum {string}
*/
goog.events.InputHandler.EventType = {
INPUT: 'input'
};
/**
* Id of a timer used to postpone firing input event in emulation mode.
* @type {?number}
* @private
*/
goog.events.InputHandler.prototype.timer_ = null;
/**
* This handles the underlying events and dispatches a new event as needed.
* @param {goog.events.BrowserEvent} e The underlying browser event.
*/
goog.events.InputHandler.prototype.handleEvent = function(e) {
if (e.type == 'input') {
// This event happens after all the other events we listen to, so cancel
// an asynchronous event dispatch if we have it queued up. Otherwise, we
// will end up firing an extra event.
this.cancelTimerIfSet_();
// Unlike other browsers, Opera fires an extra input event when an element
// is blurred after the user has input into it. Since Opera doesn't fire
// input event on drop, it's enough to check whether element still has focus
// to suppress bogus notification.
if (!goog.userAgent.OPERA || this.element_ ==
goog.dom.getOwnerDocument(this.element_).activeElement) {
this.dispatchEvent(this.createInputEvent_(e));
}
} else {
// Filter out key events that don't modify text.
if (e.type == 'keydown' &&
!goog.events.KeyCodes.isTextModifyingKeyEvent(e)) {
return;
}
// It is still possible that pressed key won't modify the value of an
// element. Storing old value will help us to detect modification but is
// also a little bit dangerous. If value is changed programmatically in
// another key down handler, we will detect it as user-initiated change.
var valueBeforeKey = e.type == 'keydown' ? this.element_.value : null;
// In IE on XP, IME the element's value has already changed when we get
// keydown events when the user is using an IME. In this case, we can't
// check the current value normally, so we assume that it's a modifying key
// event. This means that ENTER when used to commit will fire a spurious
// input event, but it's better to have a false positive than let some input
// slip through the cracks.
if (goog.userAgent.IE && e.keyCode == goog.events.KeyCodes.WIN_IME) {
valueBeforeKey = null;
}
// Create an input event now, because when we fire it on timer, the
// underlying event will already be disposed.
var inputEvent = this.createInputEvent_(e);
// Since key down, paste, cut and drop events are fired before actual value
// of the element has changed, we need to postpone dispatching input event
// until value is updated.
this.cancelTimerIfSet_();
this.timer_ = goog.Timer.callOnce(function() {
this.timer_ = null;
if (this.element_.value != valueBeforeKey) {
this.dispatchEvent(inputEvent);
}
}, 0, this);
}
};
/**
* Cancels timer if it is set, does nothing otherwise.
* @private
*/
goog.events.InputHandler.prototype.cancelTimerIfSet_ = function() {
if (this.timer_ != null) {
goog.Timer.clear(this.timer_);
this.timer_ = null;
}
};
/**
* Creates an input event from the browser event.
* @param {goog.events.BrowserEvent} be A browser event.
* @return {goog.events.BrowserEvent} An input event.
* @private
*/
goog.events.InputHandler.prototype.createInputEvent_ = function(be) {
var e = new goog.events.BrowserEvent(be.getBrowserEvent());
e.type = goog.events.InputHandler.EventType.INPUT;
return e;
};
/** @override */
goog.events.InputHandler.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.eventHandler_.dispose();
this.cancelTimerIfSet_();
delete this.element_;
};

View File

@@ -0,0 +1,377 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Constant declarations for common key codes.
*
* @author eae@google.com (Emil A Eklund)
* @see ../demos/keyhandler.html
*/
goog.provide('goog.events.KeyCodes');
goog.require('goog.userAgent');
/**
* Key codes for common characters.
*
* This list is not localized and therefore some of the key codes are not
* correct for non US keyboard layouts. See comments below.
*
* @enum {number}
*/
goog.events.KeyCodes = {
WIN_KEY_FF_LINUX: 0,
MAC_ENTER: 3,
BACKSPACE: 8,
TAB: 9,
NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
PAUSE: 19,
CAPS_LOCK: 20,
ESC: 27,
SPACE: 32,
PAGE_UP: 33, // also NUM_NORTH_EAST
PAGE_DOWN: 34, // also NUM_SOUTH_EAST
END: 35, // also NUM_SOUTH_WEST
HOME: 36, // also NUM_NORTH_WEST
LEFT: 37, // also NUM_WEST
UP: 38, // also NUM_NORTH
RIGHT: 39, // also NUM_EAST
DOWN: 40, // also NUM_SOUTH
PRINT_SCREEN: 44,
INSERT: 45, // also NUM_INSERT
DELETE: 46, // also NUM_DELETE
ZERO: 48,
ONE: 49,
TWO: 50,
THREE: 51,
FOUR: 52,
FIVE: 53,
SIX: 54,
SEVEN: 55,
EIGHT: 56,
NINE: 57,
FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
QUESTION_MARK: 63, // needs localization
A: 65,
B: 66,
C: 67,
D: 68,
E: 69,
F: 70,
G: 71,
H: 72,
I: 73,
J: 74,
K: 75,
L: 76,
M: 77,
N: 78,
O: 79,
P: 80,
Q: 81,
R: 82,
S: 83,
T: 84,
U: 85,
V: 86,
W: 87,
X: 88,
Y: 89,
Z: 90,
META: 91, // WIN_KEY_LEFT
WIN_KEY_RIGHT: 92,
CONTEXT_MENU: 93,
NUM_ZERO: 96,
NUM_ONE: 97,
NUM_TWO: 98,
NUM_THREE: 99,
NUM_FOUR: 100,
NUM_FIVE: 101,
NUM_SIX: 102,
NUM_SEVEN: 103,
NUM_EIGHT: 104,
NUM_NINE: 105,
NUM_MULTIPLY: 106,
NUM_PLUS: 107,
NUM_MINUS: 109,
NUM_PERIOD: 110,
NUM_DIVISION: 111,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
NUMLOCK: 144,
SCROLL_LOCK: 145,
// OS-specific media keys like volume controls and browser controls.
FIRST_MEDIA_KEY: 166,
LAST_MEDIA_KEY: 183,
SEMICOLON: 186, // needs localization
DASH: 189, // needs localization
EQUALS: 187, // needs localization
COMMA: 188, // needs localization
PERIOD: 190, // needs localization
SLASH: 191, // needs localization
APOSTROPHE: 192, // needs localization
TILDE: 192, // needs localization
SINGLE_QUOTE: 222, // needs localization
OPEN_SQUARE_BRACKET: 219, // needs localization
BACKSLASH: 220, // needs localization
CLOSE_SQUARE_BRACKET: 221, // needs localization
WIN_KEY: 224,
MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
WIN_IME: 229,
// We've seen users whose machines fire this keycode at regular one
// second intervals. The common thread among these users is that
// they're all using Dell Inspiron laptops, so we suspect that this
// indicates a hardware/bios problem.
// http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
PHANTOM: 255
};
/**
* Returns true if the event contains a text modifying key.
* @param {goog.events.BrowserEvent} e A key event.
* @return {boolean} Whether it's a text modifying key.
*/
goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
if (e.altKey && !e.ctrlKey ||
e.metaKey ||
// Function keys don't generate text
e.keyCode >= goog.events.KeyCodes.F1 &&
e.keyCode <= goog.events.KeyCodes.F12) {
return false;
}
// The following keys are quite harmless, even in combination with
// CTRL, ALT or SHIFT.
switch (e.keyCode) {
case goog.events.KeyCodes.ALT:
case goog.events.KeyCodes.CAPS_LOCK:
case goog.events.KeyCodes.CONTEXT_MENU:
case goog.events.KeyCodes.CTRL:
case goog.events.KeyCodes.DOWN:
case goog.events.KeyCodes.END:
case goog.events.KeyCodes.ESC:
case goog.events.KeyCodes.HOME:
case goog.events.KeyCodes.INSERT:
case goog.events.KeyCodes.LEFT:
case goog.events.KeyCodes.MAC_FF_META:
case goog.events.KeyCodes.META:
case goog.events.KeyCodes.NUMLOCK:
case goog.events.KeyCodes.NUM_CENTER:
case goog.events.KeyCodes.PAGE_DOWN:
case goog.events.KeyCodes.PAGE_UP:
case goog.events.KeyCodes.PAUSE:
case goog.events.KeyCodes.PHANTOM:
case goog.events.KeyCodes.PRINT_SCREEN:
case goog.events.KeyCodes.RIGHT:
case goog.events.KeyCodes.SCROLL_LOCK:
case goog.events.KeyCodes.SHIFT:
case goog.events.KeyCodes.UP:
case goog.events.KeyCodes.WIN_KEY:
case goog.events.KeyCodes.WIN_KEY_RIGHT:
return false;
case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
return !goog.userAgent.GECKO;
default:
return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
}
};
/**
* Returns true if the key fires a keypress event in the current browser.
*
* Accoridng to MSDN [1] IE only fires keypress events for the following keys:
* - Letters: A - Z (uppercase and lowercase)
* - Numerals: 0 - 9
* - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
* - System: ESC, SPACEBAR, ENTER
*
* That's not entirely correct though, for instance there's no distinction
* between upper and lower case letters.
*
* [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
*
* Safari is similar to IE, but does not fire keypress for ESC.
*
* Additionally, IE6 does not fire keydown or keypress events for letters when
* the control or alt keys are held down and the shift key is not. IE7 does
* fire keydown in these cases, though, but not keypress.
*
* @param {number} keyCode A key code.
* @param {number=} opt_heldKeyCode Key code of a currently-held key.
* @param {boolean=} opt_shiftKey Whether the shift key is held down.
* @param {boolean=} opt_ctrlKey Whether the control key is held down.
* @param {boolean=} opt_altKey Whether the alt key is held down.
* @return {boolean} Whether it's a key that fires a keypress event.
*/
goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
opt_shiftKey, opt_ctrlKey, opt_altKey) {
if (!goog.userAgent.IE &&
!(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
return true;
}
if (goog.userAgent.MAC && opt_altKey) {
return goog.events.KeyCodes.isCharacterKey(keyCode);
}
// Alt but not AltGr which is represented as Alt+Ctrl.
if (opt_altKey && !opt_ctrlKey) {
return false;
}
// Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
// Non-IE browsers and WebKit prior to 525 won't get this far so no need to
// check the user agent.
if (!opt_shiftKey &&
(opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
opt_heldKeyCode == goog.events.KeyCodes.ALT ||
goog.userAgent.MAC &&
opt_heldKeyCode == goog.events.KeyCodes.META)) {
return false;
}
// Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) {
switch (keyCode) {
case goog.events.KeyCodes.BACKSLASH:
case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
case goog.events.KeyCodes.TILDE:
case goog.events.KeyCodes.SEMICOLON:
case goog.events.KeyCodes.DASH:
case goog.events.KeyCodes.EQUALS:
case goog.events.KeyCodes.COMMA:
case goog.events.KeyCodes.PERIOD:
case goog.events.KeyCodes.SLASH:
case goog.events.KeyCodes.APOSTROPHE:
case goog.events.KeyCodes.SINGLE_QUOTE:
return false;
}
}
// When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
// continues to fire keydown events as the event repeats.
if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
return false;
}
switch (keyCode) {
case goog.events.KeyCodes.ENTER:
// IE9 does not fire KEYPRESS on ENTER.
return !(goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9));
case goog.events.KeyCodes.ESC:
return !goog.userAgent.WEBKIT;
}
return goog.events.KeyCodes.isCharacterKey(keyCode);
};
/**
* Returns true if the key produces a character.
* This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
*
* @param {number} keyCode A key code.
* @return {boolean} Whether it's a character key.
*/
goog.events.KeyCodes.isCharacterKey = function(keyCode) {
if (keyCode >= goog.events.KeyCodes.ZERO &&
keyCode <= goog.events.KeyCodes.NINE) {
return true;
}
if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
return true;
}
if (keyCode >= goog.events.KeyCodes.A &&
keyCode <= goog.events.KeyCodes.Z) {
return true;
}
// Safari sends zero key code for non-latin characters.
if (goog.userAgent.WEBKIT && keyCode == 0) {
return true;
}
switch (keyCode) {
case goog.events.KeyCodes.SPACE:
case goog.events.KeyCodes.QUESTION_MARK:
case goog.events.KeyCodes.NUM_PLUS:
case goog.events.KeyCodes.NUM_MINUS:
case goog.events.KeyCodes.NUM_PERIOD:
case goog.events.KeyCodes.NUM_DIVISION:
case goog.events.KeyCodes.SEMICOLON:
case goog.events.KeyCodes.FF_SEMICOLON:
case goog.events.KeyCodes.DASH:
case goog.events.KeyCodes.EQUALS:
case goog.events.KeyCodes.FF_EQUALS:
case goog.events.KeyCodes.COMMA:
case goog.events.KeyCodes.PERIOD:
case goog.events.KeyCodes.SLASH:
case goog.events.KeyCodes.APOSTROPHE:
case goog.events.KeyCodes.SINGLE_QUOTE:
case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
case goog.events.KeyCodes.BACKSLASH:
case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
return true;
default:
return false;
}
};
/**
* Normalizes key codes from their Gecko-specific value to the general one.
* @param {number} keyCode The native key code.
* @return {number} The normalized key code.
*/
goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
switch (keyCode) {
case goog.events.KeyCodes.FF_EQUALS:
return goog.events.KeyCodes.EQUALS;
case goog.events.KeyCodes.FF_SEMICOLON:
return goog.events.KeyCodes.SEMICOLON;
case goog.events.KeyCodes.MAC_FF_META:
return goog.events.KeyCodes.META;
case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
return goog.events.KeyCodes.WIN_KEY;
default:
return keyCode;
}
};

View File

@@ -0,0 +1,556 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This file contains a class for working with keyboard events
* that repeat consistently across browsers and platforms. It also unifies the
* key code so that it is the same in all browsers and platforms.
*
* Different web browsers have very different keyboard event handling. Most
* importantly is that only certain browsers repeat keydown events:
* IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
* FF/Mac and Safari 2 do not.
*
* For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
* decided that they should try to match IE's key handling behavior.
* Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
* Safari 2 behavior.
*
* Firefox, Safari, Opera prevent on keypress
*
* IE prevents on keydown
*
* Firefox does not fire keypress for shift, ctrl, alt
* Firefox does fire keydown for shift, ctrl, alt, meta
* Firefox does not repeat keydown for shift, ctrl, alt, meta
*
* Firefox does not fire keypress for up and down in an input
*
* Opera fires keypress for shift, ctrl, alt, meta
* Opera does not repeat keypress for shift, ctrl, alt, meta
*
* Safari 2 and 3 do not fire keypress for shift, ctrl, alt
* Safari 2 does not fire keydown for shift, ctrl, alt
* Safari 3 *does* fire keydown for shift, ctrl, alt
*
* IE provides the keycode for keyup/down events and the charcode (in the
* keycode field) for keypress.
*
* Mozilla provides the keycode for keyup/down and the charcode for keypress
* unless it's a non text modifying key in which case the keycode is provided.
*
* Safari 3 provides the keycode and charcode for all events.
*
* Opera provides the keycode for keyup/down event and either the charcode or
* the keycode (in the keycode field) for keypress events.
*
* Firefox x11 doesn't fire keydown events if a another key is already held down
* until the first key is released. This can cause a key event to be fired with
* a keyCode for the first key and a charCode for the second key.
*
* Safari in keypress
*
* charCode keyCode which
* ENTER: 13 13 13
* F1: 63236 63236 63236
* F8: 63243 63243 63243
* ...
* p: 112 112 112
* P: 80 80 80
*
* Firefox, keypress:
*
* charCode keyCode which
* ENTER: 0 13 13
* F1: 0 112 0
* F8: 0 119 0
* ...
* p: 112 0 112
* P: 80 0 80
*
* Opera, Mac+Win32, keypress:
*
* charCode keyCode which
* ENTER: undefined 13 13
* F1: undefined 112 0
* F8: undefined 119 0
* ...
* p: undefined 112 112
* P: undefined 80 80
*
* IE7, keydown
*
* charCode keyCode which
* ENTER: undefined 13 undefined
* F1: undefined 112 undefined
* F8: undefined 119 undefined
* ...
* p: undefined 80 undefined
* P: undefined 80 undefined
*
* @author arv@google.com (Erik Arvidsson)
* @author eae@google.com (Emil A Eklund)
* @see ../demos/keyhandler.html
*/
goog.provide('goog.events.KeyEvent');
goog.provide('goog.events.KeyHandler');
goog.provide('goog.events.KeyHandler.EventType');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.userAgent');
/**
* A wrapper around an element that you want to listen to keyboard events on.
* @param {Element|Document=} opt_element The element or document to listen on.
* @param {boolean=} opt_capture Whether to listen for browser events in
* capture phase (defaults to false).
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.KeyHandler = function(opt_element, opt_capture) {
goog.events.EventTarget.call(this);
if (opt_element) {
this.attach(opt_element, opt_capture);
}
};
goog.inherits(goog.events.KeyHandler, goog.events.EventTarget);
/**
* This is the element that we will listen to the real keyboard events on.
* @type {Element|Document|null}
* @private
*/
goog.events.KeyHandler.prototype.element_ = null;
/**
* The key for the key press listener.
* @type {goog.events.Key}
* @private
*/
goog.events.KeyHandler.prototype.keyPressKey_ = null;
/**
* The key for the key down listener.
* @type {goog.events.Key}
* @private
*/
goog.events.KeyHandler.prototype.keyDownKey_ = null;
/**
* The key for the key up listener.
* @type {goog.events.Key}
* @private
*/
goog.events.KeyHandler.prototype.keyUpKey_ = null;
/**
* Used to detect keyboard repeat events.
* @private
* @type {number}
*/
goog.events.KeyHandler.prototype.lastKey_ = -1;
/**
* Keycode recorded for key down events. As most browsers don't report the
* keycode in the key press event we need to record it in the key down phase.
* @private
* @type {number}
*/
goog.events.KeyHandler.prototype.keyCode_ = -1;
/**
* Alt key recorded for key down events. FF on Mac does not report the alt key
* flag in the key press event, we need to record it in the key down phase.
* @type {boolean}
* @private
*/
goog.events.KeyHandler.prototype.altKey_ = false;
/**
* Enum type for the events fired by the key handler
* @enum {string}
*/
goog.events.KeyHandler.EventType = {
KEY: 'key'
};
/**
* An enumeration of key codes that Safari 2 does incorrectly
* @type {Object}
* @private
*/
goog.events.KeyHandler.safariKey_ = {
'3': goog.events.KeyCodes.ENTER, // 13
'12': goog.events.KeyCodes.NUMLOCK, // 144
'63232': goog.events.KeyCodes.UP, // 38
'63233': goog.events.KeyCodes.DOWN, // 40
'63234': goog.events.KeyCodes.LEFT, // 37
'63235': goog.events.KeyCodes.RIGHT, // 39
'63236': goog.events.KeyCodes.F1, // 112
'63237': goog.events.KeyCodes.F2, // 113
'63238': goog.events.KeyCodes.F3, // 114
'63239': goog.events.KeyCodes.F4, // 115
'63240': goog.events.KeyCodes.F5, // 116
'63241': goog.events.KeyCodes.F6, // 117
'63242': goog.events.KeyCodes.F7, // 118
'63243': goog.events.KeyCodes.F8, // 119
'63244': goog.events.KeyCodes.F9, // 120
'63245': goog.events.KeyCodes.F10, // 121
'63246': goog.events.KeyCodes.F11, // 122
'63247': goog.events.KeyCodes.F12, // 123
'63248': goog.events.KeyCodes.PRINT_SCREEN, // 44
'63272': goog.events.KeyCodes.DELETE, // 46
'63273': goog.events.KeyCodes.HOME, // 36
'63275': goog.events.KeyCodes.END, // 35
'63276': goog.events.KeyCodes.PAGE_UP, // 33
'63277': goog.events.KeyCodes.PAGE_DOWN, // 34
'63289': goog.events.KeyCodes.NUMLOCK, // 144
'63302': goog.events.KeyCodes.INSERT // 45
};
/**
* An enumeration of key identifiers currently part of the W3C draft for DOM3
* and their mappings to keyCodes.
* http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
* This is currently supported in Safari and should be platform independent.
* @type {Object}
* @private
*/
goog.events.KeyHandler.keyIdentifier_ = {
'Up': goog.events.KeyCodes.UP, // 38
'Down': goog.events.KeyCodes.DOWN, // 40
'Left': goog.events.KeyCodes.LEFT, // 37
'Right': goog.events.KeyCodes.RIGHT, // 39
'Enter': goog.events.KeyCodes.ENTER, // 13
'F1': goog.events.KeyCodes.F1, // 112
'F2': goog.events.KeyCodes.F2, // 113
'F3': goog.events.KeyCodes.F3, // 114
'F4': goog.events.KeyCodes.F4, // 115
'F5': goog.events.KeyCodes.F5, // 116
'F6': goog.events.KeyCodes.F6, // 117
'F7': goog.events.KeyCodes.F7, // 118
'F8': goog.events.KeyCodes.F8, // 119
'F9': goog.events.KeyCodes.F9, // 120
'F10': goog.events.KeyCodes.F10, // 121
'F11': goog.events.KeyCodes.F11, // 122
'F12': goog.events.KeyCodes.F12, // 123
'U+007F': goog.events.KeyCodes.DELETE, // 46
'Home': goog.events.KeyCodes.HOME, // 36
'End': goog.events.KeyCodes.END, // 35
'PageUp': goog.events.KeyCodes.PAGE_UP, // 33
'PageDown': goog.events.KeyCodes.PAGE_DOWN, // 34
'Insert': goog.events.KeyCodes.INSERT // 45
};
/**
* If true, the KeyEvent fires on keydown. Otherwise, it fires on keypress.
*
* @type {boolean}
* @private
*/
goog.events.KeyHandler.USES_KEYDOWN_ = goog.userAgent.IE ||
goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525');
/**
* If true, the alt key flag is saved during the key down and reused when
* handling the key press. FF on Mac does not set the alt flag in the key press
* event.
* @type {boolean}
* @private
*/
goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_ = goog.userAgent.MAC &&
goog.userAgent.GECKO;
/**
* Records the keycode for browsers that only returns the keycode for key up/
* down events. For browser/key combinations that doesn't trigger a key pressed
* event it also fires the patched key event.
* @param {goog.events.BrowserEvent} e The key down event.
* @private
*/
goog.events.KeyHandler.prototype.handleKeyDown_ = function(e) {
// Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
// before we've caught a key-up event. If the last-key was one of these we
// reset the state.
if (goog.userAgent.WEBKIT) {
if (this.lastKey_ == goog.events.KeyCodes.CTRL && !e.ctrlKey ||
this.lastKey_ == goog.events.KeyCodes.ALT && !e.altKey ||
goog.userAgent.MAC &&
this.lastKey_ == goog.events.KeyCodes.META && !e.metaKey) {
this.lastKey_ = -1;
this.keyCode_ = -1;
}
}
if (this.lastKey_ == -1) {
if (e.ctrlKey && e.keyCode != goog.events.KeyCodes.CTRL) {
this.lastKey_ = goog.events.KeyCodes.CTRL;
} else if (e.altKey && e.keyCode != goog.events.KeyCodes.ALT) {
this.lastKey_ = goog.events.KeyCodes.ALT;
} else if (e.metaKey && e.keyCode != goog.events.KeyCodes.META) {
this.lastKey_ = goog.events.KeyCodes.META;
}
}
if (goog.events.KeyHandler.USES_KEYDOWN_ &&
!goog.events.KeyCodes.firesKeyPressEvent(e.keyCode,
this.lastKey_, e.shiftKey, e.ctrlKey, e.altKey)) {
this.handleEvent(e);
} else {
this.keyCode_ = goog.userAgent.GECKO ?
goog.events.KeyCodes.normalizeGeckoKeyCode(e.keyCode) :
e.keyCode;
if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
this.altKey_ = e.altKey;
}
}
};
/**
* Resets the stored previous values. Needed to be called for webkit which will
* not generate a key up for meta key operations. This should only be called
* when having finished with repeat key possiblities.
*/
goog.events.KeyHandler.prototype.resetState = function() {
this.lastKey_ = -1;
this.keyCode_ = -1;
};
/**
* Clears the stored previous key value, resetting the key repeat status. Uses
* -1 because the Safari 3 Windows beta reports 0 for certain keys (like Home
* and End.)
* @param {goog.events.BrowserEvent} e The keyup event.
* @private
*/
goog.events.KeyHandler.prototype.handleKeyup_ = function(e) {
this.resetState();
this.altKey_ = e.altKey;
};
/**
* Handles the events on the element.
* @param {goog.events.BrowserEvent} e The keyboard event sent from the
* browser.
*/
goog.events.KeyHandler.prototype.handleEvent = function(e) {
var be = e.getBrowserEvent();
var keyCode, charCode;
var altKey = be.altKey;
// IE reports the character code in the keyCode field for keypress events.
// There are two exceptions however, Enter and Escape.
if (goog.userAgent.IE && e.type == goog.events.EventType.KEYPRESS) {
keyCode = this.keyCode_;
charCode = keyCode != goog.events.KeyCodes.ENTER &&
keyCode != goog.events.KeyCodes.ESC ?
be.keyCode : 0;
// Safari reports the character code in the keyCode field for keypress
// events but also has a charCode field.
} else if (goog.userAgent.WEBKIT &&
e.type == goog.events.EventType.KEYPRESS) {
keyCode = this.keyCode_;
charCode = be.charCode >= 0 && be.charCode < 63232 &&
goog.events.KeyCodes.isCharacterKey(keyCode) ?
be.charCode : 0;
// Opera reports the keycode or the character code in the keyCode field.
} else if (goog.userAgent.OPERA) {
keyCode = this.keyCode_;
charCode = goog.events.KeyCodes.isCharacterKey(keyCode) ?
be.keyCode : 0;
// Mozilla reports the character code in the charCode field.
} else {
keyCode = be.keyCode || this.keyCode_;
charCode = be.charCode || 0;
if (goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_) {
altKey = this.altKey_;
}
// On the Mac, shift-/ triggers a question mark char code and no key code
// (normalized to WIN_KEY), so we synthesize the latter.
if (goog.userAgent.MAC &&
charCode == goog.events.KeyCodes.QUESTION_MARK &&
keyCode == goog.events.KeyCodes.WIN_KEY) {
keyCode = goog.events.KeyCodes.SLASH;
}
}
var key = keyCode;
var keyIdentifier = be.keyIdentifier;
// Correct the key value for certain browser-specific quirks.
if (keyCode) {
if (keyCode >= 63232 && keyCode in goog.events.KeyHandler.safariKey_) {
// NOTE(nicksantos): Safari 3 has fixed this problem,
// this is only needed for Safari 2.
key = goog.events.KeyHandler.safariKey_[keyCode];
} else {
// Safari returns 25 for Shift+Tab instead of 9.
if (keyCode == 25 && e.shiftKey) {
key = 9;
}
}
} else if (keyIdentifier &&
keyIdentifier in goog.events.KeyHandler.keyIdentifier_) {
// This is needed for Safari Windows because it currently doesn't give a
// keyCode/which for non printable keys.
key = goog.events.KeyHandler.keyIdentifier_[keyIdentifier];
}
// If we get the same keycode as a keydown/keypress without having seen a
// keyup event, then this event was caused by key repeat.
var repeat = key == this.lastKey_;
this.lastKey_ = key;
var event = new goog.events.KeyEvent(key, charCode, repeat, be);
event.altKey = altKey;
this.dispatchEvent(event);
};
/**
* Returns the element listened on for the real keyboard events.
* @return {Element|Document|null} The element listened on for the real
* keyboard events.
*/
goog.events.KeyHandler.prototype.getElement = function() {
return this.element_;
};
/**
* Adds the proper key event listeners to the element.
* @param {Element|Document} element The element to listen on.
* @param {boolean=} opt_capture Whether to listen for browser events in
* capture phase (defaults to false).
*/
goog.events.KeyHandler.prototype.attach = function(element, opt_capture) {
if (this.keyUpKey_) {
this.detach();
}
this.element_ = element;
this.keyPressKey_ = goog.events.listen(this.element_,
goog.events.EventType.KEYPRESS,
this,
opt_capture);
// Most browsers (Safari 2 being the notable exception) doesn't include the
// keyCode in keypress events (IE has the char code in the keyCode field and
// Mozilla only included the keyCode if there's no charCode). Thus we have to
// listen for keydown to capture the keycode.
this.keyDownKey_ = goog.events.listen(this.element_,
goog.events.EventType.KEYDOWN,
this.handleKeyDown_,
opt_capture,
this);
this.keyUpKey_ = goog.events.listen(this.element_,
goog.events.EventType.KEYUP,
this.handleKeyup_,
opt_capture,
this);
};
/**
* Removes the listeners that may exist.
*/
goog.events.KeyHandler.prototype.detach = function() {
if (this.keyPressKey_) {
goog.events.unlistenByKey(this.keyPressKey_);
goog.events.unlistenByKey(this.keyDownKey_);
goog.events.unlistenByKey(this.keyUpKey_);
this.keyPressKey_ = null;
this.keyDownKey_ = null;
this.keyUpKey_ = null;
}
this.element_ = null;
this.lastKey_ = -1;
this.keyCode_ = -1;
};
/** @override */
goog.events.KeyHandler.prototype.disposeInternal = function() {
goog.events.KeyHandler.superClass_.disposeInternal.call(this);
this.detach();
};
/**
* This class is used for the goog.events.KeyHandler.EventType.KEY event and
* it overrides the key code with the fixed key code.
* @param {number} keyCode The adjusted key code.
* @param {number} charCode The unicode character code.
* @param {boolean} repeat Whether this event was generated by keyboard repeat.
* @param {Event} browserEvent Browser event object.
* @constructor
* @extends {goog.events.BrowserEvent}
*/
goog.events.KeyEvent = function(keyCode, charCode, repeat, browserEvent) {
goog.events.BrowserEvent.call(this, browserEvent);
this.type = goog.events.KeyHandler.EventType.KEY;
/**
* Keycode of key press.
* @type {number}
*/
this.keyCode = keyCode;
/**
* Unicode character code.
* @type {number}
*/
this.charCode = charCode;
/**
* True if this event was generated by keyboard auto-repeat (i.e., the user is
* holding the key down.)
* @type {boolean}
*/
this.repeat = repeat;
};
goog.inherits(goog.events.KeyEvent, goog.events.BrowserEvent);

View File

@@ -0,0 +1,132 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Constant declarations for common key codes.
*
* @author eae@google.com (Emil A Eklund)
*/
goog.provide('goog.events.KeyNames');
/**
* Key names for common characters.
*
* This list is not localized and therefore some of the key codes are not
* correct for non-US keyboard layouts.
*
* @see goog.events.KeyCodes
* @enum {string}
*/
goog.events.KeyNames = {
8: 'backspace',
9: 'tab',
13: 'enter',
16: 'shift',
17: 'ctrl',
18: 'alt',
19: 'pause',
20: 'caps-lock',
27: 'esc',
32: 'space',
33: 'pg-up',
34: 'pg-down',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'insert',
46: 'delete',
48: '0',
49: '1',
50: '2',
51: '3',
52: '4',
53: '5',
54: '6',
55: '7',
56: '8',
57: '9',
59: 'semicolon',
61: 'equals',
65: 'a',
66: 'b',
67: 'c',
68: 'd',
69: 'e',
70: 'f',
71: 'g',
72: 'h',
73: 'i',
74: 'j',
75: 'k',
76: 'l',
77: 'm',
78: 'n',
79: 'o',
80: 'p',
81: 'q',
82: 'r',
83: 's',
84: 't',
85: 'u',
86: 'v',
87: 'w',
88: 'x',
89: 'y',
90: 'z',
93: 'context',
96: 'num-0',
97: 'num-1',
98: 'num-2',
99: 'num-3',
100: 'num-4',
101: 'num-5',
102: 'num-6',
103: 'num-7',
104: 'num-8',
105: 'num-9',
106: 'num-multiply',
107: 'num-plus',
109: 'num-minus',
110: 'num-period',
111: 'num-division',
112: 'f1',
113: 'f2',
114: 'f3',
115: 'f4',
116: 'f5',
117: 'f6',
118: 'f7',
119: 'f8',
120: 'f9',
121: 'f10',
122: 'f11',
123: 'f12',
186: 'semicolon',
187: 'equals',
189: 'dash',
188: ',',
190: '.',
191: '/',
192: '~',
219: 'open-square-bracket',
220: '\\',
221: 'close-square-bracket',
222: 'single-quote',
224: 'win'
};

View File

@@ -0,0 +1,323 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An interface for a listenable JavaScript object.
*/
goog.provide('goog.events.Listenable');
goog.provide('goog.events.ListenableKey');
/**
* A listenable interface. A listenable is an object with the ability
* to dispatch/broadcast events to "event listeners" registered via
* listen/listenOnce.
*
* The interface allows for an event propagation mechanism similar
* to one offered by native browser event targets, such as
* capture/bubble mechanism, stopping propagation, and preventing
* default actions. Capture/bubble mechanism depends on the ancestor
* tree constructed via {@code #getParentEventTarget}; this tree
* must be directed acyclic graph. The meaning of default action(s)
* in preventDefault is specific to a particular use case.
*
* Implementations that do not support capture/bubble or can not have
* a parent listenable can simply not implement any ability to set the
* parent listenable (and have {@code #getParentEventTarget} return
* null).
*
* Implementation of this class can be used with or independently from
* goog.events.
*
* Implementation must call {@code #addImplementation(implClass)}.
*
* @interface
* @see goog.events
* @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
*/
goog.events.Listenable = function() {};
/**
* An expando property to indicate that an object implements
* goog.events.Listenable.
*
* See addImplementation/isImplementedBy.
*
* @type {string}
* @const
*/
goog.events.Listenable.IMPLEMENTED_BY_PROP =
'closure_listenable_' + ((Math.random() * 1e6) | 0);
/**
* Marks a given class (constructor) as an implementation of
* Listenable, do that we can query that fact at runtime. The class
* must have already implemented the interface.
* @param {!Function} cls The class constructor. The corresponding
* class must have already implemented the interface.
*/
goog.events.Listenable.addImplementation = function(cls) {
cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
};
/**
* @param {Object} obj The object to check.
* @return {boolean} Whether a given instance implements
* Listenable. The class/superclass of the instance must call
* addImplementation.
*/
goog.events.Listenable.isImplementedBy = function(obj) {
return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
};
/**
* Adds an event listener. A listener can only be added once to an
* object and if it is added again the key for the listener is
* returned. Note that if the existing listener is a one-off listener
* (registered via listenOnce), it will no longer be a one-off
* listener after a call to listen().
*
* @param {string} type Event type or array of event types.
* @param {!Function} listener Callback method, or an object
* with a handleEvent function.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} Unique key for the listener.
*/
goog.events.Listenable.prototype.listen;
/**
* Adds an event listener that is removed automatically after the
* listener fired once.
*
* If an existing listener already exists, listenOnce will do
* nothing. In particular, if the listener was previously registered
* via listen(), listenOnce() will not turn the listener into a
* one-off listener. Similarly, if there is already an existing
* one-off listener, listenOnce does not modify the listeners (it is
* still a once listener).
*
* @param {string} type Event type or array of event types.
* @param {!Function} listener Callback method, or an object
* with a handleEvent function.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} Unique key for the listener.
*/
goog.events.Listenable.prototype.listenOnce;
/**
* Removes an event listener which was added with listen() or listenOnce().
*
* @param {string} type Event type or array of event types.
* @param {!Function} listener Callback method, or an object
* with a handleEvent function.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {Object=} opt_listenerScope Object in whose scope to call
* the listener.
* @return {boolean} Whether any listener was removed.
*/
goog.events.Listenable.prototype.unlisten;
/**
* Removes an event listener which was added with listen() by the key
* returned by listen().
*
* @param {goog.events.ListenableKey} key The key returned by
* listen() or listenOnce().
* @return {boolean} Whether any listener was removed.
*/
goog.events.Listenable.prototype.unlistenByKey;
/**
* Dispatches an event (or event like object) and calls all listeners
* listening for events of this type. The type of the event is decided by the
* type property on the event object.
*
* If any of the listeners returns false OR calls preventDefault then this
* function will return false. If one of the capture listeners calls
* stopPropagation, then the bubble listeners won't fire.
*
* @param {goog.events.EventLike} e Event object.
* @return {boolean} If anyone called preventDefault on the event object (or
* if any of the listeners returns false) this will also return false.
*/
goog.events.Listenable.prototype.dispatchEvent;
/**
* Removes all listeners from this listenable. If type is specified,
* it will only remove listeners of the particular type. otherwise all
* registered listeners will be removed.
*
* @param {string=} opt_type Type of event to remove, default is to
* remove all types.
* @return {number} Number of listeners removed.
*/
goog.events.Listenable.prototype.removeAllListeners;
/**
* Returns the parent of this event target to use for capture/bubble
* mechanism.
*
* NOTE(user): The name reflects the original implementation of
* custom event target ({@code goog.events.EventTarget}). We decided
* that changing the name is not worth it.
*
* @return {goog.events.Listenable} The parent EventTarget or null if
* there is no parent.
*/
goog.events.Listenable.prototype.getParentEventTarget;
/**
* Fires all registered listeners in this listenable for the given
* type and capture mode, passing them the given eventObject. This
* does not perform actual capture/bubble. Only implementors of the
* interface should be using this.
*
* @param {string} type The type of the listeners to fire.
* @param {boolean} capture The capture mode of the listeners to fire.
* @param {goog.events.Event} eventObject The event object to fire.
* @return {boolean} Whether all listeners succeeded without
* attempting to prevent default behavior. If any listener returns
* false or called goog.events.Event#preventDefault, this returns
* false.
*/
goog.events.Listenable.prototype.fireListeners;
/**
* Gets all listeners in this listenable for the given type and
* capture mode.
*
* @param {string} type The type of the listeners to fire.
* @param {boolean} capture The capture mode of the listeners to fire.
* @return {!Array.<goog.events.ListenableKey>} An array of registered
* listeners.
*/
goog.events.Listenable.prototype.getListeners;
/**
* Gets the goog.events.ListenableKey for the event or null if no such
* listener is in use.
*
* @param {string} type The name of the event without the 'on' prefix.
* @param {!Function} listener The listener function to get.
* @param {boolean} capture Whether the listener is a capturing listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} the found listener or null if not found.
*/
goog.events.Listenable.prototype.getListener;
/**
* Whether there is any active listeners matching the specified
* signature. If either the type or capture parameters are
* unspecified, the function will match on the remaining criteria.
*
* @param {string=} opt_type Event type.
* @param {boolean=} opt_capture Whether to check for capture or bubble
* listeners.
* @return {boolean} Whether there is any active listeners matching
* the requested type and/or capture phase.
*/
goog.events.Listenable.prototype.hasListener;
/**
* An interface that describes a single registered listener.
* @interface
*/
goog.events.ListenableKey = function() {};
/**
* Counter used to create a unique key
* @type {number}
* @private
*/
goog.events.ListenableKey.counter_ = 0;
/**
* Reserves a key to be used for ListenableKey#key field.
* @return {number} A number to be used to fill ListenableKey#key
* field.
*/
goog.events.ListenableKey.reserveKey = function() {
return ++goog.events.ListenableKey.counter_;
};
/**
* The source event target.
* @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
*/
goog.events.ListenableKey.prototype.src;
/**
* The event type the listener is listening to.
* @type {string}
*/
goog.events.ListenableKey.prototype.type;
/**
* The listener function.
* TODO(user): Narrow the type if possible.
* @type {Function|Object}
*/
goog.events.ListenableKey.prototype.listener;
/**
* Whether the listener works on capture phase.
* @type {boolean}
*/
goog.events.ListenableKey.prototype.capture;
/**
* The 'this' object for the listener function's scope.
* @type {Object}
*/
goog.events.ListenableKey.prototype.handler;
/**
* A globally unique number to identify the key.
* @type {number}
*/
goog.events.ListenableKey.prototype.key;

View File

@@ -0,0 +1,131 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Listener object.
* @see ../demos/events.html
*/
goog.provide('goog.events.Listener');
goog.require('goog.events.ListenableKey');
/**
* Simple class that stores information about a listener
* @param {!Function} listener Callback function.
* @param {Function} proxy Wrapper for the listener that patches the event.
* @param {EventTarget|goog.events.Listenable} src Source object for
* the event.
* @param {string} type Event type.
* @param {boolean} capture Whether in capture or bubble phase.
* @param {Object=} opt_handler Object in whose context to execute the callback.
* @implements {goog.events.ListenableKey}
* @constructor
*/
goog.events.Listener = function(
listener, proxy, src, type, capture, opt_handler) {
if (goog.events.Listener.ENABLE_MONITORING) {
this.creationStack = new Error().stack;
}
/**
* Callback function.
* @type {Function}
*/
this.listener = listener;
/**
* A wrapper over the original listener. This is used solely to
* handle native browser events (it is used to simulate the capture
* phase and to patch the event object).
* @type {Function}
*/
this.proxy = proxy;
/**
* Object or node that callback is listening to
* @type {EventTarget|goog.events.Listenable}
*/
this.src = src;
/**
* The event type.
* @const {string}
*/
this.type = type;
/**
* Whether the listener is being called in the capture or bubble phase
* @const {boolean}
*/
this.capture = !!capture;
/**
* Optional object whose context to execute the listener in
* @type {Object|undefined}
*/
this.handler = opt_handler;
/**
* The key of the listener.
* @const {number}
* @override
*/
this.key = goog.events.ListenableKey.reserveKey();
/**
* Whether to remove the listener after it has been called.
* @type {boolean}
*/
this.callOnce = false;
/**
* Whether the listener has been removed.
* @type {boolean}
*/
this.removed = false;
};
/**
* @define {boolean} Whether to enable the monitoring of the
* goog.events.Listener instances. Switching on the monitoring is only
* recommended for debugging because it has a significant impact on
* performance and memory usage. If switched off, the monitoring code
* compiles down to 0 bytes.
*/
goog.define('goog.events.Listener.ENABLE_MONITORING', false);
/**
* If monitoring the goog.events.Listener instances is enabled, stores the
* creation stack trace of the Disposable instance.
* @type {string}
*/
goog.events.Listener.prototype.creationStack;
/**
* Marks this listener as removed. This also remove references held by
* this listener object (such as listener and event source).
*/
goog.events.Listener.prototype.markAsRemoved = function() {
this.removed = true;
this.listener = null;
this.proxy = null;
this.src = null;
this.handler = null;
};

View File

@@ -0,0 +1,299 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A map of listeners that provides utility functions to
* deal with listeners on an event target. Used by
* {@code goog.events.EventTarget}.
*
* WARNING: Do not use this class from outside goog.events package.
*
* @visibility {//closure/goog/events:__pkg__}
*/
goog.provide('goog.events.ListenerMap');
goog.require('goog.array');
goog.require('goog.events.Listener');
goog.require('goog.object');
/**
* Creates a new listener map.
* @param {EventTarget|goog.events.Listenable} src The src object.
* @constructor
*/
goog.events.ListenerMap = function(src) {
/** @type {EventTarget|goog.events.Listenable} */
this.src = src;
/**
* Maps of event type to an array of listeners.
* @type {Object.<string, !Array.<!goog.events.Listener>>}
*/
this.listeners = {};
/**
* The count of types in this map that have registered listeners.
* @private {number}
*/
this.typeCount_ = 0;
};
/**
* @return {number} The count of event types in this map that actually
* have registered listeners.
*/
goog.events.ListenerMap.prototype.getTypeCount = function() {
return this.typeCount_;
};
/**
* @return {number} Total number of registered listeners.
*/
goog.events.ListenerMap.prototype.getListenerCount = function() {
var count = 0;
for (var type in this.listeners) {
count += this.listeners[type].length;
}
return count;
};
/**
* Adds an event listener. A listener can only be added once to an
* object and if it is added again the key for the listener is
* returned.
*
* Note that a one-off listener will not change an existing listener,
* if any. On the other hand a normal listener will change existing
* one-off listener to become a normal listener.
*
* @param {string} type The listener event type.
* @param {!Function} listener This listener callback method.
* @param {boolean} callOnce Whether the listener is a one-off
* listener.
* @param {boolean=} opt_useCapture The capture mode of the listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} Unique key for the listener.
*/
goog.events.ListenerMap.prototype.add = function(
type, listener, callOnce, opt_useCapture, opt_listenerScope) {
var listenerArray = this.listeners[type];
if (!listenerArray) {
listenerArray = this.listeners[type] = [];
this.typeCount_++;
}
var listenerObj;
var index = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, opt_useCapture, opt_listenerScope);
if (index > -1) {
listenerObj = listenerArray[index];
if (!callOnce) {
// Ensure that, if there is an existing callOnce listener, it is no
// longer a callOnce listener.
listenerObj.callOnce = false;
}
} else {
listenerObj = new goog.events.Listener(
listener, null, this.src, type, !!opt_useCapture, opt_listenerScope);
listenerObj.callOnce = callOnce;
listenerArray.push(listenerObj);
}
return listenerObj;
};
/**
* Removes a matching listener.
* @param {string} type The listener event type.
* @param {!Function} listener This listener callback method.
* @param {boolean=} opt_useCapture The capture mode of the listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {boolean} Whether any listener was removed.
*/
goog.events.ListenerMap.prototype.remove = function(
type, listener, opt_useCapture, opt_listenerScope) {
if (!(type in this.listeners)) {
return false;
}
var listenerArray = this.listeners[type];
var index = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, opt_useCapture, opt_listenerScope);
if (index > -1) {
var listenerObj = listenerArray[index];
listenerObj.markAsRemoved();
goog.array.removeAt(listenerArray, index);
if (listenerArray.length == 0) {
delete this.listeners[type];
this.typeCount_--;
}
return true;
}
return false;
};
/**
* Removes the given listener object.
* @param {goog.events.ListenableKey} listener The listener to remove.
* @return {boolean} Whether the listener is removed.
*/
goog.events.ListenerMap.prototype.removeByKey = function(listener) {
var type = listener.type;
if (!(type in this.listeners)) {
return false;
}
var removed = goog.array.remove(this.listeners[type], listener);
if (removed) {
listener.markAsRemoved();
if (this.listeners[type].length == 0) {
delete this.listeners[type];
this.typeCount_--;
}
}
return removed;
};
/**
* Removes all listeners from this map. If opt_type is provided, only
* listeners that match the given type are removed.
* @param {string=} opt_type Type of event to remove.
* @return {number} Number of listeners removed.
*/
goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
var count = 0;
for (var type in this.listeners) {
if (!opt_type || type == opt_type) {
var listenerArray = this.listeners[type];
for (var i = 0; i < listenerArray.length; i++) {
++count;
listenerArray[i].removed = true;
}
delete this.listeners[type];
this.typeCount_--;
}
}
return count;
};
/**
* Gets all listeners that match the given type and capture mode. The
* returned array is a copy (but the listener objects are not).
* @param {string} type The type of the listeners to retrieve.
* @param {boolean} capture The capture mode of the listeners to retrieve.
* @return {!Array.<goog.events.ListenableKey>} An array of matching
* listeners.
*/
goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
var listenerArray = this.listeners[type];
var rv = [];
if (listenerArray) {
for (var i = 0; i < listenerArray.length; ++i) {
var listenerObj = listenerArray[i];
if (listenerObj.capture == capture) {
rv.push(listenerObj);
}
}
}
return rv;
};
/**
* Gets the goog.events.ListenableKey for the event or null if no such
* listener is in use.
*
* @param {string} type The type of the listener to retrieve.
* @param {!Function} listener The listener function to get.
* @param {boolean} capture Whether the listener is a capturing listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} the found listener or null if not found.
*/
goog.events.ListenerMap.prototype.getListener = function(
type, listener, capture, opt_listenerScope) {
var listenerArray = this.listeners[type];
var i = -1;
if (listenerArray) {
i = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, capture, opt_listenerScope);
}
return i > -1 ? listenerArray[i] : null;
};
/**
* Whether there is a matching listener. If either the type or capture
* parameters are unspecified, the function will match on the
* remaining criteria.
*
* @param {string=} opt_type The type of the listener.
* @param {boolean=} opt_capture The capture mode of the listener.
* @return {boolean} Whether there is an active listener matching
* the requested type and/or capture phase.
*/
goog.events.ListenerMap.prototype.hasListener = function(
opt_type, opt_capture) {
var hasType = goog.isDef(opt_type);
var hasCapture = goog.isDef(opt_capture);
return goog.object.some(
this.listeners, function(listenerArray, type) {
for (var i = 0; i < listenerArray.length; ++i) {
if ((!hasType || listenerArray[i].type == opt_type) &&
(!hasCapture || listenerArray[i].capture == opt_capture)) {
return true;
}
}
return false;
});
};
/**
* Finds the index of a matching goog.events.Listener in the given
* listenerArray.
* @param {!Array.<!goog.events.Listener>} listenerArray Array of listener.
* @param {!Function} listener The listener function.
* @param {boolean=} opt_useCapture The capture flag for the listener.
* @param {Object=} opt_listenerScope The listener scope.
* @return {number} The index of the matching listener within the
* listenerArray.
* @private
*/
goog.events.ListenerMap.findListenerIndex_ = function(
listenerArray, listener, opt_useCapture, opt_listenerScope) {
for (var i = 0; i < listenerArray.length; ++i) {
var listenerObj = listenerArray[i];
if (!listenerObj.removed &&
listenerObj.listener == listener &&
listenerObj.capture == !!opt_useCapture &&
listenerObj.handler == opt_listenerScope) {
return i;
}
}
return -1;
};

View File

@@ -0,0 +1,116 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Tests for listenermap.js.
*
* Most of this class functionality is already tested by
* goog.events.EventTarget tests. This test file only provides tests
* for features that are not direct duplicates of tests in
* goog.events.EventTarget.
*/
goog.provide('goog.events.ListenerMapTest');
goog.setTestOnly('goog.events.ListenerMapTest');
goog.require('goog.dispose');
goog.require('goog.events.EventTarget');
goog.require('goog.events.ListenerMap');
goog.require('goog.testing.jsunit');
var et, map;
var handler1 = function() {};
var handler2 = function() {};
var handler3 = function() {};
var handler4 = function() {};
var handler5 = function() {};
function setUp() {
et = new goog.events.EventTarget();
map = new goog.events.ListenerMap(et);
}
function tearDown() {
goog.dispose(et);
}
function testGetTypeCount() {
assertEquals(0, map.getTypeCount());
map.add('click', handler1, false);
assertEquals(1, map.getTypeCount());
map.remove('click', handler1);
assertEquals(0, map.getTypeCount());
map.add('click', handler1, false, true);
assertEquals(1, map.getTypeCount());
map.remove('click', handler1, true);
assertEquals(0, map.getTypeCount());
map.add('click', handler1, false);
map.add('click', handler1, false, true);
assertEquals(1, map.getTypeCount());
map.remove('click', handler1);
assertEquals(1, map.getTypeCount());
map.remove('click', handler1, true);
assertEquals(0, map.getTypeCount());
map.add('click', handler1, false);
map.add('touchstart', handler2, false);
assertEquals(2, map.getTypeCount());
map.remove('touchstart', handler2);
assertEquals(1, map.getTypeCount());
map.remove('click', handler1);
assertEquals(0, map.getTypeCount());
}
function testGetListenerCount() {
assertEquals(0, map.getListenerCount());
map.add('click', handler1, false);
assertEquals(1, map.getListenerCount());
map.remove('click', handler1);
assertEquals(0, map.getListenerCount());
map.add('click', handler1, false, true);
assertEquals(1, map.getListenerCount());
map.remove('click', handler1, true);
assertEquals(0, map.getListenerCount());
map.add('click', handler1, false);
map.add('click', handler1, false, true);
assertEquals(2, map.getListenerCount());
map.remove('click', handler1);
map.remove('click', handler1, true);
assertEquals(0, map.getListenerCount());
map.add('click', handler1, false);
map.add('touchstart', handler2, false);
assertEquals(2, map.getListenerCount());
map.remove('touchstart', handler2);
map.remove('click', handler1);
assertEquals(0, map.getListenerCount());
}
function testListenerSourceIsSetCorrectly() {
map.add('click', handler1, false);
var listener = map.getListener('click', handler1);
assertEquals(et, listener.src);
}

View File

@@ -0,0 +1,295 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This event wrapper will dispatch an event when the user uses
* the mouse wheel to scroll an element. You can get the direction by checking
* the deltaX and deltaY properties of the event.
*
* This class aims to smooth out inconsistencies between browser platforms with
* regards to mousewheel events, but we do not cover every possible
* software/hardware combination out there, some of which occasionally produce
* very large deltas in mousewheel events. If your application wants to guard
* against extremely large deltas, use the setMaxDeltaX and setMaxDeltaY APIs
* to set maximum values that make sense for your application.
*
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/mousewheelhandler.html
*/
goog.provide('goog.events.MouseWheelEvent');
goog.provide('goog.events.MouseWheelHandler');
goog.provide('goog.events.MouseWheelHandler.EventType');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.math');
goog.require('goog.style');
goog.require('goog.userAgent');
/**
* This event handler allows you to catch mouse wheel events in a consistent
* manner.
* @param {Element|Document} element The element to listen to the mouse wheel
* event on.
* @param {boolean=} opt_capture Whether to handle the mouse wheel event in
* capture phase.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.MouseWheelHandler = function(element, opt_capture) {
goog.events.EventTarget.call(this);
/**
* This is the element that we will listen to the real mouse wheel events on.
* @type {Element|Document}
* @private
*/
this.element_ = element;
var rtlElement = goog.dom.isElement(this.element_) ?
/** @type {Element} */ (this.element_) :
(this.element_ ? /** @type {Document} */ (this.element_).body : null);
/**
* True if the element exists and is RTL, false otherwise.
* @type {boolean}
* @private
*/
this.isRtl_ = !!rtlElement && goog.style.isRightToLeft(rtlElement);
var type = goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel';
/**
* The key returned from the goog.events.listen.
* @type {goog.events.Key}
* @private
*/
this.listenKey_ = goog.events.listen(this.element_, type, this, opt_capture);
};
goog.inherits(goog.events.MouseWheelHandler, goog.events.EventTarget);
/**
* Enum type for the events fired by the mouse wheel handler.
* @enum {string}
*/
goog.events.MouseWheelHandler.EventType = {
MOUSEWHEEL: 'mousewheel'
};
/**
* Optional maximum magnitude for x delta on each mousewheel event.
* @type {number|undefined}
* @private
*/
goog.events.MouseWheelHandler.prototype.maxDeltaX_;
/**
* Optional maximum magnitude for y delta on each mousewheel event.
* @type {number|undefined}
* @private
*/
goog.events.MouseWheelHandler.prototype.maxDeltaY_;
/**
* @param {number} maxDeltaX Maximum magnitude for x delta on each mousewheel
* event. Should be non-negative.
*/
goog.events.MouseWheelHandler.prototype.setMaxDeltaX = function(maxDeltaX) {
this.maxDeltaX_ = maxDeltaX;
};
/**
* @param {number} maxDeltaY Maximum magnitude for y delta on each mousewheel
* event. Should be non-negative.
*/
goog.events.MouseWheelHandler.prototype.setMaxDeltaY = function(maxDeltaY) {
this.maxDeltaY_ = maxDeltaY;
};
/**
* Handles the events on the element.
* @param {goog.events.BrowserEvent} e The underlying browser event.
*/
goog.events.MouseWheelHandler.prototype.handleEvent = function(e) {
var deltaX = 0;
var deltaY = 0;
var detail = 0;
var be = e.getBrowserEvent();
if (be.type == 'mousewheel') {
var wheelDeltaScaleFactor = 1;
if (goog.userAgent.IE ||
goog.userAgent.WEBKIT &&
(goog.userAgent.WINDOWS || goog.userAgent.isVersionOrHigher('532.0'))) {
// In IE we get a multiple of 120; we adjust to a multiple of 3 to
// represent number of lines scrolled (like Gecko).
// Newer versions of Webkit match IE behavior, and WebKit on
// Windows also matches IE behavior.
// See bug https://bugs.webkit.org/show_bug.cgi?id=24368
wheelDeltaScaleFactor = 40;
}
detail = goog.events.MouseWheelHandler.smartScale_(
-be.wheelDelta, wheelDeltaScaleFactor);
if (goog.isDef(be.wheelDeltaX)) {
// Webkit has two properties to indicate directional scroll, and
// can scroll both directions at once.
deltaX = goog.events.MouseWheelHandler.smartScale_(
-be.wheelDeltaX, wheelDeltaScaleFactor);
deltaY = goog.events.MouseWheelHandler.smartScale_(
-be.wheelDeltaY, wheelDeltaScaleFactor);
} else {
deltaY = detail;
}
// Historical note: Opera (pre 9.5) used to negate the detail value.
} else { // Gecko
// Gecko returns multiple of 3 (representing the number of lines scrolled)
detail = be.detail;
// Gecko sometimes returns really big values if the user changes settings to
// scroll a whole page per scroll
if (detail > 100) {
detail = 3;
} else if (detail < -100) {
detail = -3;
}
// Firefox 3.1 adds an axis field to the event to indicate direction of
// scroll. See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) {
deltaX = detail;
} else {
deltaY = detail;
}
}
if (goog.isNumber(this.maxDeltaX_)) {
deltaX = goog.math.clamp(deltaX, -this.maxDeltaX_, this.maxDeltaX_);
}
if (goog.isNumber(this.maxDeltaY_)) {
deltaY = goog.math.clamp(deltaY, -this.maxDeltaY_, this.maxDeltaY_);
}
// Don't clamp 'detail', since it could be ambiguous which axis it refers to
// and because it's informally deprecated anyways.
// For horizontal scrolling we need to flip the value for RTL grids.
if (this.isRtl_) {
deltaX = -deltaX;
}
var newEvent = new goog.events.MouseWheelEvent(detail, be, deltaX, deltaY);
this.dispatchEvent(newEvent);
};
/**
* Helper for scaling down a mousewheel delta by a scale factor, if appropriate.
* @param {number} mouseWheelDelta Delta from a mouse wheel event. Expected to
* be an integer.
* @param {number} scaleFactor Factor to scale the delta down by. Expected to
* be an integer.
* @return {number} Scaled-down delta value, or the original delta if the
* scaleFactor does not appear to be applicable.
* @private
*/
goog.events.MouseWheelHandler.smartScale_ = function(mouseWheelDelta,
scaleFactor) {
// The basic problem here is that in Webkit on Mac and Linux, we can get two
// very different types of mousewheel events: from continuous devices
// (touchpads, Mighty Mouse) or non-continuous devices (normal wheel mice).
//
// Non-continuous devices in Webkit get their wheel deltas scaled up to
// behave like IE. Continuous devices return much smaller unscaled values
// (which most of the time will not be cleanly divisible by the IE scale
// factor), so we should not try to normalize them down.
//
// Detailed discussion:
// https://bugs.webkit.org/show_bug.cgi?id=29601
// http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
if (goog.userAgent.WEBKIT &&
(goog.userAgent.MAC || goog.userAgent.LINUX) &&
(mouseWheelDelta % scaleFactor) != 0) {
return mouseWheelDelta;
} else {
return mouseWheelDelta / scaleFactor;
}
};
/** @override */
goog.events.MouseWheelHandler.prototype.disposeInternal = function() {
goog.events.MouseWheelHandler.superClass_.disposeInternal.call(this);
goog.events.unlistenByKey(this.listenKey_);
this.listenKey_ = null;
};
/**
* A base class for mouse wheel events. This is used with the
* MouseWheelHandler.
*
* @param {number} detail The number of rows the user scrolled.
* @param {Event} browserEvent Browser event object.
* @param {number} deltaX The number of rows the user scrolled in the X
* direction.
* @param {number} deltaY The number of rows the user scrolled in the Y
* direction.
* @constructor
* @extends {goog.events.BrowserEvent}
*/
goog.events.MouseWheelEvent = function(detail, browserEvent, deltaX, deltaY) {
goog.events.BrowserEvent.call(this, browserEvent);
this.type = goog.events.MouseWheelHandler.EventType.MOUSEWHEEL;
/**
* The number of lines the user scrolled
* @type {number}
* NOTE: Informally deprecated. Use deltaX and deltaY instead, they provide
* more information.
*/
this.detail = detail;
/**
* The number of "lines" scrolled in the X direction.
*
* Note that not all browsers provide enough information to distinguish
* horizontal and vertical scroll events, so for these unsupported browsers,
* we will always have a deltaX of 0, even if the user scrolled their mouse
* wheel or trackpad sideways.
*
* Currently supported browsers are Webkit and Firefox 3.1 or later.
*
* @type {number}
*/
this.deltaX = deltaX;
/**
* The number of lines scrolled in the Y direction.
* @type {number}
*/
this.deltaY = deltaY;
};
goog.inherits(goog.events.MouseWheelEvent, goog.events.BrowserEvent);

View File

@@ -0,0 +1,162 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview This event handler will dispatch events when
* {@code navigator.onLine} changes. HTML5 defines two events, online and
* offline that is fired on the window. As of today 3 browsers support these
* events: Firefox 3 (Gecko 1.9), Opera 9.5, and IE8. If we have any of these
* we listen to the 'online' and 'offline' events on the current window
* object. Otherwise we poll the navigator.onLine property to detect changes.
*
* Note that this class only reflects what the browser tells us and this usually
* only reflects changes to the File -> Work Offline menu item.
*
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/onlinehandler.html
*/
// TODO(arv): We should probably implement some kind of polling service and/or
// a poll for changes event handler that can be used to fire events when a state
// changes.
goog.provide('goog.events.OnlineHandler');
goog.provide('goog.events.OnlineHandler.EventType');
goog.require('goog.Timer');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.net.NetworkStatusMonitor');
goog.require('goog.userAgent');
/**
* Basic object for detecting whether the online state changes.
* @constructor
* @extends {goog.events.EventTarget}
* @implements {goog.net.NetworkStatusMonitor}
*/
goog.events.OnlineHandler = function() {
goog.base(this);
/**
* @private {goog.events.EventHandler}
*/
this.eventHandler_ = new goog.events.EventHandler(this);
// Some browsers do not support navigator.onLine and therefore we don't
// bother setting up events or timers.
if (!goog.events.BrowserFeature.HAS_NAVIGATOR_ONLINE_PROPERTY) {
return;
}
if (goog.events.BrowserFeature.HAS_HTML5_NETWORK_EVENT_SUPPORT) {
var target =
goog.events.BrowserFeature.HTML5_NETWORK_EVENTS_FIRE_ON_BODY ?
document.body : window;
this.eventHandler_.listen(target,
[goog.events.EventType.ONLINE, goog.events.EventType.OFFLINE],
this.handleChange_);
} else {
this.online_ = this.isOnline();
this.timer_ = new goog.Timer(goog.events.OnlineHandler.POLL_INTERVAL_);
this.eventHandler_.listen(this.timer_, goog.Timer.TICK, this.handleTick_);
this.timer_.start();
}
};
goog.inherits(goog.events.OnlineHandler, goog.events.EventTarget);
/**
* Enum for the events dispatched by the OnlineHandler.
* @enum {string}
* @deprecated Use goog.net.NetworkStatusMonitor.EventType instead.
*/
goog.events.OnlineHandler.EventType = goog.net.NetworkStatusMonitor.EventType;
/**
* The time to wait before checking the {@code navigator.onLine} again.
* @type {number}
* @private
*/
goog.events.OnlineHandler.POLL_INTERVAL_ = 250;
/**
* Stores the last value of the online state so we can detect if this has
* changed.
* @type {boolean}
* @private
*/
goog.events.OnlineHandler.prototype.online_;
/**
* The timer object used to poll the online state.
* @type {goog.Timer}
* @private
*/
goog.events.OnlineHandler.prototype.timer_;
/** @override */
goog.events.OnlineHandler.prototype.isOnline = function() {
return goog.events.BrowserFeature.HAS_NAVIGATOR_ONLINE_PROPERTY ?
navigator.onLine : true;
};
/**
* Called every time the timer ticks to see if the state has changed and when
* the online state changes the method handleChange_ is called.
* @param {goog.events.Event} e The event object.
* @private
*/
goog.events.OnlineHandler.prototype.handleTick_ = function(e) {
var online = this.isOnline();
if (online != this.online_) {
this.online_ = online;
this.handleChange_(e);
}
};
/**
* Called when the online state changes. This dispatches the
* {@code ONLINE} and {@code OFFLINE} events respectively.
* @param {goog.events.Event} e The event object.
* @private
*/
goog.events.OnlineHandler.prototype.handleChange_ = function(e) {
var type = this.isOnline() ?
goog.net.NetworkStatusMonitor.EventType.ONLINE :
goog.net.NetworkStatusMonitor.EventType.OFFLINE;
this.dispatchEvent(type);
};
/** @override */
goog.events.OnlineHandler.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.eventHandler_.dispose();
this.eventHandler_ = null;
if (this.timer_) {
this.timer_.dispose();
this.timer_ = null;
}
};

View File

@@ -0,0 +1,515 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides a 'paste' event detector that works consistently
* across different browsers.
*
* IE5, IE6, IE7, Safari3.0 and FF3.0 all fire 'paste' events on textareas.
* FF2 doesn't. This class uses 'paste' events when they are available
* and uses heuristics to detect the 'paste' event when they are not available.
*
* Known issue: will not detect paste events in FF2 if you pasted exactly the
* same existing text.
* Known issue: Opera + Mac doesn't work properly because of the meta key. We
* can probably fix that. TODO(user): {@link KeyboardShortcutHandler} does not
* work either very well with opera + mac. fix that.
*
* @supported IE5, IE6, IE7, Safari3.0, Chrome, FF2.0 (linux) and FF3.0 and
* Opera (mac and windows).
*
* @see ../demos/pastehandler.html
*/
goog.provide('goog.events.PasteHandler');
goog.provide('goog.events.PasteHandler.EventType');
goog.provide('goog.events.PasteHandler.State');
goog.require('goog.Timer');
goog.require('goog.async.ConditionalDelay');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.log');
goog.require('goog.userAgent');
/**
* A paste event detector. Gets an {@code element} as parameter and fires
* {@code goog.events.PasteHandler.EventType.PASTE} events when text is
* pasted in the {@code element}. Uses heuristics to detect paste events in FF2.
* See more details of the heuristic on {@link #handleEvent_}.
*
* @param {Element} element The textarea element we are listening on.
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.events.PasteHandler = function(element) {
goog.events.EventTarget.call(this);
/**
* The element that you want to listen for paste events on.
* @type {Element}
* @private
*/
this.element_ = element;
/**
* The last known value of the element. Kept to check if things changed. See
* more details on {@link #handleEvent_}.
* @type {string}
* @private
*/
this.oldValue_ = this.element_.value;
/**
* Handler for events.
* @type {goog.events.EventHandler}
* @private
*/
this.eventHandler_ = new goog.events.EventHandler(this);
/**
* The last time an event occurred on the element. Kept to check whether the
* last event was generated by two input events or by multiple fast key events
* that got swallowed. See more details on {@link #handleEvent_}.
* @type {number}
* @private
*/
this.lastTime_ = goog.now();
if (goog.userAgent.WEBKIT ||
goog.userAgent.IE ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9')) {
// Most modern browsers support the paste event.
this.eventHandler_.listen(element, goog.events.EventType.PASTE,
this.dispatch_);
} else {
// But FF2 and Opera doesn't. we listen for a series of events to try to
// find out if a paste occurred. We enumerate and cover all known ways to
// paste text on textareas. See more details on {@link #handleEvent_}.
var events = [
goog.events.EventType.KEYDOWN,
goog.events.EventType.BLUR,
goog.events.EventType.FOCUS,
goog.events.EventType.MOUSEOVER,
'input'
];
this.eventHandler_.listen(element, events, this.handleEvent_);
}
/**
* ConditionalDelay used to poll for changes in the text element once users
* paste text. Browsers fire paste events BEFORE the text is actually present
* in the element.value property.
* @type {goog.async.ConditionalDelay}
* @private
*/
this.delay_ = new goog.async.ConditionalDelay(
goog.bind(this.checkUpdatedText_, this));
};
goog.inherits(goog.events.PasteHandler, goog.events.EventTarget);
/**
* The types of events fired by this class.
* @enum {string}
*/
goog.events.PasteHandler.EventType = {
/**
* Dispatched as soon as the paste event is detected, but before the pasted
* text has been added to the text element we're listening to.
*/
PASTE: 'paste',
/**
* Dispatched after detecting a change to the value of text element
* (within 200msec of receiving the PASTE event).
*/
AFTER_PASTE: 'after_paste'
};
/**
* The mandatory delay we expect between two {@code input} events, used to
* differentiated between non key paste events and key events.
* @type {number}
*/
goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER =
400;
/**
* The period between each time we check whether the pasted text appears in the
* text element or not.
* @type {number}
* @private
*/
goog.events.PasteHandler.PASTE_POLLING_PERIOD_MS_ = 50;
/**
* The maximum amount of time we want to poll for changes.
* @type {number}
* @private
*/
goog.events.PasteHandler.PASTE_POLLING_TIMEOUT_MS_ = 200;
/**
* The states that this class can be found, on the paste detection algorithm.
* @enum {string}
*/
goog.events.PasteHandler.State = {
INIT: 'init',
FOCUSED: 'focused',
TYPING: 'typing'
};
/**
* The initial state of the paste detection algorithm.
* @type {goog.events.PasteHandler.State}
* @private
*/
goog.events.PasteHandler.prototype.state_ =
goog.events.PasteHandler.State.INIT;
/**
* The previous event that caused us to be on the current state.
* @type {?string}
* @private
*/
goog.events.PasteHandler.prototype.previousEvent_;
/**
* A logger, used to help us debug the algorithm.
* @type {goog.log.Logger}
* @private
*/
goog.events.PasteHandler.prototype.logger_ =
goog.log.getLogger('goog.events.PasteHandler');
/** @override */
goog.events.PasteHandler.prototype.disposeInternal = function() {
goog.events.PasteHandler.superClass_.disposeInternal.call(this);
this.eventHandler_.dispose();
this.eventHandler_ = null;
this.delay_.dispose();
this.delay_ = null;
};
/**
* Returns the current state of the paste detection algorithm. Used mostly for
* testing.
* @return {goog.events.PasteHandler.State} The current state of the class.
*/
goog.events.PasteHandler.prototype.getState = function() {
return this.state_;
};
/**
* Returns the event handler.
* @return {goog.events.EventHandler} The event handler.
* @protected
*/
goog.events.PasteHandler.prototype.getEventHandler = function() {
return this.eventHandler_;
};
/**
* Checks whether the element.value property was updated, and if so, dispatches
* the event that let clients know that the text is available.
* @return {boolean} Whether the polling should stop or not, based on whether
* we found a text change or not.
* @private
*/
goog.events.PasteHandler.prototype.checkUpdatedText_ = function() {
if (this.oldValue_ == this.element_.value) {
return false;
}
goog.log.info(this.logger_, 'detected textchange after paste');
this.dispatchEvent(goog.events.PasteHandler.EventType.AFTER_PASTE);
return true;
};
/**
* Dispatches the paste event.
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.PasteHandler.prototype.dispatch_ = function(e) {
var event = new goog.events.BrowserEvent(e.getBrowserEvent());
event.type = goog.events.PasteHandler.EventType.PASTE;
this.dispatchEvent(event);
// Starts polling for updates in the element.value property so we can tell
// when do dispatch the AFTER_PASTE event. (We do an initial check after an
// async delay of 0 msec since some browsers update the text right away and
// our poller will always wait one period before checking).
goog.Timer.callOnce(function() {
if (!this.checkUpdatedText_()) {
this.delay_.start(
goog.events.PasteHandler.PASTE_POLLING_PERIOD_MS_,
goog.events.PasteHandler.PASTE_POLLING_TIMEOUT_MS_);
}
}, 0, this);
};
/**
* The main event handler which implements a state machine.
*
* To handle FF2, we enumerate and cover all the known ways a user can paste:
*
* 1) ctrl+v, shift+insert, cmd+v
* 2) right click -> paste
* 3) edit menu -> paste
* 4) drag and drop
* 5) middle click
*
* (1) is easy and can be detected by listening for key events and finding out
* which keys are pressed. (2), (3), (4) and (5) do not generate a key event,
* so we need to listen for more than that. (2-5) all generate 'input' events,
* but so does key events. So we need to have some sort of 'how did the input
* event was generated' history algorithm.
*
* (2) is an interesting case in Opera on a Mac: since Macs does not have two
* buttons, right clicking involves pressing the CTRL key. Even more interesting
* is the fact that opera does NOT set the e.ctrlKey bit. Instead, it sets
* e.keyCode = 0.
* {@link http://www.quirksmode.org/js/keys.html}
*
* (1) is also an interesting case in Opera on a Mac: Opera is the only browser
* covered by this class that can detect the cmd key (FF2 can't apparently). And
* it fires e.keyCode = 17, which is the CTRL key code.
* {@link http://www.quirksmode.org/js/keys.html}
*
* NOTE(user, pbarry): There is an interesting thing about (5): on Linux, (5)
* pastes the last thing that you highlighted, not the last thing that you
* ctrl+c'ed. This code will still generate a {@code PASTE} event though.
*
* We enumerate all the possible steps a user can take to paste text and we
* implemented the transition between the steps in a state machine. The
* following is the design of the state machine:
*
* matching paths:
*
* (1) happens on INIT -> FOCUSED -> TYPING -> [e.ctrlKey & e.keyCode = 'v']
* (2-3) happens on INIT -> FOCUSED -> [input event happened]
* (4) happens on INIT -> [mouseover && text changed]
*
* non matching paths:
*
* user is typing normally
* INIT -> FOCUS -> TYPING -> INPUT -> INIT
*
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.PasteHandler.prototype.handleEvent_ = function(e) {
// transition between states happen at each browser event, and depend on the
// current state, the event that led to this state, and the event input.
switch (this.state_) {
case goog.events.PasteHandler.State.INIT: {
this.handleUnderInit_(e);
break;
}
case goog.events.PasteHandler.State.FOCUSED: {
this.handleUnderFocused_(e);
break;
}
case goog.events.PasteHandler.State.TYPING: {
this.handleUnderTyping_(e);
break;
}
default: {
goog.log.error(this.logger_, 'invalid ' + this.state_ + ' state');
}
}
this.lastTime_ = goog.now();
this.oldValue_ = this.element_.value;
goog.log.info(this.logger_, e.type + ' -> ' + this.state_);
this.previousEvent_ = e.type;
};
/**
* {@code goog.events.PasteHandler.EventType.INIT} is the first initial state
* the textarea is found. You can only leave this state by setting focus on the
* textarea, which is how users will input text. You can also paste things using
* drag and drop, which will not generate a {@code goog.events.EventType.FOCUS}
* event, but will generate a {@code goog.events.EventType.MOUSEOVER}.
*
* For browsers that support the 'paste' event, we match it and stay on the same
* state.
*
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.PasteHandler.prototype.handleUnderInit_ = function(e) {
switch (e.type) {
case goog.events.EventType.BLUR: {
this.state_ = goog.events.PasteHandler.State.INIT;
break;
}
case goog.events.EventType.FOCUS: {
this.state_ = goog.events.PasteHandler.State.FOCUSED;
break;
}
case goog.events.EventType.MOUSEOVER: {
this.state_ = goog.events.PasteHandler.State.INIT;
if (this.element_.value != this.oldValue_) {
goog.log.info(this.logger_, 'paste by dragdrop while on init!');
this.dispatch_(e);
}
break;
}
default: {
goog.log.error(this.logger_,
'unexpected event ' + e.type + 'during init');
}
}
};
/**
* {@code goog.events.PasteHandler.EventType.FOCUSED} is typically the second
* state the textarea will be, which is followed by the {@code INIT} state. On
* this state, users can paste in three different ways: edit -> paste,
* right click -> paste and drag and drop.
*
* The latter will generate a {@code goog.events.EventType.MOUSEOVER} event,
* which we match by making sure the textarea text changed. The first two will
* generate an 'input', which we match by making sure it was NOT generated by a
* key event (which also generates an 'input' event).
*
* Unfortunately, in Firefox, if you type fast, some KEYDOWN events are
* swallowed but an INPUT event may still happen. That means we need to
* differentiate between two consecutive INPUT events being generated either by
* swallowed key events OR by a valid edit -> paste -> edit -> paste action. We
* do this by checking a minimum time between the two events. This heuristic
* seems to work well, but it is obviously a heuristic :).
*
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.PasteHandler.prototype.handleUnderFocused_ = function(e) {
switch (e.type) {
case 'input' : {
// there are two different events that happen in practice that involves
// consecutive 'input' events. we use a heuristic to differentiate
// between the one that generates a valid paste action and the one that
// doesn't.
// @see testTypingReallyFastDispatchesTwoInputEventsBeforeTheKEYDOWNEvent
// and
// @see testRightClickRightClickAlsoDispatchesTwoConsecutiveInputEvents
// Notice that an 'input' event may be also triggered by a 'middle click'
// paste event, which is described in
// @see testMiddleClickWithoutFocusTriggersPasteEvent
var minimumMilisecondsBetweenInputEvents = this.lastTime_ +
goog.events.PasteHandler.
MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER;
if (goog.now() > minimumMilisecondsBetweenInputEvents ||
this.previousEvent_ == goog.events.EventType.FOCUS) {
goog.log.info(this.logger_, 'paste by textchange while focused!');
this.dispatch_(e);
}
break;
}
case goog.events.EventType.BLUR: {
this.state_ = goog.events.PasteHandler.State.INIT;
break;
}
case goog.events.EventType.KEYDOWN: {
goog.log.info(this.logger_, 'key down ... looking for ctrl+v');
// Opera + MAC does not set e.ctrlKey. Instead, it gives me e.keyCode = 0.
// http://www.quirksmode.org/js/keys.html
if (goog.userAgent.MAC && goog.userAgent.OPERA && e.keyCode == 0 ||
goog.userAgent.MAC && goog.userAgent.OPERA && e.keyCode == 17) {
break;
}
this.state_ = goog.events.PasteHandler.State.TYPING;
break;
}
case goog.events.EventType.MOUSEOVER: {
if (this.element_.value != this.oldValue_) {
goog.log.info(this.logger_, 'paste by dragdrop while focused!');
this.dispatch_(e);
}
break;
}
default: {
goog.log.error(this.logger_,
'unexpected event ' + e.type + ' during focused');
}
}
};
/**
* {@code goog.events.PasteHandler.EventType.TYPING} is the third state
* this class can be. It exists because each KEYPRESS event will ALSO generate
* an INPUT event (because the textarea value changes), and we need to
* differentiate between an INPUT event generated by a key event and an INPUT
* event generated by edit -> paste actions.
*
* This is the state that we match the ctrl+v pattern.
*
* @param {goog.events.BrowserEvent} e The underlying browser event.
* @private
*/
goog.events.PasteHandler.prototype.handleUnderTyping_ = function(e) {
switch (e.type) {
case 'input' : {
this.state_ = goog.events.PasteHandler.State.FOCUSED;
break;
}
case goog.events.EventType.BLUR: {
this.state_ = goog.events.PasteHandler.State.INIT;
break;
}
case goog.events.EventType.KEYDOWN: {
if (e.ctrlKey && e.keyCode == goog.events.KeyCodes.V ||
e.shiftKey && e.keyCode == goog.events.KeyCodes.INSERT ||
e.metaKey && e.keyCode == goog.events.KeyCodes.V) {
goog.log.info(this.logger_, 'paste by ctrl+v while keypressed!');
this.dispatch_(e);
}
break;
}
case goog.events.EventType.MOUSEOVER: {
if (this.element_.value != this.oldValue_) {
goog.log.info(this.logger_, 'paste by dragdrop while keypressed!');
this.dispatch_(e);
}
break;
}
default: {
goog.log.error(this.logger_,
'unexpected event ' + e.type + ' during keypressed');
}
}
};