I need to find which event handlers are registered over an object.
For example:
$("#el").click(function() {...});
$("#el").mouseover(function() {...});
$("#el") has click and mouseover registered.
Is there a function to find out that, and possibly iterate over the event handlers?
If it is not possible on a jQuery object through proper methods, is it possible on a plain DOM object?
As of jQuery 1.8, the event data is no longer available from the "public API" for data. Read this jQuery blog post. You should now use this instead:
jQuery._data( elem, "events" );
elem should be an HTML Element, not a jQuery object, or selector.
Please note, that this is an internal, 'private' structure, and shouldn't be modified. Use this for debugging purposes only.
In older versions of jQuery, you might have to use the old method which is:
jQuery( elem ).data( "events" );
You can do it by crawling the events (as of jQuery 1.8+), like this:
$.each($._data($("#id")[0], "events"), function(i, event) {
// i is the event type, like "click"
$.each(event, function(j, h) {
// h.handler is the function being called
});
});
Here's an example you can play with:
$(function() {
$("#el").click(function(){ alert("click"); });
$("#el").mouseover(function(){ alert("mouseover"); });
$.each($._data($("#el")[0], "events"), function(i, event) {
output(i);
$.each(event, function(j, h) {
output("- " + h.handler);
});
});
});
function output(text) {
$("#output").html(function(i, h) {
return h + text + "<br />";
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="el">Test</div>
<code>
<span id="output"></span>
</code>
For jQuery 1.8+, this will no longer work because the internal data is placed in a different object.
The latest unofficial (but works in previous versions as well, at least in 1.7.2) way of doing it now is -
$._data(element, "events")
The underscore ("_") is what makes the difference here. Internally, it is calling $.data(element, name, null, true), the last (fourth) parameter is an internal one ("pvt").
Shameless plug, but you can use findHandlerJS
To use it you just have to include findHandlersJS (or just copy&paste the raw javascript code to chrome's console window) and specify the event type and a jquery selector for the elements you are interested in.
For your example you could quickly find the event handlers you mentioned by doing
findEventHandlers("click", "#el")
findEventHandlers("mouseover", "#el")
This is what gets returned:
element
The actual element where the event handler was registered in
events
Array with information about the jquery event handlers for the event type that we are interested in (e.g. click, change, etc)
handler
Actual event handler method that you can see by right clicking it and selecting Show function definition
selector
The selector provided for delegated events. It will be empty for direct events.
targets
List with the elements that this event handler targets. For example, for a delegated event handler that is registered in the document object and targets all buttons in a page, this property will list all buttons in the page. You can hover them and see them highlighted in chrome.
You can try it here
I use eventbug plugin to firebug for this purpose.
I've combined both solutions from #jps to one function:
jQuery.fn.getEvents = function() {
if (typeof(jQuery._data) === 'function') {
return jQuery._data(this.get(0), 'events') || {};
}
// jQuery version < 1.7.?
if (typeof(this.data) === 'function') {
return this.data('events') || {};
}
return {};
};
But beware, this function can only return events that were set using jQuery itself.
To check for events on an element:
var events = $._data(element, "events")
Note that this will only work with direct event handlers, if you are using $(document).on("event-name", "jq-selector", function() { //logic }), you will want to see the getEvents function at the bottom of this answer
For example:
var events = $._data(document.getElementById("myElemId"), "events")
or
var events = $._data($("#myElemId")[0], "events")
Full Example:
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" type="text/javascript"></script>
<script>
$(function() {
$("#textDiv").click(function() {
//Event Handling
});
var events = $._data(document.getElementById('textDiv'), "events");
var hasEvents = (events != null);
});
</script>
</head>
<body>
<div id="textDiv">Text</div>
</body>
</html>
A more complete way to check, that includes dynamic listeners, installed with $(document).on
function getEvents(element) {
var elemEvents = $._data(element, "events");
var allDocEvnts = $._data(document, "events");
for(var evntType in allDocEvnts) {
if(allDocEvnts.hasOwnProperty(evntType)) {
var evts = allDocEvnts[evntType];
for(var i = 0; i < evts.length; i++) {
if($(element).is(evts[i].selector)) {
if(elemEvents == null) {
elemEvents = {};
}
if(!elemEvents.hasOwnProperty(evntType)) {
elemEvents[evntType] = [];
}
elemEvents[evntType].push(evts[i]);
}
}
}
}
return elemEvents;
}
Example usage:
getEvents($('#myElemId')[0])
As of 1.9 there is no documented way to retrieve the events, other than to use the Migrate plugin to restore the old behavior. You could use the _.data() method as jps mentions, but that is an internal method. So just do the right thing and use the Migrate plugin if you need this functionality.
From the jQuery documentation on .data("events")
Prior to 1.9, .data("events") could be used to retrieve jQuery's
undocumented internal event data structure for an element if no other
code had defined a data element with the name "events". This special
case has been removed in 1.9. There is no public interface to retrieve
this internal data structure, and it remains undocumented. However,
the jQuery Migrate plugin restores this behavior for code that depends
upon it.
I created a custom jQuery selector that checks against both jQuery's cache of assigned event handlers as well as elements that use the native method for adding them:
(function($){
$.find.selectors[":"].event = function(el, pos, match) {
var search = (function(str){
if (str.substring(0,2) === "on") {str = str.substring(2);}
return str;
})(String(match[3]).trim().toLowerCase());
if (search) {
var events = $._data(el, "events");
return ((events && events.hasOwnProperty(search)) || el["on"+search]);
}
return false;
};
})(jQuery);
Example:
$(":event(click)")
This will return elements that have a click handler attached to them.
In a modern browser with ECMAScript 5.1 / Array.prototype.map, you can also use
jQuery._data(DOCUMENTELEMENT,'events')["EVENT_NAME"].map(function(elem){return elem.handler;});
in your browser console, which will print the source of the handlers, comma delimited. Useful for glancing at what all is running on a particular event.
Events can be retrieved using:
jQuery(elem).data('events');
or jQuery 1.8+:
jQuery._data(elem, 'events');
Note:
Events bounded using $('selector').live('event', handler)
can be retrieved using:
jQuery(document).data('events')
jQuery is not letting you just simply access the events for a given element.
You can access them using undocumented internal method
$._data(element, "events")
But it still won't give you all the events, to be precise won't show you events assigned with
$([selector|element]).on()
These events are stored inside document, so you can fetch them by browsing through
$._data(document, "events")
but that is hard work, as there are events for whole webpage.
Tom G above created function that filters document for only events of given element and merges output of both methods, but it had a flaw of duplicating events in the output (and effectively on the element's jQuery internal event list messing with your application).
I fixed that flaw and you can find the code below. Just paste it into your dev console or into your app code and execute it when needed to get nice list of all events for given element.
What is important to notice, element is actually HTMLElement, not jQuery object.
function getEvents(element) {
var elemEvents = $._data(element, "events");
var allDocEvnts = $._data(document, "events");
function equalEvents(evt1, evt2)
{
return evt1.guid === evt2.guid;
}
for(var evntType in allDocEvnts) {
if(allDocEvnts.hasOwnProperty(evntType)) {
var evts = allDocEvnts[evntType];
for(var i = 0; i < evts.length; i++) {
if($(element).is(evts[i].selector)) {
if(elemEvents == null) {
elemEvents = {};
}
if(!elemEvents.hasOwnProperty(evntType)) {
elemEvents[evntType] = [];
}
if(!elemEvents[evntType].some(function(evt) { return equalEvents(evt, evts[i]); })) {
elemEvents[evntType].push(evts[i]);
}
}
}
}
}
return elemEvents;
}
I have to say many of the answers are interesting, but recently I had a similar problem and the solution was extremely simple by going the DOM way. It is different because you don't iterate but aim directly at the event you need, but below I'll give a more general answer.
I had an image in a row:
<table>
<td><tr><img class="folder" /></tr><tr>...</tr></td>
</table>
And that image had a click event handler attached to it:
imageNode.click(function () { ... });
My intention was to expand the clickable area to the whole row, so I first got all images and relative rows:
tableNode.find("img.folder").each(function () {
var tr;
tr = $(this).closest("tr");
// <-- actual answer
});
Now in the actual anwer line I just did as follows, giving an answer to the original question:
tr.click(this.onclick);
So I fetched the event handler directly from the DOM element and put it into the jQuery click event handler. Works like a charm.
Now, to the general case. In the old pre-jQuery days you could get all events attached to an object with two simple yet powerful functions gifted to us mortals by Douglas Crockford:
function walkTheDOM(node, func)
{
func(node);
node = node.firstChild;
while (node)
{
walkTheDOM(node, func);
node = node.nextSibling;
}
}
function purgeEventHandlers(node)
{
walkTheDOM(node, function (n) {
var f;
for (f in n)
{
if (typeof n[f] === "function")
{
n[f] = null;
}
}
});
}
Try jquery debugger plugin if you're using chrome: https://chrome.google.com/webstore/detail/jquery-debugger/dbhhnnnpaeobfddmlalhnehgclcmjimi?hl=en
Another way to do it is to just use jQuery to grab the element, then go through actual Javascript to get and set and play with the event handlers. For instance:
var oldEventHandler = $('#element')[0].onclick;
// Remove event handler
$('#element')[0].onclick = null;
// Switch it back
$('#element')[0].onclick = oldEventHandler;
I combined some of the answers above and created this crazy looking but functional script that lists hopefully most of the event listeners on the given element. Feel free to optimize it here.
var element = $("#some-element");
// sample event handlers
element.on("mouseover", function () {
alert("foo");
});
$(".parent-element").on("mousedown", "span", function () {
alert("bar");
});
$(document).on("click", "span", function () {
alert("xyz");
});
var collection = element.parents()
.add(element)
.add($(document));
collection.each(function() {
var currentEl = $(this) ? $(this) : $(document);
var tagName = $(this)[0].tagName ? $(this)[0].tagName : "DOCUMENT";
var events = $._data($(this)[0], "events");
var isItself = $(this)[0] === element[0]
if (!events) return;
$.each(events, function(i, event) {
if (!event) return;
$.each(event, function(j, h) {
var found = false;
if (h.selector && h.selector.length > 0) {
currentEl.find(h.selector).each(function () {
if ($(this)[0] === element[0]) {
found = true;
}
});
} else if (!h.selector && isItself) {
found = true;
}
if (found) {
console.log("################ " + tagName);
console.log("event: " + i);
console.log("selector: '" + h.selector + "'");
console.log(h.handler);
}
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="parent-element">
<span id="some-element"></span>
</div>
I want the events click and touchstart to trigger a function.
Of course this is simple with JQuery. $('#id').on('click touchstart', function{...});
But then once that event is triggered, I want that same handler to do something else when the events are triggered,
and then later, I want to go back to the original handling function.
It seems like there must be a cleaner way to do this than using $('#id').off('click touchstart'); and then re-applying the handler.
How should I be doing this?
You can create a counter variable in some construct in your javascript code that allows you to decide how you want to handle your event.
$(function() {
var trackClicks = (function() {
var clicks = true;
var getClicks = function() {
return clicks;
};
var eventClick = function() {
clicks = !clicks;
};
return {
getClicks: getClicks,
eventClicks: eventClicks
}
})();
$('#id').on('click touchstart', function {
if (trackClicks.getClicks()) {
handler1();
} else {
handler2();
}
trackClicks.eventClick();
});
function handler1() { //firsthandler};
function handler2() { //secondhandler};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The way I would do this is by creating a couple of functions for the handler function to call based on certain flags. Sudo code would be something like this:
function beginning_action() {
...
}
function middle() {
...
}
var beginning_state = true;
$('#id').on('click touchstart', function{
if(beginning_state) {
beginning_action();
} else {
middle();
}
});
Then all you need to do is change the variable beginning_state to change which function is called. Of course you would give them better names that describe what they do and not when they do it.
Additionally, if you want the handler to call more than two functions you can change the beginning_state variable from a boolean to an int and check it's value to determine which function to call.
Good luck!
I have a page with a lot of e.stopPropagation so what I decided I would do was to create a function. here is code
function stopProp(name) {
if($(e.target).hasClass(name)) {
e.stopPropagation();
}
}
Though each time in console it does not seem to work at all and says stopProp is undefined.
Here is the actual JS I am trying to turn into a function
$('#chatting').on('click','.chatheader',function(e){
if($(e.target).hasClass('setting')) {
e.stopPropagation();
}
}
});
Anyone can help me to figure out why this is not working and how I should go about this? I figured it would be fairly easy just to change to a function so I can easily write stopProp('setting');though that is not the case here.
The handler of the click event should return with one single argument which will be the event. So you should have a function that returns an event, for example like this :
$('#chatting').on('click','.chatheader',stopProp('setting'));
function stopProp(name) {
return function(e) {
if($(e.target).hasClass(name)) {
e.stopPropagation();
}
}
}
You need to pass the event object to the stopProp method.
function stopProp(e) {
if($(e.target).hasClass(e.data.className)) {
e.stopPropagation();
}
}
Then
$('#chatting').on('click', '.chatheader', {className: 'setting'}, stopProp);
Demo: Fiddle
I'm building a simple jQuery plugin called magicForm (How ridiculous is this?). Now face to a problem that I think I'm not figuring out properly.
My plugin is supposed to be applied on a container element, that will show each of its inputs one by one as user fills them. That's not the exact purpose of my problem.
Each time I initialize the container, I declare an event click callback. Let me show an example.
(function($){
var methods = {
init: function(options){
return this.each(function(){
var form, inputs;
var settings = {
debug: false
};
settings = $.extend(settings, options);
form = $(this);
$('a.submit', form).on('click', function(event){
if (settings.submitCallback) {
settings.submitCallback.call(form, inputs);
}
return false;
});
});
},
reset: function() {
}
}
$.fn.magicForm = function(method) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist.' );
}
};
})($);
I'm focusing on a specific part of this code :
$('a.submit', form).on('click', function(event){
if (settings.submitCallback) {
settings.submitCallback.call(form, inputs);
}
return false;
});
Because each time the init method is called, that poor callback is registered.
I was experiencing this painfully, when I invoked my plugin on an element nested in a twitter bootstrap 'tab', nested itself in a bootstrap modal :
I was calling init each time the event 'shown' of my bootstrap modal was triggered.
So, this is how I fixed it in my init method :
// Prevent callback cumulation
if (!$(this).data('form_initialized')) {
$('a.submit', form).on('click', function(event){
if (settings.submitCallback) {
settings.submitCallback.call(form, inputs);
}
return false;
});
$(this).data('form_initialized', true);
}
And I'm far from feeling sure about this.
Thank your for your time !
Many jquery plugins use data to know if their plugins were initialized. Most often, they use the name of their own plugin as a part (or in whole) as the data. For example:
$(this).data('magicForm')
So your approach of using that to signal is not a bad one.
However, you have two other options:
1) Pull the event handler out so the handler is a single instance. Above your methods, do var fnOnSubmit = function() { ... } Then you can simply ensure proper binding by calling $('a.submit', form).unbind('click', fnOnSubmit) before rebinding it the way you are already doing it.
2) Another option is to use event namespaces.
$('a.submit', form).unbind('click.magicForm'); then rebinding it with .on('click.magicForm') This namespace approach ensures that when you unbind it only unbinds in the context of your namespace magicForm, thus leaving all other click events (e.g. from other plugins) intact.
I hope this helps.
You could first explicitely remove the click-handler:
$('a.submit', form).off('click').on('click', function(event){ ... })
However, I would suggest you use event namespacing to prevent all click handlers (even those perhaps set by code not your own) from being removed:
$('a.submit', form).off('click.magicForm').on('click.magicForm', function(event){ ... })
I am trying to basically disable the click event on a <div> temporarily.
I have tried the following (preview):
$('hello').observe('click', function (e) {
e.stop();
});
$('hello').observe('click', function (e) {
alert('click not stopped!');
});
However, when #hello is clicked, the alert box still appears. I do not want the second attached handler to be called, and I do not want to change the second handler.
I will also accept a solution such as:
$('hello').observe('click', function (e) {
alert('click not stopped!');
});
$('hello').disableEvent('click');
// Now, handler won't be called
$('hello').observe('click', function (e) {
alert('click not stopped (2)!');
});
// New handler won't be called, either
$('hello').enableEvent('click');
// Now, handler will be called
I am using the Prototype.js framework. This doesn't seem to be a browser-specific issue.
When you assign handlers to events; you are basically just storing a set of functions to be executed when an event fires.
When an event fires, the handlers you've added are executed in the order they we're added. So if you we're to add three handlers to a div's click-event:
$("div").observe("click", function ()
{
alert("one");
});
$("div").observe("click", function ()
{
alert("two");
});
$("div").observe("click", function ()
{
alert("three");
});
.. you would get three alerts ("one", "two" and "three") when the click event of the div element fires. Those three alerts will still get shown, if you put in:
$("div").observe("click", function (e)
{
e.stop();
})
.. because you are only canceling the event for one particular handler. Not all associated handlers.
So what you will need to do is use a reference variable, which keeps track of wether the click event is allowed to fire:
var cancelClickEvent = true;
$("div").observe("click", function ()
{
// if cancelClickEvent is true, return the function early, to
// stop the code underneath from getting executed
if (cancelClickEvent) return;
// your code goes here
});
You will then need to implement the above if-clause in all your handlers.
Can't you just set the object's disabled property to true?
As I said in comments to roosteronacid's answer, I wrote an extension to Event.observe. Works in most browsers, but not IE.
// XXX HACK XXX
(function () {
var handlerCache = $A([ ]);
function findHandler(either) {
var pair = handlerCache.find(function (pair) {
return $A(pair).member(either);
});
return pair && pair[0];
}
function addHandler(handler) {
function newHandler(e) {
if (!e.halted) {
handler.apply(this, arguments);
}
}
handlerCache.push([ handler, newHandler ]);
return newHandler;
}
Event.observe = Event.observe.extended(function ($super, element, eventName, handler) {
handler = findHandler(handler) || addHandler(handler);
$super(element, eventName, handler);
});
Event.stopObserving = Event.stopObserving.extended(function ($super, element, eventName, handler) {
handler = findHandler(handler) || handler;
$super(element, eventName, handler);
});
Element.addMethods({
observe: Event.observe
});
Event.prototype.halt = function () {
this.halted = true;
};
}());
Note: Function.prototype.extended is a custom function which puts the original Event.observe in as $super.