Stimulus loosely connected Controllers best practice

I have a form with two Datetimepickers representing a datetime-range (initialized by the same Stimulus controller) and a few buttons to quickly select times for them. This involves getting the Controller instance of each Datetimepicker and setting the appropriate time for each.

There are multiple possibilities to achieve this:

  • Wrap the “quickselect” Controller around the buttons and the datetimepickers, then define each datetimepicker as target. In the #setTime-Action, read the data-start and data-end attributes from the event.target and apply them to the datetimepickers. This involves reading the data-Attributes manually (i.e. they have no Stimulus value-accessors).
    • Pros: Each datetimepicker target is distinct
    • Cons: Start and End times are not explicitly declared as values, Datetimepicker-Controller-Instances must be retrieved by Stimulus.getControllerForElementAndIdentifier, Datetimepicker-Elements are “tightly coupled” to Quickselect, as they must declare themselves as targets.
<div data-controller="quickselect">
  <button data-action="quickselect#setTime" data-start="08:00" data-end="12:00">...</button>
  <button data-action="quickselect#setTime" data-start="12:00" data-end="16:00">...</button>

  <input data-controller="datetimepicker" data-quickselect-target="start">
  <input data-controller="datetimepicker" data-quickselect-target="end">
</div>
  • Create a “quickselect” instance for each button, then define the datetimepickers as outlets so the Controller instances of the Datetimepickers can be accessed instantly.
    • Pros: start and end can be defined as Stimulus values. Datetimepicker-Controller-Instances can be accessed instantly.
    • Cons: Repitition-heavy, outlets are distinguished by Controller identifier => there is no clear distinction between the instances of start and end aside from manual checks.
<button data-controller="quickselect" data-action="quickselect#setTime" data-quickselect-start-value="08:00" data-quickselect-end-value="12:00" data-quickselect-datetimepicker-outlet="#start,#end">...</button>
<button data-controller="quickselect" data-action="quickselect#setTime" data-quickselect-start-value="12:00" data-quickselect-end-value="16:00" data-quickselect-datetimepicker-outlet="#start,#end">...</button>

<input data-controller="datetimepicker">
<input data-controller="datetimepicker">

Is there a recommended way of doing this? What are the considerations? I think I prefer the second variant, it is pretty clear from the markup what happens and having explicit Stimulus values leaves no ambiguity as to where the values are used.

You could maybe explore a basic (reusable) controller that update the data attribute the datetimepicker controller listens for, then with a callback function update the not-yet-set values? Otherwise outlets seems like the best option here.

I may be missing something but if the goal is just to set the inputs, would something like this work?

<div data-controller="quickselect">
  <button data-action="quickselect#setValues" data-start="08:00" data-end="12:00">Morning</button>
  <button data-action="quickselect#setValues" data-start="12:00" data-end="16:00">Afternoon</button>

  <input data-quickselect-target="input" data-accepts="start">
  <input data-quickselect-target="input" data-accepts="end">
</div>
export default class extends Controller {
  static targets = ["input"]

  setValues(event) {
    const values = event.currentTarget.dataset

    this.inputTargets.forEach((input) => {
      input.value = values[input.dataset.accepts]
    })
  }
}

Only one stimulus controller, less rigid in terms of what inputs get assigned what attributes.

Thanks for your inputs.

The point is, I want to have two different controllers, i.e. there exists a datetimepicker controller, which initializes a JS datetimepicker widget. Changing the <input> value directly is insufficient, since the internal state of the JS datetimepicker widget must be updated as well.

My goal is to use as many “native” Stimulus features as possible, i.e. encode values as Stimulus values and directly access the element from within the quickselect controller.

I went for the solution with the Stimulus outlets.