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

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

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

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

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

В этом учебнике я создам новый компонент 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. Анимация выполняется с помощью функции 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 лайка