Если вы реализуете новый Modal, ознакомьтесь с основной документацией здесь. В этой теме описывается, как перенести существующий модальный компонент, основанный на контроллере, на новый компонентный API.
В прошлом Discourse использовал API на базе Ember-Controller для отрисовки модальных окон. Для вызова модального окна вы передавали строку с именем контроллера в showModal(). Под капотом это использовало API Route#renderTemplate из Ember, которое устарело в Ember 3.x и будет удалено в Ember 4.x.
Чтобы позволить Discourse обновиться до Ember 4.x и выше, мы представили новый компонентный API для модальных окон. Этот новый API следует декларативным паттернам проектирования Ember и стремится обеспечить чистую семантику DDAU (data down actions up — данные вниз, действия вверх).
Шаг 1: Перемещение файлов
Переместите JS-файл контроллера и файл шаблона в каталог /components/modal. Это сделает их «колокатируемым компонентом» (colocated component), который можно импортировать так же, как любой другой JS-модуль.
Шаг 2: Обновление JS-файла
Затем обновите определение компонента в JS, чтобы он наследовался от @ember/component, а не от @ember/controller [1]. Удалите миксин ModalFunctionality и обновите все использования его функций согласно таблице ниже:
| Было | Стало |
|---|---|
flash() и clearFlash() |
Создайте свойство flash в вашем компоненте и передайте его в аргумент @flash компонента <DModal>. По умолчанию предупреждение будет стилизовано с помощью класса alert, который является копией класса ‘error’, но это можно переопределить, используя аргумент @flashType. |
showModal() |
Импортируйте функцию showModal из discourse/lib/show-modal |
действие closeModal |
Вызовите аргумент closeModal, который автоматически передается в ваш компонент |
Контроллеры модальных окон старого стиля существовали «вечно», что означало, что нам приходилось вручную очищать состояние. С новым компонентным 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>
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
Ранее модальные окна рендерились с помощью API showModal, который принимал строку (имя контроллера) и ряд опций. Он возвращал экземпляр контроллера, с которым можно было манипулировать:
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() возвращает промис, который будет разрешен при закрытии модального окна. Промис будет разрешен с любыми данными, которые были переданы в @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 },
});
});
}
В качестве альтернативы, перейдите на декларативный API, описанный в основной документации DModal.
Функциональность старых опций можно воспроизвести следующим образом:
Старая опция 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 — лучший выбор. ↩︎