控制器外部的stimulus.js实时更新字段

Mal*_*son 3 ruby-on-rails stimulusjs

在 Rails 6 安装中,我有以下内容:

控制器:

# app/controllers/foo_controller.rb
def bar
  @items = [["firstname", "{{ FIRSTNAME }}"], ["lastname", "{{ LASTNAME }}"], ["company", "{{ COMPANY }}"]]
end
Run Code Online (Sandbox Code Playgroud)

看法:

# 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 %>
Run Code Online (Sandbox Code Playgroud)

以及相关的刺激代码(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;
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,正如您可能已经猜到的那样,我们的想法是<input>从哈希中为每个项目生成一个字段@items,预先填充相关值并与 a “链接” <span>,它会在值更改时更新。到目前为止,一切正常。

但这是我的问题。这部分是普通的旧脏香草js,感觉不太“刺激”:

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;
};
Run Code Online (Sandbox Code Playgroud)

当然有一些方法可以改善这一点。任何有关如何以更优雅的方式重构此代码的建议都将受到欢迎。

LB *_*ton 5

一种方法是拥有两个控制器,一个用于“将更改内容的事物”(我们称之为content),另一个用于“将在其他地方显示任何更新内容的事物”(我们称之为output)。

一旦设置了两个控制器,就可以更容易地推断它们是离散的。当用户交互更新值时,一个会执行某些操作,而另一个在知道更新的值时也应该执行某些操作。

Stimulus 建议跨控制器与事件协调。JavaScript 事件传递是一种强大的、浏览器本机的跨 DOM 元素通信的方式。

首先,我们从 HTML 中最简单的情况开始

  • 一般来说,无论服务器端如何生成内容,最好首先考虑 HTML,因为它将帮助您一次解决一个问题。
  • 顺便说一句,我不编写 Ruby,如果只有最小的可行 HTML 来重现问题,这个问题会更容易解析。
  • 下面我们有两个div元素,一个位于上面,用于显示标签name内的值h1和标签email内的值p
  • 第二个div 包含两个input标签,用户将在这些标签中更新值。
  • 我已经对“初始”数据进行了硬编码,因为这将来自第一个 HTML 渲染中的服务器。
<body>
  <div
    class="container"
    data-controller="output"
    data-action="content:updated@window->output#updateLabel"
  >
    <h1 class="title">
      Hello
      <span data-output-target="item" data-field="name">Joe</span>
    </h1>
    <p>
      Email:
      <span data-output-target="item" data-field="email">joe@joe.co</span>
    </p>
  </div>
  <div data-controller="content">
    <input
      type="text"
      data-action="content#update"
      data-content-field-param="name"
      value="Joe"
    />
    <input
      type="text"
      data-action="content#update"
      data-content-field-param="email"
      value="joe@joe.co"
    />
  </div>
</body>
Run Code Online (Sandbox Code Playgroud)

其次 - 浏览事件流程

  • 一旦input更新,它将conten#update在更改时触发事件。
  • data-content-field-param是一个操作参数,可在控制器上的event.params类方法的给定内部使用。updatecontent
  • 这样,一个类方法就知道已更改的元素(通过事件)以及传递信息时给出的“名称”字段。
  • 控制器output有一个单独的操作来“侦听”调用的事件content:updated,它将全局侦听该事件(在 处window),然后updateLabel使用接收到的事件调用其自己的方法。
  • 控制器output具有带有名称的目标item,每个目标都具有它应该在简单属性中引用的“字段”的映射data-field

第三 - 创建控制器

  • 下面,ContentController有一个update方法将接收任何触发的输入元素的更改事件。
  • 可以从事件中收集值currentTarget,并且可以通过 收集字段event.params.field
  • 然后用该方法触发一个新事件this.dispatch,我们给它命名updated,Stimulus 会自动附加类名,并content给出事件名称content:updated。根据文档 - https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events
  • OutputController一个名称目标item和一个方法updateLabel
  • updateLabel将接收事件并从 的调度中“提取”为其提供的详细信息ContentController
  • 最后,updateLabel将遍历每个元素itemTargets并查看该元素的数据集上是否有匹配的字段名称,然后innerText在找到匹配项时更新。这也意味着您可以在该控制器的作用域 HTML 中拥有多个“名称”占位符。
class ContentController extends Controller {
  update(event) {
    const field = event.params.field;
    const value = event.currentTarget.value;
    this.dispatch('updated', { detail: { field, value } });
  }
}

class OutputController extends Controller {
  static targets = ['item'];

  updateLabel(event) {
    const { field, value } = event.detail;

    this.itemTargets.forEach((element) => {
      if (element.dataset.field === field) {
        element.innerText = value;
      }
    });
  }
}

Run Code Online (Sandbox Code Playgroud)