william_eduards
william_eduards

Reputation: 103

Add and remove a class to a collection of items on button click (vanilla JS)

I have 3 tabs, which I am using to switch table columns in mobile version. What I am trying to acheive, is to hide/show table columns clicking on suitable column button. My idea was to manipulate each column items class, which would be the same for all elements inside that column, but I got stuck, that I can't add .hidden class to a collection of items. Would be grateful for any advise on that. Or maybe there is a better way to manipulate tabs?

document.addEventListener('DOMContentLoaded', function() {
  let tabsContainer = document.querySelector('.buynow-tabs');
  let tabsButtons = tabsContainer.querySelectorAll('.buynow-tabs__tab');
  let basicElements = document.querySelectorAll('.table-v4__tab-basic');
  let premiumElements = document.querySelectorAll('.table-v4__tab-premium');
  let eliteElements = document.querySelectorAll('.table-v4__tab-elite');
  for (var i = 0; i < tabsButtons.length; i++) {
    tabsButtons[i].addEventListener('click', function() {
      let current = tabsContainer.querySelectorAll('.active');
      current[0].className = current[0].className.replace(' active', '');
      this.className += ' active';
      if (tabsButtons[0].classList.contains('active')) {
        basicElements.classList.remove('hidden');
        premiumElements.classList.add('hidden');
        eliteElements.classList.add('hidden');
      } else if (tabsButtons[1].classList.contains('active')) {
        basicElements.classList.add('hidden');
        premiumElements.classList.remove('hidden');
        eliteElements.classList.add('hidden');
      } else if (tabsButtons[2].classList.contains('active')) {
        basicElements.classList.add('hidden');
        premiumElements.classList.add('hidden');
        eliteElements.classList.remove('hidden');
      }
    });
  }
});
.buynow-tabs {
  display: flex;
}
<div class="buynow-tabs">
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab">Basic</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab active">Premium</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab">Elite</button>
  </div>
</div>
<table>
  <thead>
    <tr>
      <th class="table-v4__plans-basic">Basic</th>
      <th class="table-v4__plans-premium">Premium</th>
      <th class="table-v4__plans-elite">Elite</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
  </tbody>
</table>

Upvotes: 1

Views: 811

Answers (2)

biberman
biberman

Reputation: 5767

You have to iterate over the table cells with for loops and have to use the if-else-blocks inside that loops. Furthermore there was no CSS definition for the class .hidden and the selectors in your js-code were wrong (table-v4__tab instead of table-v4__plans like in your html).

To let your hardcoded class active for "Premium" come into account after page load you can trigger the click event for the premium button with:

document.querySelectorAll('.buynow-tabs__tab')[1].click();

Working example:

document.addEventListener('DOMContentLoaded', function() {

  const tabsButtons = document.querySelectorAll('.buynow-tabs__tab');
  const cells = document.querySelectorAll('[class^=table-v4__plans]');
  const basicElements = document.querySelectorAll('.table-v4__plans-basic');
  const premiumElements = document.querySelectorAll('.table-v4__plans-premium');
  const eliteElements = document.querySelectorAll('.table-v4__plans-elite');
  
  for (let i = 0; i < tabsButtons.length; i++) {
    tabsButtons[i].addEventListener('click', function() {
      const current = document.querySelector('.buynow-tabs__tab.active');
      
      current.className = current.className.replace(' active', '');
      this.className += ' active';
      
      for (let i = 0; i < cells.length; i++) {
        cells[i].classList.add('hidden');
      }
      
      for (let i = 0; i < basicElements.length; i++) {
        if (tabsButtons[0].classList.contains('active')) {
          basicElements[i].classList.remove('hidden');
        }
      }
      
      for (let i = 0; i < premiumElements.length; i++) {
        if (tabsButtons[1].classList.contains('active')) {
          premiumElements[i].classList.remove('hidden');
        }
      }
      
      for (let i = 0; i < eliteElements.length; i++) {
        if (tabsButtons[2].classList.contains('active')) {
          eliteElements[i].classList.remove('hidden');
        }
      }
    });
  }
  
  document.querySelectorAll('.buynow-tabs__tab')[1].click();
  
});
.buynow-tabs {
  display: flex;
}

.hidden {
  display: none;
}
<div class="buynow-tabs">
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab">Basic</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab active">Premium</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button class="buynow-tabs__tab">Elite</button>
  </div>
</div>

<table>
  <thead>
    <tr>
      <th class="table-v4__plans-basic">Basic</th>
      <th class="table-v4__plans-premium">Premium</th>
      <th class="table-v4__plans-elite">Elite</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
  </tbody>
</table>


But there is a much easier way if you add an id to each button (basic, premium and elite). You just need to define one var for all table cells incl. th (selected by the beginning of their class name [class^=table-v4__plans]). In the click handler you then just have to compare the button id (e.target.id) with the className of the cell to decide whether you have to add or remove the class .hidden (all that in one single for loop).

Because i presumed that the class .active is just for the script i omitted it in my second example. If it is important for styling etc. you can of course handle it in the click handler like you did or with this compressed version:

document.querySelector('button.active').className = e.target.className;
e.target.className = 'active';

Working example:

document.addEventListener('DOMContentLoaded', function() {
  const cells = document.querySelectorAll('[class^=table-v4__plans]');
  
  document.querySelector('.buynow-tabs').addEventListener('click', function(e) {
    for (i = 0; i < cells.length; i++) {
      if (cells[i].className.includes(e.target.id)) {
        cells[i].classList.remove('hidden');
      } else {
        cells[i].classList.add('hidden');
      }
    }
  });
  
  document.querySelectorAll('.buynow-tabs__tab')[1].click();
});
.buynow-tabs {
  display: flex;
}

.hidden {
  display: none;
}
<div class="buynow-tabs">
  <div class="buynow-tabs__item-container">
    <button id="basic" class="buynow-tabs__tab">Basic</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button id="premium" class="buynow-tabs__tab">Premium</button>
  </div>
  <div class="buynow-tabs__item-container">
    <button id="elite" class="buynow-tabs__tab">Elite</button>
  </div>
</div>

<table>
  <thead>
    <tr>
      <th class="table-v4__plans-basic">Basic</th>
      <th class="table-v4__plans-premium">Premium</th>
      <th class="table-v4__plans-elite">Elite</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
  </tbody>
</table>

Upvotes: 1

Damiaan Dufaux
Damiaan Dufaux

Reputation: 4785

Like other people have pointed out, the reason why your code does not work is because you have to iterate over all the elements of your selector queries. But I think there is a more elegant way to solve your problem:

Suggestion

I would suggest you to alter just your style sheet when clicking the buttons instead of changing the classes of all your elements. It will greatly simplify your code and remove a lot of bugs altogether.

Explanation

Here is an example of how you can do this:

First add a scriptable style sheet to the head of your document:

const selectedTabStyles = document.createElement('style')
document.head.appendChild(selectedTabStyles)

Then create a function to alter the stylesheet

function onlyShowSelectedPlan(plan) {
  selectedTabStyles.innerHTML = `
    th, td {display: none}
    .table-v4__plans-${plan} {display: table-cell}
  ` 
}

That's it for the scripting part! Now you can add your "plan selection" function as an onclick handler to your buttons

<button onclick="onlyShowSelectedPlan('basic')">Basic</button>

Demo

const selectedTabStyles = document.createElement('style')
document.head.appendChild(selectedTabStyles)

function onlyShowSelectedPlan(plan) {
  selectedTabStyles.innerHTML = `th, td {display: none} .table-v4__plans-${plan} {display: table-cell}` 
}
.buynow-tabs {
  display: flex;
}
<div class="buynow-tabs">
    <div class="buynow-tabs__item-container">
        <button class="buynow-tabs__tab" onclick="onlyShowSelectedPlan('basic')">Basic</button>
    </div>
    <div class="buynow-tabs__item-container">
        <button class="buynow-tabs__tab" onclick="onlyShowSelectedPlan('premium')">Premium</button>
    </div>
    <div class="buynow-tabs__item-container">
        <button class="buynow-tabs__tab" onclick="onlyShowSelectedPlan('elite')">Elite</button>
    </div>
</div>
<table>
  <thead>
    <tr>
      <th class="table-v4__plans-basic">Basic</th>
      <th class="table-v4__plans-premium">Premium</th>
      <th class="table-v4__plans-elite">Elite</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
    <tr>
      <td class="table-v4__plans-basic">Basic</td>
      <td class="table-v4__plans-premium">Premium</td>
      <td class="table-v4__plans-elite">Elite</td>
    </tr>
  </tbody>
</table>

Upvotes: 1

Related Questions