简体   繁体   中英

Remove class previously added not working

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:

  • It's simpler and easier to understand in the code.
  • It's more efficient and doesn't rely on a synchronous loop immediately after the page becomes interactible.
  • It allows you to add additional cards after 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM