Toggle all siblings in a list

I’m sure this has been asked, and this is something I used to do in the jQuery days—but I can’t seem to find a Stimulus analog anywhere. Given a list of elements…

<div class="row" data-controller="contact" data-action="click->contact#toggle">Foo</div>
<div class="row" data-controller="contact" data-action="click->contact#toggle">Bar</div>
<div class="row" data-controller="contact" data-action="click->contact#toggle">Bat</div>

… I want to toggle the appearance of the target element and revert all its siblings, so the target feels “selected” when clicked. I feel like hasRowTarget may be part of the puzzle, but I can’t figure out how. Again, I realize this is pretty elementary stuff but…well, there you have it.

Controller currently looks like:

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="contact"
export default class extends Controller {
  static targets = ["row"]

  toggleRow() {
    this.rowTarget.classList.toggle("selected")
  }
}

My title might be a bit misleading; what I’m looking for is a method whereby I can apply a special class to only one of a list of targets. After further Googling/Stackoverflowing I refactored my markup to this:

<div class="row" data-id="some_unique_id" data-controller="contact" data-action="click->contact#toggle">Foo</div>
<div class="row" data-id="some_unique_id" data-controller="contact" data-action="click->contact#toggle">Bar</div>
<div class="row" data-id="some_unique_id" data-controller="contact" data-action="click->contact#toggle">Bat</div>

…and controller to this:

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="contact"
export default class extends Controller {
  static targets = ["row"]

  select(e) {
    this.currentRow = e.target.getAttribute('data-id')
     
    this.rowTargets.forEach((el, i) => {
      el.classList.toggle("selected", this.currentRow === el.getAttribute("data-id"))
    })
  }
}

but that only seems to do the same as toggling the class on a single row as I find it. Still stumped.

Are you restricted by markup? Because if not, then better make it like:

<div data-controller="contacts">
  <div class="row" data-contacts-target="contact" data-action="click->contacts#toggle">Foo</div>
  ...
</div>

https://stimulus.hotwired.dev/reference/targets#shared-targets

Then in event handler you will iterate over this.contactTargets and check if it it is not equal to event.target then collapse, else expand.

Thanks for your response. I’ve refactored my controller to this:

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="list"
export default class extends Controller {

  static targets = ["item"]

  select(e) {
    this.currentItem = e.target.getAttribute('data-id') // target of the click
    this.itemTargets.forEach((el, i) => {
      if (this.currentItem === el.getAttribute("data-id")) {
        el.classList.add("selected")
      } else {
        el.classList.remove("selected")
      }
    })
  }
}

but it still only changes the class on the clicked element. It never removes the classes on other elements. I’m sure I’m missing something incredibly obvious. My controller name has also changed to try and make it a bit more generic and reusable. Can you spot my issue? Thanks again.

Sorry, it is hard to understand the missing part due to lack of knowledge of English and by partials of source code. Is this what you wanted? stimulus-starter (forked) - CodeSandbox

This is excellent, thank you very much :+1:

1 Like