2

Hopefully this one doesn't go off the beaten track too much - but I was wondering what the quickest method is in jQuery for targeting another element (that is a close relative of $(this).whatever) with the same data id attribute. I currently have the following markup (if you use the latest Bootstrap you'll notice that this is just a simple card with nav headings):

<div class="card text-center">
  <div class="card-header pb-0">
    <ul id="contactGroups" class="nav justify-content-center nav-tabs card-header-tabs">
      <li class="nav-item" data-id="1">
        <a class="nav-link active" href="#">Contact Us</a>
      </li>
      <li class="nav-item" data-id="2">
        <a class="nav-link" href="#">Business Enquiries</a>
      </li>
      <li class="nav-item" data-id="3">
        <a class="nav-link" href="#">Follow Us</a>
      </li>
    </ul>
  </div>
  <div class="card-body active" data-id="1">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="2">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="3">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>

As you can see in the above markup I have data-id attributes added to the list elements - I want to then go in and find it's corresponding data-id (the ones with the card-body active or card-body hidden) so I can begin to play with them. However (!!) I don't want to select on some arbitary #id or .class that I assign. How would I go about doing this?

My jQuery thus far is as follows (however, this only flips the current class from non-active (or no class) to the active class giving the tab swop illusion:

$('#contactGroups').each(function(){
  $(this).find('li').each(function(){
    $(this).on('click', function() {                
      $('#contactGroups').find('.active').removeClass('active');
      $(this).find('a').addClass('active');
    })
  });
});

Any thoughts would be greatly appreciated.

2 Answers2

0

I can't quite make out whether you

  1. Want to find the div with the same data-id as the li, or

  2. Want to find other lis that are siblings ("brothers" + "sisters" = "siblings") of the li that was clicked which also have data-id attributes.

In the click handler on the li, this will refer to the li that was clicked (it or its descendant a element). With that in mind:

Assuming #1:

You can get the data-id of that element using attr:

var theId = `$(this).attr("data-id");

(Don't use data("id") unless you need the features data provides; more here.)

You can find elements within a given container using find, or siblings via siblings. So starting from the clicked li, you can either do .closest(".card-header").siblings(...) to access the header's siblings, or .closest(".card").find(...) to search all through the containing .card. The former is more specific, but more fragile; the latter is more robust if your structure changes a bit. Neither matters in terms of performance.

So I'd probably go up to .card and use find with a tag and attribute selector combination:

$(this).closest(".card").find("div[data-id='" + theId + "']")...

FWIW, there's no need to hook click on each individual li. Just use event delegation:

$("#contactGroups").on("click", "li[data-id]", function() {
    var theId = $(this).attr("data-id");
    var div = $(this).closest(".card").find("div[data-id='" + theId + "']");
    // ...
});

Assuming #2

Use $(this).siblings("[data-id]") in the li click handler to find all of its siblings with a data-id (with any value).


Full Working Example

...based on your comments:

$("#contactGroups").on("click", "li[data-id]", function() {
    var theId = $(this).attr("data-id");
    var div = $(this).closest(".card").find("div[data-id='" + theId + "']");
    div.removeClass("hidden").addClass("active");
    div.siblings("[data-id]").removeClass("active").addClass("hidden");
});
.hidden {
  display: none;
}
.active {
  font-weight: bold;
}
<div class="card text-center">
  <div class="card-header pb-0">
    <ul id="contactGroups" class="nav justify-content-center nav-tabs card-header-tabs">
      <li class="nav-item" data-id="1">
        <a class="nav-link active" href="#">Contact Us</a>
      </li>
      <li class="nav-item" data-id="2">
        <a class="nav-link" href="#">Business Enquiries</a>
      </li>
      <li class="nav-item" data-id="3">
        <a class="nav-link" href="#">Follow Us</a>
      </li>
    </ul>
  </div>
  <div class="card-body active" data-id="1">
    <h5 class="card-title">(1) Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="2">
    <h5 class="card-title">(2) Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="3">
    <h5 class="card-title">(3) Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Hi T.J, many thanks for this answer - I just want to clarify something which might not have been made obvious by my original question. If you look up the tree you'll see .card-body .active classes with the corresponding IDs...they're not inside the `#contactgroups` div... –  Feb 09 '18 at 16:27
  • P.S. I'd give you a +100 for the FWIW pro-tip - that worked a charm. –  Feb 09 '18 at 16:29
  • I think I have worked with what you've got - I changed the ID selector. However, I'm also looking for all divs with a data ID in that DOM to be able to "hide" them simultaneously. –  Feb 09 '18 at 16:36
  • 1
    @MichaelRoberts: Updated. No need for any IDs. :-) – T.J. Crowder Feb 09 '18 at 16:36
  • I like it! Now, I used your question to do the following: `div.addClass('active').removeClass('hidden');` - but I also need to do the inverse on all the other divs where the data id does not match ... i.e., `removeClass('active').addClass('hidden');` –  Feb 09 '18 at 16:38
  • I'll accept as correct, as it is. I may ask my above comment as a separate question if needed. –  Feb 09 '18 at 16:38
  • @MichaelRoberts: Just use `.siblings("[data-id]").removeClass("hidden").addClass("active")` on the one that matched. :-) – T.J. Crowder Feb 09 '18 at 16:46
  • Absolutely perfect, and with 4 lines of code. I added two - but 6 in total! I trust this is a very good answer. –  Feb 09 '18 at 16:59
0
  • You don't need an each because you have only one contactGroups.
  • Use the data function from jQuery.
  • With closest function you can get the ancestor of li elements.
  • Use toggleClass function to add or remove the class active.
  • Use this selector $('div[data-id="${id}"]') to find the related li's div.
  • You can use this selector to bind the click event $('#contactGroups li').on('click'), ...);

$('#contactGroups li').on('click', function() {
  $(this).closest('#contactGroups').find('.active').toggleClass('active');

  $(this).children('a').toggleClass('active');

  $('div.card-body.active').toggleClass('active');

  let id = $(this).data('id');
  $(`div[data-id="${id}"]`).toggleClass('active');
});
.active {
  color: green !important;
  background-color: lightgreen;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="card text-center">
  <div class="card-header pb-0">
    <ul id="contactGroups" class="nav justify-content-center nav-tabs card-header-tabs">
      <li class="nav-item" data-id="1">
        <a class="nav-link active" href="#">Contact Us</a>
      </li>
      <li class="nav-item" data-id="2">
        <a class="nav-link" href="#">Business Enquiries</a>
      </li>
      <li class="nav-item" data-id="3">
        <a class="nav-link" href="#">Follow Us</a>
      </li>
    </ul>
  </div>
  <div class="card-body active" data-id="1">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="2">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-body hidden" data-id="3">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>
Ele
  • 33,468
  • 7
  • 37
  • 75
  • Brilliant - worked perfectly. ++ for using let instead of var as well! ;) –  Feb 09 '18 at 17:02