Adicionar componentes Ember ao Discourse

No tutorial anterior, mostrei como configurar as partes do servidor e do cliente do Discourse para responder a uma solicitação.

Agora, recomendamos que você leia a documentação de componentes do Ember: Introducing Components - Components - Ember Guides

Antes de escrever um componente, verifique o UI kit de componentes, helpers e modificadores compartilhados; a maioria dos controles comuns já existe lá.

Tutorial antigo

Neste tutorial, vou criar um novo Componente Ember como uma maneira de encapsular JavaScript de terceiros. Isso será semelhante a um vídeo do YouTube que fiz há algum tempo, que você pode achar informativo, só que desta vez é específico para o Discourse e para como organizamos os arquivos em nosso projeto.

Por que Componentes?

O Handlebars é uma linguagem bastante simples e tentadora. É apenas HTML regular com algumas partes dinâmicas. É simples de aprender e ótimo para produtividade, mas não é tão bom para reutilização de código. Se você estiver desenvolvendo um aplicativo grande como o Discourse, perceberá que deseja reutilizar as mesmas coisas repetidamente.

Componentes são a solução do Ember para esse problema. Vamos criar um componente que exibirá nosso lanche de uma maneira mais bonita.

Criando um novo Componente

Componentes precisam ter um traço no nome. Vou escolher fancy-snack como o nome deste. Vamos criar nosso template:

app/assets/javascripts/admin/templates/components/fancy-snack.hbs

<div class="fancy-snack-title">
  <h1>{{snack.name}}</h1>
</div>

<div class="fancy-snack-description">
  <p>{{snack.description}}</p>
</div>

Agora, para usar nosso componente, vamos substituir nosso template existente admin/snack por este:

app/assets/javascripts/admin/templates/snack.hbs

{{fancy-snack snack=model}}

Agora podemos reutilizar nosso componente fancy-snack em qualquer outro template, passando apenas o modelo conforme necessário.

Adicionando Código JavaScript Personalizado

Além da reutilização, Componentes no Ember são ótimos para adicionar com segurança JavaScript personalizado, jQuery e outros códigos externos. Isso dá a você o controle de quando o componente é inserido na página e quando é removido. Para fazer isso, definimos um Ember.Component com algum código:

app/assets/javascripts/admin/components/fancy-snack.js

export default Ember.Component.extend({
  didInsertElement() {
    this._super();
    this.$().animate({ backgroundColor: "yellow" }, 2000);
  },

  willDestroyElement() {
    this._super();
    this.$().stop();
  },
});

Se você adicionar o código acima e atualizar a página, verá que nosso lanche tem uma animação de um fundo amarelo que desaparece lentamente.

Vamos explicar o que está acontecendo aqui:

  1. Quando o componente é renderizado na página, ele chamará didInsertElement

  2. A primeira linha de didInsertElement (e willDestroyElement) é this._super(), que é necessário porque estamos subclassando Ember.Component.

  3. A animação é feita usando a função animate do jQuery.

  4. Finalmente, a animação é cancelada no hook willDestroyElement, que é chamado quando o componente é removido da página.

Você pode se perguntar por que nos importamos com willDestroyElement; a razão é que, em um aplicativo JavaScript de longa duração como o Discourse, é importante limpar após nós mesmos, para não vazar memória ou deixar coisas rodando. Neste caso, paramos a animação, o que diz a qualquer timer do jQuery que eles não precisam mais disparar, pois o componente não está mais visível na página.

Para onde ir a partir daqui

O tutorial final desta série cobre testes automatizados.


Este documento é controlado por versão - sugira alterações no github.

18 curtidas

Hi, how do i extend a discourse component thru a plugin? Can you give me some points. Thanks

1 curtida

Generally we prefer you don’t extend Discourse plugins, and you stick to plugin outlets or using the widget decoration API to add stuff.

But if you must, you can create an initializer and use Ember’s extend code. Here’s an example that extends an Ember object.

4 curtidas

Tried with initializer but didnt worked. What i actually want to do is to add 2 more classNames and some actions:

import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  // extend component from jsapp/components/topic-list.js.es6
  const TopicList = api.container.lookupFactory('component:topic-list');

  TopicList.extend({
    classNames: ['topic-list', 'round', 'table'],
    actions: {
        clickMe: function() {
            console.log('click');
        }
    }
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};

And by “didnt worked”, i mean that topic list completly disappeared from page.
Thank you

Were there any logs in the console?

Nope, no logs at all. However i managed to fix it this way. I hope it will help someone.

import { default as TopicList } from 'discourse/components/topic-list';
import { withPluginApi } from 'discourse/lib/plugin-api';

function initializeComponentTopicList(api) {
  TopicList.reopen({
    classNames: ['topic-list', 'round', 'table'],
  });
};

export default {
  name: "extend-for-component-topic-list",

  initialize() {
    withPluginApi('0.1', initializeComponentTopicList);
  }
};
3 curtidas

I just ran into the same problem. Add the following as a css/html customisation and observe empty user cards:

<script type="text/discourse-plugin" version="0.5">
    api.container.lookupFactory('component:user-card-contents')
</script>
2 curtidas

Seria bom ver a documentação atualizada aqui, que poderia apontar para a documentação do componente Glimmer.

3 curtidas