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 */
11base.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