為了更容易地進(jìn)入 Meteor 的開(kāi)發(fā),我們將采用從外向內(nèi)的方法來(lái)搭建項(xiàng)目。換句話說(shuō),我們將首先建立一個(gè) HTML/JavaScript 的外殼,然后把它放到我們的項(xiàng)目里,內(nèi)部細(xì)節(jié)處理稍后再說(shuō)。
這意味著在本章中,我們只關(guān)注 /client
目錄里面的事情。
讓我們先在 /client
目錄創(chuàng)建一個(gè) main.html
文件,并寫(xiě)入以下代碼:
<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>
這是我們主要的 App 模板。在上面看到很多熟悉的 HTML 標(biāo)簽,除了這個(gè) {{> postsList}}
標(biāo)簽,它是 postsList
模板的插入點(diǎn),等一下我們就會(huì)說(shuō)到。現(xiàn)在,先讓我們創(chuàng)建更多的模板吧。
我們項(xiàng)目的核心是社會(huì)新聞網(wǎng)站,它是由一系列的帖子所組成的,而這正是我們要調(diào)用模板的原因。
我們先在 /client
里面創(chuàng)建一個(gè) /templates
目錄。這里用來(lái)放我們所有的模板,這樣可以保持項(xiàng)目結(jié)構(gòu)的清晰整潔,接著在 /templates
里面再創(chuàng)建 /posts
目錄來(lái)存放與帖子相關(guān)的模板。
Meteor 的強(qiáng)大之處在于文件的查找。無(wú)論你把代碼文件放在 /client
目錄下的任何地方,Meteor 都可以找到它并且正確地進(jìn)行編譯。這意味著你永遠(yuǎn)都不需要手動(dòng)編寫(xiě) JavaScript 或 CSS 文件的調(diào)用路徑。
這也意味著你可能會(huì)把所有的文件放在同一目錄,甚至所有的代碼放在同一個(gè)文件。但由于 Meteor 會(huì)把一切的代碼都編譯到一個(gè)壓縮的文件里面,因此我們更偏向于把項(xiàng)目弄得井井有條,使用更整潔的文件結(jié)構(gòu),提高項(xiàng)目的可讀性。
接下來(lái)我們開(kāi)始創(chuàng)建第二個(gè)模板。在 client/templates/posts
目錄中,創(chuàng)建 posts_list.html
:
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
和 post_item.html
:
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
</div>
</div>
</template>
注意模板的 name="postsList"
屬性,它的作用是告訴 Meteor 去根據(jù)這個(gè)名稱(chēng)來(lái)跟蹤這個(gè)模板的位置。(注意的是實(shí)際文件的文件名不相關(guān)。)
是時(shí)候來(lái)介紹 Meteor 的模板系統(tǒng) Spacebars 了。Spacebar 就是簡(jiǎn)單的 HTML 加上三件事情:Inclusion (有時(shí)也稱(chēng)作 “partial”)、Expression 和 Block Helper。
Inclusion :通過(guò) {{> templateName}}
標(biāo)記,簡(jiǎn)單直接地告訴 Meteor 這部分需要用相同名稱(chēng)的模板來(lái)取代(在我們的例子中就是 postItem
)。
Expression :比如 {{title}}
標(biāo)記,它要么是調(diào)用當(dāng)前對(duì)象的屬性,要么就是對(duì)應(yīng)到當(dāng)前模板管理器中定義的 helper 方法,并返回其方法值(后面會(huì)詳細(xì)討論)。
Block Helper :在模板中控制流程的特殊標(biāo)簽,如 {{#each}}…{{/each}}
或 {{#if}}…{{/if}}
。
你如果想了解更多關(guān)于 Spacebars,可以參考 Spacebars 文檔。
有了這些知識(shí),我們就可以很容易去理解了。
首先,在 postsList
模板里,我們通過(guò) {{#each}}…{{/each}}
Block Helper 去遍歷一個(gè) posts
對(duì)象。然后,每次迭代我們?nèi)グ?postItem
模板。
這個(gè) posts
對(duì)象來(lái)自哪里?好問(wèn)題。它實(shí)際上是一個(gè) 模板 helper,你可以想象它是動(dòng)態(tài)值的占位符(placeholder)。
postItem
這個(gè)模板本身相當(dāng)簡(jiǎn)單。它只使用三個(gè)標(biāo)簽: {{url}}
和 {{title}}
都返回其集合的屬性,而 {{domain}}
則調(diào)用模板對(duì)應(yīng)的 helper 方法。
到目前為止我們已經(jīng)學(xué)會(huì)了使用 Spacebars ,這只是在 HTML 的基礎(chǔ)上多幾個(gè)標(biāo)簽而已。不像其他語(yǔ)言如 PHP (甚至常規(guī) HTML 頁(yè)面,還包含了 JavaScript), Meteor 只是讓模板和邏輯進(jìn)行分離,而這些模板本身并不需要做很多復(fù)雜的事情。
為了讓連接變得更流暢,一個(gè)模板需要 helper。你可以想象這些 helper 就是廚師用食材(你的數(shù)據(jù))烹飪好佳肴(模板),再由服務(wù)員端到你面前。
換句話說(shuō),模板的作用局限于顯示或循環(huán)變量,而 helper 則扮演著一個(gè)相當(dāng)重要的角色:把值分配給每個(gè)變量。
我們也許會(huì)情不自禁地認(rèn)為包含所有模板 helper 的文件是個(gè) controller(控制器)。但這是很模糊的,因?yàn)?controller (至少在 MVC 情況下)通常有些不同的作用。
所以我們決定遠(yuǎn)離那個(gè)術(shù)語(yǔ),在談?wù)撽P(guān)于模板涉及的 JavaScript 代碼時(shí),就簡(jiǎn)單地稱(chēng)為“模板的 helper”或是“模板的邏輯”。
為簡(jiǎn)單起見(jiàn),我們將采用與模板同名的方式來(lái)命名包含其 helper 的文件,區(qū)別是 .js 擴(kuò)展名。那好讓我們馬上在 /client/templates/posts
目錄下創(chuàng)建 posts_list.js
文件,開(kāi)始構(gòu)建我們第一個(gè) helper:
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
});
如果你運(yùn)行是正確的,你現(xiàn)在應(yīng)該在瀏覽器中看到這樣的畫(huà)面:
我們剛剛在做兩件事情。首先我們放置一些虛擬的基本數(shù)據(jù)到 postsData
數(shù)組中。原本數(shù)據(jù)應(yīng)該是來(lái)自數(shù)據(jù)庫(kù)的,但由于我們還沒(méi)有學(xué)習(xí)到該怎么做(下一章揭曉),所以我們先通過(guò)使用靜態(tài)數(shù)據(jù)來(lái)“作弊”。
然后,我們使用的是 Meteor 的 Template.postsList.helpers()
函數(shù),建立了 posts
模板 helper 來(lái)返回剛剛定義的 postsData
數(shù)組。
如果你記得,現(xiàn)在我們?cè)?postsList
模板中使用這個(gè) posts
helper:
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
定義 posts
helper 就就讓我們的模板可以使用它,所以模板就可以遍歷 postsData
數(shù)組并將里面的每個(gè)對(duì)象發(fā)送到 postItem
模板中。
添加了基本的 post 列表模板和靜態(tài)數(shù)據(jù)
類(lèi)似地,我們現(xiàn)在創(chuàng)建一個(gè) post_item.js
文件來(lái)包含 postItem
模板的邏輯:
Template.postItem.helpers({
domain: function() {
var a = document.createElement('a');
a.href = this.url;
return a.hostname;
}
});
這一次我們 domain
helper 的值不再是一個(gè)數(shù)組,而是一個(gè)匿名函數(shù)。相比起我們之前簡(jiǎn)化的虛擬數(shù)據(jù)的例子,這種模式更為常見(jiàn)(而且更有用)。
這個(gè) domain
helper 方法通過(guò) JavaScript 來(lái)獲取一個(gè) URL 地址并返回其域名。但是它一開(kāi)始是從哪里獲得 URL 地址呢?
為了回答這個(gè)問(wèn)題,我們需要回到我們的 posts_list.html
模板。{{#each}}
代碼塊不僅遍歷我們數(shù)組,它還在代碼塊范圍內(nèi)將 this
的值賦予被遍歷的對(duì)象。
這意味著在 {{#each}}
標(biāo)記之間,每個(gè) post 都可以通過(guò) this
依次訪問(wèn),并且一直延伸到模板 helper(post_item.js
)中。
我們現(xiàn)在明白了為什么 this.url
會(huì)返回當(dāng)前 post 的 URL。而且,如果我們?cè)?post_item.html
模板里面使用 {{title}}
和 {{url}}
,Meteor 就會(huì)知道需要去調(diào)用 this.title
和 this.url
去返回我們想要的正確值。
設(shè)置 postItem
的 domain
helper
盡管這對(duì)于 Meteor 來(lái)說(shuō)并不特別,這里會(huì)簡(jiǎn)單解釋一下上面“神奇的 JavaScript 代碼”。首先,我們創(chuàng)建一個(gè)空的錨(a
)HTML 標(biāo)簽并儲(chǔ)存在內(nèi)存中。
然后我們將其 href
屬性設(shè)置為當(dāng)前 post 的 URL (正如我們剛剛講到的,this
在 helper 中正是當(dāng)前被操作的對(duì)象)。
最后,我們利用 a
標(biāo)簽的特別的 hostname
屬性來(lái)返回 URL 的域名。
如果你正確地緊跟著我們的進(jìn)度,這時(shí)候你就會(huì)在瀏覽器中看到一個(gè) post 列表。但這個(gè)列表只是調(diào)用了靜態(tài)數(shù)據(jù),它沒(méi)有利用到 Meteor 實(shí)時(shí)性的特點(diǎn)。我們將在下一章向你展示該如何去修改!
你可能已經(jīng)注意到,當(dāng)你修改文件的時(shí)候,不需要手動(dòng)刷新頁(yè)面,瀏覽器就會(huì)自動(dòng)重新加載。
這是因?yàn)?Meteor 跟蹤了項(xiàng)目目錄下的所有文件,當(dāng)檢測(cè)到其中一個(gè)文件發(fā)生改變,它就會(huì)自動(dòng)刷新你的瀏覽器。
Meteor 的動(dòng)態(tài)代碼重載是相當(dāng)智能的,它甚至可以在兩個(gè)刷新動(dòng)作之間保存你的 App 狀態(tài)。
更多建議: