Hotwire Discussion

Cross-controller communication for live-updates

I’m still new to stimulus.js, so please forgive me if this question is naive.

I have an arbitrary text provided by users, sprinkled with placeholders formatted in a particular way ("{{ FIRSTNAME }}" for example).

<p data-controller="field clause">
  Quia deserunt enim quas qui corporis accusamus lab <span data-clause-target="tag">{{ FIRSTNAME }}</span> oriosam at minus hic animi quibusd <span data-clause-target="tag">{{ FIRSTNAME }}</span> am sint culpa velit necessi <span data-clause-target="tag">{{ LASTNAME }}</span> tatibus fugiat odit ipsam quos eiu <span data-clause-target="tag">{{ FIRSTNAME }}</span> s impedit recusandae modi dolorem.

In an entirely different <div> (positioning and hierarchy still unclear), I have a form which lists all these placeholders (it’s ERB):

<% @clause.variables.each do |variable, placeholder| %>
  <div data-controller="field clause" data-action="field:send->clause#fill">
    value="<%= placeholder %>">
<% end %>

The value of variable might be ‘FIRSTNAME’, whereas the value of placeholder might be ‘{{ FIRSTNAME }}’ in order to match the placeholders in the text.

The goal is to provide a way to fill out stuff in the form, and live-update the text.

I’ve spent a couple hours trying to figure out cross-controller coordination, but alas to no avail.

Here’s my code:

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

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

  send() {
    this.dispatch("send", { detail: { content: this.inputTarget } });

as well as…

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

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

  fill({ detail: { content } }) {
    // BONUS: HOW CAN I FILL OUT THE <APPROPRIATE> TAG HERE? (ie the one corresponding to the variable)

Any help would be much appreciated!

For what it’s worth, here’s a working setup:

import { Controller } from "stimulus"

export default class extends Controller {
  fill({ detail: { content } }) {
    var tags = document.querySelectorAll('[data-field=' + content.dataset.field + ']');
    for (var i = 0; i < tags.length; i++) {
      tags[i].innerText = content.value;

But I don’t like it, as it kind of defeats the purpose of using stimulus…

Is it possible that you can merge the two different controllers into a single controller? if they share a parent element?