【问题标题】:stimulus.js live update field outside of controller控制器外部的stimulus.js 实时更新字段
【发布时间】:2022-07-23 07:50:04
【问题描述】:

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

控制器:

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

查看:

# 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 %>

以及相关的stimulus 代码(原版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;
    };
  }
}

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

这是我的问题。这部分是普通的旧脏香草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;
};

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

【问题讨论】:

    标签: ruby-on-rails stimulusjs


    【解决方案1】:

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

    一旦您设置了两个控制器,就可以更容易地推断它们是离散的。当一个值从用户交互中更新时,一个会做某事,而另一个在知道更新后的值时应该做一些事情。

    Stimulus 推荐cross controller coordination with events。 JavaScript 事件传递是一种强大的浏览器原生方式,可以在 DOM 中的元素之间进行通信。

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

    • 一般来说,最好先考虑 HTML,而不管内容是如何在服务器端生成的,因为它会帮助您一次解决一个问题。
    • 顺便说一句,我不编写 Ruby,如果它只有最小的可行 HTML 来重现问题,这个问题会更容易解析。
    • 下面我们有两个div 元素,一个位于上方,用于显示h1 标记内的name 值和p 标记内的email
    • 第二个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>
    

    第二 - 遍历事件流

    • input 更新后,将在更改时触发 conten#update 事件。
    • data-content-field-param 是一个 Action Parameter,将在 event.params 中提供给 content 控制器上的类方法 update
    • 这样,one 类方法可以了解已更改的元素(通过事件)和字段“名称”,以便在传递信息时提供此信息。
    • 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;
          }
        });
      }
    }
    
    

    【讨论】:

      【解决方案2】:

      另一种方法是遵循Publish-Subscribe 模式,只需拥有一个既可以发布事件又可以订阅它们的控制器。

      • 这利用了Cross-controller coordination with events 的推荐方法。
      • 这种方法添加了一个控制器,该控制器将“接近”需要发布/订阅的元素,并且总体上比第一个答案更简单。

      PubSubController - JS代码示例

      • 在下面的控制器中,我们有两个方法,一个是publish,它将分派一个事件,一个subscribe,它将接收一个事件并更新控制器的元素。
      • 此控制器使用的valuekey,它将作为参考,以确定哪些值对哪些订阅很重要。
      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 使用示例

      • 控制器将添加到每个输入(以发布)和您想要更新(以订阅)的每个 DOM 元素。
      • 查看inputs,您可以看到他们拥有pub-sub 控制器和一个触发发布方法的操作(默认为在输入更改时触发)。
      • 每个input 还包含对其key 的引用(例如电子邮件或姓名)。
      • 最后,“订阅”内容的两个跨度在事件pub-sub:send 上触发,并将事件传递给subscribe 方法。这些也有一把钥匙。
      <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"
              >joe@joe.co</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="joe@joe.co"
          />
        </div>
      </body>
      

      【讨论】:

        猜你喜欢
        • 2013-07-28
        • 2015-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多