Se você estiver implementando um novo Modal, confira a documentação principal aqui. Este tópico descreve como migrar um Modal baseado em controlador existente para a nova API baseada em Componentes.
No passado, o Discourse utilizava uma API baseada em Controladores do Ember para renderizar modais. Para invocar o modal, você passava uma string com o nome do controlador para showModal(). Internamente, isso fazia uso da API Route#renderTemplate do Ember, que está obsoleta no Ember 3.x e será removida no Ember 4.x.
Para permitir que o Discourse faça o upgrade para o Ember 4.x e versões posteriores, introduzimos uma nova API baseada em componentes para modais. Esta nova API abraça os padrões de design ‘declarativos’ do Ember e visa fornecer semânticas limpas de DDAU (data down actions up).
Etapa 1: Mover Arquivos
Mova o arquivo JS do controlador e o arquivo de template para o diretório /components/modal. Isso os torna um ‘componente co-localizado’, que pode ser importado da mesma forma que qualquer outro módulo JS.
Etapa 2: Atualizar o arquivo JS
Em seguida, atualize a definição do componente JS para estender @ember/component em vez de @ember/controller [1]. Remova o mixin ModalFunctionality e atualize qualquer uso de suas funções de acordo com a tabela abaixo:
| Antes | Depois |
|---|---|
flash() and clearFlash() |
Crie uma propriedade flash no seu componente e passe-a para o argumento @flash de <DModal>. Por padrão, o alerta será estilizado com a classe alert, que é uma cópia da classe ‘error’, mas pode ser sobrescrito usando o argumento @flashType. |
showModal() |
Importe a função showModal de discourse/lib/show-modal |
ação closeModal |
Invocar o argumento closeModal, que é automaticamente passado para o seu componente |
Os Controladores de Modal do estilo antigo viviam ‘para sempre’, o que significava que tínhamos que limpar o estado manualmente. Com a nova API baseada em Componentes, o componente será criado e destruído quando o modal for mostrado/oculto. Em muitos casos, isso significa que seus antigos hooks de ciclo de vida não são mais necessários.
Se você ainda precisar de alguma lógica baseada em ciclo de vida, use esta tabela:
| Antes | Depois |
|---|---|
onShow() |
Use o ciclo de vida padrão de componentes Ember (init() ou modificador Ember) |
afterRender |
Use o ciclo de vida padrão de componentes Ember (init() ou modificador Ember) |
beforeClose() |
Crie um wrapper ao redor do argumento @closeModal que é passado para o seu componente. Passe uma referência para o seu wrapper de fechamento para o DModal como <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
Use o ciclo de vida padrão de componentes Ember (willDestroy() ou modificador Ember) |
Etapa 3: Atualizar o Template
Substitua o wrapper <DModalBody> por <DModal>. Adicione alguns novos atributos:
- Passe o novo argumento
@closeModal - Adicione uma classe explícita. Para corresponder ao comportamento antigo, pegue o nome do arquivo do seu controlador e adicione
-modal.
Por exemplo, se o seu controlador de modal fosse chamado close-topic.js, a nova invocação de <DModal> seria algo assim:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
Se a invocação de DModalBody incluir quaisquer outros argumentos, atualize-os com base na tabela abaixo:
| Antes | Depois |
|---|---|
@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 |
Use a sintaxe de parênteses angulares com atributo html regular: <DModal class="blah"> |
@titleAriaElementId |
Use a sintaxe de parênteses angulares com atributo html regular: <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
Inalterado |
Se houvesse qualquer conteúdo de rodapé renderizado após o antigo componente <DModalBody>, use o novo bloco nomeado <:footer> para introduzi-lo dentro de <DModal>. Ao usar qualquer bloco nomeado, o conteúdo do corpo deve ser envolto em <:body></:body>. Por exemplo:
<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>
Etapa 4: Atualizar os pontos de chamada do showModal
Anteriormente, os modais eram renderizados usando a API showModal, que aceitava uma string (o nome do controlador) e uma série de opções. Ela retornava uma instância do controlador que podia 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 novos Modais baseados em componentes, você deve injetar o serviço ‘modal’ (ou acessá-lo usando algo como getOwner(this).lookup("service:modal")) e chamar a função show().
show() aceita uma referência para a nova classe do Componente como primeiro argumento. A única opção ainda suportada é ‘model’, que pode ser usada para passar todos os dados/ações necessários para o seu Modal.
Nenhuma referência para a instância do componente será retornada. Em vez disso, show() retorna uma promise que será resolvida quando o modal for fechado. A promise será resolvida com quaisquer dados que foram passados para @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, migre para a API declarativa descrita na documentação principal do DModal.
A funcionalidade das antigas opções pode ser replicada da seguinte forma:
Opção antiga showModal |
Solução |
|---|---|
admin |
n/a para componente - remova-o |
templateName |
n/a para componentes - remova-o |
title |
mova para <DModal @title={{i18n "blah"}}> |
titleTranslated |
mova para <DModal @title="blah">. Isso pode ser calculado com base em dados de model se necessário |
modalClass |
mova para <DModal class="blah"> |
titleAriaElementId |
mova para <DModal aria-labelledby="blah"> |
panels |
Use o bloco nomeado <:headerBelowTitle> para implementar abas no seu componente (exemplo) |
model |
inalterado |
Etapa 5: Testes
Quaisquer testes devem permanecer em grande parte os mesmos. O problema mais comum é:
-
Os Modais não têm mais uma classe padrão baseada em seus nomes. As classes devem ser especificadas explicitamente no template (veja o início da Etapa 3)
-
O wrapper
d-modalnão persiste mais no DOM quando o modal é fechado. Para verificar se todos os modais estão fechados, use uma verificação comoassert.dom('.d-modal').doesNotExist()
Lucro!
Seu modal deve agora funcionar como funcionava antes. Para aproveitar ainda mais a nova API, você pode considerar substituir as chamadas showModal por uma estratégia declarativa, e converter seu Modal para ser um componente Glimmer.
Exemplos
Aqui estão alguns exemplos de commits que demonstram a conversão de alguns dos modais do núcleo do Discourse para a nova API:
Este documento é controlado por versão - sugira alterações no github.
Os Componentes Clássicos do Ember são recomendados neste guia porque forneceram o caminho de migração mais fácil a partir dos Controladores do Ember. Mas para modais simples, ou se você estiver disposto a gastar algum tempo refatorando, os componentes Glimmer modernos são a melhor escolha. ↩︎