Stimulus Discourse

Toggling items in a list

hey, need some help toggling specific items in a list.

I have a page which lists a number of products, and each product has many variants. when the mouse clicks on variants under a product I won’t to only show the variants of the product that is clicked. this is a gif of the behaviour currently : https://share.getcloudapp.com/KouBPEqY

it always shows only the first products variants.

currently my code is like this

<tr>
  <td>
    <%= product.title %>
    <button data-action="click->toggle#toggle">Variants</button>
  </td>
  <td>
    <%= render partial: "image", collection: product.images %>
  </td>
</tr>
<tr class="hidden" data-target="toggle.field">
  <td></td>
  <td>
    <%= render partial: "variant", collection: product.variants %>
  </td>
</tr>

and the stimulus controller

export default class extends Controller {
  static targets = ['field']
  
  toggle() {
    this.fieldTarget.classList.toggle('hidden')
  }
}

just wondering how I should fix this so that only a products associate variants are shown when I click on them.

I’m thinking I should be passing the id of the product with something like data-toggle-product_id="<%= (product.id) %>" - but am a little stuck on what to do after…

please let me know if you have any suggestions…

thank you!

Arjun

Hey @arjunrajkumar!

So generally speaking, you’ll want to use one controller instance for each product rather than one controller instance that surrounds all products. This will necessitate reworking your HTML so that controller can be specified on a tag that encapsulates both the button and the hidden area with the variants. However, I’d say this would be the preferred approach - it keeps things very simple and can allow you to reuse the ToggleController elsewhere, as well.

I have a similar controller in the current app I’m working on that is used in a few very different ways, because the flexibility of simply turning a class on and off can be very powerful.

To make the ToggleController a bit more reusable, allow the class that you’ll be toggling (in this case “hidden”) to be passed in as a data attribute.

Here’s an example from my ToggleController:

export default class extends Controller {
  static targets = [ 'toggle' ]

  initialize() {
    this.classes = []
    if(this.data.has('classes')) {
      this.classes = this.data.get('classes').split(' ')
    }
  }

  toggle() {
    this.toggleTargets.forEach((target) => {
      this.classes.forEach(c => target.classList.toggle(c))
    })
  }
}

This allows me to toggle a different class each time I use it, it allows multiple classes to be toggled (for instance, if you’re using a CSS framework and want to toggle your own “hidden” class but also toggle one of the framework classes on and off, allows multiple targets to be toggled.

Hope this helps!

1 Like

hey @welearnednothing, thanks :pray:

I’ll try redoing it by having a controller instance for each product :exploding_head: :blush:

I like the way you’ve set up the ToggleController … lots to explore.