[idea] Make EventTarget API re-usable on non-DOM elements


#1

If we are to encourage the web to have useful patterns, we should make those patterns re-usable. Currently, people always make a new event pattern because the existing event pattern (in browsers) is always limited only to DOM elements, and not all JavaScript code is tied strictly to DOM (in the browser, we’re not even talking about Node).

For example, the following doesn’t work, but it should:

class Foo extends window.EventTarget {
    constructor() {
        super()
        console.log(this)
    }
}

new Foo

It throws an error:

Uncaught TypeError: Illegal constructor

It would be great to be able to use .addEventListener() et al with other things beside just DOM elements.


#2

I think this is a good idea. Right now everyone’s reinventing the wheel.


#3

While I like this idea, as an FYI, some folks have made some libraries to let you do this with the same API as EventTarget. My latest fork/branch at https://github.com/brettz9/eventdispatcher.js/tree/early-late-listeners (besides adding hasEventListener) also aims to add addEarlyEventListener and addLateEventListener and addDefaultEventListener (work not yet finished), to allow internal code to ensure their events fire before or after the standard methods (and such that a default can be added or prevented). (My fork also adds support for on* type handlers, and in the spec’d execution order.) Other custom methods might be added to allow obtaining a child or parent so that capturing/bubbling and stopping of propagation or preventing default behaviors could be implemented in a familiar way.


#4

You may find the discussion we had in the context of the Generic Sensor API weighting in various options (EventTarget, Observable, EventEmitter) interesting:

Rather long discussion, but a good read IMO.


#5

This isn’t true - EventTarget is used on lots of things that aren’t DOM elements. Here’s the current list of interfaces that implement EventTarget in blink (most of which do not implement Node):

FileReader IDBRequest IDBDatabase IDBTransaction Node TestInterfaceEventTarget TestInterfaceGarbageCollected Animation FontFaceSet MediaQueryList MessagePort Node FileReader VisualViewport Window AudioTrackList TextTrack TextTrackCue TextTrackList VideoTrackList ApplicationCache EventSource Performance WorkerPerformance SharedWorker Worker WorkerGlobalScope XMLHttpRequestEventTarget BatteryManager BluetoothDevice BluetoothRemoteGATTCharacteristic BroadcastChannel CompositorWorker MediaKeySession FileWriter IDBDatabase IDBRequest IDBTransaction MediaRecorder MediaSource SourceBuffer SourceBufferList MediaDevices MediaStream MediaStreamTrack NetworkInformation Notification PaymentRequest RTCDTMFSender RTCDataChannel RTCPeerConnection PermissionStatus PresentationAvailability PresentationConnection PresentationReceiver PresentationRequest RemotePlayback RemotePlaybackAvailability ScreenOrientation Sensor ServiceWorker ServiceWorkerContainer ServiceWorkerRegistration SpeechRecognition SpeechSynthesis SpeechSynthesisUtterance AudioContext AudioNode MIDIAccess MIDIPort WebSocket USB

But I agree with the larger issue here - the only objects that can take advantage of the DOM event machinery are built-in ones. Not very extensible-web friendly (forces you to create a dummy Node instance or something to use the same machinery). WebIDL doesn’t even have the language for this, but I think work in Houdini is helping (the specs describe the JS interface directly).


#6

The initial release is now ready on that branch (I’ve made a PR to the original repo I forked, but if I don’t hear back, I might publish to my own repo). Support exists now for capturing, bubbling, stopPropagation, preventDefault, once, defaultPrevented, passive, etc., with a polyfill Event class as well (given that the readonly nature of events makes it necessary to create our own events so we can set our own target, currentTarget, etc.).


#7

Definitely agree. I’ve started a repo in attempt to allow this functionality, but its very bare minimum at the moment.