|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "unstable" |
|
9 }; |
|
10 |
|
11 const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.'; |
|
12 const BAD_LISTENER = 'The event listener must be a function.'; |
|
13 |
|
14 const { ns } = require('../core/namespace'); |
|
15 |
|
16 const event = ns(); |
|
17 |
|
18 const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/; |
|
19 exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN; |
|
20 |
|
21 // Utility function to access given event `target` object's event listeners for |
|
22 // the specific event `type`. If listeners for this type does not exists they |
|
23 // will be created. |
|
24 const observers = function observers(target, type) { |
|
25 if (!target) throw TypeError("Event target must be an object"); |
|
26 let listeners = event(target); |
|
27 return type in listeners ? listeners[type] : listeners[type] = []; |
|
28 }; |
|
29 |
|
30 /** |
|
31 * Registers an event `listener` that is called every time events of |
|
32 * specified `type` is emitted on the given event `target`. |
|
33 * @param {Object} target |
|
34 * Event target object. |
|
35 * @param {String} type |
|
36 * The type of event. |
|
37 * @param {Function} listener |
|
38 * The listener function that processes the event. |
|
39 */ |
|
40 function on(target, type, listener) { |
|
41 if (typeof(listener) !== 'function') |
|
42 throw new Error(BAD_LISTENER); |
|
43 |
|
44 let listeners = observers(target, type); |
|
45 if (!~listeners.indexOf(listener)) |
|
46 listeners.push(listener); |
|
47 } |
|
48 exports.on = on; |
|
49 |
|
50 /** |
|
51 * Registers an event `listener` that is called only the next time an event |
|
52 * of the specified `type` is emitted on the given event `target`. |
|
53 * @param {Object} target |
|
54 * Event target object. |
|
55 * @param {String} type |
|
56 * The type of the event. |
|
57 * @param {Function} listener |
|
58 * The listener function that processes the event. |
|
59 */ |
|
60 function once(target, type, listener) { |
|
61 on(target, type, function observer(...args) { |
|
62 off(target, type, observer); |
|
63 listener.apply(target, args); |
|
64 }); |
|
65 } |
|
66 exports.once = once; |
|
67 |
|
68 /** |
|
69 * Execute each of the listeners in order with the supplied arguments. |
|
70 * All the exceptions that are thrown by listeners during the emit |
|
71 * are caught and can be handled by listeners of 'error' event. Thrown |
|
72 * exceptions are passed as an argument to an 'error' event listener. |
|
73 * If no 'error' listener is registered exception will be logged into an |
|
74 * error console. |
|
75 * @param {Object} target |
|
76 * Event target object. |
|
77 * @param {String} type |
|
78 * The type of event. |
|
79 * @params {Object|Number|String|Boolean} args |
|
80 * Arguments that will be passed to listeners. |
|
81 */ |
|
82 function emit (target, type, ...args) { |
|
83 let state = observers(target, type); |
|
84 let listeners = state.slice(); |
|
85 let count = listeners.length; |
|
86 let index = 0; |
|
87 |
|
88 // If error event and there are no handlers then print error message |
|
89 // into a console. |
|
90 if (count === 0 && type === 'error') console.exception(args[0]); |
|
91 while (index < count) { |
|
92 try { |
|
93 let listener = listeners[index]; |
|
94 // Dispatch only if listener is still registered. |
|
95 if (~state.indexOf(listener)) |
|
96 listener.apply(target, args); |
|
97 } |
|
98 catch (error) { |
|
99 // If exception is not thrown by a error listener and error listener is |
|
100 // registered emit `error` event. Otherwise dump exception to the console. |
|
101 if (type !== 'error') emit(target, 'error', error); |
|
102 else console.exception(error); |
|
103 } |
|
104 index++; |
|
105 } |
|
106 // Also emit on `"*"` so that one could listen for all events. |
|
107 if (type !== '*') emit(target, '*', type, ...args); |
|
108 } |
|
109 exports.emit = emit; |
|
110 |
|
111 /** |
|
112 * Removes an event `listener` for the given event `type` on the given event |
|
113 * `target`. If no `listener` is passed removes all listeners of the given |
|
114 * `type`. If `type` is not passed removes all the listeners of the given |
|
115 * event `target`. |
|
116 * @param {Object} target |
|
117 * The event target object. |
|
118 * @param {String} type |
|
119 * The type of event. |
|
120 * @param {Function} listener |
|
121 * The listener function that processes the event. |
|
122 */ |
|
123 function off(target, type, listener) { |
|
124 let length = arguments.length; |
|
125 if (length === 3) { |
|
126 let listeners = observers(target, type); |
|
127 let index = listeners.indexOf(listener); |
|
128 if (~index) |
|
129 listeners.splice(index, 1); |
|
130 } |
|
131 else if (length === 2) { |
|
132 observers(target, type).splice(0); |
|
133 } |
|
134 else if (length === 1) { |
|
135 let listeners = event(target); |
|
136 Object.keys(listeners).forEach(type => delete listeners[type]); |
|
137 } |
|
138 } |
|
139 exports.off = off; |
|
140 |
|
141 /** |
|
142 * Returns a number of event listeners registered for the given event `type` |
|
143 * on the given event `target`. |
|
144 */ |
|
145 function count(target, type) { |
|
146 return observers(target, type).length; |
|
147 } |
|
148 exports.count = count; |
|
149 |
|
150 /** |
|
151 * Registers listeners on the given event `target` from the given `listeners` |
|
152 * dictionary. Iterates over the listeners and if property name matches name |
|
153 * pattern `onEventType` and property is a function, then registers it as |
|
154 * an `eventType` listener on `target`. |
|
155 * |
|
156 * @param {Object} target |
|
157 * The type of event. |
|
158 * @param {Object} listeners |
|
159 * Dictionary of listeners. |
|
160 */ |
|
161 function setListeners(target, listeners) { |
|
162 Object.keys(listeners || {}).forEach(key => { |
|
163 let match = EVENT_TYPE_PATTERN.exec(key); |
|
164 let type = match && match[1].toLowerCase(); |
|
165 if (!type) return; |
|
166 |
|
167 let listener = listeners[key]; |
|
168 if (typeof(listener) === 'function') |
|
169 on(target, type, listener); |
|
170 }); |
|
171 } |
|
172 exports.setListeners = setListeners; |