1

Got a nice little accordion going here which uses headings to expand and collapse content below. Everything works the way I want it to, except for one thing: when you expand a section by clicking on the heading, you should be able to click on the heading again to collapse it. What's wrong with this picture?

<script type="text/javascript">
$(document).ready(function(){

$(".haccordion > :header").addClass("h-trigger static").next().addClass("section collapsed").attr("inert","");

document.querySelectorAll('.h-trigger').forEach(el => {
  el.setAttribute("tabindex", "0");
  el.addEventListener("keydown", e => {
    if ([" ", "Enter", "ArrowDown"].includes(e.key)) {
      e.target.click();
    }
  });
});
$(".h-trigger.static").click(function(){
    $(this).closest(".haccordion").find(".h-trigger.active").removeClass("active").addClass("static");
    $(this).closest(".haccordion").find(".section.expanded").removeClass("expanded").addClass("collapsed").attr("inert","");
    $(this).removeClass("static").addClass("active");
    $(this).next(".section.collapsed").removeClass("collapsed").addClass("expanded").removeAttr("inert","");
  });

$(".h-trigger.active").click(function(){    
    $(this).removeClass("active").addClass("static");
    $(this).next(".section.expanded").removeClass("expanded").addClass("collapsed").attr("inert","");
  });
});

</script>

<div class="haccordion">
  <h2>Section with a paragraph</h2>
  <p>Here is some text content</p>
  <h2>Section with a div</h2>
  <div>
    <h3>Heading</h3>
    <p>Here is some content with text and an image</p>
    <p><a href="#">Test link</a></p>
  </div>
  <h2>Section with a list</h2>
  <ul>
    <li>List item 1</li>
    <li>List item 2</li>
  </ul>
</div>

1 Answer 1

2

$(".h-trigger.active").click will only attach a click handler to a h-trigger that is currently active. If there are no active elements at the time this line is executed, no elements receive that click handler.

In the same way, $(".h-trigger.static").click creates a handler on all the trigger elements, since they all have .static at the time the handler is being attached. The handler executes every time a trigger element is clicked, not only when they are static. However, it is not as easy to see the effect this has, since clicking the active trigger ends up collapsing and then reactivating it, resulting effectively in a no-op.

There are two solutions (actually, just one solution; the other is jQuery helping you do more or less the first solution, by doing work behind the scenes):

  1. Attach a handler to .h-trigger, then manually check inside the handler whether the target element has .static or .active, and handle accordingly

  2. Use jQuery's delegated handlers:

$(document).ready(function(){

$(".haccordion > :header").addClass("h-trigger static").next().addClass("section collapsed").attr("inert","");

document.querySelectorAll('.h-trigger').forEach(el => {
  el.setAttribute("tabindex", "0");
  el.addEventListener("keydown", e => {
    if ([" ", "Enter", "ArrowDown"].includes(e.key)) {
      e.target.click();
    }
  });
});
// DELEGATED HANDLER HERE
$(".haccordion").on("click", ".h-trigger.static", function(){
    $(this).closest(".haccordion").find(".h-trigger.active").removeClass("active").addClass("static");
    $(this).closest(".haccordion").find(".section.expanded").removeClass("expanded").addClass("collapsed").attr("inert","");
    $(this).removeClass("static").addClass("active");
    $(this).next(".section.collapsed").removeClass("collapsed").addClass("expanded").removeAttr("inert","");
  });
// DELEGATED HANDLER HERE
$(".haccordion").on("click", ".h-trigger.active", function(){    
    $(this).removeClass("active").addClass("static");
    $(this).next(".section.expanded").removeClass("expanded").addClass("collapsed").attr("inert","");
  });
});
.collapsed {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="haccordion">
  <h2>Section with a paragraph</h2>
  <p>Here is some text content</p>
  <h2>Section with a div</h2>
  <div>
    <h3>Heading</h3>
    <p>Here is some content with text and an image</p>
    <p><a href="#">Test link</a></p>
  </div>
  <h2>Section with a list</h2>
  <ul>
    <li>List item 1</li>
    <li>List item 2</li>
  </ul>
</div>

A delegated handler will catch bubbled events on .haccordion, test whether the elements where the event originated fit the .h-trigger.active (or .h-trigger.static) selector, and only execute the handler if it does. This way, it does not matter if .h-trigger.active and .h-trigger.static exist at the time the handler is being created, only that .haccordion does.

1
  • Thanks very much for your help. I figured it was something like that, but didn't know how to fix it. Cheers!
    – DeanH
    Commented Jul 8 at 4:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.