如果您正在实现一个新的 Modal,请查看主文档 此处。该主题介绍了如何将现有的基于控制器的 Modal 迁移到新的基于组件的 API。
过去,Discourse 使用基于 Ember 控制器的 API 来渲染模态框。要调用模态框,您需要将包含控制器名称的字符串传递给 showModal()。在底层,这使用了 Ember 的 Route#renderTemplate API,该 API 在 Ember 3.x 中已被弃用,并将在 Ember 4.x 中被移除。
为了让 Discourse 能够升级到 Ember 4.x 及更高版本,我们引入了一个新的基于组件的模态框 API。这个新 API 采用了 Ember 的“声明式”设计模式,旨在提供清晰的 DDAU(数据向下,操作向上)语义。
步骤 1:移动文件
将控制器 JS 文件和模板文件移动到 /components/modal 目录。这将使它们成为“共置组件”,可以像任何其他 JS 模块一样被导入。
步骤 2:更新 JS 文件
然后,更新组件 JS 定义,使其从 @ember/component 而不是 @ember/controller 扩展 [1]。移除 ModalFunctionality 混入,并根据下表更新其函数的任何使用:
| 之前 | 之后 |
|---|---|
flash() 和 clearFlash() |
在您的组件中创建一个 flash 属性,并将其传递给 <DModal> 的 @flash 参数。默认情况下,警报将使用 alert 类进行样式设置,该类是 ‘error’ 类的副本,但可以通过 @flashType 参数进行覆盖。 |
showModal() |
从 discourse/lib/show-modal 导入 showModal 函数 |
closeModal 操作 |
调用自动传递给您的组件的 closeModal 参数 |
旧式 Modal 控制器会“永久”存在,这意味着我们必须手动清理状态。使用新的基于组件的 API,组件将在模态框显示/隐藏时创建和销毁。在许多情况下,这意味着您旧的生命周期钩子不再需要。
如果您仍然需要一些基于生命周期的逻辑,请使用下表:
| 之前 | 之后 |
|---|---|
onShow() |
使用标准 Ember 组件生命周期(init() 或 Ember 修饰符) |
afterRender |
使用标准 Ember 组件生命周期(init() 或 Ember 修饰符) |
beforeClose() |
创建一个包装器来包装传递给您的组件的 @closeModal 参数。将您的关闭包装器的引用传递给 DModal,例如 <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
使用标准 Ember 组件生命周期(willDestroy() 或 Ember 修饰符) |
步骤 3:更新模板
将 <DModalBody> 包装器替换为 <DModal>。添加一些新属性:
- 传递新的
@closeModal参数 - 添加一个显式类。为了匹配旧的行为,取您的控制器文件名并添加
-modal。
例如,如果您的模态框控制器名为 close-topic.js,新的 <DModal> 调用看起来像这样:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
如果 DModalBody 调用包含任何其他参数,请根据下表更新它们:
| 之前 | 之后 |
|---|---|
@title="title_key" |
@title={{i18n "title_key"}} |
@rawTitle="translated title" |
@title="translated title" |
@subtitle="subtitle_key" |
@subtitle={{i18n "subtitle_key"}} |
@rawSubtitle="translated subtitle" |
@subtitle="translated subtitle" |
@class |
@bodyClass |
@modalClass |
使用尖括号语法和常规 HTML 属性:<DModal class="blah"> |
@titleAriaElementId |
使用尖括号语法和常规 HTML 属性:<DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
保持不变 |
如果在旧的 <DModalBody> 组件之后渲染了任何页脚内容,请使用新的 <:footer> 命名块在 <DModal> 内部引入它。使用任何命名块时,主体内容应包裹在 <:body></:body> 中。例如:
<DModal @closeModal={{@closeModal}}>
<:body>
你好世界,这是模态框的内容
</:body>
<:footer>
这是页脚内容。将自动添加一个 `.modal-footer` 包装器
</:footer>
</DModal>
步骤 4:更新 showModal 调用位置
以前,模态框会使用 showModal API 进行渲染,该 API 接受一个字符串(控制器名称)和多个选项。它会返回一个控制器实例,可以对其进行操作:
import showModal from "discourse/lib/show-modal";
export default class extends Component {
showMyModal() {
const controller = showModal("my-modal", {
title: "My Modal Title",
modalClass: "my-modal-class",
model: { topic: this.topic },
});
controller.set("updateTopic", this.updateTopic);
});
}
要渲染新的基于组件的 Modal,您应该注入 ‘modal’ 服务(或使用类似 getOwner(this).lookup("service:modal") 的方式访问它),并调用 show() 函数。
show() 将新组件类的引用作为第一个参数。唯一仍然支持的选项是 ‘model’,可用于传递您的 Modal 所需的所有数据/操作。
不会返回对组件实例的引用。相反,show() 返回一个 Promise,该 Promise 将在模态框关闭时解析。Promise 将解析为传递给 @closeModal 的任何数据。
import MyModal from "discourse/components/my-modal";
import { service } from "@ember/service";
export default class extends Component {
@service modal;
showMyModal() {
this.modal.show(MyModal, {
model: { topic: this.topic, updateTopic: this.updateTopic },
});
});
}
或者,迁移到 主 DModal 文档 中描述的声明式 API。
旧选项的功能可以按如下方式复制:
旧 showModal 选项 |
解决方案 |
|---|---|
admin |
组件不适用 - 移除它 |
templateName |
组件不适用 - 移除它 |
title |
移动到 <DModal @title={{i18n "blah"}}> |
titleTranslated |
移动到 <DModal @title="blah">。如果需要,这可以根据 model 中的数据计算得出 |
modalClass |
移动到 <DModal class="blah"> |
titleAriaElementId |
移动到 <DModal aria-labelledby="blah"> |
panels |
使用 <:headerBelowTitle> 命名块在组件中实现选项卡(示例) |
model |
保持不变 |
步骤 5:测试
任何测试应基本保持不变。最常见的问题是:
-
模态框不再根据其名称拥有默认类。必须在模板中显式指定类(参见步骤 3 的开头)
-
当模态框关闭时,
d-modal包装器不再保留在 DOM 中。要检查所有模态框是否已关闭,请使用类似assert.dom('.d-modal').doesNotExist()的检查
大功告成!
您的模态框现在应该像以前一样工作。为了充分利用新 API,您可能需要考虑 用声明式策略替换 showModal 调用,并将您的 Modal 转换为 Glimmer 组件。
示例
以下是一些示例提交,展示了如何将 Discourse 核心的一些模态框转换为新 API:
本文档受版本控制 - 请在 GitHub 上提出更改。
本指南推荐经典 Ember 组件,因为它们提供了从 Ember 控制器迁移的最简单路径。但对于简单的模态框,或者如果您愿意花时间进行重构,现代 Glimmer 组件是更好的选择。 ↩︎