I'm adding a class to an article with js, its all working, no problem. But if I want to remove that class, this approach wont work. I cannot use jQuery.
document.addEventListener("DOMContentLoaded", function() { var blogCard = document.querySelectorAll('[data-role="blogCard"]'); for (let i = 0; i < blogCard.length; i++) { blogCard[i].addEventListener('click', function() { this.classList.add('opened-card'); var closeCard = document.querySelectorAll('[data-role="closeCard"]'); closeCard[0].addEventListener('click', function() { var pThis = this.parentNode; pThis.classList.remove('opened-card'); }); }); }; });
<div class="card" data-role="blogCard"> <button data-role="closeCard">Close</button> </div>
I've tried to change 'remove' to 'add' and it worked perfectly, it add the class. But when I go back to remove it wont work.
Because the click
event propagates to the parent, two click handlers are actually fired here. button.closeCard
click handler removes the class, but div.blogCard
one adds it back. :) That's why adding a class works, but removing doesn't.
To solve this, you might either just prevent the event propagation in that second handler, or just use the combined event handler set on div.blogCard
, choosing the behaviour depending on event.target
(essentially implementing delegation). Like this:
blogCard[i].addEventListener('click', function(event) {
if (event.target.getAttribute('data-role') === 'closeCard') {
this.classList.remove('opened-card');
}
else {
this.classList.add('opened-card');
}
});
In fact, you might consider using the same technique - checking the target's position in DOM inside event handler - by applying a single event handler to those cards' parent (if there's a single parent, of course):
// a single event handler set on some 'cardContainer' element
var blogCard = event.target.closest('[data-role="blogCard"]');
if (blogCard) {
var action = event.target.closest('[data-role="closeCard"]') ? 'remove' : 'add';
blogCard.classList[action]('opened-card');
}
This, as correctly mentioned by @IronFlare, not only is more performant and easier to read than doing in the loop for each card separately, but also handles all the cards added to the DOM after event handler is set up.
Assigning event listeners like this in a loop is possible as the other answers have shown, but it's not the easiest or most efficient option. Using a global click listener with Element.matches()
conditionals is better because:
DOMContentReady
fires without any special handling; all elements matching the selector at the time of the click will be "heard" by the listener. window.addEventListener("click", function(e) { console.log("Click!", e.target); if (e.target.matches("[data-role='blogCard']")) { e.target.classList.add('opened-card'); } if (e.target.matches("[data-role='closeCard']")) { e.target.parentElement.classList.remove('opened-card'); } });
.opened-card { background: red; }
<div class="card" data-role="blogCard"> <button data-role="closeCard">Close</button> </div>
In your code you add an event to the first "closeCard" in the document everytime you "open" a card.
This would not work if you have more than 1 card.
Try adding 2 separate events:
document.addEventListener("DOMContentLoaded", function() {
// get blogCards
var blogCard = document.querySelectorAll('[data-role="blogCard"]');
// get closeCards
var closeCard = document.querySelectorAll('[data-role="closeCard"]');
for (let i = 0; i < blogCard.length; i++) {
// apply open event
blogCard[i].addEventListener('click', function() {
this.classList.add('opened-card');
});
// apply respective close event
// this assumes you have one close button for every blogCard
// if it's not the case then maybe you should scope the querySelectors
closeCard[i].addEventListener('click', function() {
var pThis = this.parentNode;
pThis.classList.remove('opened-card');
});
};
});
This code can be optimized (you don't need to query for both "roles" individually if you know the DOM relation between the blogCard and the closeCard) but I'll leave that to OP
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.