Si estás implementando un nuevo Modal, consulta la documentación principal aquí. Este tema describe cómo migrar un Modal basado en controlador existente a la nueva API basada en Componentes.
En el pasado, Discourse utilizaba una API basada en Ember-Controller para renderizar modales. Para invocar el modal, se pasaba una cadena con el nombre del controlador a showModal(). Internamente, esto hacía uso de la API Route#renderTemplate de Ember, que está deprecada en Ember 3.x y se eliminará en Ember 4.x.
Para permitir que Discourse actualice a Ember 4.x y versiones posteriores, hemos introducido una nueva API basada en componentes para modales. Esta nueva API adopta los patrones de diseño ‘declarativos’ de Ember y busca proporcionar semánticas limpias de DDAU (data down actions up / datos hacia abajo, acciones hacia arriba).
Paso 1: Mover archivos
Mueve el archivo JS del controlador y el archivo de plantilla al directorio /components/modal. Esto los convierte en un ‘componente colocalizado’ que se puede importar igual que cualquier otro módulo JS.
Paso 2: Actualizar el archivo JS
Luego, actualiza la definición del componente JS para que extienda de @ember/component en lugar de @ember/controller [1]. Elimina el mixin ModalFunctionality y actualiza cualquier uso de sus funciones según la tabla siguiente:
| Antes | Después |
|---|---|
flash() y clearFlash() |
Crea una propiedad flash en tu componente y pásala al argumento @flash de <DModal>. Por defecto, la alerta se estilizará con la clase alert, que es una copia de la clase ‘error’, pero se puede sobrescribir usando el argumento @flashType. |
showModal() |
Importa la función showModal desde discourse/lib/show-modal |
acción closeModal |
Invoca el argumento closeModal que se pasa automáticamente a tu componente |
Los Controladores de Modal de estilo antiguo vivían ‘para siempre’, lo que significaba que teníamos que limpiar manualmente el estado. Con la nueva API basada en Componentes, el componente se creará y destruirá cuando el modal se muestre/oculte. En muchos casos, eso significa que tus antiguos hooks de ciclo de vida ya no son necesarios.
Si aún necesitas alguna lógica basada en el ciclo de vida, usa esta tabla:
| Antes | Después |
|---|---|
onShow() |
Usa el ciclo de vida estándar del componente de Ember (init() o un modificador de Ember) |
afterRender |
Usa el ciclo de vida estándar del componente de Ember (init() o un modificador de Ember) |
beforeClose() |
Crea un envoltorio (wrapper) alrededor del argumento @closeModal que se pasa a tu componente. Pasa una referencia a tu envoltorio de cierre a DModal como <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
Usa el ciclo de vida estándar del componente de Ember (willDestroy() o un modificador de Ember) |
Paso 3: Actualizar la Plantilla
Reemplaza el envoltorio <DModalBody> con <DModal>. Añade algunos atributos nuevos:
- Pasa el nuevo argumento
@closeModal - Añade una clase explícita. Para coincidir con el comportamiento antiguo, toma el nombre de tu archivo de controlador y añade
-modal.
Por ejemplo, si tu controlador de modal se llamaba close-topic.js, la nueva invocación de <DModal> se vería algo así:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
Si la invocación de DModalBody incluye otros argumentos, actualízalos según la tabla siguiente:
| Antes | Despué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 |
Usa la sintaxis de corchetes angulares con atributo html regular: <DModal class="blah"> |
@titleAriaElementId |
Usa la sintaxis de corchetes angulares con atributo html regular: <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
Sin cambios |
Si había algún contenido de pie de página renderizado después del antiguo componente <DModalBody>, usa el nuevo bloque nombrado <:footer> para introducirlo dentro de <DModal>. Al usar cualquier bloque nombrado, el contenido del cuerpo debe estar envuelto en <:body></:body>. Por ejemplo:
<DModal @closeModal={{@closeModal}}>
<:body>
Hola mundo, este es el contenido del modal
</:body>
<:footer>
Este es el contenido del pie de página. Se añadirá un envoltorio `.modal-footer`
automáticamente
</:footer>
</DModal>
Paso 4: Actualizar las llamadas a showModal
Anteriormente, los modales se renderizaban usando la API showModal, que tomaba una cadena (el nombre del controlador) y una serie de opciones. Devolvía una instancia del controlador que podía ser manipulada:
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);
});
}
Para renderizar nuevos Modales basados en componentes, debes inyectar el servicio ‘modal’ (o acceder a él usando algo como getOwner(this).lookup("service:modal")) y llamar a la función show().
show() toma una referencia a la nueva clase de Componente como primer argumento. La única opción que aún se soporta es ‘model’, que se puede usar para pasar todos los datos/acciones necesarios para tu Modal.
No se devolverá ninguna referencia a la instancia del componente. En su lugar, show() devuelve una promesa que se resolverá cuando el modal se cierre. La promesa se resolverá con cualquier dato que se haya pasado a @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 },
});
});
}
Alternativamente, migra a la API declarativa descrita en la documentación principal de DModal.
La funcionalidad de las opciones antiguas se puede replicar de la siguiente manera:
Opción antigua de showModal |
Solución |
|---|---|
admin |
No aplica para componentes - elimínalo |
templateName |
No aplica para componentes - elimínalo |
title |
Muévelo a <DModal @title={{i18n "blah"}}> |
titleTranslated |
Muévelo a <DModal @title="blah">. Esto podría calcularse basándose en datos de model si es necesario |
modalClass |
Muévelo a <DModal class="blah"> |
titleAriaElementId |
Muévelo a <DModal aria-labelledby="blah"> |
panels |
Usa el bloque nombrado <:headerBelowTitle> para implementar pestañas en tu componente (ejemplo) |
model |
Sin cambios |
Paso 5: Pruebas
Las pruebas deberían permanecer en gran medida igual. El problema más común es:
-
Los modales ya no tienen una clase predeterminada basada en su nombre. Las clases deben especificarse explícitamente en la plantilla (ver inicio del Paso 3)
-
El envoltorio
d-modalya no persiste en el DOM cuando el modal se cierra. Para verificar que todos los modales están cerrados, usa una comprobación comoassert.dom('.d-modal').doesNotExist()
¡Beneficio!
Tu modal debería funcionar ahora como lo hacía antes. Para aprovechar aún más la nueva API, es posible que quieras considerar reemplazar las llamadas a showModal con una estrategia declarativa, y convertir tu Modal en un componente Glimmer.
Ejemplos
Aquí hay algunos ejemplos de commits que demuestran la conversión de algunos de los modales del núcleo de Discourse a la nueva API:
Este documento está controlado por versiones - sugiere cambios en github.
Se recomiendan los Componentes Ember clásicos en esta guía porque proporcionaban la ruta de migración más fácil desde los Controladores de Ember. Pero para modales simples, o si estás dispuesto a pasar algo de tiempo refactorizando, los componentes Glimmer modernos son la mejor opción. ↩︎