skip to Main Content

I have made a small script in JavaScript for tabbed content.

(function() {
  let contentElms = document.querySelectorAll('.content > div');
  let tabs = document.querySelectorAll('.tabs li');

  // Show first tab initially
  contentElms[0].classList.add("active");
  tabs.forEach((tab, index) => {
    tab.addEventListener("click", () => {
      // Toggle tabs
      tabs.forEach((tab) => {
        tab.classList.remove("active")
      });
      tab.classList.add("active");

      // Toggle content
      contentElms.forEach((el) => {
        el.classList.remove("active")
      });

      contentElms[index].classList.add("active");
    });
  });
})();
body {
     margin: 0;
     padding: 0;
     font-family: Arial, Helvetica, sans-serif;
}
 body * {
     box-sizing: content-box;
}
 .tabbed-content {
     max-width: 600px;
     margin: 10px auto;
}
 .tabbed-content .tabs ul {
     list-style-type: none;
     margin: 0;
     padding: 0;
     display: flex;
}
 .tabbed-content .tabs ul li {
     flex: 1;
     border: 1px solid rgba(0, 0, 0, 0.1);
     border-radius: 4px 4px 0 0;
     overflow: hidden;
}
 .tabbed-content .tabs ul li.active {
     border-bottom: none;
     font-weight: bold;
}
 .tabbed-content .tabs ul a {
     display: block;
     padding: 10px;
     text-align: center;
     text-decoration: none;
     color: #7f7f7f;
}
 .tabbed-content .content {
     padding: 10px;
     border: 1px solid rgba(0, 0, 0, 0.1);
     border-top: none;
     border-radius: 0 0 4px 4px;
}
 .tabbed-content .content > div {
     font-size: 14px;
     line-height: 1.5;
     text-align: justify;
     display: none;
}
 .tabbed-content .content > div.active {
     display: block;
}
 
<div class="tabbed-content">
  <nav class="tabs">
    <ul>
      <li class="active"><a href="#">Tab 1</a></li>
      <li><a href="#">Tab 2</a></li>
      <li><a href="#">Tab 3</a></li>
    </ul>
  </nav>
  <div class="content">
    <div>Nesciunt velit a hic, officia animi veritatis quis obcaecati tempora omnis iusto.</div>
    <div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias dolorem, sequi quos unde tempora corporis voluptatibus officia atque voluptatum consequuntur necessitatibus fuga quidem nihil. Perspiciatis reiciendis impedit cupiditate odit veritatis!</div>
    <div>Deserunt est in, ipsum possimus dolorum! Numquam suscipit laborum, reiciendis delectus. Labore?</div>
  </div>
</div>

The tabs work fine.

The problem

I am unhappy with the fact that there are nested forEach loops in the script.

Questions

  1. Is there any (reliable) way to avoid that?
  2. Can I have just one forEach loop in the script?

2

Answers


  1. Yeah. You can use this code

    Instead of loops we will save references to active content and tab elements and update them on click

    (function() {
      let contentElms = document.querySelectorAll('.content > div');
      let tabs = document.querySelectorAll('.tabs li');
    
      // Show first tab initially
      contentElms[0].classList.add("active");
      tabs[0].classList.add("active");
      let contentActive = contentElms[0]
      let tabActive = tabs[0]
      tabs.forEach((tab, index) => {
        tab.addEventListener("click", () => {
          contentActive.classList.remove("active")
          contentActive = contentElms[index]
          contentActive.classList.add("active")
    
          tabActive.classList.remove("active")
          tabActive = tab
          tabActive.classList.add("active")
        });
      });
    })();
    body {
      margin: 0;
      padding: 0;
      font-family: Arial, Helvetica, sans-serif;
    }
    
    body * {
      box-sizing: content-box;
    }
    
    .tabbed-content {
      max-width: 600px;
      margin: 10px auto;
    }
    
    .tabbed-content .tabs ul {
      list-style-type: none;
      margin: 0;
      padding: 0;
      display: flex;
    }
    
    .tabbed-content .tabs ul li {
      flex: 1;
      border: 1px solid rgba(0, 0, 0, 0.1);
      border-radius: 4px 4px 0 0;
      overflow: hidden;
    }
    
    .tabbed-content .tabs ul li.active {
      border-bottom: none;
      font-weight: bold;
    }
    
    .tabbed-content .tabs ul a {
      display: block;
      padding: 10px;
      text-align: center;
      text-decoration: none;
      color: #7f7f7f;
    }
    
    .tabbed-content .content {
      padding: 10px;
      border: 1px solid rgba(0, 0, 0, 0.1);
      border-top: none;
      border-radius: 0 0 4px 4px;
    }
    
    .tabbed-content .content>div {
      font-size: 14px;
      line-height: 1.5;
      text-align: justify;
      display: none;
    }
    
    .tabbed-content .content>div.active {
      display: block;
    }
    <div class="tabbed-content">
      <nav class="tabs">
        <ul>
          <li class="active"><a href="#">Tab 1</a></li>
          <li><a href="#">Tab 2</a></li>
          <li><a href="#">Tab 3</a></li>
        </ul>
      </nav>
      <div class="content">
        <div>Nesciunt velit a hic, officia animi veritatis quis obcaecati tempora omnis iusto.</div>
        <div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias dolorem, sequi quos unde tempora corporis voluptatibus officia atque voluptatum consequuntur necessitatibus fuga quidem nihil. Perspiciatis reiciendis impedit cupiditate odit veritatis!</div>
        <div>Deserunt est in, ipsum possimus dolorum! Numquam suscipit laborum, reiciendis delectus. Labore?</div>
      </div>
    </div>
    Login or Signup to reply.
  2. As mentioned in comments, you can use the event delegation, to achieve the desired result.

    (function() {
      let contentElms = document.querySelectorAll('.content > div');
      let tabsContainer = document.querySelector('.tabs');
    
      // Show first tab initially
      contentElms[0].classList.add("active");
      tabsContainer.querySelector('li').classList.add("active");
    
      // Use event delegation
      tabsContainer.addEventListener("click", (event) => {
        let tab = event.target.closest('li');
        
        if (tab) {
          let index = Array.from(tabsContainer.querySelectorAll('li')).indexOf(tab);
    
          // Remove active class from all tabs and content
          tabsContainer.querySelectorAll('li').forEach(tab => tab.classList.remove("active"));
          contentElms.forEach(el => el.classList.remove("active"));
    
          // Add active class to the clicked tab and corresponding content
          tab.classList.add("active");
          contentElms[index].classList.add("active");
        }
      });
    })();
    body {
         margin: 0;
         padding: 0;
         font-family: Arial, Helvetica, sans-serif;
    }
     body * {
         box-sizing: content-box;
    }
     .tabbed-content {
         max-width: 600px;
         margin: 10px auto;
    }
     .tabbed-content .tabs ul {
         list-style-type: none;
         margin: 0;
         padding: 0;
         display: flex;
    }
     .tabbed-content .tabs ul li {
         flex: 1;
         border: 1px solid rgba(0, 0, 0, 0.1);
         border-radius: 4px 4px 0 0;
         overflow: hidden;
    }
     .tabbed-content .tabs ul li.active {
         border-bottom: none;
         font-weight: bold;
    }
     .tabbed-content .tabs ul a {
         display: block;
         padding: 10px;
         text-align: center;
         text-decoration: none;
         color: #7f7f7f;
    }
     .tabbed-content .content {
         padding: 10px;
         border: 1px solid rgba(0, 0, 0, 0.1);
         border-top: none;
         border-radius: 0 0 4px 4px;
    }
     .tabbed-content .content > div {
         font-size: 14px;
         line-height: 1.5;
         text-align: justify;
         display: none;
    }
     .tabbed-content .content > div.active {
         display: block;
    }
    <div class="tabbed-content">
      <nav class="tabs">
        <ul>
          <li class="active"><a href="#">Tab 1</a></li>
          <li><a href="#">Tab 2</a></li>
          <li><a href="#">Tab 3</a></li>
        </ul>
      </nav>
      <div class="content">
        <div>Nesciunt velit a hic, officia animi veritatis quis obcaecati tempora omnis iusto.</div>
        <div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Molestias dolorem, sequi quos unde tempora corporis voluptatibus officia atque voluptatum consequuntur necessitatibus fuga quidem nihil. Perspiciatis reiciendis impedit cupiditate odit veritatis!</div>
        <div>Deserunt est in, ipsum possimus dolorum! Numquam suscipit laborum, reiciendis delectus. Labore?</div>
      </div>
    </div>

    So, the tabsContainer (which is the parent of all tabs) listens for click events. When a click happens, it checks if the target (event.target) is a tab (li). Then the index of the clicked tab is determined by converting the NodeList of tabs into an array with Array.from and using indexOf to find the index. Lastly, all tabs and content elements have the active class removed first. Then, the clicked tab and the corresponding content are set to active.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search