20

I'd like to accomplish the following code using a wildcard (that I don't think exists?)

myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.click);
myObject.element.addEventListener('mouseover', myObject.mouseover);
//etc..

So far, I have the following

myObject.controller = function(e){
   if( (e.type in myObject) && (typeof myObject[e.type] ==='function') ){
      myObject[e.type](e);
   }
};

//but the listeners still have to be assigned as such
myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.controller);
myObject.element.addEventListener('mouseover', myObject.controller);
//etc...


// but I want to do (and it doesn't work)
myObject.element.addEventListener('*', myObject.controller);

Any suggestions on methods other than an array of events and a foreach statement?

Edit, my current solution

I've accepted an answer below (there's no wildcard available, and it's a bad idea to parse potentially hundreds of events)

For those looking for a similar function, I've settled on the following approach, at least for now.

for(var prop in myObject){
  if (!myObject.hasOwnProperty(prop){continue;}
  if (typeof myObject[prop] !== 'function'){continue;}
  myObject.element.addEventListener(prop, myObject[prop]);
}

The upside is a handler for custom events that I don't have to go back and add listeners for. The downside is that I have to ensure this function is called after every myObject.someEvent() is defined. I call it in myObject.init(); which works for me just fine. Note that this solution wouldn't fit my previous specs, because it uses a for/each loop -- but it accomplishes what i really wanted to accomplish and a big thanks to @torazaburo for clearly defining the technical limitations and lack of wisdom in my initial plan.

Community
  • 1
  • 1
Ken Sherman
  • 279
  • 1
  • 2
  • 9

8 Answers8

12

How to implement addEventListener('*') yourself


For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.

for (const key in target) {
    if(/^on/.test(key)) {
        const eventType = key.substr(2);
        target.addEventListener(eventType, listener);
    }
}

The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^

const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
    if (!alreadyListenedEventTypes.has(event.type)) {
        target.addEventListener(event.type, listener, ...otherArguments);
        alreadyListenedEventTypes.add(event.type);
    }
    dispatchEvent_original.apply(this, arguments);
};

function snippet

function addEventListenerAll(target, listener, ...otherArguments) {

    // install listeners for all natively triggered events
    for (const key in target) {
        if (/^on/.test(key)) {
            const eventType = key.substr(2);
            target.addEventListener(eventType, listener, ...otherArguments);
        }
    }

    // dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
    const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
    function dispatchEvent(event) {
        target.addEventListener(event.type, listener, ...otherArguments);  // multiple identical listeners are automatically discarded
        dispatchEvent_original.apply(this, arguments);
    }
    EventTarget.prototype.dispatchEvent = dispatchEvent;
    if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);

}


// usage example
function addEventListenerAll(target, listener, ...otherArguments) {

    // install listeners for all natively triggered events
    for (const key in target) {
        if (/^on/.test(key)) {
            const eventType = key.substr(2);
            target.addEventListener(eventType, listener, ...otherArguments);
        }
    }

    // dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
    const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
    function dispatchEvent(event) {
        target.addEventListener(event.type, listener, ...otherArguments);  // multiple identical listeners are automatically discarded
        dispatchEvent_original.apply(this, arguments);
    }
    EventTarget.prototype.dispatchEvent = dispatchEvent;
    if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);

}


// usage example
addEventListenerAll(window, (evt) => {
    console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));


// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
    window,
    (evt) => { console.log(evt.type); },
    true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
pitizzzle
  • 303
  • 3
  • 8
7

A little late to the party, but here's how I add all event listeners & log them to the console:

Object.keys(window).forEach(key => {
    if(/./.test(key)){
        window.addEventListener(key.slice(2), event => {
            console.log(key, event)
        })
    }
})
Revircs
  • 1,312
  • 3
  • 12
  • 23
  • 1
    Shouldn't the test be `/^on/` ? (starts with "on") – Sam Hasler Oct 08 '20 at 10:44
  • @SamHasler You are correct. It's been a while, but I believe I was using this with an event emitter at some point, so I just removed that part. – Revircs Dec 30 '20 at 19:10
  • 1
    An interesting thing I just noticed: Even without the /^on/, it doesn't seem to fire for GamePad events... – Revircs Dec 30 '20 at 19:33
6

There's no wild card, but using jQuery you can have 1 long event listener line, rather than multiple.

How do you log all events fired by an element in jQuery?

Like so:

('body').on("click mousedown mouseup focus blur keydown change dblclick mousemove mouseover mouseout mousewheel keydown keyup keypress textInput touchstart touchmove touchend touchcancel resize scroll zoom select change submit reset",function(e){
     console.log(e);
}); 
Nick Veys
  • 23,458
  • 4
  • 47
  • 64
Don Cheadle
  • 5,224
  • 5
  • 39
  • 54
  • That's a great resource -- thanks mmcrae, but still not what I want. i.e., it doesn't handle custom events. I'm wondering if javascript doesn't allow for what I'm looking for. (Also, i'd rather not require jquery if it's not a full solution) – Ken Sherman Oct 10 '16 at 17:49
  • What do you mean doesn't handle custom events? Then just add it to the string in on `click mycustomevent mousedown`? – Don Cheadle Oct 10 '16 at 17:52
  • I can't edit my comment about not handling custom events. But you have to add each custom event manually. Again, I'm looking for a wildcard-type solution – Ken Sherman Oct 10 '16 at 17:53
  • Seems like an odd requirement to have something bound to every possible event... but idk. Anyway, googling `jquery wildcard event` led to stuff you should probably check out such as http://stackoverflow.com/questions/9735608/how-to-bind-to-all-custom-events-in-jquery – Don Cheadle Oct 10 '16 at 17:57
  • Missing input, paste – mplungjan Oct 03 '21 at 15:33
5

There is no feature to add a * listener, and I doubt if you want hundreds of events on an element being listened for anyway.

By the way, instead of your roll-your-own architecture for a generic event handler in the form of your myObject.controller, you should use the under-appreciated approach involving the EventListener interface and the handleEvent method it defines.

Machavity
  • 30,841
  • 27
  • 92
  • 100
  • @torazabura -- thanks. This is being accepted as the answer because while the other ones tried to solve it, they didn't meet my criteria. 1. no foreach, 2. all events – Ken Sherman Oct 10 '16 at 20:29
  • 2
    This does not work in chrome. I tried it using `document.body.addEventListener('*', function(e) { console.log('event', e); });` it didn't work in chrome or firefox. What did work was individual events. What did not work was multiple events. – MrMesees Sep 14 '19 at 22:09
  • 2
    @MrMesees If you read the entire comment, you'll notice that the code above was actually user663031 quoting the OP. – Gigi Bayte 2 Sep 27 '19 at 11:00
  • Then they should use a mechanism to show that such as blockquote and cite – MrMesees Sep 28 '19 at 00:20
3

I'm not a 100% if this is what you're looking for, but I think this is what you're looking for.

the document which is what we usually tag into for our events doesn't contain the events. It's actually the grandparent class.

document -> HTMLDocument -> Document (where you could find the onclick* events)

as you could see we can then do this. just trim the on at the start of the events

Object.keys(document.__proto__.__proto__).reduce((arr, event)=> {
  if(event.startsWith('on')) return [...arr, event.substr(2)];
  return arr;
}, [])

which would return something like:

["readystatechange", "pointerlockchange", "pointerlockerror", "beforecopy", "beforecut", "beforepaste", "freeze", "resume", "search", "securitypolicyviolation", "visibilitychange", "copy", "cut", "paste", "abort", "blur", "cancel", "canplay", "canplaythrough", "change", "click", "close", "contextmenu", "cuechange", "dblclick", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop", "durationchange", "emptied", "ended", "error", "focus", "formdata", "input", "invalid", "keydown", "keypress", "keyup", "load", "loadeddata", "loadedmetadata", "loadstart", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "mousewheel", "pause", "play", "playing", "progress", "ratechange", "reset", "resize", "scroll", "seeked", "seeking", "select", "stalled", "submit", "suspend", "timeupdate", "toggle", "volumechange", "waiting", "webkitanimationend", "webkitanimationiteration", "webkitanimationstart", "webkittransitionend", "wheel", "auxclick", "gotpointercapture", "lostpointercapture", "pointerdown", "pointermove", "pointerup", "pointercancel", "pointerover", "pointerout", "pointerenter", "pointerleave", "selectstart", "selectionchange", "animationend", "animationiteration", "animationstart", "transitionend", "fullscreenchange", "fullscreenerror", "webkitfullscreenchange", "webkitfullscreenerror", "pointerrawupdate"]

this is just something quick I came up with and I'm sure there are better answers from smarter folks. you might also want to check out https://developer.mozilla.org/en-US/docs/Web/Events which contains what you just might need.

  • Nice solution, although I prefer filter-map to reduce: `.filter((prop) => prop.startsWith('on')).map((prop) => prop.substr(2))` – Sam Hasler Oct 08 '20 at 10:52
2

To find all event listeners on the page

getEventListeners(document)

Reference

To list them a little nicer

for (const [key, value] of Object.entries(getEventListeners(document))) {
      console.log(`${key}`) 
}

I will leave it up to you to actually do something with each, but now you can at least see all that exist on the page, you can open DevTools and try it out on this page and see for yourself it does work.

jasonleonhard
  • 12,047
  • 89
  • 66
1

getElementsByClassName already returns an array (or more specifically, an HTMLCollection), not a single element. Just do it in a loop.

myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
    myObject.element[i].addEventListener('click', myObject.controller);
    myObject.element[i].addEventListener('mouseover', myObject.controller);
}

Edit... after clarification.. loops and arrays are still useful

var e = ['click', 'hover', 'focus', 'mouseover', 'mouseout'];
myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
    for(var n=0; n<e.length; n++)
        myObject.element[i].addEventListener(e[n], myObject.controller);
}
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
  • 1
    I believe you're missing the point of my question. It's not to add the same listeners to multiple objects. it's to add all listeners to a single object without listing every event listener – Ken Sherman Oct 10 '16 at 17:46
  • he's talking about adding many events to an element, not events to many elements – Don Cheadle Oct 10 '16 at 17:46
  • 1
    @pamblam thanks for the update -- but I did specify in the question that I don't want an array and foreach loop. that solution wouldn't handle custom events for instance – Ken Sherman Oct 10 '16 at 17:51
  • it will account for custom events if you add them to the loop. unfortunately i think this is your best bet. – I wrestled a bear once. Oct 10 '16 at 17:55
1

Something like this ?

        document.addEventListener('click', function (event) {

            if (event.target.closest('.scroll')) {
                // Do something...
            }

            if (event.target.matches('[data-some-attribute]')) {
                // Do something else...
            }

            if (event.target.matches('#my-form')) {
                // Do another t hing...
            }

        }, false)
Corentin
  • 11
  • 2