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