This is the markup that I'm working with
<div class="checkout-row__form-column__billing">
<div class="checkout-row__form-column__billing__title billing-collapse collapse-btn">
<div class="checkout-row__form-column__billing__title__arrow">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
</div>
<h2>1. Billing Details</h2>
</div>
<div class="checkout-row__form-column__shipping__content shipping-collapse-content collapse-target"></div>
<div class="checkout-row__form-column__shipping">
<div class="checkout-row__form-column__shipping__title shipping-collapse collapse-btn">
<div class="checkout-row__form-column__shipping__title__arrow">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
</div>
<h2>2. Shipping Details</h2>
</div>
<div class="checkout-row__form-column__shipping__content shipping-collapse-content collapse-target"></div>
All this markup is wrapped in div
with a class of checkout-row__form-column
.
So, I'm grabbing the parent wrapper, on click I match target with .collapse-btn
. and then I do class toggle on the sibling which is .collapse-target
. Works great except on issue, that I can't get past. If I click on the inner element, the event doesn't happen.
Here is my js
parent = document.querySelector('.checkout-row__form-column');
parent.addEventListener('click', (e)=>{
if (e.target.matches('.collapse-btn')) {
e.stopPropagation();
e.target.nextElementSibling.classList.toggle('hide');
}
});
With these code I am able to toggle classes if I don't click on i
element or h2
. How can I write my JavaScript that event will be fired if I click anywhere inside the e.target
.
Any help is much appreciated fellow commarades.
Your code only checks if the event.target
is the .collapse-btn
element or not. You also need to check if the clicked element is a descendant element of the .collapse-btn
element.
To achieve the desired result, you can use the two conditions to check if the event.target
is the .collapse-btn
or any of its descendant. To check for descendant elements, use the universal selector *
.
parent.addEventListener('click', (e) => {
if (
e.target.matches('.collapse-btn') ||
e.target.matches('.collapse-btn *')
) {
// code
}
});
Following snippet shows an example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
if (
e.target.matches('.collapse-btn') ||
e.target.matches('.collapse-btn *')
) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>
You could also put the selectors in an array and then use .some()
method to check if event.target
matches ay of the selector in the array.
const selectors = ['.collapse-btn', '.collapse-btn *'];
parent.addEventListener('click', e => {
if (selectors.some(s => e.target.matches(s))) {
// code
}
});
Following snippet shows an example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
const selectors = ['.collapse-btn', '.collapse-btn *'];
if (selectors.some(s => e.target.matches(s))) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>
Instead of using the .matches()
method, use the combination of Element.closest()
and Node.contains()
methods to check if the event.target
is the .collapse-btn
element or is a child element of the .collapse-btn
element.
For this to work, you need to do two steps:
You first need to get the .collapse-btn
that is the closest ancestor of the event.target
.
After that, you need to check if the event.target
is a descendant of the button selected in step 1.
Code:
parent.addEventListener('click', (e) => {
const targetCollapseBtn = e.target.closest('.collapse-btn');
if (targetCollapseBtn && targetCollapseBtn.contains(e.target)) {
...
}
});
If event.target
is the .collapse-btn
itself, then
e.target.closest('.collpase-btn')
will return the e.target
, i.e. .collapse-btn
and
targetCollapseBtn.contains(e.target)
will also return true because .contains()
method returns true even if the node passed to it as an argument is the same node as the one on which .contains()
method was called.
So using .closest()
and .contains()
methods in this way covers both the use cases, i.e. when .collapse-btn
is clicked or when any of its descendant element is clicked.
Following snippet shows a simple example:
const parent = document.querySelector('.container');
parent.addEventListener('click', e => {
const targetCollapseBtn = e.target.closest('.collapse-btn');
if (targetCollapseBtn && targetCollapseBtn.contains(e.target)) {
parent.classList.toggle('active');
}
});
.container {
background: #eee;
width: 120px;
height: 120px;
}
.container.active {
border: 2px solid;
}
.collapse-btn {
background: #999;
padding: 10px;
}
.collapse-btn span {
background: #fc3;
font-size: 1.3rem;
cursor: pointer;
}
<div class="container">
<div class="collapse-btn">
<span>Click</span>
</div>
</div>