-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03-templates.md.erb
227 lines (155 loc) · 10.1 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
226
227
---
title: Templates
slug: templates
date: 0003/01/01
number: 3
level: free
photoUrl: http://www.flickr.com/photos/73449134@N04/8194499092/
photoAuthor: Mike Lewinski
contents: Learn about Meteor's templating language, Spacebars.|Create your first three templates.|Learn how Meteor managers work.|Get a basic prototype working with static data.
paragraphs: 46
---
To ease into Meteor development, we'll adopt an outside-in approach. In other words we'll build a "dumb" HTML/JavaScript outer shell first, and then hook it up to our app's inner workings later on.
This means that in this chapter we'll only concern ourselves with what's happening inside the `/client` directory.
If you haven't done so already, create a new file named `main.html` inside our `/client` directory, and fill it with the following code:
~~~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" %>
This will be our main app template. As you can see it's all HTML except for a single `{{> postsList}}` template inclusion tag, which is an insertion point for the upcoming `postsList` template. For now, let's create a couple more templates.
### Meteor Templates
At its core, a social news site is composed of posts organized in lists, and that's exactly how we'll organize our templates.
Let's create a `/templates` directory inside `/client`. This will be where we put all our templates, and to keep things tidy we'll also create `/posts` inside `/templates` just for our post-related templates.
<% note do %>
### Finding Files
Meteor is great at finding files. No matter where you put your code in the `/client` directory, Meteor will find it and compile it properly. This means you never need to manually write include paths for JavaScript or CSS files.
It also means you could very well put all your files in the same directory, or even all your code in the same file. But since Meteor will compile everything to a single minified file anyway, we'd rather keep things well-organized and use a cleaner file structure.
<% end %>
We're finally ready to create our second template. Inside `client/templates/posts`, create `posts_list.html`:
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
And `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" %>
Note the `name="postsList"` attribute of the template element. This is the name that will be used by Meteor to keep track of what template goes where (note that the name of the actual *file* is not relevant).
It's time to introduce Meteor's templating system, **Spacebars**. Spacebars is simply HTML, with the addition of three things: *inclusions* (also sometimes known as “partials”), *expressions* and *block helpers*.
*Inclusions* use the `{{> templateName}}` syntax, and simply tell Meteor to replace the inclusion with the template of the same name (in our case `postItem`).
*Expressions* such as `{{title}}` either call a property of the current object, or the return value of a template helper as defined in the current template's manager (more on this later).
Finally, *block helpers* are special tags that control the flow of the template, such as `{{#each}}…{{/each}}` or `{{#if}}…{{/if}}`.
<% note do %>
### Going Further
You can refer to the [Spacebars documentation](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md) if you'd like to learn more about Spacebars.
<% end %>
Armed with this knowledge, we can start to understand what's going on here.
First, in the `postsList` template, we're iterating over a `posts` object with the `{{#each}}…{{/each}}` block helper. Then, for each iteration we're including the `postItem` template.
Where is this `posts` object coming from? Good question. It's actually a **template helper**, and you can think of it as a placeholder for a dynamic value.
The `postItem` template itself is fairly straightforward. It only uses three expressions: `{{url}}` and `{{title}}` both return the document's properties, and `{{domain}}` calls a template helper.
### Template Helpers
Up to now we've been dealing with Spacebars, which is little more than HTML with a few tags sprinkled in. Unlike other languages like PHP (or even regular HTML pages, which can include JavaScript), Meteor keeps templates and their logic separated, and these templates don't do much by themselves.
In order to come to life, a template needs **helpers**. You can think of these helpers as the cooks that take raw ingredients (your data) and prepare them, before handing out the finished dish (the templates) to the waiter, who then presents it to you.
In other words, while the template's role is limited to displaying or looping over variables, the helpers are the one who actually do the heavy lifting by assigning a value to each variable.
<% note do %>
### Controllers?
It might be tempting to think of the file containing all of a template's helpers as a controller of sorts. But that can be ambiguous, as controllers (at least in the MVC sense) usually have a slightly different role.
So we decided to stay away from that terminology, and simply refer to “the template's helpers“ or “the template's logic” when talking about a template's companion JavaScript code.
<% end %>
To keep things simple, we'll adopt the convention of naming the file containing the helpers after the template, but with a **.js** extension. So let's create `posts_list.js` inside `client/templates/posts` right away and start building our first 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" %>
If you've done it right, you should now be seeing something similar to this in your browser:
<%= screenshot "3-1", "Our first templates with static data" %>
We're doing two things here. First we're setting up some dummy prototype data in the `postsData` array. That data would normally come from the database, but since we haven't seen how to do that yet (wait for the next chapter!) we're “cheating” by using static data.
Second, we're using Meteor's `Template.postsList.helpers()` function to create a template helper called `posts` that returns the `postsData` array we just defined above.
If you remember, we are using that `posts` helper in our `postsList` template:
~~~html
<template name="postsList">
<div class="posts page">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
Defining the `posts` helper means it is now available for our template to use, so our template will be able to iterate over our `postsData` array and pass each object contained within to the `postItem` template.
<%= commit "3-1", "Added basic posts list template and static data." %>
### The `domain` Helper
Similarly, we'll now create `post_item.js` to hold the `postItem` template's logic:
~~~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" %>
This time our `domain` helper's value is not an array, but an anonymous function. This pattern is much more common (and more useful) compared to our previous simplified dummy data example.
<%= screenshot "3-2", "Displaying domains for each links." %>
The `domain` helper takes a URL and returns its domain via a bit of JavaScript magic. But where does it take that url from in the first place?
To answer that question we need to go back to our `posts_list.html` template. The `{{#each}}` block helper not only iterates over our array, it also **sets the value of `this` inside the block to the iterated object**.
This means that between both `{{#each}}` tags, each post is assigned to `this` successively, and that extends all the way inside the included template's manager (`post_item.js`).
We now understand why `this.url` returns the current post's URL. And moreover, if we use `{{title}}` and `{{url}}` inside our `post_item.html` template, Meteor knows that we mean `this.title` and `this.url` and returns the correct values.
<%= commit "3-2", "Setup a `domain` helper on the `postItem`." %>
<% note do %>
### JavaScript Magic
Although this is not specific to Meteor, here's a quick explanation of the above bit of “JavaScript magic”. First, we're creating an empty anchor (`a`) HTML element and storing it in memory.
We then set its `href` attribute to be equal to the current post's URL (as we've just seen, in a helper `this` is the object currently being acted upon).
Finally, we take advantage of that `a` element's special `hostname` property to get back the link's domain name without the rest of the URL.
<% end %>
If you've followed along correctly, you should be seeing a list of posts in your browser. That list is just static data, so it doesn't take advantage of Meteor's real-time features just yet. We'll show you how to change that in the next chapter!
<% note do %>
### Hot Code Reload
You might have noticed that you didn't even need to manually reload your browser window whenever you changed a file.
This is because Meteor tracks all the files within your project directory, and automatically refreshes your browser for you whenever it detects a modification to one of them.
Meteor's hot code reload is pretty smart, even preserving the state of your app in-between two refreshes!
<% end %>