- 1 :
/**
- 2 :
* @file src/js/event-target.js
- 3 :
*/
- 4 :
import * as Events from './utils/events.js';
- 5 :
import window from 'global/window';
- 6 :
- 7 :
/**
- 8 :
* `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
- 9 :
* adds shorthand functions that wrap around lengthy functions. For example:
- 10 :
* the `on` function is a wrapper around `addEventListener`.
- 11 :
*
- 12 :
* @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
- 13 :
* @class EventTarget
- 14 :
*/
- 15 :
const EventTarget = function() {};
- 16 :
- 17 :
/**
- 18 :
* A Custom DOM event.
- 19 :
*
- 20 :
* @typedef {Object} EventTarget~Event
- 21 :
* @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
- 22 :
*/
- 23 :
- 24 :
/**
- 25 :
* All event listeners should follow the following format.
- 26 :
*
- 27 :
* @callback EventTarget~EventListener
- 28 :
* @this {EventTarget}
- 29 :
*
- 30 :
* @param {EventTarget~Event} event
- 31 :
* the event that triggered this function
- 32 :
*
- 33 :
* @param {Object} [hash]
- 34 :
* hash of data sent during the event
- 35 :
*/
- 36 :
- 37 :
/**
- 38 :
* An object containing event names as keys and booleans as values.
- 39 :
*
- 40 :
* > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
- 41 :
* will have extra functionality. See that function for more information.
- 42 :
*
- 43 :
* @property EventTarget.prototype.allowedEvents_
- 44 :
* @private
- 45 :
*/
- 46 :
EventTarget.prototype.allowedEvents_ = {};
- 47 :
- 48 :
/**
- 49 :
* Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
- 50 :
* function that will get called when an event with a certain name gets triggered.
- 51 :
*
- 52 :
* @param {string|string[]} type
- 53 :
* An event name or an array of event names.
- 54 :
*
- 55 :
* @param {EventTarget~EventListener} fn
- 56 :
* The function to call with `EventTarget`s
- 57 :
*/
- 58 :
EventTarget.prototype.on = function(type, fn) {
- 59 :
// Remove the addEventListener alias before calling Events.on
- 60 :
// so we don't get into an infinite type loop
- 61 :
const ael = this.addEventListener;
- 62 :
- 63 :
this.addEventListener = () => {};
- 64 :
Events.on(this, type, fn);
- 65 :
this.addEventListener = ael;
- 66 :
};
- 67 :
- 68 :
/**
- 69 :
* An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
- 70 :
* the standard DOM API.
- 71 :
*
- 72 :
* @function
- 73 :
* @see {@link EventTarget#on}
- 74 :
*/
- 75 :
EventTarget.prototype.addEventListener = EventTarget.prototype.on;
- 76 :
- 77 :
/**
- 78 :
* Removes an `event listener` for a specific event from an instance of `EventTarget`.
- 79 :
* This makes it so that the `event listener` will no longer get called when the
- 80 :
* named event happens.
- 81 :
*
- 82 :
* @param {string|string[]} type
- 83 :
* An event name or an array of event names.
- 84 :
*
- 85 :
* @param {EventTarget~EventListener} fn
- 86 :
* The function to remove.
- 87 :
*/
- 88 :
EventTarget.prototype.off = function(type, fn) {
- 89 :
Events.off(this, type, fn);
- 90 :
};
- 91 :
- 92 :
/**
- 93 :
* An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
- 94 :
* the standard DOM API.
- 95 :
*
- 96 :
* @function
- 97 :
* @see {@link EventTarget#off}
- 98 :
*/
- 99 :
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
- 100 :
- 101 :
/**
- 102 :
* This function will add an `event listener` that gets triggered only once. After the
- 103 :
* first trigger it will get removed. This is like adding an `event listener`
- 104 :
* with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
- 105 :
*
- 106 :
* @param {string|string[]} type
- 107 :
* An event name or an array of event names.
- 108 :
*
- 109 :
* @param {EventTarget~EventListener} fn
- 110 :
* The function to be called once for each event name.
- 111 :
*/
- 112 :
EventTarget.prototype.one = function(type, fn) {
- 113 :
// Remove the addEventListener aliasing Events.on
- 114 :
// so we don't get into an infinite type loop
- 115 :
const ael = this.addEventListener;
- 116 :
- 117 :
this.addEventListener = () => {};
- 118 :
Events.one(this, type, fn);
- 119 :
this.addEventListener = ael;
- 120 :
};
- 121 :
- 122 :
EventTarget.prototype.any = function(type, fn) {
- 123 :
// Remove the addEventListener aliasing Events.on
- 124 :
// so we don't get into an infinite type loop
- 125 :
const ael = this.addEventListener;
- 126 :
- 127 :
this.addEventListener = () => {};
- 128 :
Events.any(this, type, fn);
- 129 :
this.addEventListener = ael;
- 130 :
};
- 131 :
- 132 :
/**
- 133 :
* This function causes an event to happen. This will then cause any `event listeners`
- 134 :
* that are waiting for that event, to get called. If there are no `event listeners`
- 135 :
* for an event then nothing will happen.
- 136 :
*
- 137 :
* If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
- 138 :
* Trigger will also call the `on` + `uppercaseEventName` function.
- 139 :
*
- 140 :
* Example:
- 141 :
* 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
- 142 :
* `onClick` if it exists.
- 143 :
*
- 144 :
* @param {string|EventTarget~Event|Object} event
- 145 :
* The name of the event, an `Event`, or an object with a key of type set to
- 146 :
* an event name.
- 147 :
*/
- 148 :
EventTarget.prototype.trigger = function(event) {
- 149 :
const type = event.type || event;
- 150 :
- 151 :
// deprecation
- 152 :
// In a future version we should default target to `this`
- 153 :
// similar to how we default the target to `elem` in
- 154 :
// `Events.trigger`. Right now the default `target` will be
- 155 :
// `document` due to the `Event.fixEvent` call.
- 156 :
if (typeof event === 'string') {
- 157 :
event = {type};
- 158 :
}
- 159 :
event = Events.fixEvent(event);
- 160 :
- 161 :
if (this.allowedEvents_[type] && this['on' + type]) {
- 162 :
this['on' + type](event);
- 163 :
}
- 164 :
- 165 :
Events.trigger(this, event);
- 166 :
};
- 167 :
- 168 :
/**
- 169 :
* An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
- 170 :
* the standard DOM API.
- 171 :
*
- 172 :
* @function
- 173 :
* @see {@link EventTarget#trigger}
- 174 :
*/
- 175 :
EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
- 176 :
- 177 :
let EVENT_MAP;
- 178 :
- 179 :
EventTarget.prototype.queueTrigger = function(event) {
- 180 :
// only set up EVENT_MAP if it'll be used
- 181 :
if (!EVENT_MAP) {
- 182 :
EVENT_MAP = new Map();
- 183 :
}
- 184 :
- 185 :
const type = event.type || event;
- 186 :
let map = EVENT_MAP.get(this);
- 187 :
- 188 :
if (!map) {
- 189 :
map = new Map();
- 190 :
EVENT_MAP.set(this, map);
- 191 :
}
- 192 :
- 193 :
const oldTimeout = map.get(type);
- 194 :
- 195 :
map.delete(type);
- 196 :
window.clearTimeout(oldTimeout);
- 197 :
- 198 :
const timeout = window.setTimeout(() => {
- 199 :
map.delete(type);
- 200 :
// if we cleared out all timeouts for the current target, delete its map
- 201 :
if (map.size === 0) {
- 202 :
map = null;
- 203 :
EVENT_MAP.delete(this);
- 204 :
}
- 205 :
- 206 :
this.trigger(event);
- 207 :
}, 0);
- 208 :
- 209 :
map.set(type, timeout);
- 210 :
};
- 211 :
- 212 :
export default EventTarget;