I frequently use jQuery's on function to handle future events. Eg:
$(document).on('click', '.my-div', myHandler);
But, I'm not able to get this to work with the play or playing events on my video tag:
$(document).on('playing', 'video', function() {
console.log('video playing');
});
The following works just fine, though does not bind to future elements:
$('video').on('playing', function() {
console.log('video playing');
});
Does jQuery's future binding syntax of the on function only work with certain events, or does it lack suppoert for HTMLMediaElement object events? I'm using jQuery 1.11.2.
It lacks support for events that doesn't bubble, and media events don't bubble because they don't make sense on any other elements except media elements, and media elements, like <audio> and <video> can't be nested, so there's no need to let the event bubble up the chain.
The way it works is that events bubble, on a click event it starts on the element you click, which is the event.target and continues all the way up to the document level.
This is so a click on an image inside an anchor for instance, also triggers the anchor.
The way event delegation works, is that it attaches the event handler to elements higher up the event chain, and then checks the event.target
Quick jQuery example
$('#parent').on('click', function(e) {
if ( $(e.target).hasClass('child') ) func();
});
// is (somewhat) the same as
$('#parent').on('click', '.child', func);
Related
I have created a common click function in jquery which fires every time when clicked any where on document. If control has click event then that event fires first and document click fires after that but i want to fire document click event before control click event. please suggest how to fix this.
Two options for you:
You can do that on modern browsers by using the capturing phase of an event rather than the bubbling phase. This diagram from the DOM events specification helps with understanding the various phases of an event:
IE doesn't support event capture until IE9 in standards mode (IE8 and earlier simply do not have it).
jQuery doesn't provide an API for using event capture, so you'd have to use addEventListener instead:
document.querySelector("selector for the control").addEventListener("click", function(e) {
// Control code here
}, true);
document.addEventListener("click", function(e) {
// Document code here
}, true);
The true at the end means you want to use the capturing phase, rather than the bubbling phase.
Don't handle clicks on controls directly, use event delegation, handling clicks on the document and, if the click came through a control, calling your control-specific handling from the document click handler:
$(document).on("click", handleDocumentClick);
function handleDocumentClick(e) {
// On the next line, use whatever selector matches your controls
var input = $(e.target).closest("input")[0];
display("document click received");
if (input) {
handleControlClick.call(input, e);
}
}
function handleControlClick(e) {
display("control click received");
}
Live Example
You can use live(). live() binds the click event to the DOM's document element. As browser events bubble up through the DOM tree, the click event is triggered for any matching elements.
$(document).click(function() {
alert('document');
});
$(document).live('click', 'div', function() {
alert('div');
});
DEMO
I want an element to listen for a custom event that is actually triggered by itself. The custom event could possibly be triggered from a descendant but should then be ignored. It's important that it origins from itself. It also needs to be an event since I might need additional ancestors to listen for the events.
The .on (http://api.jquery.com/on/) method is able to provide this functionality. The selector argument can be used as filter. However this does not work to filter out the listener element itself.
In short:
-The event must be able to bubble
-The trigger and the listener is the same element
-The listener must ignore the custom event if it's triggered by an ancestors
How is this achieved?
Use case as requested
I use the jquery UI dialog widget
$el = $('#dialogDiv');
$el.on('customEvent', $el /* Won't work */, function() {
//Do whatever
});
$el.dialog({
open: function() {
$el.trigger('customEvent');
}
});
.on works fine; to ignore ancestors check e.target:
$el.on('customEvent', function(e) {
if(e.target === this) {
//Do whatever
}
});
The selector that you can pass to .on() is used for the delegate target to match elements that can handle the click event (and it should be a string, not an object).
But in your case that's not necessary because your selector and the delegate target is the same thing, so this should work as expected:
$el.on('customEvent', function(evt) {
//Do whatever
});
To detect if an event came from a descendent you would compare evt.target against the element itself.
Removing the part that doesn't work, will make it work.
$el = $('#dialogDiv');
$el.on('customEvent', function(e) {
//Do whatever
});
$el.dialog({
open: function() {
$el.trigger('customEvent');
}
});
However, you are asking for other features that a normal event might not support. You should look into setting up a jQuery special event. Check this awesome article by Ben Alman.
When it comes to your prerequisites:
An event is always able to bubble unless its propagation is hindered with event.stopPropagation() or event.stopImmediatePropagation()
The trigger and the listener is already on the same element
The listener will not know what triggered it unless you pass some an argument that can identify the element that triggered it and check if it's an ancestor
See test case on jsFiddle.
I want to add a load event on an image that would affect that image even when that image is added to the document after the page's initial load. For a click event I would do someting like this:
$(document).on('click', '.elem', function(e) {
// do stuff
});
When I try something similar with the load event, however, it does not to work. This is what I have tried:
$(document).on('load', '.image', function() {
// do stuff
});
This event is simply never triggered. Does anyone know what I may be doing wrong, or how to achieve this?
This answer is incorrect. It's possible to do this using the capture phase, see Dhia Louhichi's answer. I'll delete this answer when I can (i.e.., once it's no longer the accepted answer).
By their nature, delegated handlers only work for events that bubble. Not all do, and load is one of the ones that doesn't. The jQuery documentation even highlights this:
In all browsers, the load, scroll, and error events (e.g., on an element) do not bubble.
You'll have to add the load handlers to the images when you add them.
What I mean by "delegated handlers only work for events that bubble":
Events that bubble work like this (in the "bubbling" phase, which is the phase you normally work with): The event is fired on the element where it originates, and then on that element's parent, then that element's parent, etc. until it gets to the document element (html). This diagram from the DOM3 events spec may help make this clearer:
Using a delegated handler (the kind you're using in your question) relies on bubbling. Consider this HTML:
<div id="container">
<div class="content">xxxx</div>
<div class="content">xxxx</div>
<div class="content">xxxx</div>
</div>
If you do $("#container").on("click", ".content", ...) you're not hooking the event on the "content" divs, even though jQuery will make it seem a bit like you are. You're hooking the event on the "container" div. When the event bubbles down to the container div, jQuery looks at where it started and sees whether it passed through any "content" divs during its bubbling. If it did, jQuery calls your handler as though (mostly) you'd hooked the event on the "content" div. That's why delegated handlers work when you add elements later; the event isn't hooked on the element, but on the container.
So that's why it won't work for load: load doesn't bubble, so even though it fires on the img elements you add, it doesn't bubble to the parent and so on, and so you never see it. To see it, you have to hook it on the specific element, not an ancestor of it.
This code shows handling the load event for img elements created in the future, without explicitly adding a listener/handler to them, by using the capture phase of the event process on document.body (also works when attached to document, but not window because of backward compatibility issues):
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
Live Example:
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
// Brief wait, then add an image
setTimeout(function() {
document.body.insertAdjacentHTML(
"beforeend",
"<img src='https://via.placeholder.com/150/202080?text=Some+Image'>"
);
}, 400);
This is tested and works in at least the following:
IE9+
Chrome and other Chromium-based browsers (Opera, Edge, Vivaldi, ...)
Firefox
iOS Safari
The behavior is also documented. In fact, coincidentally the specification gives this example mentioning load by name (scroll down slightly from that link):
EXAMPLE 5
The following is one way to interpret the above tables: the load event will trigger event listeners attached on Element nodes for that event and on the capture and target phases. This event is not cancelable. If an event listener for the load event is attached to a node other than Window, Document, or Element nodes, or if it is attached to the bubbling phase only, this event listener would not be triggered.
That's saying load will be fired in the capture and target phases, but not the bubbling phase (since the event doesn't bubble).
By default, when you use addEventListener, the handler is attached for the target phase of the element you call addEventListener on and the bubbling phase for any element within that element. If you add the third argument with the value true, though, it attaches the handler for the target phase of the element you call addEventListener on (as before) and the capture phase for any element within that element. So the code above will handle load for document.body during the target phase (except document.body doesn't fire load) and also handle load for the capture phase of any element within document.body.
More about event flows in the specification, including this handy diagram:
I have a click event on the body of my document:
$("body").on('click.findElem', function(e) {
e.stopPropagation();
e.preventDefault();
self.hinter(e.target);
return false;
});
It basically catches the clicked target and does something to it. However, there are some targets that already have a click event on them, and they prevent my click from being executed at all. How do I overcome that issue? I tried unbinding it, but the click doesn't even work at all to actually execute the unbinding.
e.stopImmediatePropagation() does the job, but only if your handler executes before whatever other handler exists.
Unfortunately there is no way to insert your own handler in the first position - but you can use this nasty hack if the other handlers were bound using jQuery, too: How do you force your javascript event to run first, regardless of the order in which the events were added?
If you really need this you might want to bind an event handler in capture mode using the native DOM API: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
Capture handlers are triggered before bubble handlers (which are used by jQuery and most other scripts) so that way you have very high chance to execute your handler first.
try this and see demo
$( "body" ).on( "click", ".clickme:not(.clicked)", function( event ) {
$(this).addClass('clicked');
alert('here');
});
i tend to not use on and stick with the bind/unbind combo.
i have some pages that reload partial content and then has to rebind the events.
i tipically do something like this
$(".theobjectsiwant").unbind("click").bind("click", function() {
alert('hi there');
});
If you want/have to stick with the on() function, you shouldn't mix on() with unbind() and try a similar approach with .off("click").on("click")
Check here for a sample http://api.jquery.com/off/
Assuming that there are a large number of elements throughout the site that have an unknown number and type of events bound to them.
If I need to override all of these events with one single bound event, and only that event will fire, what are some recommendations?
I would be binding the event to a click event handler, and I am using jQuery.
Thanks in advance.
You’re looking for jQuery#unbind.
To remove all event handlers on an element or a set of elements, just do:
$('.some-selector').unbind();
To unbind only click handlers, use unbind('click'):
$('.some-selector').unbind('click');
To unbind all click handlers and immediately bind your own handler after that, you can do something like this:
$('.some-selector').unbind('click').click(function(event) {
// Your code goes here
});
Note that this will only work for events bound using jQuery (using .bind or any jQuery method that uses .bind internally). If you want to remove all possible onclick events from a given set of elements, you could use:
$('.some-selector')
.unbind('click') // takes care of jQuery-bound click events
.attr('onclick', '') // clears `onclick` attributes in the HTML
.each(function() { // reset `onclick` event handlers
this.onclick = null;
});
I would like to provide a thought without removing all events all together (just override them).
If your new one single bound event (we call it "click" here) is specific to the element it binds to, then I believe you can ignore any other events simply by stopPropagation() function. Like this
$("specific-selector").on("click", ".specific-class", function (e) {
e.stopPropagation()
// e.stopImmediatePropagation()
/* your code continues ... */
});
It will stop events bubbles up, so your other events won't fire. use stopImmediatePropagation() to prevent other events attached onto the same elements as "click" does.
For example, if "mouseleave" event is also bind to $("specific-selector .specific-class") element, it won't fire, too.
At last, all other events won't fire on this element but your new "click" element.
The unsolved question is, what if other events also use stopPropagation()? ... Then I think the one with best specification wins, so try to avoid complex, too many events is final suggestion.
You can see "Direct and delegated events" on jQuery site for more information.
Looks like this is pretty simple actually:
$('#foo').unbind('click');
$('#foo').bind('click', myNewFunction);
Thanks for your responses though.
Try to use live instead of bind. Then you can easily remove live binding with die from selector which is fast operation and set another live equally fast.
$('selection here').live('..', .....); // multiple invocations
$('selection here').die();
$('selection here').live('click',.....);
DOM is not touched at all. Event condition is evaluated on event occurrence.
But generally if you just want to swap handler functions why not to do it this way:
var ahandler = function(evt) { /* first implementation */ }
$('.selector').bind('click', function(evt) { ahandler(evt); });
//and then if you want to change handlers
ahandler = function(evt) { /* new implementation */ };
This gives absolutely no cost of any changes, rebinding etc.