Dan Lee
Dan Lee

Reputation: 704

Targeting One Tab Set in Multiple Tab Sets

I have some JQuery that controls the open and close of tabs. It works great - until I have multiple sets of tabs. On click functionality impacts all the sets of tabs. I want to be able to isolate the functionality to the set of tabs that are being interacted with. How do I do this efficiently?

Each tab has a unique ID associated with it

data-tab="tab-{{ blockID }}{{ tabCounter }}">

<ul class="tabs list fa-ul ml2 pl0 tf">
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30" data-tab="tab-21">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue"></i></span>TAB ONE</a>
    <div id="tab-21" class="tab-content w-100-ns pt2 pb2 dn-ns dn">CONTENT GOES HERE</div>
  </li>
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30 active" data-tab="tab-22">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue teal rm-90"></i></span>TAB TWO</a>
    <div id="tab-22" class="tab-content w-100-ns pt2 pb2 dn-ns db">
      CONTENT TWO GOES HERE
    </div>
  </li>
</ul>
$('ul.tabs li').click(function() {
  var tab_id = $(this).attr('data-tab');
  var icon = $(this).find('span i');

  //Remove on all list items
  $('ul.tabs li').removeClass('active');
  $('ul.tabs li span i').removeClass('teal rm-90');

  //Remove on all div items
  $('.tab-content').removeClass('db');
  $('.tab-content').addClass('dn');

  //Add on current list item
  $(icon).addClass('teal rm-90');
  $(this).addClass('active');

  //Add on current div item
  $('ul.tabs').find("#" + tab_id).removeClass('dn');
  $('div.tabs').find("#" + tab_id).removeClass('dn');
  $('ul.tabs').find("#" + tab_id).addClass('db');
  $('div.tabs').find("#" + tab_id).addClass('db');
})

Upvotes: 3

Views: 151

Answers (1)

Alexander Nied
Alexander Nied

Reputation: 13623

The problem is that your code is mostly iterating over the whole document looking for matches. I would recommend finding the tab container (ul.tabs) of the clicked element, then scoping all of your subsequent operations to that container. For instance, rather than searching for all li elements inside of a tab container and removing the .active class, like you have at line six of your JS:

$('ul.tabs li').removeClass('active');

...we can instead leverage .parents() to find the specific tab container, and only look inside that container, like so:

var tabContainer = $(this).parents('ul.tabs').eq(0); // this is a little overly safe-- if you are confident that the ul.tabs will always be the immediate parent, you can just use .parent()
tabContainer.find('li').removeClass('active');

I created a snippet using this technique, and verified that, while it was not working quite right with your code above, when we scope our search to the tab container of the clicked element, it works correctly. I took the liberty of guessing at a few styles to help illustrate the point:

$('ul.tabs li').click(function() {
  // cache $this
  var $this = $(this);
  
  var tab_id = $this.attr('data-tab');
  var icon = $this.find('span i');
  
  // find the parent tab container
  var tabContainer = $this.parents('ul.tabs').eq(0);

  //Remove on all list items
  tabContainer.find('li').removeClass('active');
  tabContainer.find('li span i').removeClass('teal rm-90');

  //Remove on all div items
  tabContainer.find('.tab-content').removeClass('db');
  tabContainer.find('.tab-content').addClass('dn');

  //Add on current list item
  $(icon).addClass('teal rm-90');
  $(this).addClass('active');

  //Add on current div item
  tabContainer.find("#" + tab_id).removeClass('dn');
  $('div.tabs').find("#" + tab_id).removeClass('dn');
  tabContainer.find("#" + tab_id).addClass('db');
  $('div.tabs').find("#" + tab_id).addClass('db');
})
.active {
  background-color: pink;
}

.db {
  display: block;
}

.dn {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="tabs list fa-ul ml2 pl0 tf">
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30" data-tab="tab-21">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue"></i></span>TAB ONE</a>
    <div id="tab-21" class="tab-content w-100-ns pt2 pb2 dn-ns dn">CONTENT GOES HERE</div>
  </li>
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30 active" data-tab="tab-22">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue teal rm-90"></i></span>TAB TWO</a>
    <div id="tab-22" class="tab-content w-100-ns pt2 pb2 dn-ns db">
      CONTENT TWO GOES HERE
    </div>
  </li>
</ul>

<ul class="tabs list fa-ul ml2 pl0 tf">
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30" data-tab="tab-23">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue"></i></span>TAB ALPHA</a>
    <div id="tab-23" class="tab-content w-100-ns pt2 pb2 dn-ns dn">CONTENT GOES HERE</div>
  </li>
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30 active" data-tab="tab-24">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue teal rm-90"></i></span>TAB BETA</a>
    <div id="tab-24" class="tab-content w-100-ns pt2 pb2 dn-ns db">
      CONTENT TWO GOES HERE
    </div>
  </li>
</ul>

As long as we're reviewing your code, a few other things--

  1. You're not taking advantage of chaining to shorten operations, nor are you caching repeated selections in variables-- this will hurt the performance of your code.
  2. At least as far as the example as posted is concerned, $('div.tabs') never matches anything, and could be omitted.

With these two adjustments, we can shorten the code up a bit and improve its performance:

$('ul.tabs li').click(function() {
  var $this = $(this);
  var tab_id = $this.attr('data-tab');
  var icon = $this.find('span i');
  var tabContainer = $this.parents('ul.tabs').eq(0);

  //Remove on all list items
  tabContainer.find('li').removeClass('active');
  tabContainer.find('li span i').removeClass('teal rm-90');

  //Remove on all div items
  tabContainer.find('.tab-content')
    .removeClass('db')
    .addClass('dn');

  //Add on current list item
  icon.addClass('teal rm-90');
  $this.addClass('active');

  //Add on current div item
  tabContainer.find("#" + tab_id)
    .removeClass('dn')
    .addClass('db');

})
.active {
  background-color: pink;
}

.db {
  display: block;
}

.dn {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="tabs list fa-ul ml2 pl0 tf">
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30" data-tab="tab-21">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue"></i></span>TAB ONE</a>
    <div id="tab-21" class="tab-content w-100-ns pt2 pb2 dn-ns dn">CONTENT GOES HERE</div>
  </li>
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30 active" data-tab="tab-22">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue teal rm-90"></i></span>TAB TWO</a>
    <div id="tab-22" class="tab-content w-100-ns pt2 pb2 dn-ns db">
      CONTENT TWO GOES HERE
    </div>
  </li>
</ul>

<ul class="tabs list fa-ul ml2 pl0 tf">
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30" data-tab="tab-23">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue"></i></span>TAB ALPHA</a>
    <div id="tab-23" class="tab-content w-100-ns pt2 pb2 dn-ns dn">CONTENT GOES HERE</div>
  </li>
  <li class="lh-copy pv2 ba bl-0 bt-0 br-0 b--dotted b--black-30 active" data-tab="tab-24">
    <a class="link pointer blue hover-teal no-underline d tab-hover-fix tab-link" target="_blank">
      <span class="fa-li"><i class="fas fa-caret-down blue teal rm-90"></i></span>TAB BETA</a>
    <div id="tab-24" class="tab-content w-100-ns pt2 pb2 dn-ns db">
      CONTENT TWO GOES HERE
    </div>
  </li>
</ul>

Finally, if you intend on including a lot of these on any given page, it might be advisable to leverage event delegation in .on in lieu of .click to get a bit more of a performance boost.

Upvotes: 3

Related Questions