将 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. 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 个赞