Wenn du ein neues Modal implementierst, findest du die Hauptdokumentation hier. Dieses Thema beschreibt, wie man ein bestehendes, auf Controllern basierendes Modal auf die neue, auf Komponenten basierende API migriert.
In der Vergangenheit verwendete Discourse eine auf Ember-Controllern basierende API zum Rendern von Modals. Um das Modal aufzurufen, wurde eine Zeichenkette mit dem Namen des Controllers an showModal() übergeben. Im Hintergrund nutzte dies die Route#renderTemplate-API von Ember, die in Ember 3.x veraltet ist und in Ember 4.x entfernt wird.
Um es Discourse zu ermöglichen, auf Ember 4.x und darüber hinaus zu aktualisieren, haben wir eine neue, auf Komponenten basierende API für Modals eingeführt. Diese neue API nutzt die „deklarativen“ Designmuster von Ember und zielt darauf ab, saubere DDAU-Semantiken (Data Down, Actions Up) bereitzustellen.
Schritt 1: Dateien verschieben
Verschiebe die Controller-JS-Datei und die Template-Datei in das Verzeichnis /components/modal. Dadurch werden sie zu einer „colocated component“ (zusammengefügten Komponente), die wie jedes andere JS-Modul importiert werden kann.
Schritt 2: Die JS-Datei aktualisieren
Aktualisiere dann die JS-Definition der Komponente, so dass sie von @ember/component statt von @ember/controller erbt [1]. Entferne das ModalFunctionality-Mixin und aktualisiere die Verwendung seiner Funktionen gemäß der folgenden Tabelle:
| Vorher | Nachher |
|---|---|
flash() und clearFlash() |
Erstelle eine flash-Eigenschaft in deiner Komponente und übergebe sie an das @flash-Argument von <DModal>. Standardmäßig wird die Warnung mit der alert-Klasse gestylt, die eine Kopie der 'error'-Klasse ist, aber sie kann über das @flashType-Argument überschrieben werden. |
showModal() |
Importiere die showModal-Funktion aus discourse/lib/show-modal |
closeModal-Aktion |
Rufe das closeModal-Argument auf, das automatisch in deine Komponente übergeben wird |
Altmodische Modal-Controller lebten „ewig“, was bedeutete, dass wir den Zustand manuell aufräumen mussten. Mit der neuen, auf Komponenten basierenden API wird die Komponente erstellt und zerstört, wenn das Modal ein-/ausgeblendet wird. In vielen Fällen bedeutet das, dass deine alten Lifecycle-Hooks nicht mehr erforderlich sind.
Falls du immer noch auf Lifecycle-basierte Logik angewiesen bist, verwende diese Tabelle:
| Vorher | Nachher |
|---|---|
onShow() |
Verwende den Standard-Ember-Komponenten-Lifecycle (init() oder Ember-Modifier) |
afterRender |
Verwende den Standard-Ember-Komponenten-Lifecycle (init() oder Ember-Modifier) |
beforeClose() |
Erstelle eine Hülle (Wrapper) um das @closeModal-Argument, das in deine Komponente übergeben wird. Übergebe eine Referenz zu deiner Close-Hülle an DModal wie <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
Verwende den Standard-Ember-Komponenten-Lifecycle (willDestroy() oder Ember-Modifier) |
Schritt 3: Das Template aktualisieren
Ersetze den <DModalBody>-Wrapper durch <DModal>. Füge einige neue Attribute hinzu:
- Gib das neue
@closeModal-Argument weiter - Füge eine explizite Klasse hinzu. Um das alte Verhalten zu entsprechen, nimm deinen Controller-Dateinamen und füge
-modalhinzu.
Zum Beispiel, wenn dein Modal-Controller close-topic.js hieß, würde der neue <DModal>-Aufruf ungefähr so aussehen:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
Wenn der DModalBody-Aufruf andere Argumente enthält, aktualisiere sie basierend auf der folgenden Tabelle:
| Vorher | Nachher |
|---|---|
@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 |
Verwende die Winkelklammer-Syntax mit regulärem HTML-Attribut: <DModal class="blah"> |
@titleAriaElementId |
Verwende die Winkelklammer-Syntax mit regulärem HTML-Attribut: <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
Unverändert |
Falls nach dem alten <DModalBody>-Komponenten Footer-Inhalte gerendert wurden, verwende den neuen <:footer>-Named Block, um sie innerhalb von <DModal> einzuführen. Bei der Verwendung von Named Blocks sollte der Body-Inhalt in <:body></:body> gekapselt werden. Zum Beispiel:
<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>
Schritt 4: Die showModal-Aufrufstellen aktualisieren
Früher wurden Modals mit der showModal-API gerendert, die eine Zeichenkette (den Controller-Namen) und eine Reihe von Optionen annahm. Sie gab eine Instanz des Controllers zurück, die manipuliert werden konnte:
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);
});
}
Um neue, auf Komponenten basierende Modals zu rendern, solltest du den „modal“-Service injizieren (oder darauf mit etwas wie getOwner(this).lookup("service:modal") zugreifen) und die show()-Funktion aufrufen.
show() nimmt eine Referenz auf die neue Klassenkomponente als erstes Argument entgegen. Die einzige noch unterstützte Option ist „model“, die verwendet werden kann, um alle für dein Modal erforderlichen Daten/Aktionen zu übergeben.
Es wird keine Referenz auf die Komponenteninstanz zurückgegeben. Stattdessen gibt show() eine Promise zurück, die aufgelöst wird, wenn das Modal geschlossen ist. Die Promise wird mit allen Daten aufgelöst, die an @closeModal übergeben wurden.
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 },
});
});
}
Alternativ kannst du auf die deklarativen API migrieren, die in der Haupt-Dokumentation zu DModal beschrieben ist.
Die Funktionalität der alten Optionen kann wie folgt repliziert werden:
Alte showModal-Option |
Lösung |
|---|---|
admin |
Nicht anwendbar für Komponenten – entferne sie |
templateName |
Nicht anwendbar für Komponenten – entferne sie |
title |
Verschiebe zu <DModal @title={{i18n "blah"}}> |
titleTranslated |
Verschiebe zu <DModal @title="blah">. Dies kann bei Bedarf basierend auf Daten aus model berechnet werden |
modalClass |
Verschiebe zu <DModal class="blah"> |
titleAriaElementId |
Verschiebe zu <DModal aria-labelledby="blah"> |
panels |
Verwende den <:headerBelowTitle>-Named Block, um Tabs in deiner Komponente zu implementieren (Beispiel) |
model |
Unverändert |
Schritt 5: Tests
Die meisten Tests sollten weitgehend unverändert bleiben. Die häufigsten Probleme sind:
-
Modals haben keine Standardklasse mehr, die auf ihrem Namen basiert. Klassen müssen im Template explizit angegeben werden (siehe Anfang von Schritt 3)
-
Der
d-modal-Wrapper bleibt nicht mehr im DOM erhalten, wenn das Modal geschlossen ist. Um zu prüfen, ob alle Modals geschlossen sind, verwende eine Prüfung wieassert.dom('.d-modal').doesNotExist()
Profit!
Dein Modal sollte jetzt so funktionieren wie zuvor. Um den neuen API noch weiter zu nutzen, solltest du in Betracht ziehen, showModal-Aufrufe durch eine deklarative Strategie zu ersetzen und dein Modal in eine Glimmer-Komponente umzuwandeln.
Beispiele
Hier sind einige Beispiel-Commits, die die Konvertierung einiger der Discourse-Core-Modals auf die neue API demonstrieren:
Dieses Dokument wird versioniert verwaltet – schlage Änderungen auf GitHub vor.
In diesem Leitfaden werden klassische Ember-Komponenten empfohlen, da sie den einfachsten Migrationspfad von Ember-Controllern boten. Für einfache Modals oder wenn du bereit bist, etwas Zeit für Refactoring aufzuwenden, sind moderne Glimmer-Komponenten jedoch die bessere Wahl. ↩︎