Home | History | Annotate | Download | only in base
      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