@irrelon/emitter 6.0.1 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ export var ListenerReturnFlag;
4
4
  ListenerReturnFlag["cancel"] = "!+_CANCEL";
5
5
  })(ListenerReturnFlag || (ListenerReturnFlag = {}));
6
6
  /**
7
- * Creates a new class with the capability to emit events.
7
+ * Creates a new class with the ability to emit events.
8
8
  */
9
9
  export class Emitter {
10
10
  _eventsEmitting = false;
@@ -13,6 +13,15 @@ export class Emitter {
13
13
  _eventStaticEmitters = {};
14
14
  _eventsAllowDefer = false;
15
15
  _eventsDeferTimeouts = {};
16
+ _dispatchWindowEventsEnabled = false;
17
+ _dispatchPrefix;
18
+ constructor(dispatchPrefix, shouldDispatchWindowEvents = false) {
19
+ this._dispatchPrefix = dispatchPrefix;
20
+ this._dispatchWindowEventsEnabled = shouldDispatchWindowEvents;
21
+ }
22
+ dispatchWindowEventsEnabled(shouldDispatchWindowEvents) {
23
+ this._dispatchWindowEventsEnabled = shouldDispatchWindowEvents;
24
+ }
16
25
  /**
17
26
  * Attach an event listener to the passed event only if the passed
18
27
  * id matches the id for the event being fired.
@@ -32,9 +41,7 @@ export class Emitter {
32
41
  nonNullableEventListeners[id] = nonNullableEventListeners[id] || [];
33
42
  nonNullableEventListeners[id].push(listener);
34
43
  // Check for any static emitters, and fire the event if any exist
35
- if (!this._eventStaticEmitters ||
36
- !this._eventStaticEmitters[eventName] ||
37
- !this._eventStaticEmitters[eventName].length)
44
+ if (!this._eventStaticEmitters || !this._eventStaticEmitters[eventName] || !this._eventStaticEmitters[eventName].length)
38
45
  return this;
39
46
  // Emit events for each emitter
40
47
  for (let i = 0; i < this._eventStaticEmitters[eventName].length; i++) {
@@ -110,6 +117,13 @@ export class Emitter {
110
117
  }
111
118
  return this;
112
119
  }
120
+ /**
121
+ * Creates an event listener that listens for any and all events
122
+ * emitted.
123
+ */
124
+ onAny(listener) {
125
+ this.on("*", listener);
126
+ }
113
127
  on(eventName, idOrListener, listener) {
114
128
  if (typeof idOrListener === "string") {
115
129
  return this._on(eventName, idOrListener, listener);
@@ -132,6 +146,13 @@ export class Emitter {
132
146
  this.off(eventName, rest[0]);
133
147
  return this._on(eventName, rest[0], rest[1]);
134
148
  }
149
+ /**
150
+ * Removes an event listener that listens for any and all events
151
+ * emitted.
152
+ */
153
+ offAny(listener) {
154
+ this.off("*", listener);
155
+ }
135
156
  off(eventName, ...rest) {
136
157
  if (rest.length === 0) {
137
158
  // Only event was provided, use * as the id to mean "any without"
@@ -168,6 +189,22 @@ export class Emitter {
168
189
  }
169
190
  return resultArr;
170
191
  }
192
+ /**
193
+ * Dispatches an event originating in our emitter to the browser's
194
+ * window object.
195
+ * @param eventName The name of the event to emit. This gets prefixed with the
196
+ * dispatch prefix if one is set.
197
+ * @param data
198
+ */
199
+ _emitToWindowDispatch(eventName, data) {
200
+ if (typeof window === "undefined")
201
+ return;
202
+ if (!this._dispatchWindowEventsEnabled) {
203
+ return;
204
+ }
205
+ const finalEventName = (this._dispatchPrefix ? `${this._dispatchPrefix}.${eventName}` : eventName);
206
+ window.dispatchEvent(new CustomEvent(finalEventName, { detail: data }));
207
+ }
171
208
  /**
172
209
  * Call an event listener by name. The `rpc()` function differs from `emit()` in
173
210
  * that it will only call and return data from the first event listener for the
@@ -198,10 +235,10 @@ export class Emitter {
198
235
  *
199
236
  * // Emit the event named "hello"
200
237
  * const result = myEntity.emit('hello', 'data1', 'data2');
201
- * console.log("Result:", result);
238
+ * console.log("Result:", result);
202
239
  *
203
240
  * // The console output is:
204
- * // Arguments: data1 data2
241
+ * // Arguments: data1 data2
205
242
  * // Result: "foo"
206
243
  */
207
244
  rpc(eventName, ...data) {
@@ -219,7 +256,7 @@ export class Emitter {
219
256
  emitterResult = this._emitToSingleListener(arr[0], data);
220
257
  }
221
258
  else if (id !== "*" && this._eventListeners[eventName]["*"]) {
222
- // Handle global emit if the id passed wasn't already a global (*) id
259
+ // Handle wildcard emit if the id passed wasn't already a wildcard (*) id
223
260
  const arr = this._eventListeners[eventName]["*"];
224
261
  emitterResult = this._emitToSingleListener(arr[0], data);
225
262
  }
@@ -239,7 +276,7 @@ export class Emitter {
239
276
  * @param {...any} data The arguments to send to any listening methods.
240
277
  * If you are sending multiple arguments, separate them with a comma so
241
278
  * that they are received by the function as separate arguments.
242
- * @return {number}
279
+ * @return {any[]} An array of all return values from all the listeners for the event.
243
280
  * @example #Emit an Event
244
281
  * // Emit the event named "hello"
245
282
  * myEntity.emit('hello');
@@ -252,7 +289,7 @@ export class Emitter {
252
289
  * @example #Listen for Event Data
253
290
  * // Set a listener to listen for the data (multiple values emitted
254
291
  * // from an event are passed as function arguments)
255
- * myEntity.on('hello', function (arg1, arg2) {
292
+ * myEntity.on('hello', function (arg1, arg2) {
256
293
  * console.log("Arguments:", arg1, arg2);
257
294
  * return "foo";
258
295
  * }
@@ -261,39 +298,57 @@ export class Emitter {
261
298
  * // The result is returned as an array of all return values
262
299
  * // from all the listeners for the event
263
300
  * const result = myEntity.emit('hello', 'data1', 'data2');
264
- * console.log("Result:", result);
301
+ * console.log("Result:", result);
265
302
  *
266
303
  * // The console output is:
267
- * // Arguments: data1 data2
304
+ * // Arguments: data1 data2
268
305
  * // Result: ["foo"]
269
306
  */
270
307
  emit(eventName, ...data) {
308
+ this._emitToWindowDispatch(eventName, data);
271
309
  return this.emitId(eventName, "*", ...data);
272
310
  }
311
+ /**
312
+ * Emits an event by name and id.
313
+ * @param eventName The name of the event to emit.
314
+ * @param id The id of the event to emit.
315
+ * @param {...any} data The arguments to send to any listening methods.
316
+ * If you are sending multiple arguments, separate them with a comma so
317
+ * that they are received by the function as separate arguments.
318
+ * @return {any[]} An array of all return values from all the listeners for the event.
319
+ */
273
320
  emitId(eventName, id, ...data) {
274
- if (!this._eventListeners || !this._eventListeners[eventName]) {
321
+ if (!this._eventListeners || (!this._eventListeners[eventName] && !this._eventListeners["*"])) {
275
322
  return [];
276
323
  }
277
324
  let emitterResult = [];
278
325
  this._eventsEmitting = true;
279
- // Handle id emit
280
- if (this._eventListeners[eventName][id]) {
281
- const arr = this._eventListeners[eventName][id];
282
- emitterResult = emitterResult.concat(this._emitToArrayOfListeners(arr, data));
326
+ if (this._eventListeners[eventName]) {
327
+ // Handle id emit
328
+ if (this._eventListeners[eventName][id]) {
329
+ const arr = this._eventListeners[eventName][id];
330
+ emitterResult = emitterResult.concat(this._emitToArrayOfListeners(arr, data));
331
+ }
332
+ // Handle id wildcard emit if the id passed wasn't already a wildcard (*) id
333
+ if (id !== "*" && this._eventListeners[eventName]["*"]) {
334
+ const arr = this._eventListeners[eventName]["*"];
335
+ emitterResult = emitterResult.concat(this._emitToArrayOfListeners(arr, data));
336
+ }
283
337
  }
284
- // Handle global emit if the id passed wasn't already a global (*) id
285
- if (id !== "*" && this._eventListeners[eventName]["*"]) {
286
- const arr = this._eventListeners[eventName]["*"];
287
- emitterResult = emitterResult.concat(this._emitToArrayOfListeners(arr, data));
338
+ if (this._eventListeners["*"]) {
339
+ const arr = this._eventListeners["*"]["*"];
340
+ emitterResult = emitterResult.concat(this._emitToArrayOfListeners(arr, [eventName, ...data]));
288
341
  }
289
342
  this._eventsEmitting = false;
290
343
  this._processRemovalQueue();
291
344
  return emitterResult;
292
345
  }
293
346
  /**
294
- * Creates a persistent emitter record that will fire a listener if
295
- * one is added for this event after the emitStatic() call has been
296
- * made.
347
+ * Creates a persistent event. Once emitStatic() has been called, a
348
+ * persistent event is registered. Any existing event listeners will
349
+ * be called just like when you call emit(), however, any new
350
+ * listeners that are registered for this event via on() will also be
351
+ * called as soon as they are added.
297
352
  * @param eventName The name of the event to emit.
298
353
  * @param {...any} data Optional arguments to emit with the event.
299
354
  * @returns The emitter instance.
@@ -304,7 +359,7 @@ export class Emitter {
304
359
  this._eventListeners = this._eventListeners || {};
305
360
  this._eventsEmitting = true;
306
361
  if (this._eventListeners[eventName] && this._eventListeners[eventName][id]) {
307
- // Handle global emit
362
+ // Handle id wildcard emit
308
363
  const arr = this._eventListeners[eventName][id];
309
364
  const arrCount = arr.length;
310
365
  for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
@@ -353,7 +408,7 @@ export class Emitter {
353
408
  }
354
409
  }
355
410
  }
356
- // Handle global emit
411
+ // Handle wildcard emit
357
412
  if (this._eventListeners[eventName]["*"]) {
358
413
  const arr = this._eventListeners[eventName]["*"];
359
414
  const arrCount = arr.length;
@@ -432,7 +487,7 @@ export class Emitter {
432
487
  }
433
488
  }
434
489
  }
435
- // Handle global emit
490
+ // Handle wildcard emit
436
491
  if (this._eventListeners[eventName]["*"]) {
437
492
  const arr = this._eventListeners[eventName]["*"];
438
493
  const arrCount = arr.length;
@@ -529,7 +584,9 @@ export function makeEmitter(obj, prototypeMode) {
529
584
  }
530
585
  // Convert the object prototype to have eventing capability
531
586
  operateOnObject.on = Emitter.prototype.on;
587
+ operateOnObject.onAny = Emitter.prototype.onAny;
532
588
  operateOnObject.off = Emitter.prototype.off;
589
+ operateOnObject.offAny = Emitter.prototype.offAny;
533
590
  operateOnObject.once = Emitter.prototype.once;
534
591
  operateOnObject.emit = Emitter.prototype.emit;
535
592
  operateOnObject.emitId = Emitter.prototype.emitId;
@@ -1,30 +1,32 @@
1
+ import { jest } from "@jest/globals";
1
2
  import assert from "assert";
2
3
  import { Emitter, ListenerReturnFlag } from "../src/Emitter";
3
4
  let expectedAssertions = 0;
4
5
  let actualAssertions = 0;
5
- function expect(val) {
6
+ function expectAssertionCount(val) {
6
7
  expectedAssertions = val;
7
8
  }
8
- function reset() {
9
+ function resetAssertionCount() {
9
10
  expectedAssertions = 0;
10
11
  actualAssertions = 0;
11
12
  }
12
13
  function countAssertion() {
13
14
  actualAssertions++;
14
15
  }
15
- function check() {
16
+ function checkAssertionCount() {
16
17
  if (expectedAssertions === undefined || expectedAssertions === actualAssertions) {
17
18
  return;
18
19
  }
19
20
  throw new Error("expected " + expectedAssertions + " assertions, got " + actualAssertions);
20
21
  }
21
- beforeEach(reset);
22
- afterEach(check);
22
+ beforeEach(resetAssertionCount);
23
+ afterEach(checkAssertionCount);
23
24
  describe("Emitter", () => {
24
25
  describe("new Emitter()", () => {
25
26
  it("Creates emitter instance by instantiation", () => {
26
27
  const myClass = new Emitter();
27
- myClass.on("moo", () => { });
28
+ myClass.on("moo", () => {
29
+ });
28
30
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 1, "Listener registered on event");
29
31
  myClass.off("moo");
30
32
  assert.strictEqual(!myClass._eventListeners?.moo ||
@@ -32,12 +34,8 @@ describe("Emitter", () => {
32
34
  myClass._eventListeners.moo["*"].length, true, "Listeners all removed from event");
33
35
  });
34
36
  it("Supports extending Emitter as a base class", () => {
35
- let testFuncCount = 0;
36
37
  // Test extending Emitter and modifying base functionality
37
38
  class MyClass extends Emitter {
38
- testFunc() {
39
- testFuncCount++;
40
- }
41
39
  on(eventName, ...rest) {
42
40
  const restTypes = rest.map((arg) => typeof arg);
43
41
  if (restTypes[0] === "function") {
@@ -150,6 +148,7 @@ describe("Emitter", () => {
150
148
  await Promise.all(emitter.emit("foo"));
151
149
  const delta = new Date().getTime() - time;
152
150
  assert.ok(delta > 900, "Delta was not correct, await may not have paused?");
151
+ assert.strictEqual(listenerFiredCount, 2, "Listener fire count was incorect");
153
152
  });
154
153
  it("Supports awaiting async and non-async listener return data", async () => {
155
154
  const emitter = new Emitter();
@@ -226,11 +225,15 @@ describe("Emitter", () => {
226
225
  describe("off()", () => {
227
226
  it("Cancels all listeners (event)", () => {
228
227
  const myClass = new Emitter();
229
- const listener1 = () => { };
230
- const listener2 = () => { };
231
- myClass.on("moo", () => { });
228
+ const listener1 = () => {
229
+ };
230
+ const listener2 = () => {
231
+ };
232
+ myClass.on("moo", () => {
233
+ });
232
234
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 1, "Listeners registered on event");
233
- myClass.on("moo", "testId", () => { });
235
+ myClass.on("moo", "testId", () => {
236
+ });
234
237
  assert.strictEqual(myClass._eventListeners?.moo["testId"].length, 1, "Listeners registered on event");
235
238
  myClass.on("moo", listener1);
236
239
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 2, "Listeners registered on event");
@@ -243,11 +246,15 @@ describe("Emitter", () => {
243
246
  });
244
247
  it("Cancels id-based listeners (event, id)", () => {
245
248
  const myClass = new Emitter();
246
- const listener1 = () => { };
247
- const listener2 = () => { };
248
- myClass.on("moo", () => { });
249
+ const listener1 = () => {
250
+ };
251
+ const listener2 = () => {
252
+ };
253
+ myClass.on("moo", () => {
254
+ });
249
255
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 1, "Listeners registered on event");
250
- myClass.on("moo", "testId", () => { });
256
+ myClass.on("moo", "testId", () => {
257
+ });
251
258
  assert.strictEqual(myClass._eventListeners?.moo["testId"].length, 1, "Listeners registered on event");
252
259
  myClass.on("moo", listener1);
253
260
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 2, "Listeners registered on event");
@@ -259,11 +266,15 @@ describe("Emitter", () => {
259
266
  });
260
267
  it("Cancels listener-based listeners (event, listener)", () => {
261
268
  const myClass = new Emitter();
262
- const listener1 = () => { };
263
- const listener2 = () => { };
264
- myClass.on("moo", () => { });
269
+ const listener1 = () => {
270
+ };
271
+ const listener2 = () => {
272
+ };
273
+ myClass.on("moo", () => {
274
+ });
265
275
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 1, "Listeners registered on event");
266
- myClass.on("moo", "testId", () => { });
276
+ myClass.on("moo", "testId", () => {
277
+ });
267
278
  assert.strictEqual(myClass._eventListeners?.moo["testId"].length, 1, "Listeners registered on event");
268
279
  myClass.on("moo", listener1);
269
280
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 2, "Listeners registered on event");
@@ -275,11 +286,15 @@ describe("Emitter", () => {
275
286
  });
276
287
  it("Cancels id-based + listener-based listeners (event, id, listener)", () => {
277
288
  const myClass = new Emitter();
278
- const listener1 = () => { };
279
- const listener2 = () => { };
280
- myClass.on("moo", () => { });
289
+ const listener1 = () => {
290
+ };
291
+ const listener2 = () => {
292
+ };
293
+ myClass.on("moo", () => {
294
+ });
281
295
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 1, "Listeners registered on event");
282
- myClass.on("moo", "testId", () => { });
296
+ myClass.on("moo", "testId", () => {
297
+ });
283
298
  assert.strictEqual(myClass._eventListeners?.moo["testId"].length, 1, "Listeners registered on event");
284
299
  myClass.on("moo", listener1);
285
300
  assert.strictEqual(myClass._eventListeners?.moo["*"].length, 2, "Listeners registered on event");
@@ -294,7 +309,7 @@ describe("Emitter", () => {
294
309
  it("Static emitter will emit when a new listener is added", (resolve) => {
295
310
  class MyClass extends Emitter {
296
311
  }
297
- expect(1);
312
+ expectAssertionCount(1);
298
313
  const myClass = new MyClass();
299
314
  myClass.emitStatic("moo");
300
315
  myClass.on("moo", () => {
@@ -308,7 +323,7 @@ describe("Emitter", () => {
308
323
  it("Removes static emitter", (resolve) => {
309
324
  class MyClass extends Emitter {
310
325
  }
311
- expect(1);
326
+ expectAssertionCount(1);
312
327
  const myClass = new MyClass();
313
328
  myClass.emitStatic("moo");
314
329
  myClass.on("moo", () => {
@@ -329,7 +344,7 @@ describe("Emitter", () => {
329
344
  it("Only fires the last listener added, cancelling all other listeners before it", (resolve) => {
330
345
  class MyClass extends Emitter {
331
346
  }
332
- expect(1);
347
+ expectAssertionCount(1);
333
348
  const myClass = new MyClass();
334
349
  myClass.on("moo", () => {
335
350
  countAssertion();
@@ -351,7 +366,7 @@ describe("Emitter", () => {
351
366
  it("Only fires the last listener added (with id), cancelling all other listeners before it", (resolve) => {
352
367
  class MyClass extends Emitter {
353
368
  }
354
- expect(2);
369
+ expectAssertionCount(2);
355
370
  const myClass = new MyClass();
356
371
  // Define a listener without an id, this should not be overwritten
357
372
  myClass.on("moo", () => {
@@ -406,4 +421,36 @@ describe("Emitter", () => {
406
421
  myClass._eventListeners?.moo["*"].length === 0, true, "Listeners all removed from event");
407
422
  });
408
423
  });
424
+ describe("Global listener functionality", () => {
425
+ it("Listens for any event emitted and fires the listener with the onAny() function", () => {
426
+ const emitter = new Emitter();
427
+ const spy = jest.fn();
428
+ emitter.onAny(spy);
429
+ emitter.emit("someEvent", "someArg1", true, false);
430
+ expect(spy).toHaveBeenCalledWith("someEvent", "someArg1", true, false);
431
+ });
432
+ it(`Listens for any event emitted and fires the listener with the on("*") function`, () => {
433
+ const emitter = new Emitter();
434
+ const spy = jest.fn();
435
+ emitter.on("*", spy);
436
+ emitter.emit("someEvent", "someArg1", true, false);
437
+ expect(spy).toHaveBeenCalledWith("someEvent", "someArg1", true, false);
438
+ });
439
+ it(`Cancels an event listener for any event emitted with the offAny() function`, () => {
440
+ const emitter = new Emitter();
441
+ const spy = jest.fn();
442
+ emitter.on("*", spy);
443
+ emitter.offAny(spy);
444
+ emitter.emit("someEvent", "someArg1", true, false);
445
+ expect(spy).toHaveBeenCalledTimes(0);
446
+ });
447
+ it(`Cancels an event listener for any event emitted with the off("*") function`, () => {
448
+ const emitter = new Emitter();
449
+ const spy = jest.fn();
450
+ emitter.on("*", spy);
451
+ emitter.off("*", spy);
452
+ emitter.emit("someEvent", "someArg1", true, false);
453
+ expect(spy).toHaveBeenCalledTimes(0);
454
+ });
455
+ });
409
456
  });