在上一节教程中,我展示了如何配置 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();
},
});
如果您添加上述代码并刷新页面,您会看到我们的零食有一个黄色背景缓慢淡出的动画。
让我们解释一下这里发生了什么:
-
当组件在页面上渲染时,它会调用 didInsertElement
-
didInsertElement(以及 willDestroyElement)的第一行是 this._super(),这是必需的,因为我们在子类化 Ember.Component。
-
动画是使用 jQuery 的 animate 函数完成的。
-
最后,动画在 willDestroyElement 钩子中被取消,该钩子在组件从页面中移除时被调用。
您可能会想知道我们为什么要关心 willDestroyElement;原因是在像 Discourse 这样长期运行的 JavaScript 应用程序中,清理我们的代码很重要,以免我们泄漏内存或让某些东西继续运行。在这种情况下,我们停止动画,这告诉任何 jQuery 计时器它们不需要再触发,因为组件不再显示在页面上。
接下来去哪里
本系列的最后一节教程涵盖了自动化测试。
本文档受版本控制 - 建议修改 在 github 上。
18 个赞
gor
(gor)
2
Hi, how do i extend a discourse component thru a plugin? Can you give me some points. Thanks
1 个赞
eviltrout
(Robin Ward)
3
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 个赞
gor
(gor)
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
eviltrout
(Robin Ward)
5
Were there any logs in the console?
gor
(gor)
6
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 个赞
LeoMcA
(Leo McArdle)
7
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 个赞
thoka
(Thomas Kalka)
13
这里最好能有更新的文档,可以指向glimmer component文档。
3 个赞