Hotwire Discussion

Handling Multiple Event Targets?

This is probably a dumb question but I don’t know what’s the correct way to go about this in Stimulus:

If I have multiple targets:

<div class="my-element" data-mycontroller-target="potato" data-action="click->mycontroller#doSomething">... various elements inside, all divs with no actions or JS attached to them... </div>

<div class="my-element" data-mycontroller-target="potato" data-action="click->mycontroller#doSomething">... the same elements as above ...</div>

The action:

doSomething() {
$(this.potatoTarget).toggleClass('is-expanded');
}

Toggles the class on all the “potato” targets on the page, rather than the one that was interacted with. Whereas:

doSomething() {
var target = event.target;
$(target).toggleClass('is-expanded');
}

Toggles the specific target that was interacted with, but if I click on any elements within the parent, the “is-expanded” class is applied to that element (since the event is bubbling up from that element rather than the parent).

How do I reference the particular instance of a “potato” target I interacted with when there are multiple “potato” targets on the same page?

2 possible options:

  1. CSS: Use pointer-events: none; on the sub-elements to ignore the click event and pass it on to parent as target. (note: I’d put this in a CSS class and not inline as the example below)
<div class="my-element" 
       data-mycontroller-target="potato" 
       data-action="click->mycontroller#doSomething">
    <div style="pointer-events: none;">... the same elements as above ...</div>
</div>
  1. Javascript: Use closest
doSomething(event) {
  var target = event.target.closest('[data-mycontroller-target="potato"]');
  $(target).toggleClass('is-expanded');
}

Hello,

Sometime it’s better to break a big controller into a smaller one.
In your case, you could have a controller that only toggle the class ( or just “doSomething” ):

// do_something_controller.js
import { Controller } from "stimulus";

export default class extends Controller {
  click() {
    this.element.classList.toggle("is-expanded")
  }
}
<div class="my-element-list" data-controller="mycontroller">
  <div class="my-element"
    data-controller="do-something"
    data-action="click->do-something#click"
    data-mycontroller-target="potato">
    Russet are ideal baking potatoes and are also good fried and mashed.
  </div>

  <div class="my-element"
    data-controller="do-something"
    data-action="click->do-something#click"
    data-mycontroller-target="potato">
    Jewel Yam are sweet potatoes, great baked or roasted.
  </div>
</div>

Enfin, it’s an idea.

Thank you, I see I should have been doing closest() on the event.target rather than after the fact (I was doing $(target).closest()…)!

In terms of breaking down controllers, do you recommend it’s better to isolate as much as possible?

In my scenario I’m building a complex form that creates an entry in the back end. The form has lots of UI interactions that must be validated, as well as its own data model (subsequent values entered in the form are checked against the data selected so far).

My understanding is that you want to avoid having to cross-reference data inside controllers. Would it make sense for me to track the data model of the entity in one controller, and then track all the UI elements in their own controllers? Or all the UI elements in one controller, and the data model in the other?

Some of the UI elements trigger/pass data from the markup to the data model, which would mean cross-referencing controllers. Would it make sense to put it all in one gigantic controller?

Or would I isolate everything, such that if an interaction alters the data model, that all happens inside a controller independent of everything else? (Not sure how I would be able to universally track the state tho.)

Hi,

It depend on your need but when I started using stimulus I was using some big js files.

Okay stimulus is not the same paradigm as React but it share a common philosophy: organize your code the way it could be easily maintained and extended.

And more I was using stimulus and more I was thinking “oh, let’s break this big controller into smaller one”, you know it’s like playing to understand the limit.

In another discuss thread, someone was wanting a strange behavior, he has a form and multiple file inputs but some of them need a rewrite of the filename (based on the extension, si I remember correctly) when the input change.
And why not juste create a small controller that handle that ? it just listen when the input change, check the extension (or maybe a data attribute), and do, or not do, the operation.

So the best is to start with your big controller but if you find that certain action/function/methods could be extracted outside then let’s do it; because, it’s not easy to look at a 300 lines of code controller.

Cordialement,

1 Like

I use stimulus for simple validations. When performing more complex validations I consider backend validation.

I plan to do both, but what puzzles me with Stimulus is that if the UI triggers my validations, the UI interactions and the validations would need to be inside the same controller, unless I want to cross-reference controllers (which to my understanding is a no-no in Stimulus?)

Thank you for the insight–that what I’m inclined to do as well, to break this down as much as possible.

What do you do though in situations where you’d need to reference something inside another controller?

Like say, I have a data model of the content being submitted to the backend, and multiple parts of this form will need to be able to reference it. Let’s say, for example, I have a controller for a radio list and a controller for a collapsing accordion. I also have front end validation vs. the input fields on the page. Ideally these might be in their own controllers. But what if interacting with any of these can also triggers some ajax response to the backend–I’d need to kick that off inside one of the other controllers, and then communicate to another controller that handles the ajax response. Which would mean cross-communication between controllers–is that OK? It seems like it’s not desirable in Stimulus since there are only a couple janky ways to do it.

I’ve seen several techniques for cross communication, but never had the need to do this myself.

From my perspective I believe the key to handling data models inside of stimulus controllers (especially when using with turbo which caches the HTML) is to store state in the HTML instead of memory. Then using data models in the HTML to communicate between stimulus controllers.

If you provide more specific code for your examples it can help to provide more specific solutions.