mootools stop event bubbling when using Event.Delegation (:relay) - javascript

I am adding events to links that are loaded via ajax. Here is a simplified example of the html:
<div class="ajax-content">
<div class="parent">
Click here
</div>
</div>
All content within ".ajax-content" is loaded via ajax. I don't want the click event for ".event-link" to bubble up to ".parent". If it was not loaded via ajax I could do this:
$$('.event-link').addEvent('click', function(event){
event.stopPropagation();
event.preventDefault();
//do stuff
});
However, I can't figure out how to accomplish the same behavior with (click:relay). This is what I am trying:
$$('.ajax-content').addEvent('click:relay(.event-link)', function(event){
event.stopPropagation();
event.preventDefault();
//do stuff
});
How do I prevent the click event from firing on '.parent'? (I also tried other syntax - event.stop() and returning false, but they had the same results.)

consider your DOM order and order of event propagation from the top of the DOM tree down:
<div class="ajax-content">
<div class="parent">
Click here
</div>
</div>
you attach the event to div.ajax-content (the delegator). which means, by the time that it hits it, it ALREADY needs to have bubbled through a.event-link, then div.parent and then finally down to div.ajax-content. there is no point in stopping it then, div.parent will come first anyway:
http://jsfiddle.net/dimitar/ChrCq/
here's what happens when you stop the event from the .parent before it reaches the delegator:
http://jsfiddle.net/dimitar/ChrCq/1/
delegation is not w/o it's downsides, i am afraid :)
you may want to also read this recent question / answer that went into some more caveats of delegated events and how they differ from normal events, how to work around the difficulties by alternating delegators. Make (possibly dynamically loaded) element clickable via JavaScript, but give precedence to links contained within - I am still hoping the event delegation in mootools may change somewhat but I just don't see how it will help things in your case.

I was trying to prevent a delegated click event from firing on div.parent. This can be accomplished in my particular situation by adding a check on the event target in the div.parent event:
$$('.ajax-content').addEvent('click:relay(.event-link)', function(event){
console.log("hi");
event.preventDefault();
//do stuff
});
$$('.ajax-content').addEvent('click:relay(.parent)', function(event) {
if (event.target.match('a')) return false;
console.log("parent");
});
Thanks to Dimitar for pointing me in the right direction!

Related

Why specify "document" or "body" BEFORE binding a jQuery event listener?

In jQuery, there are numerous ways to bind actions to events, and to add listeners for them. With this I have no problem.
But what I can't understand, is what would be the purpose of specifying "body" or "document" for that matter, before listening for an event?
Consider the following code :
$(".example-button").click(function() {
$(this).text("I have been clicked");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="example-button" role="button" type="button">I have not been clicked yet.</button>
<button class="example-button" role="button" type="button">I have not been clicked yet.</button>
This will bind the event "click" to the buttons with class "example-button", and when they are clicked it will change the appropriate buttons text to let you know as such.
However, I often see programmers (and often quite experienced programmers) write the following code :
$("body").on("click", ".example-button", function() {
$(this).text("I have been clicked");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="example-button" role="button" type="button">I have not been clicked yet.</button>
<button class="example-button" role="button" type="button">I have not been clicked yet.</button>
Which achieves the same perceived effect as the first code block.
My question is, Why?
More specifically, why bind it to the document or body, and then check for the clicks on that? Isn't that an additional section of redundant code?
Thinking to justify it, I came up with the theory that perhaps by specifying the click is bound to the body would ensure the body has been loaded - but this is not correct as $("body").on("click") etc does not equal $(document).ready().
Can anyone provide additional insight into this? I can't seem to find my answer in the jQuery documentation, as it pre-defaults to assuming I'm looking for the aforementioned
$(document).ready().
Why use a delegate event listener?
1) Content doesn't exist yet
//#container is empty, but we will create children in the future
//we can use a delagate now that will handle the events from the children
//created later
$('#container').on('click', '.action', function (e) {
console.log(e.target.innerText);
});
//lets create a new action that didn't exist before the binding
$('#container').append('<button class="action">Hey! You Caught Me!</button>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container"></div>
2) Content exists, but changes
//#container has an existing child, but it only matches one of our
//delegate event bindings. Lets see what happens when we change it
//so that it matches each in turn
$('#container').on('click', '.action:not(.active)', function (e) {
console.log('Awww, your not active');
$(e.target).addClass('active');
});
$('#container').on('click', '.action.active', function (e) {
console.log('Hell yeah! Active!');
$(e.target).removeClass('active');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<button class="action">Hey! You Caught Me!</button>
</div>
Why use a non-delegate event listener over a delegate event listener?
1) Content is static and pre-existing
Either because you know the content is static and will not change, and you do not have a need for one. Otherwise, you may have a preference to use delegates, which is fine as a developer preference.
2) You can prevent events from bubbling
However, using non-delegate event listeners can also be used in conjunction with delegates to prevent operations. Consider the following:
//#container has three children. Lets say we have a delegate listener for
//the buttons, but we only want it to work for two of them. How could we
//use a non-delegate to make this work?
//delegate that targets all the buttons in the container
$('#container').on('click', 'button', function (e) {
console.log('Yeah!');
});
$('.doNotDoSomething').on('click', function (e) {
console.log('Do not do the delegate logic');
//by stopping the propagation of the click event, it will not bubble up
//the DOM for the delegate event handler to process it. In this way, we
//can prevent a delegate event handler from working for a nested child.
e.stopPropagation();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<button class="doSomething">Do It!</button>
<button class="doNotDoSomething">Nooooo!</button>
<button class="doSomethingElse">Do This Instead!</button>
</div>
3) You want your bindings to be removable
Another reason to potentially want to use non-delegate event listeners is be cause they are attached to the elements themselves. So, given that, if you delete the element, the binding also goes away with it. While this may be an issue with dynamic content where you want the binding to always be there for the elements, there may be cases where you want that to happen.

In jQuery, how can I replace the body of my document and still get JavaScript to fire?

Currently, my page has a top navigation bar.
When clicking, the page currently does a full reload which causes a flickering effect.
To avoid this, I'm planning to use the jQuery html function to just replace the main body of the page and leave the header intact.
But this appears to stop JavaScript function from running on the main body of the page.
Is there a way I can use the jQuery html function and still get my JavaScript to work on the HTML elements in the body?
You can use event bubbling to listen to the events on document, and based on the target element, execute appropriate event handling logic.
jQuery makes it easier to register such event handlers by simplifying the logic around identification of the target element. The syntax for registering such an event handler would be: $(document).on(events, selector, handler).
These are called Delegated Event Handlers in jQuery's documentation:
Delegated event handlers have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers.
I have illustrated an example in the snippet below. Notice how you do not need to re-register the event listener even after replacing the HTML content.
$(document).on("click", "#clickMe", function() {
alert("Element clicked.");
});
// Change the innerHTML after 10 seconds.
setTimeout(function() {
$(document.body).html(`
<div id="clickMe">[New] Click Me</div>
`);
}, 10000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="clickMe">Click me</div>
You can read more about event bubbling here and more about jQuery's event handling syntax here.
Note: In my opinion, you need to rethink your architecture. While the suggested approach works, given the unconventional nature of your architecture, you may run across other challenges in future.

how to select the rest of a div?

i got a problem
<div id='parent'>
<div id='child'>
</div>
</div>
what i want is when the child is clicked addClass,and when the rest of parent is clicked removeClass,so when i try to do
$('#child').click(function(){
$(this).addClass();
})
$('#parent').click(function(){
$('#child').removeClass();
})
its not working i think its because the child is actually inside the parent,so when the child is clicked the parent clicked right?
so how can i do that?
try this:
$('#child').click(function(evt){
evt.stopPropagation();
$(this).addClass("myClass");
});
You could use event.stopPropagation to prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
$('#child').click(function(e){
e.stopPropagation();
$(this).addClass();
});
Several users have already suggested a good solution - here's an explanation of why it works:
When you click an HTML element (actually a DOM object...), the click event "bubbles" all the way up to the root element. For example, a click in #child also triggers a click in #parent, as you expected.
To stop this behavior, you need to call .stopPropagation() on the click event - that will tell the browser that you do not want the event to propagate, but keep it "local". Basically, when you've handled it here, you're done with it and don't want to see it again.
Conveniently, jQuery event handlers take the event as the first argument, so if you assign any function with the signature function (e) { ... }, you can stop event propagation by e.stopPropagation(); as others have suggested. In your case, you want
$('#child').click(function(e){
$(this).addClass();
e.stopPropagation();
});
$('#parent').click(function(){
$('#child').removeClass();
});

Performing an action on Parent/Child elements independently

I have HTML similar to the following in my page
<div id="someDiv">
<img src="foo.gif" class="someImg" />
</div>
The wrapper div is set up such that when it is clicked, it's background-color changes using the following jQuery code.
$("div").click(function(event){
$(this).css("background-color", "blue");
});
I also have some jQuery associated with my img that will do some other function (for the sake of argument I am going to display and alert box) like so:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
});
The issue I have come across is that when I click on the img, the event associated with the div is also triggered. I'm pretty sure that this is due to the way that jQuery (or indeed JavaScript) is handling the two DOM elements - clicking the img would require you to also technically click the div, thus triggering both events.
Two questions then really:
Is my understanding of the
DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
Are there any jQuery methods that
would allow me to perform actions on
a child element without invoking
those associated with its parent?
That is known as event bubbling, you can prevent it with stopPropagation():
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
.
Is my understanding of the DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
That is because of what is known event bubbling.
Are there any jQuery methods that would allow me to perform actions
on a child element without invoking
those associated with its parent?
Yes, you need stopPropagation()
No, this is by design. Events bubble up through the entire dom, if you put another handler on body, it would fire too
Yes :) JQuery normalizes the event object, so adding event.stopPropagation() in your img click handler will give you the behavior you expect on all browsers
The problem you just facing is called "event bubbling". That means, if you click on a nested
element, that click event will "bubble up" the DOM tree.
If other elements also are bound to an click event, their listeners will fire aswell.
Solution to prevent this is called:
stopPropagation()
which is used within your event handler
$("img[class=someImg]").click(function(event){
event.stopPropagation();
alert("Image clicked");
});
This is what's called event bubbling, and you can stop it to get the behavior you want with .stopPropagation() (or return false; if you want to stop the event completely, including handlers on the same level), like this:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
You can view a demo here, comment it out and click run again to see the difference.
The short version is that when most event types happen, they happen on the immediate element, then bubble up the DOM, occurring on each parent as they go. This is not jQuery specific at all, native JavaScript does this. If you're more curious, I'd read the linked article, it has a great explanation of what's going on.

How to stop event bubbling with jquery live?

I am trying to stop some events but stopPropagation does not work with "live" so I am not sure what to do. I found this on their site.
Live events do not bubble in the
traditional manner and cannot be
stopped using stopPropagation or
stopImmediatePropagation. For example,
take the case of two click events -
one bound to "li" and another "li a".
Should a click occur on the inner
anchor BOTH events will be triggered.
This is because when a
$("li").bind("click", fn); is bound
you're actually saying "Whenever a
click event occurs on an LI element -
or inside an LI element - trigger this
click event." To stop further
processing for a live event, fn must
return false
It says that fn must return false so what I tried to do
$('.MoreAppointments').live('click', function(e) {
alert("Hi");
return false;
});
but that did not work so I am not sure how to make it return false.
Update
Here is some more information.
I have a table cell and I bind a click event to it.
$('#CalendarBody .DateBox').click(function(e)
{
AddApointment(this);
});
So the AddApointment just makes some ui dialog box.
Now the live code(MoreAppointments) sits in this table cell and is basically an anchor tag. So when I click on the anchor tag it first goes to the above code(addApointment - so runs that event first) runs that but does not launch my dialog box instead it goes straight to the (MoreAppointment) event and runs that code. Once that code has run it launches the dialog box from "addApointment".
Update 2
Here is some of the html. I did not copy the whole table since it is kinda big and all the cells repeat itself with the same data. If needed I will post it.
<td id="c_12012009" class="DateBox">
<div class="DateLabel">
1</div>
<div class="appointmentContainer">
<a class="appointments">Fkafkafk fakfka kf414<br />
</a><a class="appointments">Fkafkafk fakfka kf414<br />
</a><a class="appointments">Fkafkafk fakfka kf414<br />
</a><a class="appointments">Fkafkafk fakfka kf414<br />
</a><a class="appointments">Fkafkafk fakfka kf414<br />
</a>
</div>
<div class="appointmentOverflowContainer">
<div>
<a class="MoreAppointments">+1 More</a></div>
</div>
</td>
The short answer is simply, you can't.
The problem
Normally, you can stop an event from "bubbling up" to event handlers on outer elements because the handlers for inner elements are called first. However, jQuery's "live events" work by attaching a proxy handler for the desired event to the document element, and then calling the appropriate user-defined handler(s) after the event bubbles up the document.
(source: shog9.com)
This generally makes "live" binding a rather efficient means of binding events, but it has two big side-effects: first, any event handler attached to an inner element can prevent "live" events from firing for itself or any of its children; second, a "live" event handler cannot prevent any event handlers attached directly to children of the document from firing. You can stop further processing, but you can't do anything about processing that has already occurred... And by the time your live event fires, the handler attached directly to the child has already been called.
Solution
Your best option here (so far as I can tell from what you've posted) is to use live binding for both click handlers. Once that's done, you should be able to return false from the .MoreAppointments handler to prevent the .DateBox handler from being called.
Example:
$('.MoreAppointments').live('click', function(e)
{
alert("Hi");
return false; // prevent additional live handlers from firing
});
// use live binding to allow the above handler to preempt
$('#CalendarBody .DateBox').live('click', function(e)
{
AddApointment(this);
});
I've used such kind if code and it worked for me:
$('#some-link').live('click', function(e) {
alert("Link clicked 1");
e.stopImmediatePropagation();
});
$('#some-link').live('click', function(e) {
alert("Link clicked 2");
});
so, it seems to me, that now JQuery support stopImmediatePropagation with live events
Maybe you could check that the click event didn't occur on an a element:
$('#CalendarBody .DateBox').click(function(e) {
// if the event target is an <a> don't process:
if ($(e.target).is('a')) return;
AddApointment(this);
});
Might Work?
I'm using this:
if(event.target != this)return; // stop event bubbling for "live" event
I use
e.stopPropagation(); // to prevent event from bubbling up
e.preventDefault(); // then cancel the event (if it's cancelable)
I've used this in certain situations. Note: not always applicable, so assess for your needs as always:
html:
Click me
js (in your live event handler):
if(e.target.className == 'my-class-name') {
e.preventDefault();
// do something you want to do...
}
This way, my live event only 'runs' when a particular element type/classname attr is clicked.
The e.preventDefault() here is to stop the link I'm clicking moving the scroll-position to the top of the page.
Simply use **"on"** function to bind click event of child as well as parent element.
Example : $("#content-container").on("click","a.childElement",function(e){
alert("child clicked");
e.stopPropagation() ;
});
$("#content-container").on("click","div.parentElement",function(e){
alert("parent clicked");
});
( where content-container is the outer div containing both parent as well as child elements. )
Here only "child clicked" alert will occur.
Thanks.

Categories

Resources