-
Notifications
You must be signed in to change notification settings - Fork 79
/
03-templates.md.erb
225 lines (153 loc) · 9.77 KB
/
03-templates.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
---
title: 模板
slug: templates
date: 0003/01/01
number: 3
points: 1
contents: 学习关于 Meteor 的模板语言:Spacebars | 创建三个模板 | 学习 Meteor 如何处理控制 | 通过静态数据建立一个基本原型
paragraphs: 46
---
为了更容易地进入 Meteor 的开发,我们将采用从外向内的方法来搭建项目。换句话说,我们将首先建立一个 HTML/JavaScript 的外壳,然后把它放到我们的项目里,内部细节处理稍后再说。
这意味着,在本章中,我们只关注 `/client` 目录里面的事情。
让我们先在 `/client` 目录中创建一个文件 `main.html`,并写入以下代码:
~~~html
<head>
<title>Microscope</title>
</head>
<body>
<div class="container">
<header class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="/">Microscope</a>
</div>
</header>
<div id="main">
{{> postsList}}
</div>
</div>
</body>
~~~
<%= caption "client/main.html" %>
这是我们的 App 的主要模板。在上面我们看到很多熟悉的 HTML 标签,除了这个 `{{> postsList}}` 标签,它是 `postsList` 模板的插入点,等一下我们就会说到。现在,先让我们创建更多的模板吧。
### Meteor 模板
我们项目的核心是社会新闻网站,它是由一系列的帖子所组成的,而这正是我们要使用模板的原因。
我们先在 `/client` 里面创建一个目录 `/templates`。这里用来存放所有的模板,这样可以保持项目结构的清晰整洁,接着在 `/templates` 里面再创建 `/posts` 目录,用来存放与帖子相关的模板。
<% note do %>
### 查找文件
Meteor 的强大之处在于文件的查找。无论你把代码文件放在 `/client` 目录下的任何地方,Meteor 都可以找到并且正确地进行编译。这意味着你永远都不需要手动编写 JavaScript 或 CSS 文件的调用路径。
这也意味着,你可能会把所有的文件放在同一目录中,甚至所有的代码放在同一个文件里。但由于 Meteor 会把一切的代码都编译到一个压缩的文件里面,因此我们更偏向于把项目布置得井井有条,使用更整洁的文件结构,提高项目的可读性。
<% end %>
接下来,我们开始创建第二个模板。在 `client/templates/posts` 目录中,创建 `posts_list.html`:
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
和 `post_item.html` :
~~~html
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
</div>
</div>
</template>
~~~
<%= caption "client/templates/posts/post_item.html" %>
注意模板的 `name="postsList"` 属性,它的作用是告诉 Meteor 去根据这个名称跟踪这个模板的位置。(注意,与实际文件的文件名不相关。)
现在介绍 Meteor 的模板系统 **Spacebars**。Spacebar 就是简单的 HTML 加上三件事情:*Inclusion* (有时也称作 “partial”)、*Expression* 和 *Block Helper*。
*Inclusion* :通过 `{{> templateName}}` 标记,简单直接地告诉 Meteor 这部分需要用相同名称的模板来取代(在我们的例子中就是 `postItem` )。
*Expression* :比如 `{{title}}` 标记,它要么是调用当前对象的属性,要么就是对应到当前模板管理器中定义的 helper 方法,并返回其方法值(后面会详细讨论)。
*Block Helper* :在模板中控制流程的特殊标签,如 `{{#each}}…{{/each}}` 或 `{{#if}}…{{/if}}` 。
<% note do %>
### 进一步学习
你如果想了解更多关于 Spacebars 的内容,可以参考 [Spacebars 文档](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md)。
<% end %>
有了这些知识,我们就可以很容易理解上面的内容了。
首先,在 `postsList` 模板里,我们通过 `{{#each}}…{{/each}}` Block Helper 遍历 `posts` 对象。然后,每次迭代包含了 `postItem` 模板。
这个 `posts` 对象来自哪里?好问题。它实际上是一个 **模板 helper**,你可以想象它是动态值的占位符(placeholder)。
`postItem` 这个模板本身相当简单。它只使用三个标签: `{{url}}` 和 `{{title}}` 都返回其集合的属性,而 `{{domain}}` 则调用模板对应的 helper 方法。
### 模板 Helper
到目前为止我们已经学会了使用 Spacebars ,这只是在 HTML 的基础上多加了几个标签而已。不像其他语言如 PHP (甚至常规 HTML 页面,还包含了 JavaScript), Meteor 只是让模板和逻辑进行分离,而这些模板本身并不需要做很多复杂的事情。
为了让连接变得更流畅,一个模板需要 **helper**。你可以想象这些 helper 就是厨师用食材(你的数据)烹饪好佳肴(模板),再由服务员端到你面前。
换句话说,模板的作用仅限于显示或循环变量,而 helper 则扮演着一个相当重要的角色:把值分配给每个变量。
<% note do %>
### Contoller 控制器?
我们也许会情不自禁地认为,包含所有模板 helper 的文件是一个 controller(控制器)。但这是很模糊的,因为 controller (至少在 MVC 情况下)通常有不同的作用。
所以我们决定远离那个术语,在谈论关于模板涉及的 JavaScript 代码时,就简单地称为 “模板的 helper” 或 “模板的逻辑”。
<% end %>
为简单起见,我们将采用与模板同名的方式来命名包含其 helper 的文件,区别是扩展名为 **.js**。好了,我们马上在 `/client/templates/posts` 目录中创建 `posts_list.js` 文件,开始构建我们的第一个 helper:
~~~js
var postsData = [
{
title: 'Introducing Telescope',
url: 'http://sachagreif.com/introducing-telescope/'
},
{
title: 'Meteor',
url: 'http://meteor.com'
},
{
title: 'The Meteor Book',
url: 'http://themeteorbook.com'
}
];
Template.postsList.helpers({
posts: postsData
});
~~~
<%= caption "client/templates/posts/posts_list.js" %>
如果运行正确,你现在应该在浏览器中看到这样的画面:
<%= screenshot "3-1", "我们的第一个带静态数据的模板" %>
我们刚刚在做两件事情。首先,我们放置一些虚拟的基本数据到 `postsData` 数组中。原本数据应该是来自数据库的,但是由于我们还没有学习到该怎么做(在下一章揭晓),所以我们先通过使用静态数据来“作弊”。
然后,我们使用的是 Meteor 的 `Template.postsList.helpers()` 函数,建立了 `posts` 模板 helper 来返回刚刚定义的 `postsData` 数组。
如果你记得,现在我们在 `postsList` 模板中使用这个 `posts` helper:
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
定义 `posts` helper 就是让我们的模板可以使用它,所以模板就可以遍历 `postsData` 数组并将里面的每个对象发送到 `postItem` 模板中。
<%= commit "3-1", "添加了基本的 post 列表模板和静态数据。" %>
### 关于 `domain` Helper
类似地,我们现在创建一个 `post_item.js` 文件来包含 `postItem` 模板的逻辑:
~~~js
Template.postItem.helpers({
domain: function() {
var a = document.createElement('a');
a.href = this.url;
return a.hostname;
}
});
~~~
<%= caption "client/templates/posts/post_item.js" %>
这一次, `domain` helper 的值不再是一个数组,而是一个匿名函数。相比起我们之前简化的虚拟数据的例子,这种模式更为常见(而且更有用)。
<%= screenshot "3-2", "显示每个链接的域名。" %>
这个 `domain` helper 方法通过 JavaScript 来获取一个 URL 地址并返回其域名。但是它一开始是从哪里获得 URL 地址的呢?
为了回答这个问题,我们需要回到 `posts_list.html` 模板。`{{#each}}` 代码块不仅遍历数组,它还在**代码块范围内将 `this` 的值赋予被遍历的对象**。
这意味着,在 `{{#each}}` 标记之间,每个 post 都可以通过 `this` 依次访问,并且一直延伸到模板 helper(`post_item.js`)中。
我们现在明白了为什么 `this.url` 会返回当前 post 的 URL。而且,如果我们在 `post_item.html` 模板里面使用 `{{title}}` 和 `{{url}}`,Meteor 就会知道需要调用 `this.title` 和 `this.url` 返回我们想要的正确值。
<%= commit "3-2", "设置 `postItem` 的 `domain` helper。" %>
<% note do %>
### 神奇的 JavaScript
尽管这对于 Meteor 来说并不特别,这里会简单解释一下上面 “神奇的 JavaScript 代码”。首先,我们创建一个空的锚(`a`)HTML 标签并储存在内存中。
然后我们将其 `href` 属性设置为当前 post 的 URL (正如我们刚刚讲到的,`this` 在 helper 中正是当前被操作的对象)。
最后,我们利用 `a` 标签的特别属性 `hostname` 返回 URL 的域名。
<% end %>
如果你正确地紧跟着我们的进度,这时候你就会在浏览器中看到一个 post 列表。但是这个列表只是调用了静态数据,它没有利用到 Meteor 实时性的特点。我们将在下一章向你展示该如何修改!
<% note do %>
### 动态代码重载
你可能已经注意到,当你修改文件的时候,不需要手动刷新页面,浏览器就会自动重新加载。
这是因为 Meteor 跟踪了项目目录下的所有文件,当检测到其中一个文件发生了改变,它就会自动刷新浏览器。
Meteor 的动态代码重载是相当智能的,它甚至可以在两个刷新动作之间保存 App 的状态。
<% end %>