如何使用热线(Hotwire)处理数据禁用功能(data-disable-with)?

20

我开始在我的Rails应用程序中使用Hotwire,并实现了类似于教程视频的功能,在其中提交表单并刷新页面的另一部分。就像视频中一样,我不得不实现自己的重置表单控制器:

import {Controller} from "stimulus"

export default class extends Controller {
  reset() {
    this.element.reset()
  }
}

重置表单中的值。表单大致如下:

<%= form_with(model: @invitation,
              data: {controller: "reset-form",
                     action: "turbo:submit-end->reset-form#reset"}) do |form| %>
  <!-- other form elements -->
  <%= form.submit "Invite" %>
<% end %>

它会生成如下所示的按钮:

<input type="submit" name="commit" value="Invite" data-disable-with="Invite">

因为有一个{{data-disable-with}},当你按下按钮时,它会被禁用以避免双重提交,这很好。问题在于{{this.element.reset()}}无法重置它。
应该如何正确处理这个问题?
我不是在寻找解决方法,我知道很多种,但我正在寻找解决这个问题的干净方法。
这个按钮的禁用是由UJS引起的吗?这意味着在Stimulus应用程序中不应使用UJS吗?
我可以从{{reset}} JavaScript函数重新启用按钮,但如果输入按钮是这样指定的:
<%= form.submit "Invite", data: {disable_with: "Inviting, please wait..."} %>

然后按钮的原始标签(value)会丢失,我没有重新建立它的方法,这让我想到实现此功能的任何内容(UJS?)都不适用于hotwire/spa应用程序,并且需要完全重新加载页面。

我可以简单地抛出:

config.action_view.automatically_disable_submit_tag = false

我想用Stimulus控制器实现自己的双击预防,但这种做法感觉不太对。问题不在于属性data-disable-with,而是它是如何实现的。


你能否在控制器的 Turbo-Stream 响应中替换整个表单?我想你可以通过 turbo-stream[append] 向页面添加新元素,并使用 turbo-stream[replace] 来替换表单为“清新”的表单。这样,你也可以移除 Stimulus 控制器。但这种解决方案会导致光标位置丢失(对于类似聊天应用程序来说不太好)。 - stwienert
5个回答

6

Turbo一直以来都在禁用提交按钮,最近还添加了data-turbo-submits-with作为UJS data-disable-with的替代方案:

# available since: turbo v7.3.0 (turbo-rails v1.4.0) thx @PaulOdeon
<%= form_with model: @invitation do |form| %>
  <%= form.submit "Invite", data: {turbo_submits_with: "Inviting..."} %>
<% end %>

重置表单是可选的:

<%= form_with model: @invitation, data: {
  controller: "reset-form",
  action: "turbo:submit-end->reset-form#reset"
} do |form| %>
  <%= form.submit "Invite", data: {turbo_submits_with: "Inviting..."} %>
<% end %>
# NOTE: form reset is implemented as stimulus controller in the question

https://turbo.hotwired.dev/reference/attributes


是的,你应该把这个扔进去。
config.action_view.automatically_disable_submit_tag = false

它只添加了data-disable-with属性,这是特定于UJS的,并不影响Turbo
然而,这并不意味着以前做不到。如果你想实现自定义行为,这只是一个简化的例子。为了在这里明确区分,我使用非 Turbo 和非 UJS 属性(Turbo 只为我们设置和移除 disabled 属性)。
<%= form_with model: @invitation, data: {reset: true} do |form| %>
  <%= form.submit "Invite" data: {disable_append: "..."} %>
<% end %>

对于简单的一次性操作,您可以跳过Stimulus,只使用Turbo事件:
// app/javascript/application.js

document.addEventListener('turbo:submit-start', (event) => {
  const { detail: { formSubmission, formSubmission: { submitter } } } = event
  // disable-append or maybe disable-loading and make it spin
  if (submitter.dataset.disableAppend) {
    // TODO: handle <button> element and use innerHTML instead of value.
    // save original text
    formSubmission.originalText = submitter.value
    submitter.value += submitter.dataset.disableAppend
  }
})

document.addEventListener('turbo:submit-end', (event) => {
  const { detail: { formSubmission, formSubmission: { formElement, submitter } } } = event
  // put it back (formSubmission is the same object during form submission lifecycle)
  if (formSubmission.originalText) {
    submitter.value = formSubmission.originalText
  }
  // data-reset
  if (formElement.dataset.reset === 'true') {
    formElement.reset()
  }
})

这基本上就是 Turbo 现在的实际操作方式了:
https://github.com/hotwired/turbo/blob/v7.3.0/src/core/drive/form_submission.ts#L217-L239

1
谢谢!在我的情况下,第一次尝试时 turbo_submits_with 并没有起作用,我不得不升级 turbo-rails gem 到 1.4.0 版本,所以显然是最近添加的功能。 - Paul Odeon
1
值得注意的是,在视频(6:20)中提到了重置控制器,你可能不需要它,除非在表单提交后仍停留在同一页上。 - Paul Odeon

0
在用户操作时替换页面上的HTML内容最“Hotwire方式”是使用turbo stream响应。
如果我理解正确,您希望在表单提交后将表单(或仅按钮)返回到其原始状态。
您可以像这样用turbo frame包装您的表单。
<%= turbo_frame_tag dom_id(@invitation) do %>
  <%= form_with(...) %>
  <% end %>
<% end %>

接着,就像 @stwienert 所说的那样,让你的控制器返回一个 format.turbo_stream 响应,渲染表单的部分/模板。以下是一个从 Turbo 文档 改编的 turbo_stream 响应示例:

format.turbo_stream do
  render turbo_stream: turbo_stream.replace(@invitation, partial: "your/form",
    locals: { invitation: @invitation }) # or Invitation.new, perhaps
end

目标是使您的控制器响应针对表单的DOM ID,以便在表单提交时,您的数据得到适当处理,然后客户端接收到Turbo流响应,其中包含您想要在turbo_frame标记内看到的HTML。

如果您只想替换按钮,请仅在turbo_frame中包装按钮,为其提供唯一的DOM ID,并将该ID传递给turbo_stream.replace/append等,例如turbo_stream.replace(:button_dom_id_here, partial: ...)


-1
TL;DR:在您的TurboFrame中添加target="_top" 例如:
<%= turbo_frame_tag @invitation, target: '_top' do %>
  <!-- Your form here -->
<% end %>

这将刷新整个页面,这是推荐的工作流程(见下文)。

完整回答

这是否意味着在Stimulus应用程序中不应使用UJS?

是的。由于它已经正式弃用,所以不应该在Hotwire中使用UJS。当用户提交时按钮禁用现在是Hotwire的一部分。如果需要,您可以安全地删除RailsUJS。请参阅升级指南

提交表单+更新页面的多个部分

使用Hotwire有两种方法可以实现此目的:

汉译英:
TurboFrames:将目标设置为顶部(即target="_top")以重新呈现整个页面。我强烈推荐这种方法。这就是我们所做的。
TurboStreams:返回多个TurboStreams。请注意,尽管有其他答案,但您根本不需要Websockets。这可以通过HTTP Post请求完成。
使用TurboFrames(推荐)
迄今为止最简单的方法。将表单包装在turbo_frame标记中,您可能已经这样做了,但使用target: '_top'来定位整个页面重新加载:
<%= turbo_frame_tag @invitation, target: '_top' do %>
  <!-- Your form here -->
<% end %.

注意,默认情况下,Hotwire 只会重新加载当前的 TurboFrame。添加额外的 target: '_top' 会使用 Hotwire 重新加载整个页面,这是一件好事并且 在其文档中得到很好的支持

使用 TurboFrames

这种方法要复杂得多。简而言之,您需要:

  1. turbo_stream helpers 中包装表单和您希望更新的页面的其他部分。
  2. 由于您的表单上的提交操作默认会重定向到 show 操作,因此您需要创建一个名为 show.turbo_stream.erb 的文件。
  3. 在该文件中,您需要添加两个要更新的 turbo frames。
这对大多数应用来说都有些过头了,我个人认为。如果你想走这条路,这里有一个完整的讨论,其中包括用户“kfk”在底部提供的一个完整示例(得到了DHH的认可)。
祝你好运!希望能帮到你!

-1
我可能会在form.submit中添加“原始”文本:
<% = form.submit "Invite", data: { 
  disable_with: "...", original: "Invite", "data-reset-form-button-target" } %>

你可以增强你的刺激控制器:

 static targets = ["button"]
 reset() {
   this.element.reset()
   this.buttonTarget.value  = this.buttonTarget.dataset.original
 }

无法测试,只是涂鸦。


-2

data-disable-with来自于UJS,它并不是真正意义上与Turbo兼容的(尽管这可能会让人感到困惑,因为有努力使从UJS过渡到Turbo更加平滑)。

Turbo使用Websockets,而UJS利用AJAX。这两种方法是正交的。

当你从UJS切换到Turbo时,你会失去disable-with的便利性,并且你将通过turbo_stream而不是js(即UJS/AJAX方式)返回内容。

重置表单会清除输入,但不会切换提交按钮的disabled属性。

通过Websockets替换整个表单将给你一个新的表单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接