924
votes

I have a page where some event listeners are attached to input boxes and select boxes. Is there a way to find out which event listeners are observing a particular DOM node and for what event?

Events are attached using:

  1. Prototype's Event.observe;
  2. DOM's addEventListener;
  3. As element attribute element.onclick.
19
How are the events attached in the first place? Are you using a library (eg Prototype, jQuery, etc)?Crescent Fresh
It's important to note that multiple callback functions can be attached for the same type of event via element.addEventListener(type, callback, [bubble]), while element.onclick = function will overwrite every time you assign.Braden Best
Gets all events alongside their respective element: Array.from(document.querySelectorAll("*")).forEach(e => { const ev = getEventListeners(e); if (Object.keys(ev).length !== 0) {console.log(e, ev)} })Gabriel Petersson
You can hijack the addEventListener and capture a list of all listeners in the DOM element. Here is sample code stackoverflow.com/a/64484951/1812732John Henckel

19 Answers

516
votes

If you just need to inspect what's happening on a page, you might try the Visual Event bookmarklet.

Update: Visual Event 2 available.

471
votes

Chrome, Firefox, Vivaldi and Safari support getEventListeners(domElement) in their Developer Tools console.

For majority of the debugging purposes, this could be used.

Below is a very good reference to use it: https://developers.google.com/web/tools/chrome-devtools/console/utilities#geteventlisteners

383
votes

It depends on how the events are attached. For illustration presume we have the following click handler:

var handler = function() { alert('clicked!') };

We're going to attach it to our element using different methods, some which allow inspection and some that don't.

Method A) single event handler

element.onclick = handler;
// inspect
console.log(element.onclick); // "function() { alert('clicked!') }"

Method B) multiple event handlers

if(element.addEventListener) { // DOM standard
    element.addEventListener('click', handler, false)
} else if(element.attachEvent) { // IE
    element.attachEvent('onclick', handler)
}
// cannot inspect element to find handlers

Method C): jQuery

$(element).click(handler);
  • 1.3.x

     // inspect
     var clickEvents = $(element).data("events").click;
     jQuery.each(clickEvents, function(key, value) {
         console.log(value) // "function() { alert('clicked!') }"
     })
    
  • 1.4.x (stores the handler inside an object)

     // inspect
     var clickEvents = $(element).data("events").click;
     jQuery.each(clickEvents, function(key, handlerObj) {
         console.log(handlerObj.handler) // "function() { alert('clicked!') }"
         // also available: handlerObj.type, handlerObj.namespace
     })
    
  • 1.7+ (very nice)

    Made using knowledge from this comment.

     events = $._data(this, 'events');
     for (type in events) {
       events[type].forEach(function (event) {
         console.log(event['handler']);
       });
     }
    

(See jQuery.fn.data and jQuery.data)

Method D): Prototype (messy)

$(element).observe('click', handler);
  • 1.5.x

     // inspect
     Event.observers.each(function(item) {
         if(item[0] == element) {
             console.log(item[2]) // "function() { alert('clicked!') }"
         }
     })
    
  • 1.6 to 1.6.0.3, inclusive (got very difficult here)

     // inspect. "_eventId" is for < 1.6.0.3 while 
     // "_prototypeEventID" was introduced in 1.6.0.3
     var clickEvents = Event.cache[element._eventId || (element._prototypeEventID || [])[0]].click;
     clickEvents.each(function(wrapper){
         console.log(wrapper.handler) // "function() { alert('clicked!') }"
     })
    
  • 1.6.1 (little better)

     // inspect
     var clickEvents = element.getStorage().get('prototype_event_registry').get('click');
     clickEvents.each(function(wrapper){
         console.log(wrapper.handler) // "function() { alert('clicked!') }"
     })
    

When clicking the resulting output in the console (which shows the text of the function), the console will navigate directly to the line of the function's declaration in the relevant JS file.

95
votes

WebKit Inspector in Chrome or Safari browsers now does this. It will display the event listeners for a DOM element when you select it in the Elements pane.

75
votes

It is possible to list all event listeners in JavaScript: It's not that hard; you just have to hack the prototype's method of the HTML elements (before adding the listeners).

function reportIn(e){
    var a = this.lastListenerInfo[this.lastListenerInfo.length-1];
    console.log(a)
}


HTMLAnchorElement.prototype.realAddEventListener = HTMLAnchorElement.prototype.addEventListener;

HTMLAnchorElement.prototype.addEventListener = function(a,b,c){
    this.realAddEventListener(a,reportIn,c); 
    this.realAddEventListener(a,b,c); 
    if(!this.lastListenerInfo){  this.lastListenerInfo = new Array()};
    this.lastListenerInfo.push({a : a, b : b , c : c});
};

Now every anchor element (a) will have a lastListenerInfo property wich contains all of its listeners. And it even works for removing listeners with anonymous functions.

57
votes

Use getEventListeners in Google Chrome:

getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
43
votes

(Rewriting the answer from this question since it's relevant here.)

When debugging, if you just want to see the events, I recommend either...

  1. Visual Event
  2. The Elements section of Chrome's Developer Tools: select an element and look for "Event Listeners" on the bottom right (similar in Firefox)

If you want to use the events in your code, and you are using jQuery before version 1.8, you can use:

$(selector).data("events")

to get the events. As of version 1.8, using .data("events") is discontinued (see this bug ticket). You can use:

$._data(element, "events")

Another example: Write all click events on a certain link to the console:

var $myLink = $('a.myClass');
console.log($._data($myLink[0], "events").click);

(see http://jsfiddle.net/HmsQC/ for a working example)

Unfortunately, using $._data this is not recommended except for debugging since it is an internal jQuery structure, and could change in future releases. Unfortunately I know of no other easy means of accessing the events.

31
votes

1: Prototype.observe uses Element.addEventListener (see the source code)

2: You can override Element.addEventListener to remember the added listeners (handy property EventListenerList was removed from DOM3 spec proposal). Run this code before any event is attached:

(function() {
  Element.prototype._addEventListener = Element.prototype.addEventListener;
  Element.prototype.addEventListener = function(a,b,c) {
    this._addEventListener(a,b,c);
    if(!this.eventListenerList) this.eventListenerList = {};
    if(!this.eventListenerList[a]) this.eventListenerList[a] = [];
    this.eventListenerList[a].push(b);
  };
})();

Read all the events by:

var clicks = someElement.eventListenerList.click;
if(clicks) clicks.forEach(function(f) {
  alert("I listen to this function: "+f.toString());
});

And don't forget to override Element.removeEventListener to remove the event from the custom Element.eventListenerList.

3: the Element.onclick property needs special care here:

if(someElement.onclick)
  alert("I also listen tho this: "+someElement.onclick.toString());

4: don't forget the Element.onclick content attribute: these are two different things:

someElement.onclick = someHandler; // IDL attribute
someElement.setAttribute("onclick","otherHandler(event)"); // content attribute

So you need to handle it, too:

var click = someElement.getAttribute("onclick");
if(click) alert("I even listen to this: "+click);

The Visual Event bookmarklet (mentioned in the most popular answer) only steals the custom library handler cache:

It turns out that there is no standard method provided by the W3C recommended DOM interface to find out what event listeners are attached to a particular element. While this may appear to be an oversight, there was a proposal to include a property called eventListenerList to the level 3 DOM specification, but was unfortunately been removed in later drafts. As such we are forced to looked at the individual Javascript libraries, which typically maintain a cache of attached events (so they can later be removed and perform other useful abstractions).

As such, in order for Visual Event to show events, it must be able to parse the event information out of a Javascript library.

Element overriding may be questionable (i.e. because there are some DOM specific features like live collections, which can not be coded in JS), but it gives the eventListenerList support natively and it works in Chrome, Firefox and Opera (doesn't work in IE7).

24
votes

You could wrap the native DOM methods for managing event listeners by putting this at the top of your <head>:

<script>
    (function(w){
        var originalAdd = w.addEventListener;
        w.addEventListener = function(){
            // add your own stuff here to debug
            return originalAdd.apply(this, arguments);
        };

        var originalRemove = w.removeEventListener;
        w.removeEventListener = function(){
            // add your own stuff here to debug
            return originalRemove.apply(this, arguments);
        };
    })(window);
</script>

H/T @les2

18
votes

The Firefox developer tools now does this. Events are shown by clicking the "ev" button on the right of each element's display, including jQuery and DOM events.

Screenshot of Firefox developer tools' event listener button in the inspector tab

12
votes

If you have Firebug, you can use console.dir(object or array) to print a nice tree in the console log of any JavaScript scalar, array, or object.

Try:

console.dir(clickEvents);

or

console.dir(window);
12
votes

Fully working solution based on answer by Jan Turon - behaves like getEventListeners() from console:

(There is a little bug with duplicates. It doesn't break much anyway.)

(function() {
  Element.prototype._addEventListener = Element.prototype.addEventListener;
  Element.prototype.addEventListener = function(a,b,c) {
    if(c==undefined)
      c=false;
    this._addEventListener(a,b,c);
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(!this.eventListenerList[a])
      this.eventListenerList[a] = [];
    //this.removeEventListener(a,b,c); // TODO - handle duplicates..
    this.eventListenerList[a].push({listener:b,useCapture:c});
  };

  Element.prototype.getEventListeners = function(a){
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(a==undefined)
      return this.eventListenerList;
    return this.eventListenerList[a];
  };
  Element.prototype.clearEventListeners = function(a){
    if(!this.eventListenerList)
      this.eventListenerList = {};
    if(a==undefined){
      for(var x in (this.getEventListeners())) this.clearEventListeners(x);
        return;
    }
    var el = this.getEventListeners(a);
    if(el==undefined)
      return;
    for(var i = el.length - 1; i >= 0; --i) {
      var ev = el[i];
      this.removeEventListener(a, ev.listener, ev.useCapture);
    }
  };

  Element.prototype._removeEventListener = Element.prototype.removeEventListener;
  Element.prototype.removeEventListener = function(a,b,c) {
    if(c==undefined)
      c=false;
    this._removeEventListener(a,b,c);
      if(!this.eventListenerList)
        this.eventListenerList = {};
      if(!this.eventListenerList[a])
        this.eventListenerList[a] = [];

      // Find the event in the list
      for(var i=0;i<this.eventListenerList[a].length;i++){
          if(this.eventListenerList[a][i].listener==b, this.eventListenerList[a][i].useCapture==c){ // Hmm..
              this.eventListenerList[a].splice(i, 1);
              break;
          }
      }
    if(this.eventListenerList[a].length==0)
      delete this.eventListenerList[a];
  };
})();

Usage:

someElement.getEventListeners([name]) - return list of event listeners, if name is set return array of listeners for that event

someElement.clearEventListeners([name]) - remove all event listeners, if name is set only remove listeners for that event

12
votes

To get all eventListeners on a page printed alongside their elements

Array.from(document.querySelectorAll("*")).forEach(e => {
    const ev = getEventListeners(e)
    if (Object.keys(ev).length !== 0) console.log(e, ev)
})
8
votes

Opera 12 (not the latest Chrome Webkit engine based) Dragonfly has had this for a while and is obviously displayed in the DOM structure. In my opinion it is a superior debugger and is the only reason remaining why I still use the Opera 12 based version (there is no v13, v14 version and the v15 Webkit based lacks Dragonfly still)

enter image description here

4
votes

Prototype 1.7.1 way

function get_element_registry(element) {
    var cache = Event.cache;
    if(element === window) return 0;
    if(typeof element._prototypeUID === 'undefined') {
        element._prototypeUID = Element.Storage.UID++;
    }
    var uid =  element._prototypeUID;           
    if(!cache[uid]) cache[uid] = {element: element};
    return cache[uid];
}
4
votes

I was recently working with events and wanted to view/control all events in a page. Having looked at possible solutions, I've decided to go my own way and create a custom system to monitor events. So, I did three things.

First, I needed a container for all the event listeners in the page: that's theEventListeners object. It has three useful methods: add(), remove(), and get().

Next, I created an EventListener object to hold the necessary information for the event, i.e.: target, type, callback, options, useCapture, wantsUntrusted, and added a method remove() to remove the listener.

Lastly, I extended the native addEventListener() and removeEventListener() methods to make them work with the objects I've created (EventListener and EventListeners).

Usage:

var bodyClickEvent = document.body.addEventListener("click", function () {
    console.log("body click");
});

// bodyClickEvent.remove();

addEventListener() creates an EventListener object, adds it to EventListeners and returns the EventListener object, so it can be removed later.

EventListeners.get() can be used to view the listeners in the page. It accepts an EventTarget or a string (event type).

// EventListeners.get(document.body);
// EventListeners.get("click");

Demo

Let's say we want to know every event listener in this current page. We can do that (assuming you're using a script manager extension, Tampermonkey in this case). Following script does this:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @include      https://stackoverflow.com/*
// @grant        none
// ==/UserScript==

(function() {
    fetch("https://raw.githubusercontent.com/akinuri/js-lib/master/EventListener.js")
        .then(function (response) {
            return response.text();
        })
        .then(function (text) {
            eval(text);
            window.EventListeners = EventListeners;
        });
})(window);

And when we list all the listeners, it says there are 299 event listeners. There "seems" to be some duplicates, but I don't know if they're really duplicates. Not every event type is duplicated, so all those "duplicates" might be an individual listener.

screenshot of console listing all event listeners in this page

Code can be found at my repository. I didn't want to post it here because it's rather long.


Update: This doesn't seem to work with jQuery. When I examine the EventListener, I see that the callback is

function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}

I believe this belongs to jQuery, and is not the actual callback. jQuery stores the actual callback in the properties of the EventTarget:

$(document.body).click(function () {
    console.log("jquery click");
});

enter image description here

To remove an event listener, the actual callback needs to be passed to the removeEventListener() method. So in order to make this work with jQuery, it needs further modification. I might fix that in the future.

3
votes

I am trying to do that in jQuery 2.1, and with the "$().click() -> $(element).data("events").click;" method it doesn't work.

I realized that only the $._data() functions works in my case :

	$(document).ready(function(){

		var node = $('body');
		
        // Bind 3 events to body click
		node.click(function(e) { alert('hello');  })
			.click(function(e) { alert('bye');  })
			.click(fun_1);

        // Inspect the events of body
		var events = $._data(node[0], "events").click;
		var ev1 = events[0].handler // -> function(e) { alert('hello')
		var ev2 = events[1].handler // -> function(e) { alert('bye')
		var ev3 = events[2].handler // -> function fun_1()
        
		$('body')
			.append('<p> Event1 = ' + eval(ev1).toString() + '</p>')
			.append('<p> Event2 = ' + eval(ev2).toString() + '</p>')
			.append('<p> Event3 = ' + eval(ev3).toString() + '</p>');        
	
	});

	function fun_1() {
		var txt = 'text del missatge';	 
		alert(txt);
	}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
</body>
3
votes
1
votes

changing these functions will allow you to log the listeners added:

EventTarget.prototype.addEventListener
EventTarget.prototype.attachEvent
EventTarget.prototype.removeEventListener
EventTarget.prototype.detachEvent

read the rest of the listeners with

console.log(someElement.onclick);
console.log(someElement.getAttribute("onclick"));