A simple persistent collapse/expand toggle with jquery – Wasya Co
Published
= A simple persistent collapse/expand toggle with jquery =

I’m maintaining a legacy project and rewrote this piece recently. it was originally written about a decade ago. jQuery is still available in the environment and no decision has been made to move this piece to React, so we’ll use jQuery.

2022-10-04 update: Added the detail stopPropagation(), in case your clickable toggle contains other interactive elements such as buttons or a search bar.

The formalism I’m using, carried over from decisions made a decade ago, is to add the class .collapse-expand to an icon, and clicking that would toggle the visibility of the next DOM element.

Without persistence the solution is a one-liner of javascript:


  $(".collapse-expand").click(function() { $(this).next().slideToggle() })

With persistence, we’ll use local storage and add another formalism: the key storing which element is collapsed/expanded, and the keyword for the state. The keyword I use is "collapsed", and it’s just a string in this case.

(Side note: I have a formalism for constantizing and keeping track of javascript keywords such as this one. It works pretty well, is robust and does not require switching the whole project to typescript and disposing of your build pipeline. If you would like me to describe it — please leave a comment, and if people are interested, I’ll post another article just on this topic.)

The key storing the keyword is `collapse-expand#${thisId}`. The DOM element having the class .collapse-expand has to have an id, so that each toggled element can be kept track of. Therefore, if the id is #simpleId, and the dom element is .collapse-expand#simpleId, then the key becomes “collapse-expand#simpleId".

(Side note: if you ever thought of which naming convention is more correct: simpleID or simpleId, my opinion is that simpleId is the correct one. The reason behind it is: “id” stands for “identifier” and is not a two-word acronym. Letter “d” in “id” does not stand for a separate word and therefore in a conversion from camelCase to snake_case, “id" should not become "i_d". It is one word, without a capital letter in the middle.)

Note also that the DOM structure necessitates the collapsible element to be the next one, not a child. So this structure is correct:


  <i class="collapse-expand" id="simpleId" />
  <div>The content I want to toggle</div>

Contrast it to the incorrect one where the folding section is not a next element:


  <i class="collapse-expand" id="simpleId">
    <div>The content I want to toggle</div>
  </i>

Lastly, we need the persistent, collapsed elements to be collapsed on page load. I iterate over all elements with class .collapse-expand, and fold them if persistence says they are folded. Additionally, upon clicking, the state is persistently stored.

With that, here is the entirety of the javascript code:


  $(".collapse-expand").each(function() {
    const thisId = $(this).attr('id')
    const state = localStorage.getItem("collapse-expand#"+thisId)
    if (state === 'collapsed') {
      $(this).next().slideToggle();
    }
  })
  $(".collapse-expand").click(function () {
    const thisId = $(this).attr('id')
    const state = localStorage.getItem("collapse-expand#"+thisId)
    if (state === 'collapsed') {
      localStorage.removeItem("collapse-expand#"+thisId)
    } else {
      localStorage.setItem("collapse-expand#"+thisId, "collapsed")
    }
    $(this).next().slideToggle();
  }).children().click(function (e) {
    e.stopPropagation()
  })

An improvement can be to change the icon, from collapsed to expanded, of the clickable element, and to style the pointer to signify that it’s clickable. I’ll leave it as an exercise to the reader. The basic idea is to add/remove .collapsed .expanded classes (separate from the .collapse-expand class) and style each class’ icon separately.

By Victor Pudeyev

A technical lead and business developer residing in Austin, TX. I specialize in systems built with ruby, javascript and solidity.



0 comments

Leave a Reply