jquery event bubbling and event.targets - javascript

I'm trying to get my head around how to stop a click event from bubbling up,out,down when a particular element is clicked.
<div class="clickable">
clicking here shouldn't affect parents or children
<div class="clickable">
clicking here shouldn't affect parents or children
<div class="clickable">
clicking here shouldn't affect parents
</div>
</div>
</div>
Basically, if an element with the class "clickable" is clicked, I only want that item to be affected, toggling an even/odd class.
Here's the fiddle: http://jsfiddle.net/LDaA7/
When you run the fiddle and click a particular item, you'll see that various parents and children also get toggled.
From experimenting, event.stopPropagation(), returning false/true, .one, etc. impacts click events not targets per se. I may want other (unrelated) click events to work.
How do I target only the element I'm clicking?

You can just check if the target equals the bound element (this):
$("body").on "click", ".clickable", (event) ->
if this is event.target
clickTarget = $(event.currentTarget).closest(".clickable")
clickTarget.addClass "clicked"
if clickTarget.hasClass "odd"
clickTarget.addClass("even").removeClass("odd")
else
clickTarget.addClass("odd").removeClass("even")
FIDDLE

You want to use $(this) to target the element you have clicked. Also, .stopPropagation() so it won't bubble up the events.
See Demo here
$('.clickable').click(function (e) {
e.stopPropagation();
var elem = $(this);
elem.addClass('clicked');
if (elem.hasClass('odd')) {
elem.addClass('even');
elem.removeClass('odd');
} else {
elem.removeClass('even');
elem.addClass('odd');
}
});

Related

jQuery .html() not setting html at all

I have a simple thing. When a user clicks on the edit link it turns the previous element into an input element for editing and the edit into a cancel. If the user decides not to edit he can click on cancel and everything should revert to its initial state.
Right now this is not working at all:
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
HTML:
<div class='photo-section'>
<div class='photo-head'>
<div class='photo-info'>
Photo Name : <span class='photo-name'>Work selfie</span>
<span class='title-edit'><a href='#'>edit</a></span>
</div>
</div>
<div class='photo'>
<img src='' alt='' title=''>
</div>
<div class='tag-section'>
<div class='tags'>Photo Tags:
<span>#code#coffee#late#night</span>
<span class="tags-edit">edit</span>
</div>
</div>
</div>
CSS:
// JavaScript to handle photo operations
$(document).ready(function() {
// show/hide edit option
$('.photo-info, .tags').hover(function() {
$(this).find('.title-edit > a, .tags-edit > a').addClass('visible');
}, function() {
$(this).find('.title-edit > a, .tags-edit > a').removeClass('visible');
});
// show editable area
$('.title-edit, .tags-edit').on('click', function () {
edit(this);
});
});
function edit(elem) {
// change element into an input elemnt for editing
var $item = $(elem).prev();
var text = $item.text();
$item.html("<input type='text'>").find('input').attr('value', text);
// change edit to cancel if input element present
if ($('input').length) {
$item.next().html("<a href='#' class='cancel'>cancel</a>");
}
// change cancel back to edit
if ($('.cancel').length) {
$('.cancel').on('click', function() {
$(this).parent().html("<a href='#'>edit</a>");
});
}
}
Result: https://jsfiddle.net/hgwkxygz/6/
Any help would be great!
This is a very common case of attaching event at wrong time in javascript.
Actually you are removing and re-adding a DOM element. So the already attached event to .cancel won't work this time. You again have to write event listener on .cancel whenever you attach a new DOM element after clicking on edit button.
Basically it means whenever you do .html(), you have to re-add event listener for click.
There are various approach to solve this problem.
1) make a function which attach the DOM element as well as click event to that DOM element. Call that function only on click events.
2)Do event delegation.
3)Do not remove DOM elements on click events, rather hide and show elements so that you wont loose your event listeners.
4)If you really have to do remove and re-add DOM elements then in my opinion, best approach is to make a class, where you make DOM elements, add event listeners privately in that class, and on click events just make new instance of that class.
You can check out these options in detail on web.

different types of jQuery selectors

What’s the difference between $(“#foo .bar”) and $(“#foo”).find(“.bar”)?
$('#foo').on('click', function(){
$(this).find('.bar').css('background-color', 'yellow');
})
$('#foo_two .bar_two').on('click', function(){
$(this).css('background-color', 'red');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id = "foo">
<p class= 'bar' style='background: green'> Hello there</p>
</div>
<div id = 'foo_two'>
<p class = 'bar_two' style='background: orange'> Hello there</p>
</div>
In the snippet, I tried to outline what I thought was the difference but seem to now not know what's going on...
$('#foo').on('click', function(){
means that the parent #foo is the click target Element
$('#foo_two .bar_two').on('click', function(){
... click on the parent #foo_two if you dare! http://jsfiddle.net/0qcssuue/2/
(.bar_two has now the click event bound to it. #foo_two just helped jQuery and the JS parser to find it's child .bar_two Element)
To conclude, the $(this) inside the function refers to the targeted Selector.
In the first case it's #foo,
in the second it's #foo_two .bar_two (the #foo_two's children .bar_two)
In your case you could not notice the difference cause the parent was wrapping so close the child element that every click seemed to target the same selector. Adding some padding to the parent (like in my demo) makes the difference more clear.
The difference is in which object you are attaching the listener for click event.
For case:
$('#foo').on('click', function(){
$(this).find('.bar').css('background-color', 'yellow');
})
You are attaching the listener to the #foo object.
For case:
$('#foo_two .bar_two').on('click', function(){
$(this).css('background-color', 'red');
})
You are attaching the listener to the #foo_two .bar_two object
I adjust your fiddle to show the difference. Green label changes when you click de div element but orange label changes when you click the p element
http://jsfiddle.net/0qcssuue/3/

jQuery - remove element on blur BUT catch click on his children

I have following code:
$('#myEl').blur(function(){
$(this).remove('.children');
});
But the children element have links inside, with another jQuery actions which doesn't trigger because the .children is removed on blur, which I guess is triggered before the click action. Simple example:
Children is visible and #myEl have focus
I click on the children link
#myEl loses his focus
Children element is removed
Children link action is not triggered, because I guess link is not present anymore
How to solve this? I was trying to delay remove:
$(this).delay(100).remove('.children');
With no luck.
If you are working with the delay way, you can't use jQuery .delay() since it only work on queued element (with animation).
You can use setTimeout :
$('#myEl').blur(function(){
var $this = $(this);
setTimeout(function(){
$this.remove('.children');
}, 100)
});
I've tried it with mousedown event and it worked fine. I don't thing adding a delay is always a good option.
<input type="text" id="myEl"></input>
<div class="children" >div child</div>
<script>
$('#myEl').blur(function(e){
$('.children').remove();
});
$(".children").mousedown(function() {
window.open('http://www.google.com')
});
</script>
And if you really want to add the click event for a specific reason then you can try this:-
$('#myEl').blur(function(e){
if(mousedown){
window.open('http://www.google.com');
mousedown = false;
}
$('.children').remove();
});
$('.children').click(function(e){
window.open('http://www.google.com')
});
$(".children").mousedown(function() {
mousedown = true
});
what about simply making the child elements hidden after a click? Or maybe even having the child itself remove all children from its parent container after it has processed the click?
$('#myEl').blur(function(){
$(this).children('.children').hide();
});
$('.children').on("click",function(){
// perform your click-code actions here
alert("I did it!");
// now remove your child elements from the parent
$(this).parent().children('.children').remove();
});

Two OnClick events overlap

I have an element inside an element, when I click the element underneath I want the slider to open. When I click on the outermost element I want the slider to close.
Unfortunately when I click on the outermost it clicks the underneath element as well. Is there a way to click only on the outermost element ignoring the click on the underneath element? The events are triggered on click and executed with javascript.
I tried with z-index but it still captures the underneath element clicked as well, and because the functions are contrary to one another nothing happens.
edit: on a "code is worth 1000 words" tip
var $target = $(this).data('pos', i) //give each li an index #
$target.data('hpaneloffsetw', $target.find('.hpanel:eq(0)').outerWidth()) //get offset width of each .hpanel DIV (config.dimensions.fullw + any DIV padding)
$target[haccordion.ismobile? "click" : "mouseenter"](function(){
haccordion.expandli(config.accordionid, this)
config.$lastexpanded=$(this)
})
if (config.collapsecurrent){ //if previous content should be contracted when expanding current
$('.close').click(function(){
$target.stop().animate({width:config.paneldimensions.peekw}, config.speed) //contract previous content
})
$target.dblclick(function(){
$(this).stop().animate({width:config.paneldimensions.peekw}, config.speed) //contract previous content
})
}
Because the code is borrowed, I don't understand much of it. But basically I want the "click" : "mousteenter" function to work on click, without interfering with the .close().click
It sounds like you need to stop the click event bubbling up the DOM to be caught by parent elements. You can use stopPropagation() to do this:
$('.close').click(function(e) {
e.stopPropagation();
$target.stop().animate({ width: config.paneldimensions.peekw }, config.speed);
})
$target.dblclick(function(e) {
e.stopPropagation();
$(this).stop().animate({ width: config.paneldimensions.peekw }, config.speed);
})
Try the following fiddle
$("#outer").click(function(){alert("outer clicked")});
$("#inner").click(function(e){
alert("inner clicked")
e.stopPropagation();
});
To identify the element you have "really" clicked on, you can try to identify it through accessing the target property of the jquery-event-object.
After you identified the target you clicked on, you could prevent other event handlers from firing.
Use CSS specific jquery to point exact element like below, use > to point exact child
table > tbody > tr > td > input[type='text']
like this.

How to stop propagation of a bound function not the entire event?

I have a click function bound to many elements. It is possible that sometimes these elements may sit within one another. So, the click event is bound to a child and also bound to its parent. The method is specific to the element clicked. Naturally, because of event bubbling, the child's event is fired first, and then the parents. I cannot have them both called at the same time because the parents event overwrites the event of the child. So I could use event.stopPropagation() so only the first element clicked receives the event. The problem is that there are other click events also attached to the element, for example, I am using jQuery's draggable on these elements. If I stop the propagation of the click event, then draggable doesn't work, and the following click events are not called.
So my question is: Is there a way to stop the event bubbling of the method the event will call and not the entire event?
Brilliant John, but here is the problem:
<div id="Elm1"><!-- relative -->
<div class="Elmchildren"></div><!-- absolute-->
<div class="Elmchildren"></div><!-- absolute-->
<div class="Elmchildren"></div><!-- absolute-->
<div id="Elm2"><!-- relative -->
<div class="Elmchildren"></div><!-- absolute-->
<div class="Elmchildren"></div><!-- absolute-->
<div class="Elmchildren"></div><!-- absolute-->
</div>
</div>
Click event is bound to #Elm1 and #Elm2. The .Elmchildren are width and height 100%. So they are actually the current targets.
try someting like this
$(mySelector).click(function(evt) {
if (evt.target == evt.currentTarget) {
///run your code. The if statment will only run this click event on the target element
///all other click events will still run.
}
});
The suggested solution
evt.target == evt.currentTarget
is nice, but there are cases where it does not help.
Example: A (suckerfish-style) menu structure with nested ul/li lists.
The mousemove event comes from a link inside a list item, which is a child of an ul-list, which is again a child of another list item. Typical for a html menu structure with submenus.
The evt.target would be the link tag, but we are interested in the mousemove on the list item.
Even worse: The link tag could contain span or img tags or other nested stuff. Then evt.target would be this span or img.
What seems to work here is to catch the event on a parent / root item, and then check the parents of evt.target.
Like this (with jQuery),
var $menu = $('div#menu');
$('body').mousemove(function(evt){
var element = evt.target;
// find the deepest list item that was affected by this mouseover event.
var list_item;
var in_menu = false;
while (element) {
if (element == $menu[0]) {
in_menu = true;
break;
}
else if (!list_item && element.tagName == 'LI') {
// we found a list item, but we are not sure if we are inside the menu tree.
list_item = element;
}
}
// do something with the result.
if (!in_menu) {
.. // close all submenus
}
if (list_item) {
.. // open the submenu for this list item.
}
else {
// mouse in menu, but not hovering an item.
// leave the submenus open. (?)
}
});
Maybe some of this could be abbreviated with jQuery like $(evt.target).parents().is($menu), but I did not get this to work. Also, I would guess that this explicit loop with element.tagName is faster.

Categories