I have a button which opens a popup when clicked, and in this popup there is a close button to close the popup but that close button is not working!
Here is my code:
var fromToDate = document.getElementById('from-to-date');
var openFromToDate = document.getElementById('open-from-to-date');
var closeFromToDate = document.getElementById('close-from-to-date');
openFromToDate.addEventListener('click', function() {
fromToDate.classList.remove('dis-none');
});
closeFromToDate.addEventListener('click', function() {
fromToDate.classList.add('dis-none');
});
.dis-none {
display: none;
}
/* To make the button visible here */
.fa-times::after {
content: 'X';
}
<li id="open-from-to-date">
<span >
From: 2022-09-16 To: 2022-09-18
</span>
<div id="from-to-date">
<div >
<div id="close-from-to-date">
<i aria-hidden="true"></i>
</div>
</div>
</div>
</li>
CodePudding user response:
The problem is that when you click the close button, two listeners fire: First, the close button listener fires and adds dis-none
, but then the open "button" listener also fires and removes dis-none
again. This is because the close button is contained within the open "button", so any click on the close button also constitutes a click on the open "button" at the same time.
You can easily check this by adding console.log
statements to the two listeners.
The solution is therefore to not let the event bubble up to any listeners on parent elements once you handled it on the close button itself, which can be done using stopPropagation
:
var fromToDate = document.getElementById('from-to-date');
var openFromToDate = document.getElementById('open-from-to-date');
var closeFromToDate = document.getElementById('close-from-to-date');
openFromToDate.addEventListener('click', function() {
fromToDate.classList.remove('dis-none');
});
closeFromToDate.addEventListener('click', function(e) {
fromToDate.classList.add('dis-none');
e.stopPropagation(); // <---- THIS
});
.dis-none {
display: none;
}
/* To make the button visible here */
.fa-times::after {
content: 'X';
}
<li id="open-from-to-date">
<span >
From: 2022-09-16 To: 2022-09-18
</span>
<div id="from-to-date">
<div >
<div id="close-from-to-date">
<i aria-hidden="true"></i>
</div>
</div>
</div>
</li>
(Note: I also added the event argument e
to the listener function so that we could call stopPropgation
on it.)
CodePudding user response:
The problem is when you're clicking on the "closer" you are also clicking on the "opener" as well, say not? See the code below.
var fromToDate = document.getElementById('from-to-date');
var openFromToDate = document.getElementById('open-from-to-date');
var closeFromToDate = document.getElementById('close-from-to-date');
openFromToDate.addEventListener('click', function() {
fromToDate.classList.remove('dis-none');
console.log("I opened");
});
closeFromToDate.addEventListener('click', function() {
fromToDate.classList.add('dis-none');
console.log("I closed");
});
.dis-none {
display: none;
}
<li id="open-from-to-date">
<span >From: 2022-09-16 To: 2022-09-18</span>
<div id="from-to-date">
<div >
<div id="close-from-to-date">
<i aria-hidden="true"></i>
Close!
</div>
</div>
</div>
</li>
Now you have two ways to correct this:
window.setTimeout
var fromToDate = document.getElementById('from-to-date');
var openFromToDate = document.getElementById('open-from-to-date');
var closeFromToDate = document.getElementById('close-from-to-date');
openFromToDate.addEventListener('click', function() {
fromToDate.classList.remove('dis-none');
});
closeFromToDate.addEventListener('click', function() {
setTimeout(function() {
fromToDate.classList.add('dis-none');
}, 2);
});
.dis-none {
display: none;
}
<li id="open-from-to-date">
<span >From: 2022-09-16 To: 2022-09-18</span>
<div id="from-to-date">
<div >
<div id="close-from-to-date">
<i aria-hidden="true"></i> Close!
</div>
</div>
</div>
</li>
In this method, the removing method runs 2ms later which means two lines of JS code later.
Capture parameter of addEventListener which usually is overseen
RECOMMENDED
See this example:
var fromToDate = document.getElementById('from-to-date');
var openFromToDate = document.getElementById('open-from-to-date');
var closeFromToDate = document.getElementById('close-from-to-date');
openFromToDate.addEventListener('click', function() {
fromToDate.classList.remove('dis-none');
console.log("I'm opened");
}, true); //Magic happens here
closeFromToDate.addEventListener('click', function() {
fromToDate.classList.add('dis-none');
console.log("I'm closed");
});
.dis-none {
display: none;
}
<li id="open-from-to-date">
<span >From: 2022-09-16 To: 2022-09-18</span>
<div id="from-to-date">
<div >
<div id="close-from-to-date">
<i aria-hidden="true"></i> Close!
</div>
</div>
</div>
</li>
In this example, the capturing method of the opening event is set to true which means, the event listening is in the capturing phase instead of the bubbling phase which is the default.
In the capturing phase, the outmost event listener runs first so the opening event is captured first and closing event, next.