Getting an array of all DOM events possible
Asked Answered
P

4

13

I've created a multi purpose factory event emitter factory function. With it I can turn objects into event emitters. The code for the event emitter factory is below if anyone would like to have a look or use it.

My question is how can I get a list of events from the DOM. Please note I'm not trying to get a list of bound events. I want a list of all events possible. I want to add a "pipe" method to emitters. This method would take a DOM object and bind to all possible events, then when any of those events fire each would trigger an event of the same name in the emitter.

I don't imagine there is a way to do this. I'm prepared to make a hard coded array of event names, but if I can get the array for the DOM instead that would be much better and would still work if the W3C standardizes more event types.

P.S. If you work for the W3C this is the kind of crap that makes everyone hate the DOM. Please stop treating JavaScript like a toy language. It is not a toy language and needs more than your toy DOM.

/**
 * Creates a event emitter
 */
function EventEmitter() {
    var api, callbacks;

    //vars
    api = {
        "on": on,
        "trigger": trigger
    };
    callbacks = {};

    //return the api
    return api;

    /**
     * Binds functions to events
     * @param event
     * @param callback
     */
    function on(event, callback) {
        var api;

        if(typeof event !== 'string') { throw new Error('Cannot bind to event emitter. The passed event is not a string.'); }
        if(typeof callback !== 'function') { throw new Error('Cannot bind to event emitter. The passed callback is not a function.'); }

        //return the api
        api = {
            "clear": clear
        };

        //create the event namespace if it doesn't exist
        if(!callbacks[event]) { callbacks[event] = []; }

        //save the callback
        callbacks[event].push(callback);

        //return the api
        return api;

        function clear() {
            var i;
            if(callbacks[event]) {
                i = callbacks[event].indexOf(callback);
                callbacks[event].splice(i, 1);

                if(callbacks[event].length < 1) {
                    delete callbacks[event];
                }

                return true;
            }
            return false;
        }
    }

    /**
     * Triggers a given event and optionally passes its handlers all additional parameters
     * @param event
     */
    function trigger(event    ) {
        var args;

        if(typeof event !== 'string' && !Array.isArray(event)) { throw new Error('Cannot bind to event emitter. The passed event is not a string or an array.'); }

        //get the arguments
        args = Array.prototype.slice.apply(arguments).splice(1);

        //handle event arrays
        if(Array.isArray(event)) {

            //for each event in the event array self invoke passing the arguments array
            event.forEach(function(event) {

                //add the event name to the begining of the arguments array
                args.unshift(event);

                //trigger the event
                trigger.apply(this, args);

                //shift off the event name
                args.shift();

            });

            return;
        }

        //if the event has callbacks then execute them
        if(callbacks[event]) {

            //fire the callbacks
            callbacks[event].forEach(function(callback) { callback.apply(this, args); });
        }
    }
}
Periosteum answered 20/2, 2012 at 21:17 Comment(2)
I've asked a similar question: Is it possible to programmatically catch all events on the page in the browser?.Cyder
This sounds a lot like my old Discrete Event Model from the past century. I had to lay it off because of Firefox & Co dirty war on IE soon after 2K because to dynamically build a list of all events supported was only possible on MSIE's.I was using "at" as opposed to built-in "on" to distinguish the event models. Discrete mostly because it doesn't bubble...Seismic
L
15

Here is a version that works in Chrome, Safari and FF.

Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).filter(function(i){return !i.indexOf('on')&&(document[i]==null||typeof document[i]=='function');})

UPD 1:

And here is the version that works in IE9+, Chrome, Safari and FF.

Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function(i){return !i.indexOf('on')&&(document[i]==null||typeof document[i]=='function');}).filter(function(elem, pos, self){return self.indexOf(elem) == pos;})

UPD 2:

And here's a version using newer JavaScript features (the [...new Set(...)] is to filter out duplicates, replacing the filter approach).

[...new Set([
 ...Object.getOwnPropertyNames(document),
 ...Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document))),
 ...Object.getOwnPropertyNames(Object.getPrototypeOf(window)),
].filter(k => k.startsWith("on") && (document[k] == null || typeof document[k] == "function")))];

PS: the result is an array of events name like ["onwebkitpointerlockerror", "onwebkitpointerlockchange", "onwebkitfullscreenerror", "onwebkitfullscreenchange", "onselectionchange", "onselectstart", "onsearch", "onreset", "onpaste", "onbeforepaste", "oncopy"] ... ect.

Locally answered 11/9, 2013 at 21:55 Comment(1)
and here is a version that works in them all var eventList = []; for( var x in this )if( /\bon/.test(x) )eventList.push(x); "this" ie window can normally be substituted with document or any other element of interest.Seismic
S
6

All DOM events start with on. You can loop through any Element instance, and list all properties which start with on.

Example. Copy-paste the following code in the console (Firefox, using Array comprehensions ;)):

[i for(i in document)].filter(function(i){return i.substring(0,2)=='on'&&(document[i]==null||typeof document[i]=='function');})

Another method to get the events is by looking at the specification, which reveals:

  // event handler IDL attributes
  [TreatNonCallableAsNull] attribute Function? onabort;
  [TreatNonCallableAsNull] attribute Function? onblur;
  [TreatNonCallableAsNull] attribute Function? oncanplay;
  [TreatNonCallableAsNull] attribute Function? oncanplaythrough;
  [TreatNonCallableAsNull] attribute Function? onchange;
  [TreatNonCallableAsNull] attribute Function? onclick;
  [TreatNonCallableAsNull] attribute Function? oncontextmenu;
  [TreatNonCallableAsNull] attribute Function? oncuechange;
  [TreatNonCallableAsNull] attribute Function? ondblclick;
  [TreatNonCallableAsNull] attribute Function? ondrag;
  [TreatNonCallableAsNull] attribute Function? ondragend;
  [TreatNonCallableAsNull] attribute Function? ondragenter;
  [TreatNonCallableAsNull] attribute Function? ondragleave;
  [TreatNonCallableAsNull] attribute Function? ondragover;
  [TreatNonCallableAsNull] attribute Function? ondragstart;
  [TreatNonCallableAsNull] attribute Function? ondrop;
  [TreatNonCallableAsNull] attribute Function? ondurationchange;
  [TreatNonCallableAsNull] attribute Function? onemptied;
  [TreatNonCallableAsNull] attribute Function? onended;
  [TreatNonCallableAsNull] attribute Function? onerror;
  [TreatNonCallableAsNull] attribute Function? onfocus;
  [TreatNonCallableAsNull] attribute Function? oninput;
  [TreatNonCallableAsNull] attribute Function? oninvalid;
  [TreatNonCallableAsNull] attribute Function? onkeydown;
  [TreatNonCallableAsNull] attribute Function? onkeypress;
  [TreatNonCallableAsNull] attribute Function? onkeyup;
  [TreatNonCallableAsNull] attribute Function? onload;
  [TreatNonCallableAsNull] attribute Function? onloadeddata;
  [TreatNonCallableAsNull] attribute Function? onloadedmetadata;
  [TreatNonCallableAsNull] attribute Function? onloadstart;
  [TreatNonCallableAsNull] attribute Function? onmousedown;
  [TreatNonCallableAsNull] attribute Function? onmousemove;
  [TreatNonCallableAsNull] attribute Function? onmouseout;
  [TreatNonCallableAsNull] attribute Function? onmouseover;
  [TreatNonCallableAsNull] attribute Function? onmouseup;
  [TreatNonCallableAsNull] attribute Function? onmousewheel;
  [TreatNonCallableAsNull] attribute Function? onpause;
  [TreatNonCallableAsNull] attribute Function? onplay;
  [TreatNonCallableAsNull] attribute Function? onplaying;
  [TreatNonCallableAsNull] attribute Function? onprogress;
  [TreatNonCallableAsNull] attribute Function? onratechange;
  [TreatNonCallableAsNull] attribute Function? onreset;
  [TreatNonCallableAsNull] attribute Function? onscroll;
  [TreatNonCallableAsNull] attribute Function? onseeked;
  [TreatNonCallableAsNull] attribute Function? onseeking;
  [TreatNonCallableAsNull] attribute Function? onselect;
  [TreatNonCallableAsNull] attribute Function? onshow;
  [TreatNonCallableAsNull] attribute Function? onstalled;
  [TreatNonCallableAsNull] attribute Function? onsubmit;
  [TreatNonCallableAsNull] attribute Function? onsuspend;
  [TreatNonCallableAsNull] attribute Function? ontimeupdate;
  [TreatNonCallableAsNull] attribute Function? onvolumechange;
  [TreatNonCallableAsNull] attribute Function? onwaiting;

  // special event handler IDL attributes that only apply to Document objects
  [TreatNonCallableAsNull,LenientThis] attribute Function? onreadystatechange;
Stheno answered 20/2, 2012 at 23:11 Comment(10)
Note. The previous list of events is not complete. For example, the <body> element also defines a set of events. Simply search for [TreatNonCallableAsNull] attribute Function? on in the spec to find all (HTML5) events.Stheno
I've tried looping through the DOM level 0 events already and they do not show up in a for in loop because on* methods are non enumerable when left as null. Please note that I'm not trying to capture existing bindings. I'm trying to get a dynamic list of possible bindings.Periosteum
@RobertHurst In conformant (modern) browsers, all events are enumerable. When they're not defined yet, they have to be null, per definition. Since the events are known beforehand, I recommend to create a list of events, and implement it. That's much more efficient than looping through the properties of many elements, and filtering the property names.Stheno
Chrome does not agree. for in loops skip all the onevent properties. It looks like I will have to make a hard coded array after all. I don't want to though because I don't want to have to update the array if a new event type is introduced by new standards later.Periosteum
@RobertHurst New events are not added very often. The reliability and efficiency of a hard-coded list outweights the possibility of a new event (at least, to me). Another advantage is that it's easier to debug your code (different lists for all browsers (versions) are making the job tougher).Stheno
I completely disagree. Hard-coding the list means I have to maintain it. If I can ask the DOM then any issues with missing events is not my libraries fault, Its the DOM's since I got the list from it. Its 1:1 with the DOM's events and doesn't require any extra code.Periosteum
@RobertHurst When all browsers return a full list, then that's a huge +1 for the first method. However, this is not the case. Therefore, it will cause headaches when handling bug reports, because you don't know which events are captured. A full list of events can easily be extracted from the specification, MDN: DOM Event reference or even Wikipedia: DOM Events. New events aren't added every day, so don't fear having to update your list of events.Stheno
What is [i for(i in document)]? When did JavaScript become Python?Detrude
@Rocket It's an array comprehension, available since JavaScript 1.7 in Firefox 2. It's also on the proposal list of ES-Harmony.Stheno
@RobW well in any case I got around it. Checkout my answer.Periosteum
S
0

This is how I used to get the dynamic eventList in the past century when building for IE was the equivalent of building for 87% to 92% of the world. It was a oneliner such as :: eventList = []; for( var x in this )if( x.match(/\bon/) )eventList.push(x); and I just tested it on my win 7 up to date Opera, IE11, a pretty old Chrome, and about 2 years old Firefox... and damn (!) - worked like a charm.

var eventList = [];

for( var x in this )if( /\bon/.test(x) )eventList.push(x),
console.info( x );

console.info( eventList.length );
Seismic answered 29/7, 2020 at 23:31 Comment(1)
for those wondering where are the other 50+ events missing - they are being printed logged in the SO console! (?! Promises).Seismic
D
0

You can't have an exhaustive list, for one because we can fire synthetic events with any name.

For instance:

// Since we can even make it bubble
// overriding dispatchEvent wouldn't do either.
// Here we listen on document
document.addEventListener("foo", evt => console.log("foo fired"));
const event = new Event("foo", { bubbles: true });
// And we fire on <body>
document.body.dispatchEvent( event );

Even having a list of all "built-in" events is near impossible. Many of these events don't have an .onevent IDL attribute exposed anywhere, like Document's or Window's DOMContentLoaded, or inputs Elements compositionXXX or many others hidden in various specs.

// won't fire
window.onDOMContentLoaded = evt => console.log('caught through onDOMContentLoaded');
// will fire
window.addEventListener('DOMContentLoaded', evt => console.log('caught through addEventListener'));

Even catching all these onevent IDL attributes would require to walk through all the Constructor's prototypes since window only exposes some of these.

console.log("onaddtrack available in window?", "onaddtrack" in window);
console.log("onaddtrack available in MediaStream's proto?", "onaddtrack" in MediaStream.prototype);

And while we can find quite big lists of such events online, since the specs are changing continuously and the browsers do not support everything from the specs, or at the contrary supporting features that are not in the specs, no such list will catch them all.

(For instance on this last point, the dragexit event is currently being removed from the specs, when only Firefox does support it.)

Daughterly answered 13/8, 2020 at 6:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.