Если вы реализуете новый Modal, ознакомьтесь с основной документацией здесь. В этой теме описывается процесс миграции существующего 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. Это сделает их «ко-локализованным компонентом», который можно импортировать так же, как и любой другой 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>
Привет, мир, это контент модального окна
</:body>
<:footer>
Это контент нижнего колонтитула. Обертка `.modal-footer` будет добавлена
автоматически
</: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. ↩︎