I don't understand why when I add a click event listener on an element, its children triggers it too.
I want that the parent is triggered even if the children are clicked, which should be the normal behavior I think.
Here's the code :
var jobsList = document.querySelectorAll('.job_list .job');
for (var i = 0; i < jobsList.length; i++) {
jobsList[i].addEventListener('click', _onChangeJob, false);
}
function _onChangeJob(e){
// When The children (.job_title, .job_date) is clicked, this function is called. I want only the parent to be clicked event if the children are clicked.
}
<div class="job_list">
<div class="job active" data-job="1">
<p class="job_title">Job</p>
<p class="job_date">21.11.16</p>
</div>
<div class="job active" data-job="1">
<p class="job_title">Job</p>
<p class="job_date">21.11.16</p>
</div>
</div>
You can use e.target to check what has been clicked, then for example check if the jobsList collection contains the target element. If it does then one of the .job elements is the target, otherwise the target is a child element.
// convert to a regular array to get indexOf method
var jobsList = [].slice.call(document.querySelectorAll('.job_list .job'));
for (var i = 0; i < jobsList.length; i++) {
jobsList[i].addEventListener('click', _onChangeJob, false);
}
function _onChangeJob(e){
if( jobsList.indexOf( e.target ) !== -1 ){
console.log( 'clicked on .job')
}
else {
console.log( 'clicked on a child element')
}
}
https://jsfiddle.net/4r7hLua2/
Related
How do I get the highest parent element of an element that was clicked on that is still a child of the element that the event listener was added to? Does this exist as a property for the event object?
I think you've to go with the "old-fashion" way with this, i.e. iterate through the event.target parents until the correct child is found.
const target = document.getElementById('currentTarget');
target.addEventListener('click', function(e) {
let target = e.target,
parent = target,
outmost = e.currentTarget;
console.log('The clicked element is', target.id);
while (parent) {
if (parent.parentElement === outmost) {
break;
}
parent = parent.parentElement;
}
console.log(parent ? 'Hit ' + parent.id : 'didn\'t hit');
});
div {
margin: 20px;
border: 3px solid #ccc;
}
<div id="currentTarget">
<div id="wantedElement">
<div id="intermediate1">
<div id="intermediate2">
<div id="mostInner"></div>
</div>
</div>
</div>
</div>
This algorithm is generic, it can be used with any HTML structure, and the result doesn't depend on any identifiers of the elements. See a jsFiddle with two children in the outer wrapper.
I have created 3 div having the same class in a parent , and on the child element i am adding the active class and on click of second child adding the active class again but this time i want to remove the active state for first element.
How can i remove it in effective way?
Here is my code
<div class="tab-div">
<div class="tab">default</div>
<div class="tab" >hover</div>
<div class="tab">active</div>
</div>
Here is my javascript
var elements = document.querySelectorAll('.tab');
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('active');
elements[i].onclick = function (event) {
console.log("ONCLICK");
if (event.target.innerHTML === this.innerHTML) {
this.classList.add("active");
}
}
}
You are not removing the active class from all elements when click event is triggered. So, what you can do is to loop over again to all the div and remove the active class on click event. I have created a custom function removeClass() that removes the active class on click event.
var elements = document.querySelectorAll('.tab');
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('active');
elements[i].onclick = function (event) {
console.log("ONCLICK");
//remove all active class
removeClass();
if (event.target.innerHTML === this.innerHTML) {
this.classList.add("active");
}
}
}
function removeClass(){
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove('active');
}
}
.active{
color: green;
}
<div class="tab-div">
<div class="tab">default</div>
<div class="tab" >hover</div>
<div class="tab">active</div>
</div>
I suppose it depends how many divs you will ultimately have, and if only one div should be active at a time, but I think it would be more efficient to just find the active div and remove the class from that one rather than looping through all of them e.g.
var oldActiveElement = document.querySelector('.active');
oldActiveElement.classList.remove('active');
var newActiveElement = event.target;
newActiveElement.classList.add('active');
Since all of them have a class called tab, make sure you remove the class or property of active from all the classes by targeting the class tab and it would remove from all without doing any loop. Then add the property to the current one that is clicked.
$(".tab").click(function(){
$(".tab").removeClass('active');
$("this").addClass('active');
});
If the class is in the parent you can do sth like
$(".tab").click(function({
$(".tab").parent().removeClass('active');
$("this").parent().addClass('active');
});
I'm having problem with removing a class from an element. I've tried it many ways, but still it won't work. What could the problem be?
Thanks!
function boxClick(e) {
e.currentTarget.classList.add("valami");
e.currentTarget.setAttribute("id", "currentBox");
}
function closeBox() {
var openedBox = document.getElementsByClassName("valami");
var curBox = document.getElementById("currentBox");
curBox.classList.remove("valami");
}
var gridBoxok = document.getElementsByClassName("grid-box-content");
for (var i = 0; i < gridBoxok.length; i++) {
gridBoxok[i].addEventListener('click', boxClick, false);
}
var close = document.getElementsByClassName("close-container");
for (var i = 0; i < close.length; i++) {
close[i].addEventListener('click', closeBox, false);
}
.valami {
outline: solid yellow 1px;
}
<div class="grid-container">
<div class="grid-box">
<div class="grid-box-content">
<div class="close-container">
fdfadsf
<i class="fas fa-times"></i>
</div>
</div>
</div>
</div>
You just need to stop event bubbling in close event handler, otherwise after closeBox (removes class) event keeps propagating up the DOM tree and when it reaches .grid-box-content it causes boxClick (add class) execute again.
Try this:
function closeBox(e) {
e.stopPropagation(); // <--- add this line
var openedBox = document.getElementsByClassName("valami");
var curBox = document.getElementById("currentBox")
curBox.classList.remove("valami");
}
Demo: https://jsfiddle.net/cy5ukzcg/
I think the way you are binding your close is incorrect, I would do it like below. Also .add and .remove don't work in certain browsers so I changed the way that the class was added and removed:
function boxClick(e) {
e.currentTarget.classList += " valami";
e.currentTarget.removeEventListener('click', boxClick); // remove box click
e.currentTarget.addEventListener('click', closeBox); // bind close click
}
function closeBox(e) {
e.currentTarget.classList = e.currentTarget.className.replace(" valami", "");
e.currentTarget.removeEventListener('click', closeBox); // remove close click
e.currentTarget.addEventListener('click', boxClick); // bind box click
}
var gridBoxok = document.getElementsByClassName("grid-box-content");
for (var i = 0; i < gridBoxok.length; i++) {
gridBoxok[i].addEventListener('click', boxClick);
}
.valami {
outline: solid yellow 1px;
}
<div class="grid-container">
<div class="grid-box">
<div class="grid-box-content">
<div class="close-container">
fdfadsf
<i class="fas fa-times"></i>
</div>
</div>
</div>
</div>
Edit, sorry, didn't realise the close binding was on a different element within the parent. So close binding was correct, but you needed to stop propagation of click like in dfsq's answer. Will leave this though as an alternate way of updating class list (in case you need to support ie)
Using vanilla JS, I'm trying to create an off click - as in, if the body is clicked and it is not a certain element that is clicked, close that element.
However, it works when you click the specified element (it doesn't close), but it fires the event when you click any of that element's child nodes. I'd like the if statement to include any child nodes of that parent element, as well as the parent node itself.
HTML:
<ul id="NavSocial-target" class="Nav_social">
<li class="Nav_social_item">Facebook</li>
<li class="Nav_social_item">Twitter</li>
<li class="Nav_social_item">Google</li>
</ul>
<ul class="Nav_options FlexList">
<li class="Nav_options_item FlexList_item" id="NavSocial-trigger">Social</li>
<li class="Nav_options_item FlexList_item">Curr/Lang</li>
</ul>
Javascript:
this.triggerDOM = document.getElementById('NavSocial-trigger');
this.targetDOM = document.getElementById('NavSocial-target');
// If not list item that triggers model && if not model itself
document.getElementsByTagName('body')[0].onclick = (e) => {
if(e.target !== this.triggerDOM && e.target !== this.targetDOM){
this.removeClass();
}
};
I assume this.triggerDOM is the element you want to ignore. You need to see if the click passed through the element, with a loop:
this.triggerDOM = document.getElementById('NavSocial-trigger');
// ...
document.body.addEventListener("click", e => {
var element = e.target;
while (element && element !== document.body) {
if (element === this.triggerDOM) {
// It was a click in `this.triggerDOM` or one of its
// children; ignore it
return;
}
element = element.parentNode;
}
// It wasn't a click anywhere in `this.triggerDOM`
this.removeClass();
}, false);
Side notes on the above:
document.getElementsByTagName("body")[0] is just a long way to write document.body :-)
Using onclick doesn't play nicely with others; use addEventListener (or attachEvent on IE8 and earlier, if you still need to support seriously obsolete browsers).
Arrow functions accepting a single argument don't need () around the argument list (though of course it's fine to have them there if you prefer the style).
Working example:
this.triggerDOM = document.getElementById('NavSocial-trigger');
// ...
document.body.addEventListener("click", e => {
var element = e.target;
while (element && element !== document.body) {
if (element === this.triggerDOM) {
// It was a click in `this.triggerDOM` or one of its
// children; ignore it
return;
}
element = element.parentNode;
}
// It wasn't a click anywhere in `this.triggerDOM`
this.triggerDOM.parentNode.removeChild(this.triggerDOM);
}, false);
#NavSocial-trigger {
border: 1px solid #ddd;
background-color: #dd0;
}
<!-- Note the really deep nesting -->
<div><div><div><div><div><div><div><div><div><div>
Click
<div>
anywhere
<div>
on
<div>
this
<div>
page
<div>
except
<div id="NavSocial-trigger">
here
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div></div></div></div></div></div></div></div></div>
Let's say we have next html/css: http://jsfiddle.net/rbDKm/2/
<div id="header">header</div>
<div id="content">
<div class="actions">
<div class="actions-row">
<div class="action ">home</div>
<div class="action ">search</div>
<div class="action ">settings</div>
</div>
<div class="actions-row">
<div class="action">...</div>
<div class="action">...</div>
<div class="action">...</div>
</div>
</div>
</div>
<div id="footer">footer</div>
What I need is to detect any clicks, which were made on an "empty" area.
In provided fiddle, empty area would be everything, which has white or orange color.
Is there any technique/approach for doing this?
With your markup as you have it now, I think that means you need to detect clicks outside of any div.action, #header or #footer.
To do that, you hook click on body, and then look at the path it took to get to body:
document.body.addEventListener("click", function(e) {
var elm = e.target;
while (elm !== document.body) {
if (elm.id === "header" || elm.id === "footer") {
return;
}
if (elm.tagName.toUpperCase() === "DIV" && /(?:^| )action(?:$| )/.test(elm.className)) {
return;
}
elm = elm.parentNode;
}
// If we got here, it's on one of the desired areas
});
There are a couple of variations on that you could do. For instance, instead of className.match you could use classList on modern browsers. And modern browsers also offer Element#closest which you could use instead of the loop.
Alternately, you hook click on body and hook click on the relevant other elements and prevent the click from propagating:
function stopTheEvent(e) {
e.stopPropagation();
}
document.body.addEventListener("click", function(e) {
// It's one of the desired areas
});
var list = document.querySelectorAll("#header, #footer, div.action");
var index;
for (index = 0; index < list.length; ++index) {
list[index].addEventListener("click", stopTheEvent);
}
...but that seems messier.
Note: In the above, I haven't allowed for IE8's lack of addEventListener. If you need to support IE8, look at attachEvent("onclick", ...) and use window.event.returnValue = false; to stop propagation (or use a library that abstracts these things away).
Use the "target" of the event, and check it's class/id
http://jsfiddle.net/yryQ6/
When you bind the click event, use the event, get the target and ask for it's class:
$('#content').on('click',function(e){
if ($(e.target).hasClass('action')) {
// click on an action
} else {
// click on smething that's not an action, in this case, action-row o actions, your empty area
}
}
EDIT: assuming you use jquery, you should be able to use something similar with javascript events