Si vous implémentez une nouvelle Modal, consultez la documentation principale ici. Ce sujet décrit comment migrer une Modal basée sur un contrôleur existant vers la nouvelle API basée sur les composants.
Auparavant, Discourse utilisait une API basée sur les contrôleurs Ember pour le rendu des modals. Pour invoquer une modal, vous passiez une chaîne contenant le nom du contrôleur à showModal(). En coulisses, cela utilisait l’API Route#renderTemplate d’Ember, qui est obsolète dans Ember 3.x et sera supprimée dans Ember 4.x.
Pour permettre à Discourse de passer à Ember 4.x et au-delà, nous avons introduit une nouvelle API basée sur les composants pour les modals. Cette nouvelle API adopte les modèles de conception « déclaratifs » d’Ember et vise à fournir une sémantique DDAU (data down actions up) claire.
Étape 1 : Déplacer les fichiers
Déplacez le fichier JS du contrôleur et le fichier de modèle dans le répertoire /components/modal. Cela en fait un « composant co-localisé » qui peut être importé comme n’importe quel autre module JS.
Étape 2 : Mettre à jour le fichier JS
Ensuite, mettez à jour la définition du composant JS pour qu’elle étende @ember/component au lieu de @ember/controller [1]. Supprimez le mixin ModalFunctionality et mettez à jour toutes les utilisations de ses fonctions selon le tableau ci-dessous :
| Avant | Après |
|---|---|
flash() et clearFlash() |
Créez une propriété flash dans votre composant et passez-la à l’argument @flash de <DModal>. Par défaut, l’alerte sera stylisée avec la classe alert, qui est une copie de la classe ‘error’, mais elle peut être remplacée en utilisant l’argument @flashType. |
showModal() |
Importez la fonction showModal depuis discourse/lib/show-modal |
action closeModal |
Appelez l’argument closeModal qui est automatiquement passé à votre composant |
Les contrôleurs de type ancien vivaient « éternellement », ce qui signifiait que nous devions nettoyer manuellement l’état. Avec la nouvelle API basée sur les composants, le composant est créé et détruit lorsque la modal est affichée/cachée. Dans de nombreux cas, cela signifie que vos anciens hooks de cycle de vie ne sont plus nécessaires.
Si vous avez toujours besoin d’une logique basée sur le cycle de vie, utilisez ce tableau :
| Avant | Après |
|---|---|
onShow() |
Utilisez le cycle de vie standard des composants Ember (init() ou un modificateur Ember) |
afterRender |
Utilisez le cycle de vie standard des composants Ember (init() ou un modificateur Ember) |
beforeClose() |
Créez un wrapper autour de l’argument @closeModal qui est passé à votre composant. Passez une référence à votre wrapper de fermeture dans DModal comme <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
Utilisez le cycle de vie standard des composants Ember (willDestroy() ou un modificateur Ember) |
Étape 3 : Mettre à jour le modèle
Remplacez l’enveloppe <DModalBody> par <DModal>. Ajoutez de nouveaux attributs :
- Passez le nouvel argument
@closeModal - Ajoutez une classe explicite. Pour correspondre à l’ancien comportement, prenez le nom de fichier de votre contrôleur et ajoutez
-modal.
Par exemple, si votre contrôleur de modal s’appelait close-topic.js, l’appel <DModal> ressemblerait à ceci :
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
Si l’appel DModalBody inclut d’autres arguments, mettez-les à jour selon le tableau ci-dessous :
| Avant | Après |
|---|---|
@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 |
Utilisez la syntaxe avec des crochets angulaires pour un attribut HTML régulier : <DModal class="blah"> |
@titleAriaElementId |
Utilisez la syntaxe avec des crochets angulaires pour un attribut HTML régulier : <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
Inchangé |
S’il y avait du contenu de pied de page rendu après l’ancien composant <DModalBody>, utilisez le nouveau bloc nommé <:footer> pour l’introduire dans <DModal>. Lors de l’utilisation de blocs nommés, le contenu du corps doit être enveloppé dans <:body></:body>. Par exemple :
<DModal @closeModal={{@closeModal}}>
<:body>
Bonjour le monde, ceci est le contenu de la modal
</:body>
<:footer>
Ceci est le contenu du pied de page. Un wrapper `.modal-footer` sera ajouté
automatiquement
</:footer>
</DModal>
Étape 4 : Mettre à jour les sites d’appel showModal
Auparavant, les modals étaient rendus en utilisant l’API showModal, qui prenait une chaîne (le nom du contrôleur) et plusieurs options. Elle retournait une instance du contrôleur qui pouvait être manipulée :
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);
});
}
Pour rendre de nouvelles modals basées sur des composants, vous devez injecter le service modal (ou y accéder en utilisant quelque chose comme getOwner(this).lookup("service:modal")) et appeler la fonction show().
show() prend une référence vers la nouvelle classe de composant comme premier argument. La seule option encore prise en charge est model, qui peut être utilisée pour passer toutes les données/actions requises pour votre Modal.
Aucune référence à l’instance du composant ne sera retournée. À la place, show() retourne une promesse qui sera résolue lorsque la modal sera fermée. La promesse sera résolue avec toutes les données qui ont été passées à @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 },
});
});
}
Alternativement, migrez vers l’API déclarative décrite dans la documentation principale de DModal.
La fonctionnalité des anciennes options peut être répliquée comme suit :
Ancienne option showModal |
Solution |
|---|---|
admin |
n/a pour les composants - supprimez-le |
templateName |
n/a pour les composants - supprimez-le |
title |
déplacez vers <DModal @title={{i18n "blah"}}> |
titleTranslated |
déplacez vers <DModal @title="blah">. Cela pourrait être calculé en fonction des données de model si nécessaire |
modalClass |
déplacez vers <DModal class="blah"> |
titleAriaElementId |
déplacez vers <DModal aria-labelledby="blah"> |
panels |
Utilisez le bloc nommé <:headerBelowTitle> pour implémenter des onglets dans votre composant (exemple) |
model |
inchangé |
Étape 5 : Tests
Les tests devraient largement rester les mêmes. Les problèmes les plus courants sont :
-
Les modals n’ont plus de classe par défaut basée sur leur nom. Les classes doivent être spécifiées explicitement dans le modèle (voir le début de l’étape 3)
-
L’enveloppe
d-modalne persiste plus dans le DOM lorsque la modal est fermée. Pour vérifier que toutes les modals sont fermées, utilisez une vérification commeassert.dom('.d-modal').doesNotExist()
Profit !
Votre modal devrait maintenant fonctionner comme avant. Pour tirer davantage parti de la nouvelle API, vous voudrez peut-être envisager de remplacer les appels showModal par une stratégie déclarative, et de convertir votre Modal en un composant Glimmer.
Exemples
Voici quelques commits d’exemple qui démontrent la conversion de certaines modals du cœur de Discourse vers la nouvelle API :
Ce document est versionné - suggérez des modifications sur github.
Les composants Ember classiques sont recommandés dans ce guide car ils offrent le chemin de migration le plus simple depuis les contrôleurs Ember. Cependant, pour des modals simples, ou si vous êtes prêt à consacrer du temps à la refactorisation, les composants Glimmer modernes sont un meilleur choix. ↩︎