---
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 %>