将 Ember Components 添加到 Discourse

上一个教程中,我展示了如何配置 Discourse 的服务器端和客户端部分以响应请求。

我们现在建议您阅读 Ember 组件文档:Introducing Components - Components - Ember Guides

旧教程

在本教程中,我将创建一个新的 Ember 组件,作为包装第三方 Javascript 的方法。这将类似于我很久以前制作的一个 YouTube 视频,您可能会觉得它很有启发性,只是这次它是针对 Discourse 以及我们在项目中布局文件的方式。

为什么使用组件?

Handlebars 是一种非常简单的模板语言。它只是带有动态部分的常规 HTML。这很容易学习,对提高生产力很有帮助,但对于代码重用来说就不那么好了。如果您正在开发像 Discourse 这样的大型应用程序,您会发现自己想要一次又一次地重用一些相同的东西。

组件是 Ember 解决此问题的方案。让我们创建一个组件,以更漂亮的方式显示我们的零食(snack)。

创建新组件

组件的名称中必须包含一个连字符。我将选择 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. 动画是使用 jQuery 的 animate 函数完成的。

  4. 最后,动画在 willDestroyElement 钩子中被取消,当组件从页面中移除时会调用该钩子。

您可能会想,我们为什么要关心 willDestroyElement;原因是,在像 Discourse 这样长期运行的 Javascript 应用程序中,清理现场非常重要,以免泄漏内存或让东西继续运行。在这种情况下,我们停止动画,这会告诉任何 jQuery 定时器不必再触发,因为组件不再在页面上可见。

接下来的步骤

本系列的最后一个教程涵盖了自动化测试。


本文档是版本控制的 - 在 github 上建议更改。

17 个赞

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

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 component文档。

3 个赞