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

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

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

Перед созданием компонента проверьте UI kit с общими компонентами, хелперами и модификаторами; большинство распространённых элементов управления уже существуют там.

Старый учебник

В этом учебнике я создам новый Ember Component как способ обернуть сторонний 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. Первая строка в didInsertElement (и willDestroyElement) — это this._super(), что необходимо, потому что мы наследуем Ember.Component.

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

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