I have a Marionette ItemView which is listening for a click on an li; all click events for ItemViews in the ui below that are being ignored for the click handler on the parent li.
I have found a workaround that involves calling e.stopPropagation but it seems the actual solution may be to target the anchor tag rather than the li.
Some sample code:
var LiView = Marionette.ItemView.extend({
events : {
'click' : function(e){
console.log(e.target);
}
}
});
var AnchorView = Marionette.ItemView.extend({
events : {
'click' : function(e){
e.preventDefault();
console.log(e.target);
}
}
});
new LiView({el: 'li'});
new AnchorView({el: 'li a'});
Without stopPropagation the li click event still fires - just as it would when you have multiple jQuery click handlers. The problem is that e.target will refer to the anchor in both the click handlers - something that is undesirable.
Any thoughts on a better way to structure this?
I would just use e.stopPropagation since this is how event bubbling is supposed to work and you're wanting to prevent it from bubbling up.
Since you're binding to click in your LiView there isn't a way to get around that being called unless you bind to something specific in the li that is not the a or parent of the a (as bubbling would still be a problem here).
The alternative (which is messy in my opinion) is to check inside of the LiView click handler against e.target and e.currentTarget to make sure they are the same (if the li itself is what you want clicked unless other elements bubbling up is desirable):
events: {
'click' : function(e) {
if(e.target == e.currentTarget) {
// the person actually clicked on the LI and not something inside of it.
}
}
}
Related
first i created navigation click event
$('#inner-navigation li a')
.on('click', function (e) {
e.preventDefault();
AjaxNavUrl.checkURL(this.hash);
});
then it conducts ajax call and response html data
based on navigation key
$(".panel-body").html(data);
first ajax click working nicely..
then whithin that responese html data there is rest of click event and ajax call like
$(document.body).on('click', '.page-demos .page-wrapper', function (e) {
e.preventDefault();
and this
$(document.body).on('click', '.button-next', function (e) {
e.preventDefault();
but it seems like click event or e.preventDeafult() function is not working
I got the answer from jQuery doc here is what I learned,
Event Propagation
Understanding how events propagate is an important factor in being able to leverage Event Delegation. Any time one of our anchor tags is clicked, a click event is fired for that anchor, and then bubbles up the DOM tree, triggering each of its parent click event handlers:
<a>
<li>
<ul #list>
<div #container>
<body>
<html>
document root
This means that anytime you click one of our bound anchor tags, you are effectively clicking the entire document body! This is called event bubbling or event propagation.
Since we know how events bubble, we can create a delegated event:
$("#list").on("click", "a", function(event) {
event.preventDefault();
console.log($(this).text());
});
Notice how we have moved the a part from the selector to the second parameter position of the .on() method. This second, selector parameter tells the handler to listen for the specified event, and when it hears it, check to see if the triggering element for that event matches the second parameter. In this case, the triggering event is our anchor tag, which matches that parameter. Since it matches, our anonymous function will execute. We have now attached a single click event listener to our <ul> that will listen for clicks on its descendant anchors, instead of attaching an unknown number of directly bound events to the existing anchor tags only.
linkUsing the Triggering Element
What if we wanted to open the link in a new window if that link is an external one (as denoted here by beginning with "http")?
// Attach a delegated event handler
$("#list").on("click", "a", function(event) {
var elem = $(this);
if (elem.is("[href^='http']")) {
elem.attr("target", "_blank");
}
});
This simply passes the .is() method a selector to see if the href attribute of the element starts with "http". We have also removed the event.preventDefault(); statement as we want the default action to happen (which is to follow the href).
We can actually simplify our code by allowing the selector parameter of .on() do our logic for us:
// Attach a delegated event handler with a more refined selector
$("#list").on( "click", "a[href^='http']", function(event) {
$(this).attr("target", "_blank");
});
The click binding adds an event handler so that your chosen JavaScript function will be invoked when the associated DOM element is clicked. This is most commonly used with elements like button, input, and a, but actually works with any visible DOM element.
Example
<div>
You've clicked <span data-bind="text: numberOfClicks"></span> times
<button data-bind="click: incrementClickCounter">Click me</button>
</div>
<script type="text/javascript">
var viewModel = {
numberOfClicks : ko.observable(0),
incrementClickCounter : function() {
var previousCount = this.numberOfClicks();
this.numberOfClicks(previousCount + 1);
}
};
</script>
Each time you click the button, this will invoke incrementClickCounter() on the view model, which in turn changes the view model state, which causes the UI to update.
So, i wondered, why this code doesn't work properly, and what can i do, to prevent such a behaviour:
If I would need to prevent event propagation of parent, whilst particular child got clicked, i used method 1, but it seems not to be working, but method 2 is working fine though.
//method 1
$(document).on({
click: function(e) {
console.log('clicked!');
e.preventDefault();
return false;
}
}, '.hax');
//method 2
/*$('.hax').on('click', function(e){
e.preventDefault();
return false;
});*/
//uncommenting will prevent event propagation
.hax {
background-color: whitesmoke;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='wrapper' onclick='alert("hello")'>
<div class='hax'>hax!</div>
</div>
Method 1 Is using event delegation ,so in it event is not directly bind with the element , its bound with the parent ,So in your case the parent is document . in this the case whatever event will be fired for that particular element it will be tracked down from the DOM tree and will execute the parent call before. In your case it will first call the alert from parent .
In method 2 - event is directly bound with the element , The event of parent will still got fired unless you prevent that in the handler but since the handler is bound to the target , you will not face any other action(alert in your case)
Get better Idea of
Event Delegation
You are creating an event delegation by method 1, which can be created the following way too:
$(document).on('click', '.hax', function (e) {
console.log('clicked!');
e.preventDefault();
return false;
});
For clarifying event delegation briefly:
Understanding how events propagate is an important factor in being able to leverage Event Delegation. Any time one of our anchor tags is clicked, a click event is fired for that anchor, and then bubbles up the DOM tree(Up to DOM top), triggering each of its parent click event handlers.
It does not mean you can't achieve your goal here with this method, but in order to make it work, you can create a middle parent for div.hax which is descendant of div.wrapper. I mean:
<div class='wrapper' onclick='alert("hello")'>
<div id="stopHere">
<div class='hax'>hax!</div>
</div>
</div>
Now, we can use method 1, but we only need to stop event propagation / event delegation before it reach div.wrapper. Thus in our newly added div#stopHere:
$("div#stopHere").on('click', '.hax', function (e) {
console.log('clicked!');
e.preventDefault();
return false;
});
I have two elements, like this:
var parent = document.getElementById("el");
var child = parent.children[0];
and then a eventListener:
parent.addEventListener("click", function() {
alert("Hello World!")
});
and the problem is that when you click the child, the parents click event will be fired.
I want only when you click on the parent to fire the alert, not when you click on the child.
I am not using JQuery and won't.
I have searched on google, and all results uses e.stopPropogation which is JQuery.
So what's the best way to do this in plain JS?
Thanks alot.
You need to prevent event bubbling up to the parent element. For this you have to bind one more event handler to the child element:
child.addEventListener("click", function(e) {
e.stopPropagation();
});
Click event bubbles which means that event travels up DOM tree from child to parent unless its propagation is stopped.
You can check the ID of the clicked element and use that to control the trigger:
http://jsfiddle.net/nccjgtp6/
<div id="el">
This is a thing.
<div id="el2"> This is a second thing. </div>
</div>
var parent = document.getElementById("el");
var child = parent.children[0];
parent.addEventListener("click", function(e) {
console.log(e);
if(e.srcElement.id == 'el') {
alert('Hello world');
}
});
I do not know if this will work consistently in all browsers, but works for me in Chrome.
If I'm not mistaken, it's the event.stopPropagation(); line of code you need.
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 have a div and within the div there is a child img element.
Both div and img have their very own javascript click handler.
My objective is to have when I click on the img, only the img click is triggered.
But currently the div click handler is responding before the img click handler.
Here's the example: http://jsfiddle.net/5j73w/
I'm not sure if parent's position: relative is the culprit. But that's a compulsory style.
I also tried with z-index for img but no avail.
Thanks in advance!
Replace the deprecated .live() by .on() as you're using jQuery 1.7+.
$('#parent > img').on('click', function(e) {
Fiddle
Or, if you need the event delegation (e.g. in case you're adding content dynamically to #parent):
//run this line when #parent is in the DOM
$('#parent').on('click', '> img', function(e) {
Fiddle
.live bubbles the event all the way up to the document to then check if the given selector matches the target element, by then you can't stop the event propagation anymore. From the docs:
Calling event.stopPropagation() in the event handler is ineffective in stopping event handlers attached lower in the document; the event has already propagated to document.
Also, to answer the "parent takes precedence" question, that's not case. When you call .live, you're actually attaching a handler to the document.
In this case, the handler attached through .click(function(){}) (which in jQuery 1.7+ is a shorthand for .on('click'[, null], function(){}), executes before the handler attached to the document, which is the expected event propagation bubbling behavior.
Was able to fix by doing the following:
$('#parent').click(function(e)
{
console.log("parent click");
});
$('#child').click(function(e)
{
console.log("child click");
e.stopPropagation();
});
Here's the Fiddle.