Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

stimulus.js live update field outside of controller

On a rails 6 installation, I have the following:

Controller:

# app/controllers/foo_controller.rb
def bar
  @items = [["firstname", "{{ FIRSTNAME }}"], ["lastname", "{{ LASTNAME }}"], ["company", "{{ COMPANY }}"]]
end

View:

# app/views/foo/bar.html.erb
<p>Quia <span data-field="firstname">{{&nbsp;FIRSTNAME&nbsp;}}</span> quibusd <span data-field="firstname">{{&nbsp;FIRSTNAME&nbsp;}}</span> am sint culpa velit necessi <span data-field="lastname">{{&nbsp;LASTNAME&nbsp;}}</span> tatibus  s impedit recusandae modi dolorem  <span data-field="company">{{&nbsp;COMPANY&nbsp;}}</span> aut illo ducimus unde quo u <span data-field="firstname">{{&nbsp;FIRSTNAME&nbsp;}}</span> tempore voluptas.</p>

<% @items.each do |variable, placeholder| %>
<div data-controller="hello">
  <input
  type="text"
  data-hello-target="name"
  data-action="hello#greet"
  data-field="<%= variable %>"
  value="<%= placeholder %>">
</div>
<% end %>

and the relevant stimulus code (vanilla JS):

//app/javascript/controllers/hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name" ]

  greet() {
    var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
    for (var i = 0; i < elements.length; i++) {
      elements[i].innerText = this.nameTarget.value;
    };
  }
}

Now, as you might have guessed, the idea is to generate one <input> field per item from the @items hash, pre-filled with the relevant value and "linked" with a <span>, which it updates on value change. So far, everything works.

Here's my issue though. This part is plain old dirty vanilla js, which doesn't feel too 'stimulusy':

var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
for (var i = 0; i < elements.length; i++) {
  elements[i].innerText = this.nameTarget.value;
};

Surely there's some way to improve this. Any suggestion as to how to refactor this code in a more elegant way would be most welcome.

like image 753
Malcolm Isaacson Avatar asked Sep 19 '25 23:09

Malcolm Isaacson


1 Answers

An alternate approach is to follow the Publish-Subscribe pattern and simply have one controller that can both publish events and subscribe to them.

  • This leverages the recommended approach of Cross-controller coordination with events.
  • This approach adds a single controller that will be 'close' to the elements that need to publish/subscribe and is overall simpler to the first answer.

PubSubController - JS code example

  • In the controller below we have two methods, a publish which will dispatch an event, and a subscribe which will receive an event and update the contoller's element.
  • The value used by this controller is a key which will serve as the reference for what values matter to what subscription.
class PubSubController extends Controller {
  static values = { key: String };

  publish(event) {
    const key = this.keyValue;
    const value = event.target.value;
    this.dispatch('send', { detail: { key, value } });
  }

  subscribe(event) {
    const { key, value } = event.detail;

    if (this.keyValue !== key) return;

    this.element.innerText = value;
  }
}

PubSubController - HTML usage example

  • The controller will be added to each input (to publish) and each DOM element you want to be updated (to subscribe).
  • Looking at the inputs you can see that they have the controller pub-sub and also an action (defaults to triggering when the input changes) to fire the publish method.
  • Each input also contains a reference to its key (e.g email or name).
  • Finally, the two spans that 'subscribe' to the content are triggered on the event pub-sub:send and pass the event to the subscribe method. These also have a key.
<body>
  <div class="container">
    <h1 class="title">
      Hello
      <span
        data-controller="pub-sub"
        data-action="pub-sub:send@window->pub-sub#subscribe"
        data-pub-sub-key-value="name"
        >Joe</span
      >
    </h1>
    <p>
      Email:
      <span
        data-controller="pub-sub"
        data-action="pub-sub:send@window->pub-sub#subscribe"
        data-pub-sub-key-value="email"
        >[email protected]</span
      >
    </p>
  </div>
  <div>
    <input
      type="text"
      data-controller="pub-sub"
      data-action="pub-sub#publish"
      data-pub-sub-key-value="name"
      value="Joe"
    />
    <input
      type="text"
      data-controller="pub-sub"
      data-action="pub-sub#publish"
      data-pub-sub-key-value="email"
      value="[email protected]"
    />
  </div>
</body>
like image 92
LB Ben Johnston Avatar answered Sep 22 '25 13:09

LB Ben Johnston