我们已经实现了第一个控制器,并学习了 Stimulus 是怎样将 HTML 连接到 JavaScript 的。现在,我们看一下 Basecamp 是怎样创建控制器并将它用在真实的应用程序中的。

封装 DOM 剪贴板 API

在 Basecamp 的 UI 中,有类似这样的按钮:
bc3-clipboard-ui.png
点击按钮,Basecamp 就会将文字、URL 或者邮箱地址复制到你的剪贴板中。

Web 平台有这样的系统剪贴板访问 API,但是我们并不需要其中的 HTML 元素。要实现这样的复制按钮,我们必须使用 JavaScript。

实现复制按钮

比如说,我们有一个 APP,允许我们使用生成的 PIN 来授权别人访问。如果在生成的 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 "stimulus"

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

在最外层的 <div> 中添加 data-controller="clipboard"。这个属性将在元素上显示,Stimulus 将会连接一个控制器实例:

<div data-controller="clipboard">

定义目标

我们需要引用文本域,这样我们就能在调用剪贴板 API 前选择它的内容了。在文本域元素上添加 `data-target="clipboard.source":

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

现在,给控制器添加一个目标定义,这样我们就能使用 this.sourceTarget 访问文本域元素:

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

  // ...
}

static targets 这行是什么? 在 Stimulus 加载控制器类时,它会在被称为目标(targets)的静态数组中寻找目标名称字符串。针对数组中的每个目标名称,Stimulus

会为你的控制器添加三个新属性。这里 "source" 这个目标名称就变成了这样三个属性:

  • this.sourceTarget 在控制器作用域内寻找到的第一个来源目标。如果没有找到,访问此属性会抛出一个错误
  • this.sourceTargets 在控制器作用域内寻找到的由所有来源目标组成的数组。
  • this.hasSourceTarget 如果找到了来源目标,返回 true,否则,返回 false。

连接操作

我们已经准备好装配这个复制按钮了。

我们想要点击这个难就能调用控制器的 copy() 方法,所以需要添加 data-action="clipboard#copy":

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

常见的事件有一个速记符号 你可能已经注意到了我们省略了操作描述符中的 click->。那是因为 Stimulus 将点击事件设置成了操作 <button> 元素的默认事件。

当然其他元素都有默认事件。这里是完整的列表:

元素默认事件
aclick
buttonclick
formsubmit
inputchange
input type=submitclick
selectchange
textareachange

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

  copy() {
    this.sourceTarget.select()
    document.execCommand("copy")
  }

刷新页面,然后点击复制按钮。然后在文本编辑器中粘贴,你应该看到 PIN 值是1234

设计有弹性的用户界面

尽管剪贴板 API 在当前浏览器里能得到很好的支持,但可能还会有一小部分人在通过老版本浏览器使用我们的应用程序。

我们还应该意识到人民在访问应用程序时,可能会遇到一些问题。例如,网络连接问题或者 CDN 可用性问题等,都有可能影响到 JavaScript 的加载。

你可能觉得向后支持老版本浏览器是不值得去做的,或者,网络问题只是一时的,只需要刷新一下就好了。但是往往能有一些简单的方式来优雅地应对此类问题。

所谓有弹性的方式,通常被理解为渐进增强,它是构建 web 界面的一种实践,最基础的功能由 HTML 和 CSS 实现,然后再在此基础上使用 CSS 和 JavaScript 简历基础的用户体验,逐步地根据浏览器支持实现最佳效果。

PIN 字段的渐进增强

我们一起看看,如何对 PIN 字段进行渐进增强,隐藏复制按钮,除非它被浏览器支持。这样就能避免给别人展示不能用的按钮。

首先在 CSS 中隐藏复制按钮。然后测试 Stimulus 控制器中剪贴板 API 的支持情况。如果 API 被支持,我们可以控制器元素上添加一个 CSS 类名将按钮显示出来。

为按钮添加 class="clipboard-button"

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

然后,将下拉样式添加到 public/main.css

.clipboard-button {
  display: none;
}

.clipboard--supported .clipboard-button {
  display: initial;
}

在控制器中实现 connect() 方法,它可以在 API 被支持时添加 CSS 类名 到控制器元素上:

  connect() {
    if (document.queryCommandSupported("copy")) {
      this.element.classList.add("clipboard--supported")
    }
  }

如果愿意,你可以在浏览器中禁用 JavaScript,刷新页面,注意,复制按钮不见了。

我们对 PIN 字段进行了渐进增强:复制按钮的基线状态是隐藏,只有在 JavaScript 探测到剪贴板 API 被支持时才显示出来。

Stimulus 控制器可复用

到目前位置,我们看到了同一时间页面上有一个控制器实例时会发生什么。

在页面上同时存在多个控制器实例也不是罕见现象。例如,我们想要显示一个 PIN 的列表,每一个都有它的复制按钮。

控制器是可复用的:任意时候我们想要提供一种方式去向剪贴板复制一段文本,我们只需要页面上添加一段带着正确注解的代码。

我们来试试再在页面上添加一个 PIN。复制并粘贴<div>便有了两个完全一样的 PIN 字段,修改第二个的 value 属性:

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

刷新并确认两个按钮都能使用。

操作和目标可以用在任意元素上

再添加一个 PIN 字段。这次我们使用复制链接来替换原有的按钮:

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

Stimulus 允许我们使用任意元素,只要它拥有正确的 data-action 就行。

注意,在此时,点击链接同样会引起浏览器跳转到链接的 href 所指地址。可以在 action 中调用 event.preventDefault() 禁用默认行为:

  copy(event) {
    event.preventDefault()
    this.sourceTarget.select()
    document.execCommand("copy")
  }

同样地,来源目标不需要是 <input type="text">。控制器只期望它拥有 value 熟悉和 select() 方法。这意味着,可以使用 <textarea> 替代:

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

总结和下一步

在本章,我们了解在 Stimulus 控制器中封装浏览器 API 的真是案例。还稍作修改让控制器针对旧版浏览器和网络问题变得有弹性。还了解了在页面上可以同时存在控制器的多个实例。最后,我们探索了操作和目标的解耦。

下一步,我们将学习 Stimulus 控制器的状态管理。

Next: Managing State

标签: none