Agregar componentes Ember a Discourse

En el tutorial anterior mostré cómo configurar tanto la parte del servidor como la del cliente de Discourse para responder a una solicitud.

Ahora recomendamos que leas la documentación sobre componentes de Ember: Introducing Components - Components - Ember Guides

Tutorial antiguo

En este tutorial, voy a crear un nuevo componente de Ember como una forma de encapsular JavaScript de terceros. Esto será similar a un video de YouTube que hice hace un tiempo, que quizás encuentres informativo; solo que esta vez es específico de Discourse y de cómo organizamos los archivos en nuestro proyecto.

¿Por qué componentes?

Handlebars es un lenguaje bastante simple y tentador. Es simplemente HTML regular junto con algunas partes dinámicas. Es sencillo de aprender y excelente para la productividad, pero no tanto para la reutilización de código. Si estás desarrollando una aplicación grande como Discourse, descubrirás que quieres reutilizar algunas de las mismas cosas una y otra vez.

Los componentes son la solución de Ember a este problema. Creemos un componente que muestre nuestro snack de una manera más atractiva.

Crear un nuevo componente

Los componentes deben tener un guion en su nombre. Voy a elegir fancy-snack como nombre para este. Creemos nuestra plantilla:

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>

Ahora, para usar nuestro componente, reemplazaremos nuestra plantilla existente admin/snack con esta:

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

{{fancy-snack snack=model}}

Ahora podemos reutilizar nuestro componente fancy-snack en cualquier otra plantilla, simplemente pasando el modelo según sea necesario.

Agregar código JavaScript personalizado

Además de la reutilización, los componentes en Ember son excelentes para agregar de forma segura JavaScript personalizado, jQuery y otro código externo. Te dan control sobre cuándo se inserta el componente en la página y cuándo se elimina. Para hacer esto, definimos un Ember.Component con algo de 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();
  },
});

Si agregas el código anterior y actualizas la página, verás que nuestro snack tiene una animación de un fondo amarillo que se desvanece lentamente.

Explicaremos qué está sucediendo aquí:

  1. Cuando el componente se renderiza en la página, se llamará a didInsertElement.

  2. La primera línea de didInsertElement (y de willDestroyElement) es this._super(), lo cual es necesario porque estamos subclaseando Ember.Component.

  3. La animación se realiza utilizando la función animate de jQuery.

  4. Finalmente, la animación se cancela en el hook willDestroyElement, que se llama cuando el componente se elimina de la página.

Quizás te preguntes por qué nos importa willDestroyElement en absoluto; la razón es que en una aplicación de JavaScript de larga duración como Discourse, es importante limpiar después de nosotros mismos, para no filtrar memoria ni dejar cosas ejecutándose. En este caso, detenemos la animación, lo que le indica a cualquier temporizador de jQuery que ya no necesita activarse, ya que el componente ya no es visible en la página.

A dónde ir desde aquí

El tutorial final de esta serie cubre las pruebas automatizadas.


Este documento está bajo control de versiones: sugiere cambios en GitHub.

18 Me gusta

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

1 me gusta

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 Me gusta

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 Me gusta

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 Me gusta

Sería bueno ver documentación actualizada aquí, que podría apuntar a la documentación del componente Glimmer.

3 Me gusta