Path: blob/trunk/third_party/closure/goog/labs/events/nondisposableeventtarget.js
2868 views
// Copyright 2005 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview An implementation of {@link goog.events.Listenable} that does16* not need to be disposed.17*/1819goog.provide('goog.labs.events.NonDisposableEventTarget');202122goog.require('goog.array');23goog.require('goog.asserts');24goog.require('goog.events.Event');25goog.require('goog.events.Listenable');26goog.require('goog.events.ListenerMap');27goog.require('goog.object');28293031/**32* An implementation of {@code goog.events.Listenable} with full W3C33* EventTarget-like support (capture/bubble mechanism, stopping event34* propagation, preventing default actions).35*36* You may subclass this class to turn your class into a Listenable.37*38* Unlike {@link goog.events.EventTarget}, this class does not implement39* {@link goog.disposable.IDisposable}. Instances of this class that have had40* It is not necessary to call {@link goog.dispose}41* or {@link #removeAllListeners} in order for an instance of this class42* to be garbage collected.43*44* Unless propagation is stopped, an event dispatched by an45* EventTarget will bubble to the parent returned by46* {@code getParentEventTarget}. To set the parent, call47* {@code setParentEventTarget}. Subclasses that don't support48* changing the parent can override the setter to throw an error.49*50* Example usage:51* <pre>52* var source = new goog.labs.events.NonDisposableEventTarget();53* function handleEvent(e) {54* alert('Type: ' + e.type + '; Target: ' + e.target);55* }56* source.listen('foo', handleEvent);57* source.dispatchEvent('foo'); // will call handleEvent58* </pre>59*60* TODO(chrishenry|johnlenz): Consider a more modern, less viral61* (not based on inheritance) replacement of goog.Disposable, which will allow62* goog.events.EventTarget to not be disposable.63*64* @constructor65* @implements {goog.events.Listenable}66* @final67*/68goog.labs.events.NonDisposableEventTarget = function() {69/**70* Maps of event type to an array of listeners.71* @private {!goog.events.ListenerMap}72*/73this.eventTargetListeners_ = new goog.events.ListenerMap(this);74};75goog.events.Listenable.addImplementation(76goog.labs.events.NonDisposableEventTarget);777879/**80* An artificial cap on the number of ancestors you can have. This is mainly81* for loop detection.82* @const {number}83* @private84*/85goog.labs.events.NonDisposableEventTarget.MAX_ANCESTORS_ = 1000;868788/**89* Parent event target, used during event bubbling.90* @private {goog.events.Listenable}91*/92goog.labs.events.NonDisposableEventTarget.prototype.parentEventTarget_ = null;939495/** @override */96goog.labs.events.NonDisposableEventTarget.prototype.getParentEventTarget =97function() {98return this.parentEventTarget_;99};100101102/**103* Sets the parent of this event target to use for capture/bubble104* mechanism.105* @param {goog.events.Listenable} parent Parent listenable (null if none).106*/107goog.labs.events.NonDisposableEventTarget.prototype.setParentEventTarget =108function(parent) {109this.parentEventTarget_ = parent;110};111112113/** @override */114goog.labs.events.NonDisposableEventTarget.prototype.dispatchEvent = function(115e) {116this.assertInitialized_();117var ancestorsTree, ancestor = this.getParentEventTarget();118if (ancestor) {119ancestorsTree = [];120var ancestorCount = 1;121for (; ancestor; ancestor = ancestor.getParentEventTarget()) {122ancestorsTree.push(ancestor);123goog.asserts.assert(124(++ancestorCount <125goog.labs.events.NonDisposableEventTarget.MAX_ANCESTORS_),126'infinite loop');127}128}129130return goog.labs.events.NonDisposableEventTarget.dispatchEventInternal_(131this, e, ancestorsTree);132};133134135/** @override */136goog.labs.events.NonDisposableEventTarget.prototype.listen = function(137type, listener, opt_useCapture, opt_listenerScope) {138this.assertInitialized_();139return this.eventTargetListeners_.add(140String(type), listener, false /* callOnce */, opt_useCapture,141opt_listenerScope);142};143144145/** @override */146goog.labs.events.NonDisposableEventTarget.prototype.listenOnce = function(147type, listener, opt_useCapture, opt_listenerScope) {148return this.eventTargetListeners_.add(149String(type), listener, true /* callOnce */, opt_useCapture,150opt_listenerScope);151};152153154/** @override */155goog.labs.events.NonDisposableEventTarget.prototype.unlisten = function(156type, listener, opt_useCapture, opt_listenerScope) {157return this.eventTargetListeners_.remove(158String(type), listener, opt_useCapture, opt_listenerScope);159};160161162/** @override */163goog.labs.events.NonDisposableEventTarget.prototype.unlistenByKey = function(164key) {165return this.eventTargetListeners_.removeByKey(key);166};167168169/** @override */170goog.labs.events.NonDisposableEventTarget.prototype.removeAllListeners =171function(opt_type) {172return this.eventTargetListeners_.removeAll(opt_type);173};174175176/** @override */177goog.labs.events.NonDisposableEventTarget.prototype.fireListeners = function(178type, capture, eventObject) {179// TODO(chrishenry): Original code avoids array creation when there180// is no listener, so we do the same. If this optimization turns181// out to be not required, we can replace this with182// getListeners(type, capture) instead, which is simpler.183var listenerArray = this.eventTargetListeners_.listeners[String(type)];184if (!listenerArray) {185return true;186}187listenerArray = goog.array.clone(listenerArray);188189var rv = true;190for (var i = 0; i < listenerArray.length; ++i) {191var listener = listenerArray[i];192// We might not have a listener if the listener was removed.193if (listener && !listener.removed && listener.capture == capture) {194var listenerFn = listener.listener;195var listenerHandler = listener.handler || listener.src;196197if (listener.callOnce) {198this.unlistenByKey(listener);199}200rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;201}202}203204return rv && eventObject.returnValue_ != false;205};206207208/** @override */209goog.labs.events.NonDisposableEventTarget.prototype.getListeners = function(210type, capture) {211return this.eventTargetListeners_.getListeners(String(type), capture);212};213214215/** @override */216goog.labs.events.NonDisposableEventTarget.prototype.getListener = function(217type, listener, capture, opt_listenerScope) {218return this.eventTargetListeners_.getListener(219String(type), listener, capture, opt_listenerScope);220};221222223/** @override */224goog.labs.events.NonDisposableEventTarget.prototype.hasListener = function(225opt_type, opt_capture) {226var id = goog.isDef(opt_type) ? String(opt_type) : undefined;227return this.eventTargetListeners_.hasListener(id, opt_capture);228};229230231/**232* Asserts that the event target instance is initialized properly.233* @private234*/235goog.labs.events.NonDisposableEventTarget.prototype.assertInitialized_ =236function() {237goog.asserts.assert(238this.eventTargetListeners_,239'Event target is not initialized. Did you call the superclass ' +240'(goog.labs.events.NonDisposableEventTarget) constructor?');241};242243244/**245* Dispatches the given event on the ancestorsTree.246*247* TODO(chrishenry): Look for a way to reuse this logic in248* goog.events, if possible.249*250* @param {!Object} target The target to dispatch on.251* @param {goog.events.Event|Object|string} e The event object.252* @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors253* tree of the target, in reverse order from the closest ancestor254* to the root event target. May be null if the target has no ancestor.255* @return {boolean} If anyone called preventDefault on the event object (or256* if any of the listeners returns false) this will also return false.257* @private258*/259goog.labs.events.NonDisposableEventTarget.dispatchEventInternal_ = function(260target, e, opt_ancestorsTree) {261var type = e.type || /** @type {string} */ (e);262263// If accepting a string or object, create a custom event object so that264// preventDefault and stopPropagation work with the event.265if (goog.isString(e)) {266e = new goog.events.Event(e, target);267} else if (!(e instanceof goog.events.Event)) {268var oldEvent = e;269e = new goog.events.Event(type, target);270goog.object.extend(e, oldEvent);271} else {272e.target = e.target || target;273}274275var rv = true, currentTarget;276277// Executes all capture listeners on the ancestors, if any.278if (opt_ancestorsTree) {279for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;280i--) {281currentTarget = e.currentTarget = opt_ancestorsTree[i];282rv = currentTarget.fireListeners(type, true, e) && rv;283}284}285286// Executes capture and bubble listeners on the target.287if (!e.propagationStopped_) {288currentTarget = e.currentTarget = target;289rv = currentTarget.fireListeners(type, true, e) && rv;290if (!e.propagationStopped_) {291rv = currentTarget.fireListeners(type, false, e) && rv;292}293}294295// Executes all bubble listeners on the ancestors, if any.296if (opt_ancestorsTree) {297for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {298currentTarget = e.currentTarget = opt_ancestorsTree[i];299rv = currentTarget.fireListeners(type, false, e) && rv;300}301}302303return rv;304};305306307