Добавление компонентов Ember в Discourse

В предыдущем уроке я показал, как настроить как серверную, так и клиентскую части Discourse для обработки запроса.

Теперь мы рекомендуем вам ознакомиться с документацией по компонентам Ember: Introducing Components - Components - Ember Guides

Старый урок

В этом уроке я создам новый компонент Ember как способ инкапсуляции стороннего JavaScript-кода. Это будет похоже на видео с YouTube, которое я снял некоторое время назад и которое, возможно, окажется полезным. Однако в этот раз речь пойдёт конкретно о Discourse и о том, как мы организуем файлы в нашем проекте.

Зачем нужны компоненты?

Handlebars — довольно простой и привлекательный язык. Это обычный HTML с добавлением динамических частей. Он лёгок в освоении и отлично подходит для повышения продуктивности, но не так хорош для повторного использования кода. Если вы разрабатываете большое приложение, такое как Discourse, вы обнаружите, что хотите многократно использовать одни и те же элементы.

Компоненты — это решение этой проблемы в Ember. Давайте создадим компонент, который будет отображать наш «закуску» более красиво.

Создание нового компонента

Имена компонентов должны содержать дефис. Я выберу fancy-snack в качестве имени для этого компонента. Давайте создадим наш шаблон:

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>

Теперь, чтобы использовать наш компонент, мы заменяем существующий шаблон admin/snack следующим:

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

{{fancy-snack snack=model}}

Теперь мы можем повторно использовать компонент fancy-snack в любом другом шаблоне, просто передавая модель по мере необходимости.

Добавление пользовательского JavaScript-кода

Помимо возможности повторного использования, компоненты в Ember отлично подходят для безопасного добавления пользовательского JavaScript, jQuery и другого внешнего кода. Они дают вам контроль над тем, когда компонент вставляется на страницу и когда удаляется. Для этого мы определяем Ember.Component с некоторым кодом:

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();
  },
});

Если вы добавите код выше и обновите страницу, вы увидите, что наша «закуска» имеет анимацию плавного появления жёлтого фона.

Давайте объясним, что здесь происходит:

  1. Когда компонент отображается на странице, вызывается метод didInsertElement.

  2. Первая строка didInsertElementwillDestroyElement) — это this._super(), что необходимо, поскольку мы наследуемся от Ember.Component.

  3. Анимация выполняется с помощью функции jQuery’s animate.

  4. Наконец, анимация отменяется в хуке willDestroyElement, который вызывается, когда компонент удаляется со страницы.

Вы можете спросить, зачем нам вообще нужен willDestroyElement. Причина в том, что в долгоживущем JavaScript-приложении, таком как Discourse, важно очищать за собой, чтобы избежать утечек памяти или оставить работающие процессы. В данном случае мы останавливаем анимацию, что сообщает любым таймерам jQuery, что им больше не нужно срабатывать, так как компонент больше не виден на странице.

Куда двигаться дальше

Финальный урок в этой серии посвящён автоматизированному тестированию.


Этот документ находится под контролем версий — предлагайте изменения на GitHub.

18 лайков

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

1 лайк

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 лайка

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 лайка

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 лайка

Было бы здорово увидеть здесь обновлённую документацию, которая могла бы ссылаться на документацию по компонентам glimmer.

3 лайка