Se você estiver implementando um novo Modal, consulte 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 Ember-Controller 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 foi depreciada no Ember 3.x e será removida no Ember 4.x.
Para permitir que o Discourse seja atualizado para o Ember 4.x e versões posteriores, introduzimos uma nova API baseada em componentes para modais. Essa nova API adota os padrões de design “declarativos” do Ember e visa fornecer semântica limpa de DDAU (dados descendo, ações subindo).
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 como 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() e 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 substituída usando o argumento @flashType. |
showModal() |
Importe a função showModal de discourse/lib/show-modal |
ação closeModal |
Invoque o argumento closeModal que é passado automaticamente para o seu componente |
Os Controladores de Modal do estilo antigo permaneceriam “para sempre”, o que significava que precisávamos limpar manualmente o estado. Com a nova API baseada em Componentes, o componente será criado e destruído quando o modal for exibido/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 do componente Ember (init() ou modificador Ember) |
afterRender |
Use o ciclo de vida padrão do componente Ember (init() ou modificador Ember) |
beforeClose() |
crie um wrapper em torno do argumento @closeModal que é passado para o seu componente. Passe uma referência para o seu wrapper de fechamento em DModal como <DModal @closeModal={{this.myCloseModalWrapper}}> |
onClose() |
Use o ciclo de vida padrão do componente 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 se chamava close-topic.js, a nova invocação de <DModal> ficaria mais ou menos assim:
<DModal @closeModal={{@closeModal}} class="close-topic-modal">
Se a invocação 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 colchetes angulares com atributo HTML regular: <DModal class="blah"> |
@titleAriaElementId |
Use a sintaxe de colchetes angulares com atributo HTML regular: <DModal aria-labelledby="blah"> |
@dismissable, @submitOnEnter, @headerClass |
Inalterado |
Se havia algum 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 envolvido em :body e /:body. Por exemplo:
<DModal @closeModal={{@closeModal}}>
:body
Olá mundo, este é o conteúdo do modal
/:body
:footer
Este é o conteúdo do rodapé. Um wrapper `.modal-footer` será adicionado
automaticamente
/:footer
</DModal>
Etapa 4: Atualizar os locais de chamada showModal
Anteriormente, os modais seriam renderizados usando a API showModal, que aceitava uma string (o nome do controlador) e vários opts. 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 à nova classe de Componente como o 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 à instância do componente será retornada. Em vez disso, show() retorna uma promessa que será resolvida quando o modal for fechado. A promessa 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 opções antigas 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
Qualquer teste deve permanecer essencialmente o mesmo. Os problemas mais comuns são:
-
Modais não têm mais uma classe padrão baseada em seu nome. 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 agora deve funcionar como 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 commits de exemplo que demonstram a conversão de alguns modais do núcleo do Discourse para a nova API:
Este documento está sob controle de versão - sugira alterações no github.
Componentes Ember Clássicos são recomendados neste guia porque oferecem o caminho de migração mais fácil a partir dos Controladores Ember. No entanto, para modais simples, ou se você estiver disposto a gastar algum tempo refactorando, os componentes Glimmer modernos são a melhor escolha. ↩︎