向 Discourse 添加 Ember 组件

上一教程中,我展示了如何配置 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 组件,只需按需传入 model 即可。

添加自定义 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 上提交

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

3 个赞