Stop propagation of event from child to parent and inbetween ones - javascript

Sorry the title maybe a bit bogus. but here its, imagine I have 3 divs like this :
<div id="1" class="clickable">
<div id="2" class="some random thing">
<div id="3" class="clickable">
</div>
</div>
</div>
now imagine I have
$('.clickable').on('click',function(){blahblah});
I want them both to be clickable but not at the same time.
When I click the inside clickable class div (where the id is 3) both the the inner one and the parent one will trigger the blahblah. I know I can use something like
$('.clickable').on('click',function(e){e.stopPropagation(); blahblah});
but the problem is even if I do that, clicking the middle child (the one with the id of 2) will trigger the blah blah on the parent as well.
Is there anyway to stop that? For example if this div and only this div not parent not child, only this div has the class of clickable, be clickable.
Thank you very much.

event.stopPropogation stops the event from bubbling up the event chain, but this conflicts when you click on the middle div. I am posting the javascript code that you can refer to. Inside the click event listener, the conditional if block checks whether the event was triggered in that particular div element, hence stopping the event bubbling.
document.getElementById("outer").addEventListener('click', function(event) {
if (event.target !== this) {
return;
}
alert("You clicked outer div!");
});
document.getElementById("middle").addEventListener('click', function(event) {
if (event.target !== this) {
return;
}
alert("You clicked middle div!");
});
document.getElementById("inner").addEventListener('click', function(event) {
alert("You clicked inner div!");
});
You can also refer to this fiddle : https://jsfiddle.net/9fskuunr/3/

You might want to do something like this
$(".clickable").click(function(e){
e.stopPropagation();
var id = $(e.target).attr('id');
alert(id + ' is clicked');
}).children(':not(.clickable)').click(function(e) {
return false;
});
See the JSFiddle here: https://jsfiddle.net/3h4yvfv4/1/

One approach you might want to consider is to use event delegation. That way you only assign one event handler, rather than multiple event handlers for every .clickable element, which may give you a performance benefit depending on how many .clickable elements you have on a given page.
The code using event delegation looks something like this:
$(document).on('click', function(e) {
var target = $(e.target),
isClickable,
closestClickable,
isNestedInClickable;
isClickable = target.hasClass('clickable');
if (isClickable) {
handleClick(e);
} else {
closestClickable = target.closest('.clickable');
isNestedInClickable = !!closestClickable;
}
if (isClickable || isNestedInClickable) {
e.stopPropagation();
}
});
function handleClick(e) {
console.log(e.target.id + ' clicked');
}
JSFiddle: https://jsfiddle.net/sytkvgng/2/

Related

javascript - Mouse Click on parent element , but prevent it's child element to fire event

How to define on child element, not to trigger an event,
like on .mouseclick = 'no-event' or : .mousedown = 'no-event'
with natural Javascript command?
So, it's parent will get the the first event before bubbling-up,
the parent element event will get e. target = e.currentTarget
I need to do it on some specific child elements, and some not.
On parent element the 'capture', does not give the correct behaviour
as mentioned above.
All other solutions I follow on the web, are giving many 'tricky' solutions
that don't work correctly.
var parentEl = document.getElementById('parent');
var childEl = document.getElementById('child');
parentEl.addEventListener('click', function(event) {
}, true)
childEl.addEventListener('click', function(event) {
}, true)
If you pass the true on eventListener it stops the event from bubbling.
You could give the children a custom class which determines it should be clickable or not.
Inside the callback function for it's parent container check which class the target is using and act accordingly.
Here's an example:
document.getElementById("container").addEventListener('click', function(e) {
if (e.target.className == "clickable") {
console.log("I should do something!");
} else {
console.log("I should NOT do something!");
}
});
<div id="container">
<div class="clickable">
this is clickable
</div>
<div>
this is not clickable
</div>
</div>

js line causes code above it not to execute

Here's a pen with the full html: https://codepen.io/froggomad/pen/WLdzoB
I'm writing 2 functions - one to show hidden content, and one to hide it. I'm wanting the show() function to execute on the parent div and the hide() function to execute on the div with the selector .click-text.
However, I'm switching text on .click-text from show to hide so I don't want the hide function to remain on the text at all times. I also want it obvious that its interactive text when changing to a hide function, so I make it a link.
That's all well, but when attempting to set the onclick Attr of the parent back to the show() function, nothing in the hide block executes at all.
If I remove the line setting the parent's onclick Attr, the script executes as expected. If I set another element's onclick Attr, the script executes as expected.
However, with that line in there, nothing happens and there's no output in the console to indicate an error. I even set an alert with the type of element and classname to ensure I'm targeting the right element.
Get closest parent of element matching selector:
var getClosest = function (element, selector) {
for ( ; element && element !== document; element = element.parentNode ) {
if ( element.matches(selector) ) return element;
}
return null;
}
Show Hidden Element ul.service-category-menu
function show(elem) {
var menu = elem.querySelector("ul.service-category-menu"),
click = elem.querySelector(".click-text"),
parent = getClosest(elem, '.service-category');
;
if (menu.style.display === "none" || menu.style.display === "") {
menu.style.display = "block";
click.innerHTML = "<a href=\"#\">Click to Hide<\/a>";
click.setAttribute('onclick','hide(this);');
elem.setAttribute('onclick', 'null');
}
}
Hide Element
function hide(elem) {
var parent = getClosest(elem, '.service-category'),
menu = parent.querySelector("ul.service-category-menu"),
click = parent.querySelector(".click-text")
;
alert(parent + "\n" + parent.className);
//Outputs div element with expected class name (class name is unique on each div)
if (menu.style.display === "block") {
menu.style.display = "none";
click.innerHTML = "Click to Show";
click.setAttribute('onclick', 'null');
//the above lines don't execute when the following line is in place. There's no error in console.
parent.setAttribute('onclick','show(this)');
}
}
First off, I must confess that I'm against using onclick attributes. If you're not using a framework such as VueJS or React, I think HTML and JS should remain separated for better control and maintainability.
You can use addEventListener, removeEventListener, and e.stopPropagation() to avoid triggering multiple event handlers.
Events have two phases:
Event capture: the event spreads from the document all the way down to the target element.
To catch an event during this phase, do:
elm.addEventListener('click', myFunc, true);
Event bubbling: the event bounces back from the target to the document.
To catch an event during this phase, do:
elm.addEventListener('click', myFunc, false); /* or just omit the 3rd param */
Using e.stopPropagation() allows you to break that chain.
// When the DOM is ready
window.addEventListener("DOMContentLoaded", init);
function init() {
// Get all categories
var $categories = document.querySelectorAll(".service-category");
// For each of them
Array.from($categories).forEach(function($category) {
// Add an event listener for clicks
$category.addEventListener("click", show);
});
}
function getClosest(element, selector) {
for (; element && element !== document; element = element.parentNode) {
if (element.matches(selector)) return element;
}
return null;
}
function show(e) {
var $menu = this.querySelector("ul.service-category-menu"),
$click = this.querySelector(".click-text");
if (["none", ""].includes($menu.style.display)) {
$menu.style.display = "block";
$click.innerHTML = 'Click to Hide';
$click.addEventListener("click", hide);
// Remove the `show` event listener
this.removeEventListener("click", show);
}
e.stopPropagation();
}
function hide(e) {
var $parent = getClosest(this, ".service-category"),
$menu = $parent.querySelector("ul.service-category-menu"),
$click = $parent.querySelector(".click-text");
if (!["none", ""].includes($menu.style.display)) {
$menu.style.display = "none";
$click.innerHTML = "Click to Show";
$click.removeEventListener("click", hide);
$parent.addEventListener("click", show);
}
e.stopPropagation();
}
.service-category{display:inline-block;border:3px solid #ccc;margin:1%;font-weight:700;font-size:3.5vw;cursor:pointer;background-color:#fff;z-index:3;background-position:center;background-size:cover;color:#000}.click-text{text-align:right;font-size:1.25vw;font-style:italic;font-weight:700;padding-right:1%}.service-category:hover .click-text{color:#b22222}.service-category-menu{display:none;margin-left:8%;margin-right:8%;margin-top:1%;background-color:#fff;font-weight:700;font-size:1.6vw;border-radius:10px}
<div class="service-category web-back" id="web-back">
<div class="row-overlay">
Web <br /> Development
<div class="click-text">Click to Show</div>
<ul class="service-category-menu web">
<li>
Some text...
</li>
</ul>
</div>
</div>
<div class="service-category web-front" id="web-front">
<div class="row-overlay">
Web <br /> Design
<div class="click-text">Click to Show</div>
<ul class="service-category-menu web">
<li>
Some text...
</li>
</ul>
</div>
</div>
It is executed, it's just after you click that Click to Hide, the event continues to parent and the event handler of the parent executed. Thus, what exactly happen is (with that line), after hide() called, you inadvertently called show().
In javascript it's usually called bubbles (when you click the children, the click handler of parent will also be executed after click handler of children complete).
So the solution, you can add this line at the end of the hide() function
event.stopPropagation();
To stop the event from continuing to the parent
Setting event.stopPropagation as mentioned in the other answer will potentially fix your issue. Alternatively, you can change the last line of your hide function to window.setTimeout(e => parent.setAttribute('onclick','show(this)'), 0).
What's happening right now is:
You click
it executes your hide function, and during that function it binds a click event to the parent
The click propagates to the parent and executes the newly bound function, re-showing the content.
By using setTimeout(fn, 0), you're making sure the click event completes before the function is bound to the parent.

attach an event to the body when ul is visible, then remove it when invisible

I have a <ul> that when clicked, toggles the visibility of another <ul>. How can I attach an event to the body of the page when the <ul>s are revealed so that the body will hide the <ul>.
I am new to writing these sorts things which bubble, and I cannot figure out why what I have done so far seems to work intermittently. When clicked several times, it fails to add the class open when the secondary <ul> is opened.
And of course, there may be an entirely better way to do this.
$(document).on('click', '.dd_deploy', function (e) {
var ul = $(this).children('ul');
var height = ul.css('height');
var width = ul.css('width');
ul.css('top', "-" + height);
ul.fadeToggle(50, function () {
//add open class depending on what's toggled to
if (ul.hasClass('open')) {
ul.removeClass('open');
} else {
ul.addClass('open');
}
//attach click event to the body to hide the ul when
//body is clickd
$(document).on('click.ddClick', ('*'), function (e) {
e.stopPropagation();
//if (ul.hasClass('open')) {
ul.hide();
ul.removeClass('open')
$(document).off('click.ddClick');
// }
});
});
});​
http://jsfiddle.net/JYVwR/
I'd suggest not binding a click event in a click event, even if you are unbinding it. Instead, i would do it this way:
http://jsfiddle.net/JYVwR/2/
$(document).on('click', function (e) {
if ( $(e.target).is(".dd_deploy") ) {
var ul = $(e.target).children('ul');
var height = ul.css('height');
var width = ul.css('width');
ul.css('top', "-" + height);
ul.fadeToggle(50, function () {
//add open class depending on what's toggled to
if (ul.hasClass('open')) {
ul.removeClass('open');
} else {
ul.addClass('open');
}
});
}
else {
$('.dd_deploy').children('ul:visible').fadeOut(50,function(){
$(this).removeClass("open");
})
}
});​
If you need to further prevent clicking on the opened menu from closing the menu, add an else if that tests for children of that menu.
You dont' really need all that code. All you need is jquery's toggle class to accomplish what you want. simple code like one below should work.
Example Code
$(document).ready(function() {
$('ul.dd_deploy').click(function(){
$('ul.dd').toggle();
});
});​​​​
Firstly, you are defining a document.on function within a document.on function which is fundamentally wrong, you just need to check it once and execute the function once the document is ready.
Secondly why do you want to bind an event to body.click ? it's not really a good idea.
Suggestion
I think you should also look at the hover function which might be useful to you in this case.
Working Fiddles
JSfiddle with click function
JSfiddle with hover function

Handle clicks on an element but not clicks on links within that element

How do I handle clicks on an element but not clicks on links within that element?
Say I have the following
<div class="section">
Some text blah blah
Link
</div>
<script>
$(".section").click(function() {
// code to expand/collapse section
});
</script>
And I want users to be able to click on .section in order to expand/collapse it but I don't want that to happen if they clicked on a link within that section.
You can check the event target, and only do something if it is the div. Here is one example:
$(".section").click(function(e) {
if(e.target.tagName !== "DIV") {
//Not the div!
}
});
You can test various properties of the target, so if for example you had other child div elements, but didn't want to register clicks on those, you could use the className property:
e.target.className !== "section"
Here is a working example (using the tagName property).
var $section = $(".section");
$section.click(function(e) {
if ($(e.target).get(0) == $section.get(0)) {
alert("expanding...");
}
});
http://jsfiddle.net/yLMVy/
$(".section").click(function(e) {
if($(e.target).is('a')) {
return;
}
// code to expand/collapse section
});
e.target points to the element on which the user had clicked. So the function checks whether the target was a a element. if yes, it just returns from the function
use return false; in the end of function

jquery: get mouse click if inside a div or not

i have this HTML page
<html>
<body>
<div>a</div>
<div>b</div>
<div>c</div>
<div>d</div>
<div id='in_or_out'>e</div>
<div>f</div>
</body>
</html>
a,b,c,d,e and f could be divs also not just a plain text.
I want to get the mouse click event, but how could i know if it's inside or outside #in_or_out div ?
EDIT :: guys, i know how to check if the div is click or not, but i want my event to be fired when the click is outside that div
$("body > div").click(function() {
if ($(this).attr("id") == "in_or_out") {
// inside
} else {
// not inside
}
});
EDIT: just learned, that there is a negate:
$("body > div:not(#in_or_out)").click(function(e) {
// not inside
});
If you want to detect whether or not you've clicked inside or outside the div, set the event handler on the documentElement and let it propagate from the other elements upwards:
$("html").click(function (e)
{
if (e.target == document.getElementById("in_or_out"))
alert("In");
else
alert("Out!");
});
Maybe this one will help you
$('body').click(function(){
//do smth
});
$('div#in_or_out').click(function(e){
e.stopPropagation();
// do smth else
});
Depends what you want. If you only want to execute code, when it was inside #in_or_out, you can do:
$('#in_or_out').click(function(){ /* your code here */ });
You can have a status variable that says whether the mouse is in #in_or_out or not:
var inside = false;
$('#in_or_out').hover(function() { inside = true; }, function() { inside = false; });
Then whenever a click occurs you can check with inside whether the click was inside in_or_out or not.
Reference: .hover()
Update:
No matter to which element you bind the click handler, you can always do this:
$('element').click(function() {
if ($(this).attr('id') !== 'in_or_not') {
}
});
for inside it would be
$("#in_or_out").click(function() {
// do something here
});
for outside...I've got no idea.
Edit: You could try to do the same for body-tag (assigning a click-handler to the document itself). But I'm not sure if both events would fire by that.
Like this?
$("#in_or_out").click(function() {
alert("IN DIV!");
});

Categories

Resources