Does Disqus have public events to attach to? - javascript

I need to perform some re-calculations after disqus form gets an update. A new comment, error message just to name a few. In essence any event that causes the Disqus iframe to expand vertically. Checked the API, but didn't find any public events. Seems like the events are not publicly accessibly atm. So the first question is – does Disqus have any public events to attach to?
The second would be – if I have no way to attach to events from Disqus I wonder would MutationEvent do the trick for me taking into account that Disqus stuff is within an iFrame?

Best I have come up with so far
function disqus_config() {
this.callbacks.onNewComment = [function() { trackComment(); }];
}
from here:
http://help.disqus.com/customer/portal/articles/466258-how-can-i-capture-disqus-commenting-activity-in-my-own-analytics-tool-
Doing a console.log(DISQUS) in the chrome console shows the disqus object, and there are other callbacks mentioned
_callbacks: Object
switches.changed: Array[2]
window.click: Array[2]
window.hashchange: Array[2]
window.resize: Array[2]
window.scroll: Array[2]
and on and trigger methods

I'm not sure about public events for Disqus in particular, but if you just need to monitor changes to an iframe's height, here's one way:
var iframe = document.getElementById('myIframe');
var iframeHeight = iframe.clientHeight;
setInterval(function() {
if(iframe.clientHeight != iframeHeight) {
// My iframe's height has changed - do some stuff!
iframeHeight = iframe.clientHeight;
}
}, 1000);
Granted, it's basically a hack. But it should work!

Well, they don't have any public events documented (as far I can tell). But, application is triggering a lot of events on its parent window. So it's possible to listen to them and make some actions. You can do that with following snippet:
window.addEventListener('message', function (event) {
// if message is not from discus frame, leap out
if (event.origin != 'https://disqus.com' && event.origin != 'http://disqus.com') return;
// parse data
var data = JSON.parse(event.data);
// do stuff with data. type of action can be detected with data.name
// property ('ready', 'resize', 'fakeScroll', etc)
}, false);
In webkit based browsers it works just fine. With firefox there might be some issues. With IE... well, I don't have any IE on hand to test it.

You can find list of available events in embed payload:
callbacks:{
preData:[],
preInit:[],
onInit:[],
afterRender:[],
onReady:[],
onNewComment:[],
preReset:[],
onPaginate:[],
onIdentify:[],
beforeComment:[]
}
I haven't found any documentation (except example for onNewComment), so you need guess how they works from event name.
You can use them in this way:
var disqus_config = function () {
this.callbacks.onNewComment = [
function() {
// your code
}
];
};
or
var disqus_config = function () {
this.callbacks.onNewComment = this.callbacks.onNewComment || [];
this.callbacks.onNewComment.push(function() {
// your code
});
}
On the side note, I found them completely useless for detecting change in height of comments iframe. I ended with using ResizeSensor from css-element-queries.

Related

Is there any similar functions to getEventListeners() [duplicate]

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:
Prototype's Event.observe;
DOM's addEventListener;
As element attribute element.onclick.
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:
getEventListeners function
Highly voted tip from Clifford Fajardo from the comments:
getEventListeners($0) will get the event listeners for the element you have focused on in the Chrome dev tools.
If you just need to inspect what's happening on a page, you might try the Visual Event bookmarklet.
Update: Visual Event 2 available.
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.
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.
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.
Use getEventListeners in Google Chrome:
getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
(Rewriting the answer from this question since it's relevant here.)
When debugging, if you just want to see the events, I recommend either...
Visual Event
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.
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).
Paste in console to get all eventListeners printed beside their HTML element
Array.from(document.querySelectorAll("*")).forEach(element => {
const events = getEventListeners(element)
if (Object.keys(events).length !== 0) {
console.log(element, events)
}
})
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
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.
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);
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
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)
Update 2022:
In the Chrome Developer Tools, in the Elements panel, there is the Event Listeners tab, where you can see listeners for the element.
You can also unselect "Ancestors" so it only shows the listeners for that element
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];
}
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>
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.
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");
});
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.
There exists nice jQuery Events extension :
(topic source)
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"));

object.onload fails with IE and Edge

I'm trying to dynamically preload list of files which may be anything between images and JavaScript files. Everything is going supersmooth with Chrome and Firefox, but failing when I'm trying to preload JavaScript files with Edge. Edge still can handle images for example but no js files. And yes I've tried with addEventListener, it's not working either.
Edge doesn't give me any errors.
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = path/to/javascriptfile.js
body.appendChild(object);
object.onload = function(){
console.log('hello world')
//not firing with edge
}
Anything relevant I'm missing?
UPDATE: Didn't get any success after the day. Will probably leave it for now and just skip preloading script files with edge until i find a solution.
Perhaps worth a check - from msdn:
The client loads applications, embedded objects, and images as soon as
it encounters the applet, embed, and img objects during parsing.
Consequently, the onload event for these objects occurs before the
client parses any subsequent objects. To ensure that an event handler
receives the onload event for these objects, place the script object
that defines the event handler before the object and use the onload
attribute in the object to set the handler.
https://msdn.microsoft.com/en-us/library/windows/apps/hh465984.aspx
Edit, a clarification:
You should attach the event listener before the element is added to the page.
Even doing that I'm not sure if it'll work or not though. But to make sure you've exhausted all options try the example below:
function doLoad() {
console.log('The load event is executing');
}
var object = {};
object = document.createElement('object');
object.width = object.height = 0;
object.data = 'path/to/javascriptfile.js';
object.onreadystatechange = function () {
if (object.readyState === 'loaded' || object.readyState === 'complete') doLoad();
console.log('onreadystatechange');
}
if (object.addEventListener) {
object.addEventListener( "load", doLoad, false );
console.log('addEventListener');
}
else
{
if (object.attachEvent) {
object.attachEvent( "onload", doLoad );
console.log('attachEvent');
} else if (object.onLoad) {
object.onload = doLoad;
console.log('onload');
}
}
var body = document.getElementsByTagName("body")[0];
body.appendChild(object);
If this doesn't work, you could perhaps preload using "image" instead of "object" in IE: https://stackoverflow.com/a/11103087/1996783
Working temporary solution is to put onload event directly to script element instead of object. It's sad since it works like a charm in Chrome & FF.
It turns out, object.data with css source did not load either. I don't know if it's a bug since it still can load image from to object.data.
But show must go on.
Cheers, eljuko

How to catch a function call from class name in Javascript? [duplicate]

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:
Prototype's Event.observe;
DOM's addEventListener;
As element attribute element.onclick.
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:
getEventListeners function
Highly voted tip from Clifford Fajardo from the comments:
getEventListeners($0) will get the event listeners for the element you have focused on in the Chrome dev tools.
If you just need to inspect what's happening on a page, you might try the Visual Event bookmarklet.
Update: Visual Event 2 available.
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.
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.
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.
Use getEventListeners in Google Chrome:
getEventListeners(document.getElementByID('btnlogin'));
getEventListeners($('#btnlogin'));
(Rewriting the answer from this question since it's relevant here.)
When debugging, if you just want to see the events, I recommend either...
Visual Event
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.
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).
Paste in console to get all eventListeners printed beside their HTML element
Array.from(document.querySelectorAll("*")).forEach(element => {
const events = getEventListeners(element)
if (Object.keys(events).length !== 0) {
console.log(element, events)
}
})
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
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.
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);
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
Update 2022:
In the Chrome Developer Tools, in the Elements panel, there is the Event Listeners tab, where you can see listeners for the element.
You can also unselect "Ancestors" so it only shows the listeners for that element
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)
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];
}
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>
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.
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");
});
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.
There exists nice jQuery Events extension :
(topic source)
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"));

How to get notified about changes of the history via history.pushState?

So now that HTML5 introduces history.pushState to change the browsers history, websites start using this in combination with Ajax instead of changing the fragment identifier of the URL.
Sadly that means that those calls cannot be detect anymore by onhashchange.
My question is: Is there a reliable way (hack? ;)) to detect when a website uses history.pushState? The specification does not state anything about events that are raised (at least I couldn't find anything).
I tried to create a facade and replaced window.history with my own JavaScript object, but it didn't have any effect at all.
Further explanation: I'm developing a Firefox add-on that needs to detect these changes and act accordingly.
I know there was a similar question a few days ago that asked whether listening to some DOM events would be efficient but I would rather not rely on that because these events can be generated for a lot of different reasons.
Update:
Here is a jsfiddle (use Firefox 4 or Chrome 8) that shows that onpopstate is not triggered when pushState is called (or am I doing something wrong? Feel free to improve it!).
Update 2:
Another (side) problem is that window.location is not updated when using pushState (but I read about this already here on SO I think).
5.5.9.1 Event definitions
The popstate event is fired in certain cases when navigating to a session history entry.
According to this, there is no reason for popstate to be fired when you use pushState. But an event such as pushstate would come in handy. Because history is a host object, you should be careful with it, but Firefox seems to be nice in this case. This code works just fine:
(function(history){
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
// ... whatever else you want to do
// maybe call onhashchange e.handler
return pushState.apply(history, arguments);
};
})(window.history);
Your jsfiddle becomes:
window.onpopstate = history.onpushstate = function(e) { ... }
You can monkey-patch window.history.replaceState in the same way.
Note: of course you can add onpushstate simply to the global object, and you can even make it handle more events via add/removeListener
I do this with simple proxy. This is an alternative to prototype
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray) => {
// trigger here what you need
return target.apply(thisArg, argArray);
},
});
Finally found the "correct" (no monkeypatching, no risk of breaking other code) way to do this! It requires adding a privilege to your extension (which, yes person who helpfully pointed this out in the comments, it's for the extension API which is what was asked for) and using the background page (not just a content script), but it does work.
The event you want is browser.webNavigation.onHistoryStateUpdated, which is fired when a page uses the history API to change the URL. It only fires for sites that you have permission to access, and you can also use a URL filter to further cut down on the spam if you need to. It requires the webNavigation permission (and of course host permission for the relevant domain(s)).
The event callback gets the tab ID, the URL that is being "navigated" to, and other such details. If you need to take an action in the content script on that page when the event fires, either inject the relevant script directly from the background page, or have the content script open a port to the background page when it loads, have the background page save that port in a collection indexed by tab ID, and send a message across the relevant port (from the background script to the content script) when the event fires.
Thank #KalanjDjordjeDjordje for his answer. I tried to make his idea a complete solution:
const onChangeState = (state, title, url, isReplace) => {
// define your listener here ...
}
// set onChangeState() listener:
['pushState', 'replaceState'].forEach((changeState) => {
// store original values under underscored keys (`window.history._pushState()` and `window.history._replaceState()`):
window.history['_' + changeState] = window.history[changeState]
window.history[changeState] = new Proxy(window.history[changeState], {
apply (target, thisArg, argList) {
const [state, title, url] = argList
onChangeState(state, title, url, changeState === 'replaceState')
return target.apply(thisArg, argList)
},
})
})
I used to use this:
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');
window.addEventListener('replaceState', function(e) {
console.warn('THEY DID IT AGAIN!');
});
It's almost the same as galambalazs did.
It's usually overkill though. And it might not work in all browsers. (I only care about my version of my browser.)
(And it leaves a var _wr, so you might want to wrap it or something. I didn't care about that.)
In addition to other answers. Instead of storing the original function, we can use the History interface.
history.pushState = function()
{
// ...
History.prototype.pushState.apply(history, arguments);
}
I'd rather not overwrite the native history method so this simple implementation creates my own function called eventedPush state which just dispatches an event and returns history.pushState(). Either way works fine but I find this implementation a bit cleaner as native methods will continue to perform as future developers expect.
function eventedPushState(state, title, url) {
var pushChangeEvent = new CustomEvent("onpushstate", {
detail: {
state,
title,
url
}
});
document.dispatchEvent(pushChangeEvent);
return history.pushState(state, title, url);
}
document.addEventListener(
"onpushstate",
function(event) {
console.log(event.detail);
},
false
);
eventedPushState({}, "", "new-slug");
galambalazs's answer monkey patches window.history.pushState and window.history.replaceState, but for some reason it stopped working for me. Here's an alternative that's not as nice because it uses polling:
(function() {
var previousState = window.history.state;
setInterval(function() {
if (previousState !== window.history.state) {
previousState = window.history.state;
myCallback();
}
}, 100);
})();
Since you're asking about a Firefox addon, here's the code that I got to work. Using unsafeWindow is no longer recommended, and errors out when pushState is called from a client script after being modified:
Permission denied to access property history.pushState
Instead, there's an API called exportFunction which allows the function to be injected into window.history like this:
var pushState = history.pushState;
function pushStateHack (state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
return pushState.apply(history, arguments);
}
history.onpushstate = function(state) {
// callback here
}
exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});
Well, I see many examples of replacing the pushState property of history but I'm not sure that's a good idea, I'd prefer to create a service event based with a similar API to history that way you can control not only push state but replace state as well and it open doors for many other implementations not relying on global history API. Please check the following example:
function HistoryAPI(history) {
EventEmitter.call(this);
this.history = history;
}
HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);
const prototype = {
pushState: function(state, title, pathname){
this.emit('pushstate', state, title, pathname);
this.history.pushState(state, title, pathname);
},
replaceState: function(state, title, pathname){
this.emit('replacestate', state, title, pathname);
this.history.replaceState(state, title, pathname);
}
};
Object.keys(prototype).forEach(key => {
HistoryAPI.prototype = prototype[key];
});
If you need the EventEmitter definition, the code above is based on the NodeJS event emitter: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js. utils.inherits implementation can be found here: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970
Based on the solution given by #gblazex, in case you want to follow the same approach, but using arrow functions, follow up the below example in your javascript logic:
private _currentPath:string;
((history) => {
//tracks "forward" navigation event
var pushState = history.pushState;
history.pushState =(state, key, path) => {
this._notifyNewUrl(path);
return pushState.apply(history,[state,key,path]);
};
})(window.history);
//tracks "back" navigation event
window.addEventListener('popstate', (e)=> {
this._onUrlChange();
});
Then, implement another function _notifyUrl(url) that triggers any required action you may need when the current page url is updated ( even if the page has not been loaded at all )
private _notifyNewUrl (key:string = window.location.pathname): void {
this._path=key;
// trigger whatever you need to do on url changes
console.debug(`current query: ${this._path}`);
}
Since I just wanted the new URL, I've adapted the codes of #gblazex and #Alberto S. to get this:
(function(history){
var pushState = history.pushState;
history.pushState = function(state, key, path) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state, path: path})
}
pushState.apply(history, arguments)
}
window.onpopstate = history.onpushstate = function(e) {
console.log(e.path)
}
})(window.history);
I don't think it's an good idea do modify native functions even if you can, and you should always keep your application scope, so an good approach is not using the global pushState function, instead, use one of your own:
function historyEventHandler(state){
// your stuff here
}
window.onpopstate = history.onpushstate = historyEventHandler
function pushHistory(...args){
history.pushState(...args)
historyEventHandler(...args)
}
<button onclick="pushHistory(...)">Go to happy place</button>
Notice that if any other code use the native pushState function, you will not get an event trigger (but if this happens, you should check your code)
You could bind to the window.onpopstate event?
https://developer.mozilla.org/en/DOM%3awindow.onpopstate
From the docs:
An event handler for the popstate
event on the window.
A popstate event is dispatched to the
window every time the active history
entry changes. If the history entry
being activated was created by a call
to history.pushState() or was affected
by a call to history.replaceState(),
the popstate event's state property
contains a copy of the history entry's
state object.
I think this topic needs a more modern solution.
I'm sure nsIWebProgressListener was around back then I'm surprised no one mentioned it.
From a framescript (for e10s compatability):
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);
Then listening in the onLoacationChange
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
That will apparently catch all pushState's. But there is a comment warning that it "ALSO triggers for pushState". So we need to do some more filtering here to ensure it's just pushstate stuff.
Based on: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18
And: resource://gre/modules/WebNavigationContent.js
As standard states:
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by doing a browser action such as clicking on the back button (or calling history.back() in JavaScript)
we need to call history.back() to trigeer WindowEventHandlers.onpopstate
So insted of:
history.pushState(...)
do:
history.pushState(...)
history.pushState(...)
history.back()

MooTools events not firing in IE8

I have a Mootools asset created like so:
// Create a new asset
var asset = new Asset.image(path, {
title: this.language.download,
events: {click: this.download.bind(this, link)},
});
I have a method of a MooTools object defined as such:
download: function(e) {
// The path to download
console.log('download: ' + e);
},
In Firefox the console.log print shows up. In IE8, however, I have no luck. Am I missing something?
Any tips or advice would be greatly appreciated. TIA!
I have tried to implement this using the two methods described. Neither seem to work in IE8 or IE8 with compatibility mode.
var path = this.options.assetBasePath + 'disk.png';
var _this = this;
var icon = new Asset.image(path, {
/*onload: function() {
this.set({
title: _this.language.download,
events: {click: function() {
alert('test');
}
}
}).inject(el, 'top');
}*/
});
icon.set({title: _this.language.download,
events: {click: function() {
alert('test');
}
}
});
icon.inject(el, 'top');
icon.addClass('browser-icon');
icons.push(icon);
Both of these methods display an alert() just fine in FF but fail in IE.
In view of further communication via email... and the fact that the issue is caused by the use of filemanager mootools plugin by cpojer (of the mootools core team), I am updating my reply to the advice given via email as well.
source code in question: http://github.com/cpojer/mootools-filemanager/raw/master/Source/FileManager.js - line 408 is the problem
The reason why the original code may fail (in IE8) but i'd consider it unsafe as is anyway - is that events in mootools are UID driven per element. I.E. - if you inject an element or pass it through a selector and possibly create it via new Element() class (tbc), it assigns it a UID against which element storage is enabled. It is possible that the chaining used in the original form results in the addEvent running before the element exists which would cause it to fail. The issue here is WHY would it fail to attach the events when all tests I have conducted seem to work. Basically, the Asset.image code goes:
var image = new Image();
var element = document.id(image) || new Element('img');
element.store("foo", "bar");
alert(element.retrieve("foo") || "failed");
this DOES assign a UID and makes it available for events even before it's a member of the DOM. tescase: http://fragged.org/dev/ie8-new-image-bug.php - does that output 'bar' for you? if so, there is no reason to suspect foul play on the order of assignment of events vs DOM insertion due to storage anyway, it could be a total manipulation issue / onclick problem.
Either way, you can try replacing the code in the class with this, аlthough I have not tested it:
var _this = this;
icons.push(new Asset.image(this.options.assetBasePath + 'disk.png', {
title: this.language.download
"class": 'browser-icon',
onload: function() {
// once the src has been set and image loaded
// inject into DOM (hence open to manilulations) and then add the event
this.inject(el, 'top').addEvent('click', function(e) {
_this.tips.hide();
e.stop();
window.open(_this.options.baseURL + _this.normalize(_this.Directory + '/' + file.name));
});
}
}));
also to consider: IE has - in the past - been known to have issues with cached images being loaded up failing to trigger events if the assignment of the event is wrong way around:
// wrong:
var image = new Image();
image.src = 'image.jpg';
image.onload = function() { // evil, won't work if image already cached, it won't trigger
...
};
// right:
var image = new Image();
image.onload = function() { // always fires the event.
...
};
image.src = 'image.jpg';
this is just as a clue, mootools ensures the correct order of insertion vs event assignment anyway - but that does not mean that IE8 has no other similar issues to do with events.

Categories