-
Notifications
You must be signed in to change notification settings - Fork 6
/
03-templates.md.erb
224 lines (152 loc) · 20.7 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
---
title: เทมเพลต
slug: templates
date: 0003/01/01
number: 3
contents: เรียนรู้วิธีใช้เทมเพลตด้วย Spacebars|สร้างเทมเพลตสามตัวแรกของคุณ|เข้าใจการทำงานของตัวจัดการใน Meteor|สร้างแอพต้นแบบที่ทำงานกับข้อมูลง่ายๆ
paragraphs: 46
---
เพื่อให้สร้างแอพได้ง่ายขึ้น เราจะเริ่มด้วยการสร้างเปลือกนอกของแอพขึ้นมาก่อนด้วย HTML แล้วค่อยๆ เพิ่มจาวาสคริปต์เข้าไปข้างในจนแอพสามารถทำงานได้ หรือที่เรียกว่า วิธีการแบบนอกเข้าใน (outside-in )
ซึ่งในบทนี้ เราจะแก้ไขและสร้างไฟล์เฉพาะที่อยู่ในโฟลเดอร์ `/client` เท่านั้น
ขั้นแรกให้คุณเปิดไฟล์ `main.html` ที่สร้างไว้แล้วในโฟลเดอร์ `/client` แล้วป้อนโค้ดต่อไปนี้ลงไป :
~~~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" %>
โดยเราจะใช้ไฟล์นี้เป็นเทมเพลตหลักของแอพ และโค้ดที่คุณเห็นในไฟล์ก็เป็น HTML เกือบทั้งหมด ยกเว้นตรงตำแหน่ง `{{> postList}}` ที่ Meteor จะนำเนื้อหาข้อมูลซึ่งได้จากเทมเพลต `postsList` แทรกเข้าไปแทน สิ่งที่เราจะทำต่อไปก็คือ สร้างเทมเพลทเพิ่มขึ้นอีกสองสามตัว
### เทมเพลทของ Meteor
หลักๆ แล้ว เว็บข่าวสังคมก็จะมีข่าวต่างๆ แสดงไล่เรียงกันลงมาเป็นรายการ ในรูปแบบที่เราจะนำมาใช้กับเทมเพลทของเรานั่นเอง
เริ่มกันต่อโดยการสร้างโฟลเดอร์ `/templates` ข้างใน `/client` เพื่อไว้เก็บเทมเพลททั้งหมด และสร้างโฟลเดอร์ย่อย `/posts` ไว้ใน `/templates` อีกชั้น เพื่อแยกเก็บเทมเพลทต่างๆที่เกี่ยวกับข่าวที่โพสท์
<% note do %>
### การค้นหาไฟล์
Meteor เก่งในเรื่องการค้นหาไฟล์ ไม่ว่าคุณจะสร้างไฟล์ไว้ที่ไหนในโฟลเดอร์ `/client` มันก็จะค้นหาจนเจอและจัดการคอมไพล์ได้อย่างถูกต้อง สิ่งที่เราได้คือเราไม่ต้องคอยผูกไฟล์เข้าไปในแอพอยู่ตลอด ไม่ว่าจะเป็นไฟล์จาวาสคริปต์หรือสไตล์ชีต (CSS)
ยิ่งกว่านั้น คุณยังสามารถที่จะแบ่งโค้ดออกเป็นหลายๆ ไฟล์ในโฟลเดอร์เดียว หรือรวมโค้ดทั้งหมดไว้ในไฟล์เดียวกันก็ได้ ซึ่งไม่ว่าจะทำแบบไหน Meteor ก็จะคอมไพล์ทุกไฟล์ที่พบให้เป็นไฟล์ที่ย่อขนาดเพื่อเอาไว้ใช้งานเพียงไฟล์เดียว ดังนั้นเพื่อให้สะดวกในการจัดการ เราจะแยกไฟล์ตามโฟลเดอร์ในแบบที่เราทำมาแล้วต่อไป
<% end %>
ตอนนี้เราก็พร้อมจะสร้างเทมเพลทที่สองแล้ว ให้คุณสร้าง `posts_list.html` ในโฟลเดอร์ `client/templates/posts` ด้วยโค้ดต่อไปนี้
~~~html
<template name="postsList">
<div class="posts page">
{{#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"` ในแท็ก `template` ซึ่งเป็นชื่อที่ Meteor ใช้เพื่อเชื่อมโยงเทมเพลทต่างๆเข้าด้วยกัน (โดยไม่สนใจชื่อไฟล์)
และแล้วก็ถึงเวลาที่เราจะมาทำความรู้จักกับระบบเทมเพลทของ Meteor ที่เรียกว่า **Spacebars** ซึ่งจริงๆแล้วก็คือ HTML ที่มีส่วนขยายเพิ่มสามส่วน ได้แก่ *ตัวแทนที่ (inclusions หรือ partials)*, *นิพจน์ (expressions)* และ *บล็อกตัวช่วย (block helpers)*
*ตัวแทนที่* มีรูปแบบเป็น `{{> templateName}}` ทำหน้าที่บอกให้ Meteor นำเทมเพลทตามชื่อที่ระบุมาแทนที่ (ในโค้ดของเราคือ `postItem`)
*นิพจน์* เช่น `{{title}}` เป็นได้ทั้งคุณสมบัติของอ็อบเจกต์ตัวที่กำลังใช้งาน หรือค่าที่คืนกลับมาจากตัวช่วยเทมเพลท ที่สร้างไว้ในตัวจัดการของเทมเพลทปัจจุบัน ดังจะได้อธิบายในลำดับต่อไป
*บล็อกตัวช่วย* เป็นแท็กพิเศษที่ใช้ควบคุมลำดับการทำงานของเทมเพลท เช่น `{{#each}}...{{/each}}` หรือ `{{#if}}...{{/if}}`
<% note do %>
### ต้องการข้อมูลเพิ่มเติม
คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ Spacebars ได้ที่ [หน้าคู่มือการใช้งาน Spacebars](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md)
<% end %>
รู้มาถึงตรงนี้ เราก็พอจะทำความเข้าใจกับสิ่งที่เกิดขึ้นได้ดังนี้
สิ่งที่เกิดขึ้นในเทมเพลท `postsList` ก็คือ บล็อกตัวช่วย `{{#each}}..{{/each}}` จะทำงานซ้ำเป็นจำนวนครั้งเท่ากับจำนวนข้อมูลที่อยู่ในอ็อบเจกต์ `posts` โดยในแต่ละรอบของการทำงาน เนื้อหาจากเทมเพลท `postItem` จะถูกใส่เพิ่มเข้าไปในเทมเพลทหลัก
แล้วอ็อบเจกต์ `posts` ได้มาจากไหน คำตอบคือ ได้มาจาก **ตัวช่วยเทมเพลท** ซึ่งทำหน้าที่คล้ายกับถังข้อมูลที่เตรียมไว้ให้เทมเพลทเรียกใช้
ส่วนในเทมเพลท `postItem` ก็ไม่มีอะไรซับซ้อน มันใช้แค่ 3 นิพจน์ คือ `{{url}}` และ `{{title}}` ที่ดึงค่ามาจากข่าวที่โพสท์ และ `{{domain}}` ที่ได้ค่ามาจากตัวช่วยเทมเพลท
### ตัวช่วยเทมเพลท
ถึงตรงนี้เราก็ได้ลองใช้ Spacebars กันมาบ้างแล้ว อันที่จริงมันก็คือ HTML บวกกับแท็กพิเศษอีกนิดหน่อย ที่แตกต่างจากภาษาอื่น เช่น PHP (หรือแม้กระทั่งหน้า HTML เองที่สามารถใส่จาวาสคริปต์เข้าไปได้) ก็คือ Meteor แยกส่วนที่เป็นโค้ดคำสั่งออกจากเทมเพลท โดยตัวเทมเพลทเองทำงานได้แค่คำสั่งพื้นฐานเท่านั้น
การที่เทมเพลทจะทำงานได้นั้น ก็ต้องมี **ตัวช่วย** ที่เปรียบเสมือนกุ๊กทำหน้าที่ปรุงวัตถุดิบ (ข้อมูล) ให้เป็นอาหาร ก่อนจะตักใส่จาน (เทมเพลท) และส่งให้พนักงานเสิร์ฟ นำมาให้เราอีกที
มองอีกมุมหนึ่ง เทมเพลทจะทำหน้าที่แค่แสดงหรือทำงานซ้ำๆ ตามค่าตัวแปรที่ได้มา ส่วนตัวช่วยจะทำหน้าที่กำหนดค่าให้ตัวแปรก่อนที่เทมเพลทจะนำไปใช้
<% note do %>
### มีคอนโทรลเลอร์ใน Meteor บ้างมั้ย
เราอาจมองว่าตัวช่วยเทมเพลท เป็นเหมือนคอนโทรลเลอร์ แต่เอาเข้าจริงแล้วคอนโทรลเลอร์ ในมุมมองของระบบ MVC จะทำหน้าที่บางอย่างแตกต่างออกไป
ดังนั้นเพื่อป้องกันไม่ให้เกิดความสับสนกับความหมายของคำ เราจะใช้คำว่า ตัวช่วยเทมเพลท หรือ โค้ดคำสั่งเทมเพลท เมื่อพูดถึงเทมเพลทที่ใช้จาวาสคริปต์
<% end %>
เพื่อให้เข้าใจง่าย เราจะตั้งชื่อไฟล์ที่เก็บโค้ดตัวช่วยเทมเพลท ตามชื่อเทมเพลท แต่เปลี่ยนนามสกุลเป็น **.js** แทน โดยเริ่มด้วยการสร้างไฟล์ `posts_list.js` ใน `/client/templates/posts` แล้วก็ใส่โค้ดตัวช่วยเทมเพลทตัวแรกของเราเข้าไป ดังนี้
~~~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", "Our first templates with static data" %>
สิ่งที่เราทำไปมีสองอย่างคือ เราได้สร้างข้อมูลตัวอย่างในอาร์เรย์ `postsData` ซึ่งที่จริงควรจะดึงมาจากฐานข้อมูล แต่เพราะเรายังไม่รู้ว่าจะทำยังไง (เราจะสอนคุณในบทถัดไป !) เราก็เลยหลอกแอพด้วยการใช้ข้อมูลที่เราสมมุติขึ้นแทน
และเราได้สร้างตัวช่วยเทมเพลทชื่อ `posts` ที่จะส่งคืนค่าอาร์เรย์ `postsData` โดยใช้ฟังก์ชัน `Template.postsList.helpers()` ของ Meteor
ถ้าคุณยังจำได้ เรามีการเรียกใช้ตัวช่วย `posts` ในเทมเพลท `postsList` แบบนี้
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
~~~
<%= caption "client/templates/posts/posts_list.html" %>
การสร้างตัวช่วยเทมเพลท `posts` ทำให้เราสามารถเรียกใช้มันได้จากเทมเพลทของเรา โดยเทมเพลทจะทำงานซ้ำตามจำนวนข้อมูลในอาร์เรย์ `postsData` และส่งต่อข้อมูลแต่ละตัวนั้นให้กับเทมเพลท `postItem` ทำงานต่อไป
<%= commit "3-1", "Added basic posts list template and static data." %>
### ตัวช่วย `domain`
ก็คล้ายๆกับที่เราทำมาแล้ว เราก็สร้างไฟล์ `post_item.js` เพื่อเก็บโค้ดคำสั่งของเทมเพลท ดังนี้
~~~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` ไม่ใช่อาร์เรย์แต่เป็นฟังก์ชัน ซึ่งเป็นวิธีการเขียนโค้ดแบบที่นิยมใช้กันมาก และใช้ได้ดีกว่าแบบที่เราเขียนก่อนหน้านี้
<%= screenshot "3-2", "Displaying domains for each links." %>
ตัวช่วย `domain` นี้ จะรับค่า URL และส่งคืนค่าเป็นชื่อโดเมนด้วยการใช้คุณสมบัติพิเศษบางอย่างของจาวาสคริปต์ แต่ว่ามันได้ URL มาจากไหนกัน
เพื่อที่จะตอบปัญหานี้ เราต้องย้อนกลับไปดูที่เทมเพลท `posts_list.html` ตรงบล็อคตัวช่วย `{{#each}}` ที่ไม่เพียงทำงานซ้ำตามข้อมูลในอาร์เรย์ มันยังทำการ **กำหนดค่า `this` ภายในบล็อกให้มีค่าเป็นอ็อบเจกต์ที่ได้จากอาร์เรย์อีกด้วย**
นั่นก็หมายความว่า ในแต่ละรอบการทำงานของแท็ก `{{#each}}` ข้อมูลข่าวที่โพสต์ไว้แต่ละตัว จะถูกใส่เข้าไปในตัวแปร `this` และส่งต่อไปจนถึงตัวจัดการเทมเพลทใน `post_item.js` ด้วย
ถึงตรงนี้เราก็รู้แล้วว่าทำไม `this.url` ถึงคืนค่ากลับมาเป็น URL ของข่าวนั้นได้ และยิ่งไปกว่านั้น ถ้าเราใช้ `{{title}}` และ `{{url}}` ในเทมเพลท `post_item.html` Meteor ก็รู้ว่าเราหมายถึง `this.title` และ `this.url` และส่งค่าที่ถูกต้องกลับมาให้
<%= commit "3-2", "Setup a `domain` helper on the `postItem`." %>
<% note do %>
### คุณสมบัติพิเศษของจาวาสคริปต์
แม้จะไม่เกี่ยวกับ Meteor แต่เราก็จะอธิบายเรื่อง "คุณสมบัติพิเศษของจาวาสคริปต์" ให้คุณรู้ โดยการทำงานของโค้ดที่ตัวช่วย `domain` นั้น เริ่มจากการสร้างแท็ก anchor (`a`) แบบว่างๆ ขึ้นมาหนึ่งตัว และเก็บค่าไว้ในตัวแปร
จากนั้นเราก็นำค่า URL ของข่าวที่โพสต์ (ได้จากอ็อบเจกต์ `this`) ใส่เข้าไปที่แอตทริบิวต์ `href` ของตัวแปรที่เราสร้างไว้
สุดท้ายก็เรียกใช้ประโยชน์จากคุณสมบัติพิเศษ `hostname` ของแท็ก `a` เพื่อนำค่าโดเมนของ URL นั้นส่งคืนกลับมาให้เทมเพลทใช้งานต่อไป
<% end %>
ถ้าคุณทำตามเรามาตลอด คุณก็จะเห็นรายการหัวข้อข่าวที่โพสต์ปรากฎขึ้นในเบราว์เซอร์ โดยข้อมูลที่เห็นเป็นแค่ข้อมูลนิ่งๆ ยังไม่ได้ใช้ความสามารถแบบเรียลไทม์ของ Meteor แต่ไม่ต้องห่วงในบทต่อไปเราจะสอนให้คุณทำตรงนี้ได้ !
<% note do %>
### การรีโหลดแบบอัตโนมัติ
คุณอาจสังเกตุเห็นว่า เราไม่จำเป็นต้องรีโหลดหน้าเบราว์เซอร์เลยเมื่อคุณทำการเปลี่ยนแปลงโค้ด
นั้นก็เพราะ Meteor จะติดตามการแก้ไขไฟล์ในแอพทั้งหมด และทำการรีเฟรชหน้าเบราว์เซอร์ให้คุณโดยอัตโนมัติ เมื่อตรวจพบการเปลี่ยนแปลงกับไฟล์ใดไฟล์หนึ่ง
การรีโหลดแบบอัตโนมัติของ Meteor ทำงานได้อย่างชาญฉลาด ทั้งยังเก็บสถานะของแอพคุณในระหว่างการรีเฟรชไว้ให้ด้วย !
<% end %>