1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 'use strict'; 6 7 /** 8 * @fileoverview This contains an implementation of the EventTarget interface 9 * as defined by DOM Level 2 Events. 10 */ 11 base.exportTo('base', function() { 12 13 /** 14 * Creates a new EventTarget. This class implements the DOM level 2 15 * EventTarget interface and can be used wherever those are used. 16 * @constructor 17 */ 18 function EventTarget() { 19 } 20 21 EventTarget.prototype = { 22 23 /** 24 * Adds an event listener to the target. 25 * @param {string} type The name of the event. 26 * @param {!Function|{handleEvent:Function}} handler The handler for the 27 * event. This is called when the event is dispatched. 28 */ 29 addEventListener: function(type, handler) { 30 if (!this.listeners_) 31 this.listeners_ = Object.create(null); 32 if (!(type in this.listeners_)) { 33 this.listeners_[type] = [handler]; 34 } else { 35 var handlers = this.listeners_[type]; 36 if (handlers.indexOf(handler) < 0) 37 handlers.push(handler); 38 } 39 }, 40 41 /** 42 * Removes an event listener from the target. 43 * @param {string} type The name of the event. 44 * @param {!Function|{handleEvent:Function}} handler The handler for the 45 * event. 46 */ 47 removeEventListener: function(type, handler) { 48 if (!this.listeners_) 49 return; 50 if (type in this.listeners_) { 51 var handlers = this.listeners_[type]; 52 var index = handlers.indexOf(handler); 53 if (index >= 0) { 54 // Clean up if this was the last listener. 55 if (handlers.length == 1) 56 delete this.listeners_[type]; 57 else 58 handlers.splice(index, 1); 59 } 60 } 61 }, 62 63 /** 64 * Dispatches an event and calls all the listeners that are listening to 65 * the type of the event. 66 * @param {!cr.event.Event} event The event to dispatch. 67 * @return {boolean} Whether the default action was prevented. If someone 68 * calls preventDefault on the event object then this returns false. 69 */ 70 dispatchEvent: function(event) { 71 if (!this.listeners_) 72 return true; 73 74 // Since we are using DOM Event objects we need to override some of the 75 // properties and methods so that we can emulate this correctly. 76 var self = this; 77 event.__defineGetter__('target', function() { 78 return self; 79 }); 80 event.preventDefault = function() { 81 this.returnValue = false; 82 }; 83 84 var type = event.type; 85 var prevented = 0; 86 if (type in this.listeners_) { 87 // Clone to prevent removal during dispatch 88 var handlers = this.listeners_[type].concat(); 89 for (var i = 0, handler; handler = handlers[i]; i++) { 90 if (handler.handleEvent) 91 prevented |= handler.handleEvent.call(handler, event) === false; 92 else 93 prevented |= handler.call(this, event) === false; 94 } 95 } 96 97 return !prevented && event.returnValue; 98 }, 99 100 hasEventListener: function(type) { 101 return this.listeners_[type] !== undefined; 102 } 103 }; 104 105 var EventTargetHelper = { 106 decorate: function(target) { 107 for (var k in EventTargetHelper) { 108 if (k == 'decorate') 109 continue; 110 var v = EventTargetHelper[k]; 111 if (typeof v !== 'function') 112 continue; 113 target[k] = v; 114 } 115 target.listenerCounts_ = {}; 116 }, 117 118 addEventListener: function(type, listener, useCapture) { 119 this.__proto__.addEventListener.call( 120 this, type, listener, useCapture); 121 if (this.listenerCounts_[type] === undefined) 122 this.listenerCounts_[type] = 0; 123 this.listenerCounts_[type]++; 124 }, 125 126 removeEventListener: function(type, listener, useCapture) { 127 this.__proto__.removeEventListener.call( 128 this, type, listener, useCapture); 129 this.listenerCounts_[type]--; 130 }, 131 132 hasEventListener: function(type) { 133 return this.listenerCounts_[type] > 0; 134 } 135 }; 136 137 // Export 138 return { 139 EventTarget: EventTarget, 140 EventTargetHelper: EventTargetHelper 141 }; 142 }); 143