跳至内容

构建真实内容

我们已经实现了第一个控制器,并了解了 Stimulus 如何将 HTML 连接到 JavaScript。现在,让我们通过重新创建 Basecamp 中的一个控制器,来看看我们可以在实际应用程序中使用的东西。

封装 DOM 剪贴板 API

在 Basecamp 的用户界面中分散着这样的按钮

Screenshot showing a text field with an email address inside and a ”Copy to clipboard“ button to the right

当你单击其中一个按钮时,Basecamp 会将一段文本(例如 URL 或电子邮件地址)复制到你的剪贴板。

网络平台具有 用于访问系统剪贴板的 API,但没有 HTML 元素可以执行我们需要执行的操作。要实现“复制到剪贴板”按钮,我们必须使用 JavaScript。

实现复制按钮

假设我们有一个应用程序,它允许我们通过为其他人生成 PIN 来授予他们访问权限。如果我们可以在生成的 PIN 旁边显示一个按钮,以便将其复制到剪贴板以方便共享,那将非常方便。

打开 public/index.html,并将 <body> 的内容替换为按钮的草图

<div>
PIN: <input type="text" value="1234" readonly>
<button>Copy to Clipboard</button>
</div>

设置控制器

接下来,创建 src/controllers/clipboard_controller.js 并添加一个空方法 copy()

// src/controllers/clipboard_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
copy() {
}
}

然后将 data-controller="clipboard" 添加到外部 <div>。每当此属性出现在元素上时,Stimulus 都会连接我们控制器的实例

<div data-controller="clipboard">

定义目标

我们需要引用文本字段,以便在调用剪贴板 API 之前选择其内容。将 data-clipboard-target="source" 添加到文本字段

  PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>

现在向控制器添加一个目标定义,以便我们可以将文本字段元素作为 this.sourceTarget 访问

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

// ...
}

static targets 行有什么作用?

当 Stimulus 加载你的控制器类时,它会在名为 targets 的静态数组中查找目标名称字符串。对于数组中的每个目标名称,Stimulus 都会向你的控制器添加三个新属性。在此,我们的 "source" 目标名称变为以下属性

  • this.sourceTarget 计算为控制器作用域中的第一个 source 目标。如果没有 source 目标,则访问该属性会引发错误。
  • this.sourceTargets 计算为控制器作用域中所有 source 目标的数组。
  • this.hasSourceTarget 如果有 source 目标则计算为 true,如果没有则计算为 false

您可以在参考文档中阅读更多有关目标的信息。

连接操作

现在我们准备连接“复制”按钮。

我们希望单击按钮调用控制器中的 copy() 方法,因此我们将添加 data-action="clipboard#copy"

  <button data-action="clipboard#copy">Copy to Clipboard</button>

常见事件具有简写操作符号

您可能已经注意到我们从操作描述符中省略了 click->。这是因为 Stimulus 将 click 定义为 <button> 元素上操作的默认事件。

某些其他元素也具有默认事件。以下是完整列表

元素 默认事件
a click
button click
details toggle
form submit
input input
input type=submit click
select change
textarea input

最后,在我们的 copy() 方法中,我们可以选择输入字段的内容并调用剪贴板 API

  copy() {
navigator.clipboard.writeText(this.sourceTarget.value)
}

在浏览器中加载页面并单击“复制”按钮。然后切换回文本编辑器并粘贴。您应该看到 PIN 1234

Stimulus 控制器可重复使用

到目前为止,我们已经了解了当页面上一次只有一个控制器实例时会发生什么。

页面上同时出现多个控制器实例的情况并不少见。例如,我们可能希望显示 PIN 列表,每个 PIN 都有自己的“复制”按钮。

我们的控制器是可重复使用的:任何时候我们希望提供一种将文本复制到剪贴板的方法,我们只需要在页面上使用带有正确注释的标记。

让我们继续在页面上添加另一个 PIN。复制并粘贴 <div>,以便有两个相同的 PIN 字段,然后更改第二个字段的 value 属性

<div data-controller="clipboard">
PIN: <input data-clipboard-target="source" type="text" value="3737" readonly>
<button data-action="clipboard#copy">Copy to Clipboard</button>
</div>

重新加载页面并确认两个按钮都可以正常工作。

操作和目标可以放在任何类型的元素上

现在让我们再添加一个 PIN 字段。这一次,我们将使用“复制”链接而不是按钮

<div data-controller="clipboard">
PIN: <input data-clipboard-target="source" type="text" value="3737" readonly>
<a href="#" data-action="clipboard#copy">Copy to Clipboard</a>
</div>

只要具有适当的 data-action 属性,Stimulus 允许我们使用任何类型的元素。

请注意,在这种情况下,单击链接也会导致浏览器遵循链接的 href。我们可以通过在操作中调用 event.preventDefault() 来取消此默认行为

  copy(event) {
event.preventDefault()
navigator.clipboard.writeText(this.sourceTarget.value)
}

类似地,我们的 source 目标不必是 <input type="text">。控制器只希望它具有 value 属性和 select() 方法。这意味着我们可以改用 <textarea>

  PIN: <textarea data-clipboard-target="source" readonly>3737</textarea>

总结和后续步骤

在本章中,我们研究了一个将浏览器 API 封装在 Stimulus 控制器中的真实示例。我们了解了如何同时在页面上显示控制器的多个实例,并且我们探讨了操作和目标如何使您的 HTML 和 JavaScript 保持松散耦合。

现在让我们看看对控制器设计进行的微小更改如何使我们实现更强大的实现。

下一步:设计弹性