Si estás implementando un nuevo Modal, consulta la documentación principal aquí. Este tema describe cómo migrar un Modal basado en controladores 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, pasabas una cadena con el nombre del controlador a showModal(). Internamente, esto hacía uso de la API Route#renderTemplate de Ember, que está obsoleta en Ember 3.x y será eliminada en Ember 4.x.
Para permitir que Discourse se actualice a Ember 4.x y versiones posteriores, hemos introducido una nueva API basada en componentes para los modales. Esta nueva API adopta los patrones de diseño ‘declarativos’ de Ember y tiene como objetivo proporcionar una semántica limpia de DDAU (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 co-ubicado’, que se puede importar como 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 puede sobrescribirse 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 crea y destruye cuando el modal se muestra/oculta. En muchos casos, esto significa que ya no se requieren tus hooks de ciclo de vida antiguos.
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 de los componentes de Ember (init() o modificador de Ember) |
afterRender |
Usa el ciclo de vida estándar de los componentes de Ember (init() o modificador de Ember) |
beforeClose() |
Crea un envoltorio 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 de los componentes de Ember (willDestroy() o 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 igualar el comportamiento anterior, toma el nombre de archivo de tu 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 un atributo HTML regular: <DModal class="blah"> |
@titleAriaElementId |
Usa la sintaxis de corchetes angulares con un 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á automáticamente un envoltorio `.modal-footer`
</:footer>
</DModal>
Paso 4: Actualizar los sitios de llamada a showModal
Anteriormente, los modales se renderizaban usando la API showModal, que aceptaba una cadena (el nombre del controlador) y varias opciones. Devolvía una instancia del controlador que podía manipularse:
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 aún soportada es ‘model’, que se puede usar para pasar todos los datos/acciones requeridos 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 puede replicarse de la siguiente manera:
Opción antigua de showModal |
Solución |
|---|---|
admin |
n/a para componentes - elimínala |
templateName |
n/a para componentes - elimínala |
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
Cualquier prueba debería permanecer en gran medida igual. Los problemas más comunes son:
-
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()
¡Éxito!
Tu modal debería funcionar ahora como 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 de Glimmer.
Ejemplos
Aquí tienes algunos commits de ejemplo que demuestran la conversión de algunos modales del núcleo de Discourse a la nueva API:
Este documento está bajo control de versiones: sugiere cambios en github.
Los componentes clásicos de Ember se recomiendan en esta guía porque ofrecen la ruta de migración más sencilla desde los controladores de Ember. Pero para modales simples, o si estás dispuesto a dedicar tiempo a la refactorización, los componentes modernos de Glimmer son la mejor opción. ↩︎