http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/listenable.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/listenable.js b/externs/GCL/externs/goog/events/listenable.js new file mode 100644 index 0000000..a05b348 --- /dev/null +++ b/externs/GCL/externs/goog/events/listenable.js @@ -0,0 +1,335 @@ +// 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. + * @author [email protected] (Chris Henry) + */ + +goog.provide('goog.events.Listenable'); +goog.provide('goog.events.ListenableKey'); + +/** @suppress {extraRequire} */ +goog.require('goog.events.EventId'); + + + +/** + * 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|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +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|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ + */ +goog.events.Listenable.prototype.listenOnce; + + +/** + * Removes an event listener which was added with listen() or listenOnce(). + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call + * the listener. + * @return {boolean} Whether any listener was removed. + * @template SCOPE,EVENTOBJ + */ +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(chrishenry): 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|!goog.events.EventId<EVENTOBJ>} type The type of the + * listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @param {EVENTOBJ} 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. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.fireListeners; + + +/** + * Gets all listeners in this listenable for the given type and + * capture mode. + * + * @param {string|!goog.events.EventId} 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. + * @template EVENTOBJ + */ +goog.events.Listenable.prototype.getListeners; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event + * without the 'on' prefix. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The + * listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template SCOPE,EVENTOBJ + */ +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|!goog.events.EventId<EVENTOBJ>=} 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. + * @template EVENTOBJ + */ +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. + * @type {function(?):?|{handleEvent:function(?):?}|null} + */ +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;
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/listener.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/listener.js b/externs/GCL/externs/goog/events/listener.js new file mode 100644 index 0000000..60c7370 --- /dev/null +++ b/externs/GCL/externs/goog/events/listener.js @@ -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; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/listenermap.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/listenermap.js b/externs/GCL/externs/goog/events/listenermap.js new file mode 100644 index 0000000..c20cdb9 --- /dev/null +++ b/externs/GCL/externs/goog/events/listenermap.js @@ -0,0 +1,308 @@ +// 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/bin/sizetests:__pkg__} + * @visibility {//closure/goog/events:__pkg__} + * @visibility {//closure/goog/labs/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 + * @final + */ +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|!goog.events.EventId} 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 typeStr = type.toString(); + var listenerArray = this.listeners[typeStr]; + if (!listenerArray) { + listenerArray = this.listeners[typeStr] = []; + 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, typeStr, !!opt_useCapture, opt_listenerScope); + listenerObj.callOnce = callOnce; + listenerArray.push(listenerObj); + } + return listenerObj; +}; + + +/** + * Removes a matching listener. + * @param {string|!goog.events.EventId} 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) { + var typeStr = type.toString(); + if (!(typeStr in this.listeners)) { + return false; + } + + var listenerArray = this.listeners[typeStr]; + 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[typeStr]; + 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|!goog.events.EventId=} opt_type Type of event to remove. + * @return {number} Number of listeners removed. + */ +goog.events.ListenerMap.prototype.removeAll = function(opt_type) { + var typeStr = opt_type && opt_type.toString(); + var count = 0; + for (var type in this.listeners) { + if (!typeStr || type == typeStr) { + var listenerArray = this.listeners[type]; + for (var i = 0; i < listenerArray.length; i++) { + ++count; + listenerArray[i].markAsRemoved(); + } + 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|!goog.events.EventId} 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.toString()]; + 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|!goog.events.EventId} 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.toString()]; + 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|!goog.events.EventId=} 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 typeStr = hasType ? opt_type.toString() : ''; + 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 == typeStr) && + (!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; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/mousewheelhandler.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/mousewheelhandler.js b/externs/GCL/externs/goog/events/mousewheelhandler.js new file mode 100644 index 0000000..6729064 --- /dev/null +++ b/externs/GCL/externs/goog/events/mousewheelhandler.js @@ -0,0 +1,296 @@ +// 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 [email protected] (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} + * @final + */ +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); http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/onlinehandler.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/onlinehandler.js b/externs/GCL/externs/goog/events/onlinehandler.js new file mode 100644 index 0000000..5c9fb16 --- /dev/null +++ b/externs/GCL/externs/goog/events/onlinehandler.js @@ -0,0 +1,159 @@ +// 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 [email protected] (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'); + + + +/** + * Basic object for detecting whether the online state changes. + * @constructor + * @extends {goog.events.EventTarget} + * @implements {goog.net.NetworkStatusMonitor} + */ +goog.events.OnlineHandler = function() { + goog.events.OnlineHandler.base(this, 'constructor'); + + /** + * @private {goog.events.EventHandler<!goog.events.OnlineHandler>} + */ + 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. + * @private + */ +goog.events.OnlineHandler.prototype.handleTick_ = function() { + var online = this.isOnline(); + if (online != this.online_) { + this.online_ = online; + this.handleChange_(); + } +}; + + +/** + * Called when the online state changes. This dispatches the + * {@code ONLINE} and {@code OFFLINE} events respectively. + * @private + */ +goog.events.OnlineHandler.prototype.handleChange_ = function() { + 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.events.OnlineHandler.base(this, 'disposeInternal'); + this.eventHandler_.dispose(); + this.eventHandler_ = null; + if (this.timer_) { + this.timer_.dispose(); + this.timer_ = null; + } +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/pastehandler.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/pastehandler.js b/externs/GCL/externs/goog/events/pastehandler.js new file mode 100644 index 0000000..4992ff0 --- /dev/null +++ b/externs/GCL/externs/goog/events/pastehandler.js @@ -0,0 +1,517 @@ +// 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<!goog.events.PasteHandler>} + * @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<T>} The event handler. + * @protected + * @this T + * @template T + */ +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'); + } + } +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/wheelevent.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/wheelevent.js b/externs/GCL/externs/goog/events/wheelevent.js new file mode 100644 index 0000000..1f172eb --- /dev/null +++ b/externs/GCL/externs/goog/events/wheelevent.js @@ -0,0 +1,169 @@ +// Copyright 2014 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 class aims to smooth out inconsistencies between browser + * handling of wheel events by providing an event that is similar to that + * defined in the standard, but also easier to consume. + * + * It is based upon the WheelEvent, which allows for up to 3 dimensional + * scrolling events that come in units of either pixels, lines or pages. + * http://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#interface-WheelEvent + * + * The significant difference here is that it also provides reasonable pixel + * deltas for clients that do not want to treat line and page scrolling events + * specially. + * + * Clients of this code should be aware that some input devices only fire a few + * discrete events (such as a mouse wheel without acceleration) whereas some can + * generate a large number of events for a single interaction (such as a + * touchpad with acceleration). There is no signal in the events to reliably + * distinguish between these. + * + * @see ../demos/wheelhandler.html + */ + +goog.provide('goog.events.WheelEvent'); + +goog.require('goog.asserts'); +goog.require('goog.events.BrowserEvent'); + + + +/** + * A common class for wheel events. This is used with the WheelHandler. + * + * @param {Event} browserEvent Browser event object. + * @param {goog.events.WheelEvent.DeltaMode} deltaMode The delta mode units of + * the wheel event. + * @param {number} deltaX The number of delta units the user in the X axis. + * @param {number} deltaY The number of delta units the user in the Y axis. + * @param {number} deltaZ The number of delta units the user in the Z axis. + * @constructor + * @extends {goog.events.BrowserEvent} + * @final + */ +goog.events.WheelEvent = function( + browserEvent, deltaMode, deltaX, deltaY, deltaZ) { + goog.events.WheelEvent.base(this, 'constructor', browserEvent); + goog.asserts.assert(browserEvent, 'Expecting a non-null browserEvent'); + + /** @type {goog.events.WheelEvent.EventType} */ + this.type = goog.events.WheelEvent.EventType.WHEEL; + + /** + * An enum corresponding to the units of this event. + * @type {goog.events.WheelEvent.DeltaMode} + */ + this.deltaMode = deltaMode; + + /** + * The number of delta units in the X axis. + * @type {number} + */ + this.deltaX = deltaX; + + /** + * The number of delta units in the Y axis. + * @type {number} + */ + this.deltaY = deltaY; + + /** + * The number of delta units in the Z axis. + * @type {number} + */ + this.deltaZ = deltaZ; + + // Ratio between delta and pixel values. + var pixelRatio = 1; // Value for DeltaMode.PIXEL + switch (deltaMode) { + case goog.events.WheelEvent.DeltaMode.PAGE: + pixelRatio *= goog.events.WheelEvent.PIXELS_PER_PAGE_; + break; + case goog.events.WheelEvent.DeltaMode.LINE: + pixelRatio *= goog.events.WheelEvent.PIXELS_PER_LINE_; + break; + } + + /** + * The number of delta pixels in the X axis. Code that doesn't want to handle + * different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaX = this.deltaX * pixelRatio; + + /** + * The number of pixels in the Y axis. Code that doesn't want to + * handle different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaY = this.deltaY * pixelRatio; + + /** + * The number of pixels scrolled in the Z axis. Code that doesn't want to + * handle different deltaMode units can just look here. + * @type {number} + */ + this.pixelDeltaZ = this.deltaZ * pixelRatio; +}; +goog.inherits(goog.events.WheelEvent, goog.events.BrowserEvent); + + +/** + * Enum type for the events fired by the wheel handler. + * @enum {string} + */ +goog.events.WheelEvent.EventType = { + /** The user has provided wheel-based input. */ + WHEEL: 'wheel' +}; + + +/** + * Units for the deltas in a WheelEvent. + * @enum {number} + */ +goog.events.WheelEvent.DeltaMode = { + /** The units are in pixels. From DOM_DELTA_PIXEL. */ + PIXEL: 0, + /** The units are in lines. From DOM_DELTA_LINE. */ + LINE: 1, + /** The units are in pages. From DOM_DELTA_PAGE. */ + PAGE: 2 +}; + + +/** + * A conversion number between line scroll units and pixel scroll units. The + * actual value per line can vary a lot between devices and font sizes. This + * number can not be perfect, but it should be reasonable for converting lines + * scroll events into pixels. + * @const {number} + * @private + */ +goog.events.WheelEvent.PIXELS_PER_LINE_ = 15; + + +/** + * A conversion number between page scroll units and pixel scroll units. The + * actual value per page can vary a lot as many different devices have different + * screen sizes, and the window might not be taking up the full screen. This + * number can not be perfect, but it should be reasonable for converting page + * scroll events into pixels. + * @const {number} + * @private + */ +goog.events.WheelEvent.PIXELS_PER_PAGE_ = 30 * + goog.events.WheelEvent.PIXELS_PER_LINE_; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/wheelhandler.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/events/wheelhandler.js b/externs/GCL/externs/goog/events/wheelhandler.js new file mode 100644 index 0000000..9d0f1e3 --- /dev/null +++ b/externs/GCL/externs/goog/events/wheelhandler.js @@ -0,0 +1,159 @@ +// Copyright 2014 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 wheel on an element. The event provides details of the unit type (pixel / + * line / page) and deltas in those units in up to 3 dimensions. Additionally, + * simplified pixel deltas are provided for code that doesn't need to handle the + * different units differently. This is not to be confused with the scroll + * event, where an element in the dom can report that it was scrolled. + * + * This class aims to smooth out inconsistencies between browser platforms with + * regards to wheel events, but we do not cover every possible software/hardware + * combination out there, some of which occasionally produce very large deltas + * in wheel events, especially when the device supports acceleration. + * + * Relevant standard: + * http://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#interface-WheelEvent + * + * Clients of this code should be aware that some input devices only fire a few + * discrete events (such as a mouse wheel without acceleration) whereas some can + * generate a large number of events for a single interaction (such as a + * touchpad with acceleration). There is no signal in the events to reliably + * distinguish between these. + * + * @author [email protected] (Erik Arvidsson) + * @see ../demos/wheelhandler.html + */ + +goog.provide('goog.events.WheelHandler'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.WheelEvent'); +goog.require('goog.style'); +goog.require('goog.userAgent'); +goog.require('goog.userAgent.product'); +goog.require('goog.userAgent.product.isVersion'); + + + +/** + * This event handler allows you to catch wheel events in a consistent manner. + * @param {!Element|!Document} element The element to listen to the wheel event + * on. + * @param {boolean=} opt_capture Whether to handle the wheel event in capture + * phase. + * @constructor + * @extends {goog.events.EventTarget} + */ +goog.events.WheelHandler = function(element, opt_capture) { + goog.events.WheelHandler.base(this, 'constructor'); + + /** + * This is the element that we will listen to the real wheel events on. + * @private {!Element|!Document} + */ + this.element_ = element; + + var rtlElement = goog.dom.isElement(this.element_) ? + /** @type {!Element} */ (this.element_) : + /** @type {!Document} */ (this.element_).body; + + /** + * True if the element exists and is RTL, false otherwise. + * @private {boolean} + */ + this.isRtl_ = !!rtlElement && goog.style.isRightToLeft(rtlElement); + + /** + * The key returned from the goog.events.listen. + * @private {goog.events.Key} + */ + this.listenKey_ = goog.events.listen( + this.element_, goog.events.WheelHandler.getDomEventType(), + this, opt_capture); +}; +goog.inherits(goog.events.WheelHandler, goog.events.EventTarget); + + +/** + * Returns the dom event type. + * @return {string} The dom event type. + */ +goog.events.WheelHandler.getDomEventType = function() { + // Prefer to use wheel events whenever supported. + if (goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher(17) || + goog.userAgent.IE && goog.userAgent.isVersionOrHigher(9) || + goog.userAgent.product.CHROME && goog.userAgent.product.isVersion(31)) { + return 'wheel'; + } + + // Legacy events. Still the best we have on Opera and Safari. + return goog.userAgent.GECKO ? 'DOMMouseScroll' : 'mousewheel'; +}; + + +/** + * Handles the events on the element. + * @param {!goog.events.BrowserEvent} e The underlying browser event. + */ +goog.events.WheelHandler.prototype.handleEvent = function(e) { + var deltaMode = goog.events.WheelEvent.DeltaMode.PIXEL; + var deltaX = 0; + var deltaY = 0; + var deltaZ = 0; + var be = e.getBrowserEvent(); + if (be.type == 'wheel') { + deltaMode = be.deltaMode; + deltaX = be.deltaX; + deltaY = be.deltaY; + deltaZ = be.deltaZ; + } else if (be.type == 'mousewheel') { + // Assume that these are still comparable to pixels. This may not be true + // for all old browsers. + if (goog.isDef(be.wheelDeltaX)) { + deltaX = -be.wheelDeltaX; + deltaY = -be.wheelDeltaY; + } else { + deltaY = -be.wheelDelta; + } + } else { // Historical Gecko + // Gecko returns multiple of 3 (representing the number of lines) + deltaMode = goog.events.WheelEvent.DeltaMode.LINE; + // Firefox 3.1 adds an axis field to the event to indicate axis. + if (goog.isDef(be.axis) && be.axis === be.HORIZONTAL_AXIS) { + deltaX = be.detail; + } else { + deltaY = be.detail; + } + } + // For horizontal deltas we need to flip the value for RTL grids. + if (this.isRtl_) { + deltaX = -deltaX; + } + var newEvent = new goog.events.WheelEvent( + be, deltaMode, deltaX, deltaY, deltaZ); + this.dispatchEvent(newEvent); +}; + + +/** @override */ +goog.events.WheelHandler.prototype.disposeInternal = function() { + goog.events.WheelHandler.superClass_.disposeInternal.call(this); + goog.events.unlistenByKey(this.listenKey_); + this.listenKey_ = null; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/format/emailaddress.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/format/emailaddress.js b/externs/GCL/externs/goog/format/emailaddress.js new file mode 100644 index 0000000..670bc33 --- /dev/null +++ b/externs/GCL/externs/goog/format/emailaddress.js @@ -0,0 +1,499 @@ +// 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 functions to parse and manipulate email addresses. + * + */ + +goog.provide('goog.format.EmailAddress'); + +goog.require('goog.string'); + + + +/** + * Formats an email address string for display, and allows for extraction of + * the individual components of the address. + * @param {string=} opt_address The email address. + * @param {string=} opt_name The name associated with the email address. + * @constructor + */ +goog.format.EmailAddress = function(opt_address, opt_name) { + /** + * The name or personal string associated with the address. + * @type {string} + * @private + */ + this.name_ = opt_name || ''; + + /** + * The email address. + * @type {string} + * @protected + */ + this.address = opt_address || ''; +}; + + +/** + * Match string for opening tokens. + * @type {string} + * @private + */ +goog.format.EmailAddress.OPENERS_ = '"<(['; + + +/** + * Match string for closing tokens. + * @type {string} + * @private + */ +goog.format.EmailAddress.CLOSERS_ = '">)]'; + + +/** + * Match string for characters that require display names to be quoted and are + * not address separators. + * @type {string} + * @const + * @package + */ +goog.format.EmailAddress.SPECIAL_CHARS = '()<>@:\\\".[]'; + + +/** + * Match string for address separators. + * @type {string} + * @const + * @private + */ +goog.format.EmailAddress.ADDRESS_SEPARATORS_ = ',;'; + + +/** + * Match string for characters that, when in a display name, require it to be + * quoted. + * @type {string} + * @const + * @private + */ +goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_ = + goog.format.EmailAddress.SPECIAL_CHARS + + goog.format.EmailAddress.ADDRESS_SEPARATORS_; + + +/** + * A RegExp to match all double quotes. Used in cleanAddress(). + * @type {RegExp} + * @private + */ +goog.format.EmailAddress.ALL_DOUBLE_QUOTES_ = /\"/g; + + +/** + * A RegExp to match escaped double quotes. Used in parse(). + * @type {RegExp} + * @private + */ +goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_ = /\\\"/g; + + +/** + * A RegExp to match all backslashes. Used in cleanAddress(). + * @type {RegExp} + * @private + */ +goog.format.EmailAddress.ALL_BACKSLASHES_ = /\\/g; + + +/** + * A RegExp to match escaped backslashes. Used in parse(). + * @type {RegExp} + * @private + */ +goog.format.EmailAddress.ESCAPED_BACKSLASHES_ = /\\\\/g; + + +/** + * A string representing the RegExp for the local part of an email address. + * @private {string} + */ +goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ = + '[+a-zA-Z0-9_.!#$%&\'*\\/=?^`{|}~-]+'; + + +/** + * A string representing the RegExp for the domain part of an email address. + * @private {string} + */ +goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ = + '([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9]{2,63}'; + + +/** + * A RegExp to match the local part of an email address. + * @private {!RegExp} + */ +goog.format.EmailAddress.LOCAL_PART_ = + new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '$'); + + +/** + * A RegExp to match the domain part of an email address. + * @private {!RegExp} + */ +goog.format.EmailAddress.DOMAIN_PART_ = + new RegExp('^' + goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$'); + + +/** + * A RegExp to match an email address. + * @private {!RegExp} + */ +goog.format.EmailAddress.EMAIL_ADDRESS_ = + new RegExp('^' + goog.format.EmailAddress.LOCAL_PART_REGEXP_STR_ + '@' + + goog.format.EmailAddress.DOMAIN_PART_REGEXP_STR_ + '$'); + + +/** + * Get the name associated with the email address. + * @return {string} The name or personal portion of the address. + * @final + */ +goog.format.EmailAddress.prototype.getName = function() { + return this.name_; +}; + + +/** + * Get the email address. + * @return {string} The email address. + * @final + */ +goog.format.EmailAddress.prototype.getAddress = function() { + return this.address; +}; + + +/** + * Set the name associated with the email address. + * @param {string} name The name to associate. + * @final + */ +goog.format.EmailAddress.prototype.setName = function(name) { + this.name_ = name; +}; + + +/** + * Set the email address. + * @param {string} address The email address. + * @final + */ +goog.format.EmailAddress.prototype.setAddress = function(address) { + this.address = address; +}; + + +/** + * Return the address in a standard format: + * - remove extra spaces. + * - Surround name with quotes if it contains special characters. + * @return {string} The cleaned address. + * @override + */ +goog.format.EmailAddress.prototype.toString = function() { + return this.toStringInternal( + goog.format.EmailAddress.CHARS_REQUIRE_QUOTES_); +}; + + +/** + * Check if a display name requires quoting. + * @param {string} name The display name + * @param {string} specialChars String that contains the characters that require + * the display name to be quoted. This may change based in whereas we are + * in EAI context or not. + * @return {boolean} + * @private + */ +goog.format.EmailAddress.isQuoteNeeded_ = function(name, specialChars) { + for (var i = 0; i < specialChars.length; i++) { + var specialChar = specialChars[i]; + if (goog.string.contains(name, specialChar)) { + return true; + } + } + return false; +}; + + +/** + * Return the address in a standard format: + * - remove extra spaces. + * - Surround name with quotes if it contains special characters. + * @param {string} specialChars String that contains the characters that require + * the display name to be quoted. + * @return {string} The cleaned address. + * @protected + */ +goog.format.EmailAddress.prototype.toStringInternal = function(specialChars) { + var name = this.getName(); + + // We intentionally remove double quotes in the name because escaping + // them to \" looks ugly. + name = name.replace(goog.format.EmailAddress.ALL_DOUBLE_QUOTES_, ''); + + // If the name has special characters, we need to quote it and escape \'s. + if (goog.format.EmailAddress.isQuoteNeeded_(name, specialChars)) { + name = '"' + + name.replace(goog.format.EmailAddress.ALL_BACKSLASHES_, '\\\\') + '"'; + } + + if (name == '') { + return this.address; + } + if (this.address == '') { + return name; + } + return name + ' <' + this.address + '>'; +}; + + +/** + * Determines is the current object is a valid email address. + * @return {boolean} Whether the email address is valid. + */ +goog.format.EmailAddress.prototype.isValid = function() { + return goog.format.EmailAddress.isValidAddrSpec(this.address); +}; + + +/** + * Checks if the provided string is a valid email address. Supports both + * simple email addresses (address specs) and addresses that contain display + * names. + * @param {string} str The email address to check. + * @return {boolean} Whether the provided string is a valid address. + */ +goog.format.EmailAddress.isValidAddress = function(str) { + return goog.format.EmailAddress.parse(str).isValid(); +}; + + +/** + * Checks if the provided string is a valid address spec ([email protected]). + * @param {string} str The email address to check. + * @return {boolean} Whether the provided string is a valid address spec. + */ +goog.format.EmailAddress.isValidAddrSpec = function(str) { + // This is a fairly naive implementation, but it covers 99% of use cases. + // For more details, see http://en.wikipedia.org/wiki/Email_address#Syntax + return goog.format.EmailAddress.EMAIL_ADDRESS_.test(str); +}; + + +/** + * Checks if the provided string is a valid local part (part before the '@') of + * an email address. + * @param {string} str The local part to check. + * @return {boolean} Whether the provided string is a valid local part. + */ +goog.format.EmailAddress.isValidLocalPartSpec = function(str) { + return goog.format.EmailAddress.LOCAL_PART_.test(str); +}; + + +/** + * Checks if the provided string is a valid domain part (part after the '@') of + * an email address. + * @param {string} str The domain part to check. + * @return {boolean} Whether the provided string is a valid domain part. + */ +goog.format.EmailAddress.isValidDomainPartSpec = function(str) { + return goog.format.EmailAddress.DOMAIN_PART_.test(str); +}; + + +/** + * Parses an email address of the form "name" <address> ("name" is + * optional) into an email address. + * @param {string} addr The address string. + * @param {function(new: goog.format.EmailAddress, string=,string=)} ctor + * EmailAddress constructor to instantiate the output address. + * @return {!goog.format.EmailAddress} The parsed address. + * @protected + */ +goog.format.EmailAddress.parseInternal = function(addr, ctor) { + // TODO(ecattell): Strip bidi markers. + var name = ''; + var address = ''; + for (var i = 0; i < addr.length;) { + var token = goog.format.EmailAddress.getToken_(addr, i); + if (token.charAt(0) == '<' && token.indexOf('>') != -1) { + var end = token.indexOf('>'); + address = token.substring(1, end); + } else if (address == '') { + name += token; + } + i += token.length; + } + + // Check if it's a simple email address of the form "[email protected]". + if (address == '' && name.indexOf('@') != -1) { + address = name; + name = ''; + } + + name = goog.string.collapseWhitespace(name); + name = goog.string.stripQuotes(name, '\''); + name = goog.string.stripQuotes(name, '"'); + // Replace escaped quotes and slashes. + name = name.replace(goog.format.EmailAddress.ESCAPED_DOUBLE_QUOTES_, '"'); + name = name.replace(goog.format.EmailAddress.ESCAPED_BACKSLASHES_, '\\'); + address = goog.string.collapseWhitespace(address); + return new ctor(address, name); +}; + + +/** + * Parses an email address of the form "name" <address> into + * an email address. + * @param {string} addr The address string. + * @return {!goog.format.EmailAddress} The parsed address. + */ +goog.format.EmailAddress.parse = function(addr) { + return goog.format.EmailAddress.parseInternal( + addr, goog.format.EmailAddress); +}; + + +/** + * Parse a string containing email addresses of the form + * "name" <address> into an array of email addresses. + * @param {string} str The address list. + * @param {function(string)} parser The parser to employ. + * @param {function(string):boolean} separatorChecker Accepts a character and + * returns whether it should be considered an address separator. + * @return {!Array<!goog.format.EmailAddress>} The parsed emails. + * @protected + */ +goog.format.EmailAddress.parseListInternal = function( + str, parser, separatorChecker) { + var result = []; + var email = ''; + var token; + + // Remove non-UNIX-style newlines that would otherwise cause getToken_ to + // choke. Remove multiple consecutive whitespace characters for the same + // reason. + str = goog.string.collapseWhitespace(str); + + for (var i = 0; i < str.length; ) { + token = goog.format.EmailAddress.getToken_(str, i); + if (separatorChecker(token) || + (token == ' ' && parser(email).isValid())) { + if (!goog.string.isEmptyOrWhitespace(email)) { + result.push(parser(email)); + } + email = ''; + i++; + continue; + } + email += token; + i += token.length; + } + + // Add the final token. + if (!goog.string.isEmptyOrWhitespace(email)) { + result.push(parser(email)); + } + return result; +}; + + +/** + * Parses a string containing email addresses of the form + * "name" <address> into an array of email addresses. + * @param {string} str The address list. + * @return {!Array<!goog.format.EmailAddress>} The parsed emails. + */ +goog.format.EmailAddress.parseList = function(str) { + return goog.format.EmailAddress.parseListInternal( + str, goog.format.EmailAddress.parse, + goog.format.EmailAddress.isAddressSeparator); +}; + + +/** + * Get the next token from a position in an address string. + * @param {string} str the string. + * @param {number} pos the position. + * @return {string} the token. + * @private + */ +goog.format.EmailAddress.getToken_ = function(str, pos) { + var ch = str.charAt(pos); + var p = goog.format.EmailAddress.OPENERS_.indexOf(ch); + if (p == -1) { + return ch; + } + if (goog.format.EmailAddress.isEscapedDlQuote_(str, pos)) { + + // If an opener is an escaped quote we do not treat it as a real opener + // and keep accumulating the token. + return ch; + } + var closerChar = goog.format.EmailAddress.CLOSERS_.charAt(p); + var endPos = str.indexOf(closerChar, pos + 1); + + // If the closer is a quote we go forward skipping escaped quotes until we + // hit the real closing one. + while (endPos >= 0 && + goog.format.EmailAddress.isEscapedDlQuote_(str, endPos)) { + endPos = str.indexOf(closerChar, endPos + 1); + } + var token = (endPos >= 0) ? str.substring(pos, endPos + 1) : ch; + return token; +}; + + +/** + * Checks if the character in the current position is an escaped double quote + * ( \" ). + * @param {string} str the string. + * @param {number} pos the position. + * @return {boolean} true if the char is escaped double quote. + * @private + */ +goog.format.EmailAddress.isEscapedDlQuote_ = function(str, pos) { + if (str.charAt(pos) != '"') { + return false; + } + var slashCount = 0; + for (var idx = pos - 1; idx >= 0 && str.charAt(idx) == '\\'; idx--) { + slashCount++; + } + return ((slashCount % 2) != 0); +}; + + +/** + * @param {string} ch The character to test. + * @return {boolean} Whether the provided character is an address separator. + */ +goog.format.EmailAddress.isAddressSeparator = function(ch) { + return goog.string.contains(goog.format.EmailAddress.ADDRESS_SEPARATORS_, ch); +};
