如果你正在实现一个新的 Modal,请查看此处的主文档。本主题描述了如何将现有的基于控制器(Controller)的 Modal 迁移到新的基于组件(Component)的 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 目录。这使得它们成为一个“共置组件”(colocated component),可以像任何其他 JS 模块一样被导入。
步骤 2:更新 JS 文件
然后,更新组件的 JS 定义,使其继承自 @ember/component 而不是 @ember/controller [1]。移除 ModalFunctionality mixin,并根据下表更新其函数的使用方式:
| 之前 | 之后 |
|---|---|
flash() 和 clearFlash() |
在你的组件中创建一个 flash 属性,并将其传递给 <DModal> 的 @flash 参数。默认情况下,警报将使用 alert 类进行样式设置,该类是 ‘error’ 类的副本,但可以使用 @flashType 参数进行覆盖。 |
showModal() |
从 discourse/lib/show-modal 导入 showModal 函数 |
closeModal 动作 |
调用自动传递到你的组件中的 closeModal 参数 |
旧式的模态框控制器会“永远”存在,这意味着我们必须手动清理状态。使用新的基于组件的 API 后,组件将在模态框显示/隐藏时创建和销毁。在许多情况下,这意味着你旧的生命周期钩子不再需要。
如果你仍然需要一些基于生命周期的逻辑,请使用此表:
| 之前 | 之后 |
|---|---|
onShow() |
使用标准的 Ember 组件生命周期(init() 或 Ember modifier) |
afterRender |
使用标准的 Ember 组件生命周期(init() 或 Ember modifier) |
beforeClose() |
创建一个包装器,包裹传递到你的组件中的 @closeModal 参数。将你的关闭包装器的引用传递给 DModal,例如 <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
使用标准的 Ember 组件生命周期(willDestroy() 或 Ember modifier) |
步骤 3:更新模板
用 <DModal> 替换 <DModalBody> 包装器。添加一些新的属性:
- 传递新的
@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>
Hello world, this is the content of the modal
</:body>
<:footer>
This is the footer content. A `.modal-footer` wrapper will be added
automatically
</:footer>
</DModal>
步骤 4:更新 showModal 调用点
以前,模态框是使用 showModal 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’ 服务(或者使用类似 getOwner(this).lookup("service:modal") 的方式访问它),然后调用 show() 函数。
show() 将新组件类的引用作为第一个参数。唯一仍然支持的选项是 ‘model’,它可以用于传递你的模态框所需的所有数据/动作。
不会返回组件实例的引用。相反,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 调用,并将你的模态框转换为 Glimmer 组件。
示例
以下是一些示例提交,展示了如何将 Discourse 核心的一些模态框转换为新 API:
本文档受版本控制 - 在 github 上建议更改。
本指南推荐经典 Ember 组件,因为它们提供了从 Ember 控制器迁移的最简单路径。但对于简单的模态框,或者如果你愿意花一些时间进行重构,现代 Glimmer 组件是更好的选择。 ↩︎