forked from benlesh/event-target-polyfill
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Basic implementation - Basic tests as a simple script - Update README
- Loading branch information
Showing
5 changed files
with
380 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,25 @@ | ||
# event-target-polyfill | ||
An EventTarget Polyfill | ||
|
||
A polyfill for `EventTarget` (and `Event`), meant to run in older version of node or possibly IE 11, that has the most accurate set of characteristics of `EventTarget` that can be provided. | ||
|
||
If you find this implementation can be improved, please submit a PR and ping me [on Twitter via DM](https://twitter.com/benlesh). | ||
|
||
**NOTE: If you are using Node 14 and higher, [EventTarget is available directly](https://nodejs.org/api/events.html#events_eventtarget_and_event_api) via experimental features** | ||
MDN: [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) | ||
|
||
## Usage | ||
|
||
``` | ||
import '@benlesh/event-target-polyfill'; | ||
const et = new EventTarget(); | ||
et.addEventListener('test', () => console.log('hit!')); | ||
et.dispatchEvent(new Event('test')); | ||
``` | ||
|
||
## Development | ||
|
||
This library has no dependencies. Even development dependencies. To test just run `npm test`. It runs a script, and if it finishes without error, the tests pass. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
const root = | ||
(typeof globalThis !== "undefined" && globalThis) || | ||
(typeof self !== "undefined" && self) || | ||
(typeof global !== "undefined" && global); | ||
|
||
if (typeof root.Event === "undefined") { | ||
root.Event = (function () { | ||
function Event(type, options) { | ||
if (options) { | ||
for (let key of options) { | ||
if (options.hasOwnProperty(key)) { | ||
this[key] = options[key]; | ||
} | ||
} | ||
} | ||
this.type = type; | ||
} | ||
|
||
return Event; | ||
})(); | ||
} | ||
|
||
if (typeof root.EventTarget === "undefined") { | ||
root.EventTarget = (function () { | ||
function EventTarget() { | ||
this.__listeners = new Map(); | ||
} | ||
|
||
EventTarget.prototype = Object.create(Object.prototype); | ||
|
||
EventTarget.prototype.addEventListener = function ( | ||
type, | ||
listener, | ||
options | ||
) { | ||
if (arguments.length < 2) { | ||
throw new TypeError( | ||
`TypeError: Failed to execute 'addEventListener' on 'EventTarget': 2 arguments required, but only ${arguments.length} present.` | ||
); | ||
} | ||
const __listeners = this.__listeners; | ||
const actualType = type.toString(); | ||
if (!__listeners.has(actualType)) { | ||
__listeners.set(actualType, new Map()); | ||
} | ||
const listenersForType = __listeners.get(actualType); | ||
if (!listenersForType.has(listener)) { | ||
// Any given listener is only registered once | ||
listenersForType.set(listener, options); | ||
} | ||
}; | ||
|
||
EventTarget.prototype.removeEventListener = function ( | ||
type, | ||
listener, | ||
_options | ||
) { | ||
if (arguments.length < 2) { | ||
throw new TypeError( | ||
`TypeError: Failed to execute 'addEventListener' on 'EventTarget': 2 arguments required, but only ${arguments.length} present.` | ||
); | ||
} | ||
const __listeners = this.__listeners; | ||
const actualType = type.toString(); | ||
if (__listeners.has(actualType)) { | ||
const listenersForType = __listeners.get(actualType); | ||
if (listenersForType.has(listener)) { | ||
listenersForType.delete(listener); | ||
} | ||
} | ||
}; | ||
|
||
EventTarget.prototype.dispatchEvent = function (event) { | ||
if (!(event instanceof Event)) { | ||
throw new TypeError( | ||
`Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.` | ||
); | ||
} | ||
const type = event.type; | ||
const __listeners = this.__listeners; | ||
const listenersForType = __listeners.get(type); | ||
if (listenersForType) { | ||
for (const [listener, options] of listenersForType.entries()) { | ||
try { | ||
if (typeof listener === "function") { | ||
// Listener functions must be executed with the EventTarget as the `this` context. | ||
listener.call(this, event); | ||
} else if (listener && typeof listener.handleEvent === "function") { | ||
// Listener objects have their handleEvent method called, if they have one | ||
listener.handleEvent(event); | ||
} | ||
} catch (err) { | ||
// We need to report the error to the global error handling event, | ||
// but we do not want to break the loop that is executing the events. | ||
// Unfortunately, this is the best we can do, which isn't great, because the | ||
// native EventTarget will actually do this synchronously before moving to the next | ||
// event in the loop. | ||
setTimeout(() => { | ||
throw err; | ||
}); | ||
} | ||
if (options && options.once) { | ||
// If this was registered with { once: true }, we need | ||
// to remove it now. | ||
listenersForType.delete(listener); | ||
} | ||
} | ||
} | ||
// Since there are no cancellable events on a base EventTarget, | ||
// this should always return true. | ||
return true; | ||
}; | ||
|
||
return EventTarget; | ||
})(); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"name": "@benlesh/event-target-polyfill", | ||
"version": "0.0.1", | ||
"description": "An EventTarget Polyfill", | ||
"type": "module", | ||
"main": "index.js", | ||
"exports": { | ||
".": "./index.js" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": {}, | ||
"scripts": { | ||
"test": "node ./test.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/benlesh/event-target-polyfill.git" | ||
}, | ||
"keywords": [ | ||
"EventTarget", | ||
"Polyfill" | ||
], | ||
"author": "Ben Lesh <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/benlesh/event-target-polyfill/issues" | ||
}, | ||
"homepage": "https://github.com/benlesh/event-target-polyfill#readme" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
import "./index.js"; | ||
|
||
if (typeof EventTarget === "undefined") { | ||
fail("EventTarget does not exist"); | ||
} | ||
|
||
if (typeof Event === "undefined") { | ||
fail("Event does not exist"); | ||
} | ||
|
||
{ | ||
// Should pass the proper stuff to the listener function | ||
const et = new EventTarget(); | ||
|
||
const event = new Event("test1"); | ||
|
||
et.addEventListener("test1", function (e) { | ||
if (e.type !== "test1") { | ||
fail(`Incorrect event type ${e.type}`); | ||
} | ||
if (e !== event) { | ||
fail("Event instance not passed to listener"); | ||
} | ||
if (this !== et) { | ||
fail(`context should be the EventTarget that dispatched`); | ||
} | ||
}); | ||
|
||
et.dispatchEvent(event); | ||
} | ||
|
||
{ | ||
// adding and removing event listeners should work | ||
|
||
let handlerCalls = 0; | ||
const handler = () => handlerCalls++; | ||
const listener = { | ||
handleEvent() { | ||
this.calls++; | ||
}, | ||
calls: 0 | ||
}; | ||
|
||
const et = new EventTarget(); | ||
|
||
et.addEventListener('registration', handler); | ||
et.addEventListener('registration', listener); | ||
|
||
et.dispatchEvent(new Event('registration')); | ||
et.dispatchEvent(new Event('registration')); | ||
|
||
if (listener.calls !== 2) { | ||
fail(`Expected 2 calls to listener.handleEvent, got ${listener.calls}.`); | ||
} | ||
if (handlerCalls !== 2) { | ||
fail(`Expected 2 calls to handler, got ${handlerCalls}.`); | ||
} | ||
|
||
et.removeEventListener('registration', handler); | ||
|
||
et.dispatchEvent(new Event('registration')); | ||
et.dispatchEvent(new Event('registration')); | ||
|
||
if (listener.calls !== 4) { | ||
fail(`Expected 4 calls to listener.handleEvent, got ${listener.calls}.`); | ||
} | ||
if (handlerCalls !== 2) { | ||
fail(`Expected 2 calls to handler, got ${handlerCalls}.`); | ||
} | ||
|
||
et.removeEventListener('registration', listener); | ||
et.dispatchEvent(new Event('registration')); | ||
et.dispatchEvent(new Event('registration')); | ||
|
||
if (listener.calls !== 4) { | ||
fail(`Expected 4 calls to listener.handleEvent, got ${listener.calls}.`); | ||
} | ||
if (handlerCalls !== 2) { | ||
fail(`Expected 2 calls to handler, got ${handlerCalls}.`); | ||
} | ||
} | ||
|
||
{ | ||
// Registering the same handler more than once should be idempotent | ||
const et = new EventTarget(); | ||
|
||
let handlerCalls = 0; | ||
const handler = () => { | ||
handlerCalls++; | ||
}; | ||
|
||
et.addEventListener('idem', handler); | ||
et.addEventListener('idem', handler, { once: true }); | ||
et.addEventListener('idem', handler); | ||
et.addEventListener('idem', handler); | ||
|
||
et.dispatchEvent(new Event('idem')); | ||
|
||
if (handlerCalls !== 1) { | ||
fail(`Expected handler to have been called once. Was called ${handlerCalls} times`); | ||
} | ||
} | ||
|
||
|
||
{ | ||
// Should handle registration of listeners that only fire once | ||
// using the options argument | ||
const et = new EventTarget(); | ||
|
||
let calls = 0; | ||
et.addEventListener( | ||
"testOnce", | ||
function () { | ||
calls++; | ||
}, | ||
{ once: true } | ||
); | ||
|
||
et.dispatchEvent(new Event("testOnce")); | ||
et.dispatchEvent(new Event("testOnce")); | ||
et.dispatchEvent(new Event("testOnce")); | ||
|
||
if (calls !== 1) { | ||
fail(`Registering once did not work. Expected 1 call, got ${calls}.`); | ||
} | ||
} | ||
|
||
{ | ||
// addEventListener Should not throw if boolean is passed as the third argument | ||
const et = new EventTarget(); | ||
|
||
et.addEventListener('test', function () { }, true); | ||
} | ||
|
||
{ | ||
// Should handle registration of listener objects. | ||
const et = new EventTarget(); | ||
|
||
const listener = { | ||
handleEvent() { | ||
if (this !== listener) { | ||
fail("Expected context to be the listener object itself"); | ||
} | ||
this.calls++; | ||
}, | ||
calls: 0, | ||
}; | ||
|
||
et.addEventListener("listenerObject", listener); | ||
|
||
et.dispatchEvent(new Event("listenerObject")); | ||
et.dispatchEvent(new Event("listenerObject")); | ||
et.dispatchEvent(new Event("listenerObject")); | ||
|
||
if (listener.calls !== 3) { | ||
fail( | ||
`handleEvent should have been called 3 times, called ${listener.calls} times.` | ||
); | ||
} | ||
} | ||
|
||
{ | ||
// dispatchEvent should return true | ||
const et = new EventTarget(); | ||
const defaultNotPrevented = et.dispatchEvent(new Event("test")); | ||
|
||
if (defaultNotPrevented !== true) { | ||
fail( | ||
"basic EventTarget does not have cancellable events, so dispatchEvent should always return true" | ||
); | ||
} | ||
} | ||
|
||
{ | ||
// Events should be dispatched synchronous | ||
const et = new EventTarget(); | ||
|
||
const order = []; | ||
let n = 0; | ||
et.addEventListener("sync", () => { | ||
order.push(n++, "event"); | ||
}); | ||
|
||
order.push(n++, "start"); | ||
et.dispatchEvent(new Event("sync")); | ||
et.dispatchEvent(new Event("sync")); | ||
et.dispatchEvent(new Event("sync")); | ||
order.push(n++, "end"); | ||
|
||
if ( | ||
order.join(",") !== | ||
[0, "start", 1, "event", 2, "event", 3, "event", 4, "end"].join(",") | ||
) { | ||
fail("Events not triggered synchronously"); | ||
} | ||
} | ||
|
||
// Kill the node process with a failure message if a test | ||
// is failing. | ||
function fail(reason) { | ||
console.error(reason); | ||
process.exit(1); | ||
} | ||
|
||
// We've reached the end of this test script | ||
console.log('All tests pass'); |