在我之前的教程中,我介紹了如何使用 Ember.Object 來定義模型并使用數(shù)據(jù)集。在本節(jié)中,我們將更仔細(xì)地了解 Ember 如何使用 Handlebars 模板框架來定義應(yīng)用的用戶界面。
客戶端模板
大多數(shù)服務(wù)器端開發(fā)人員習(xí)慣于使用模板來定義將動態(tài)填充的標(biāo)記。如果您曾經(jīng)使用過 ASP.net、ColdFusion、php 或 Rails,那么您肯定知道我在說什么。
JavaScript 客戶端模板最近確實開始流行,特別是因為它專注于構(gòu)建更多類似桌面的體驗。這意味著更多的處理是在客戶端完成的,數(shù)據(jù)主要通過服務(wù)器端 API 請求拉取。
我記得不久前 jquery 模板插件首次發(fā)布時寫過有關(guān)客戶端模板的文章。近三年后,它仍然是我博客上閱讀量最大的帖子,表明人們對客戶端模板的興趣有多么高漲。從那時起,許多其他框架已經(jīng)發(fā)布,提供了豐富的功能和支持社區(qū)。 Handlebars 是更流行的選項之一,也是 Ember 項目選擇的框架來滿足其模板需求。這是有道理的,因為 Handlerbars 是由 Ember.JS 聯(lián)合創(chuàng)始人兼核心團(tuán)隊成員 Yehuda Katz 創(chuàng)建的。但請注意,我不打算在模板框架之間進(jìn)行比較,我將嚴(yán)格關(guān)注 Handelbars,因為這是 Ember.js 默認(rèn)使用的。
在之前的文章中,我在代碼中展示了一些非常基本的模板:
<script type="text/x-handlebars"> <h2><strong>{{firstName}} {{lastName}} </script>
有兩件事很突出,那就是腳本標(biāo)簽的類型聲明和大括號,它們充當(dāng) Handlebars 將作用的表達(dá)式的分隔符。這是非常典型的語法,我將很快詳細(xì)討論,您將在構(gòu)建 Ember 模板時一致地使用它。
語法
盡管 Handlebars 使用特殊語法,但歸根結(jié)底,您實際上主要使用標(biāo)準(zhǔn) html 標(biāo)記。 Handlebars 用于將內(nèi)容注入到此標(biāo)記中,以將數(shù)據(jù)呈現(xiàn)給用戶。它通過解析分隔表達(dá)式并將其替換為您要求 Handlebars 使用的數(shù)據(jù)來實現(xiàn)此目的。對于 Ember,Handlebars 提供掛鉤,Ember 使用它們。該數(shù)據(jù)通常來自您的控制器(請記住,控制器充當(dāng)模型的接口)。
任何模板首先需要的是腳本標(biāo)記定義。大多數(shù)人可能已經(jīng)定義了腳本標(biāo)簽來加載 JavaScript 庫。事實上,您已經(jīng)這樣做了,將 Handlebars 加載到您的 Ember 項目中:
<script src="js/libs/jquery-1.9.1.js"></script><script src="js/libs/handlebars-1.0.0-rc.3.js"></script><script src="js/libs/ember-1.0.0-rc.1.js"></script><script src="js/app.js"></script>
與使用它來定義模板略有不同。首先,我們指定“text/x-handlebars”的 type 屬性。瀏覽器會忽略此 type,但保留文本可供檢查,并允許 Ember 識別應(yīng)用程序內(nèi)的模板。此外,Ember 使用名為“data-template-name”的數(shù)據(jù)屬性,Ember 可以使用該屬性將應(yīng)用程序的特定部分與模板關(guān)聯(lián)起來。例如,以下聲明定義了一個名為“employee”的模板:
<script type="text/x-handlebars" data-template-name="employee"> ... </script>
當(dāng)您的應(yīng)用程序啟動時,Ember 會掃描 dom 中的 type=”text/x-handlebars,編譯它找到的模板,并將它們存儲在 Ember 對象的一個??屬性中,該屬性名為 Ember.TEMPLATES 并使用它找出為給定路線呈現(xiàn)的內(nèi)容。這就是為什么遵循 Ember 的命名約定如此重要。在上面的示例中,此模板將自動關(guān)聯(lián)到您在應(yīng)用程序中創(chuàng)建的員工路線和控制器。同樣,我可以’不必過多強調(diào)這些命名約定將如何使您的開發(fā)變得更加容易。
Ember 依賴 URL 來確定需要使用的資源和需要渲染的模板。假設(shè)您有一個 URL 為“/profile”的個人資料頁面。您將擁有一個名為 profile 的資源,它將加載該 URL 的特定資源(如路由對象),并且您還將擁有一個同名的模板。我們在 Ember 系列的第 2 部分中回顧了定義資源和路由對象,因此如果您不確定我正在討論的內(nèi)容,請務(wù)必跳回那里重新了解一下。
當(dāng)您訪問該 URL 時,Ember 知道它需要加載這些資源并解析您定義的模板。它通過其命名約定來執(zhí)行此操作,知道因為您轉(zhuǎn)到“/profile”,所以它需要加載 profile 中定義的資源,并呈現(xiàn)名為 data-template-name=”profile” 的模板。
- 路線:配置文件路線
- 控制器: ProfileController
- 模板:個人資料(注意是小寫)
再次查看命名約定,您會發(fā)現(xiàn)路由、控制器和模板都使用相同的 URL 名稱綁定在一起,但模板拼寫為小寫。這使得 Ember 能夠在幕后管理一切,而無需進(jìn)行大量接線。
還需要注意的是,如果您聲明的模板沒有 data-template-name 屬性,Ember 將假定它是應(yīng)用程序范圍的模板 – 通常用作站點范圍模板來創(chuàng)建用戶界面元素,例如頁眉、頁腳和導(dǎo)航。如果您沒有為應(yīng)用程序甚至資源(例如 URL)顯式定義模板,Ember 會自動為您執(zhí)行此操作,以確保應(yīng)用程序的穩(wěn)定性和一致性。
表達(dá)式
下一步是包含您的標(biāo)記和用于表示數(shù)據(jù)的分隔表達(dá)式。表達(dá)式通過雙花括號進(jìn)行分隔,這使得它們可以通過從控制器傳遞的數(shù)據(jù)輕松識別和解析。這是一個例子:
<script type="text/x-handlebars"> <h2><strong>{{firstName}} {{lastName}} </script>
在這種情況下,{{firstName}} 和 {{lastName}} 表達(dá)式將被 Ember 解析并替換為實際數(shù)據(jù)。此外,Ember 設(shè)置了觀察者,以便當(dāng)您的數(shù)據(jù)發(fā)生變化時,您的模板會自動更新,并將更新反映給應(yīng)用程序的用戶。
到目前為止,我已經(jīng)向您展示了一個非常簡單的示例,但要點是:
- Ember 使用特殊的類型屬性來定義模板。
- 模板使用標(biāo)準(zhǔn)標(biāo)記以及分隔表達(dá)式,這些表達(dá)式在客戶端進(jìn)行解析。
- 這些模板具有 Handlebars 的完整功能集。
- Ember 設(shè)置觀察者來動態(tài)更新您的用戶界面數(shù)據(jù)(當(dāng)用戶界面數(shù)據(jù)發(fā)生變化時)。
這為您構(gòu)建用戶界面的方式提供了很大的靈活性。讓我們繼續(xù)看看可用的功能。
高級表達(dá)式
請記住,Ember 利用了 Handlebars,因此您可以在此處訪問其完整的表達(dá)式。為了使幾乎任何東西變得有用,條件表達(dá)式是必須的;車把提供了相當(dāng)多的選擇。
假設(shè)我有一個如下所示的 json 數(shù)據(jù)集:
"items": [{ "title": "Tearable Cloth Simulation in JavaScript", "url": "http://codepen.io/stuffit/pen/KrAwx", "id": 5592679, "commentCount": 20, "points": 127, "postedAgo": "1 hour ago", "postedBy": "NathanKP" }, { "title": "Netflix now bigger than HBO", "url": "http://qz.com/77067/netflix-now-bigger-than-hbo/", "id": 5592403, "commentCount": 68, "points": 96, "postedAgo": "2 hours ago", "postedBy": "edouard1234567" }
如果我想確保 title 數(shù)據(jù)可用,我可以使用 #if 表達(dá)式添加條件“if”語句:
{{#if item.title}}
{{/if}}
這會檢查 item.title 是否未定義,并繼續(xù)處理 title、postedAgo 和 postedBy 數(shù)據(jù)表達(dá)式的后續(xù)表達(dá)式。
由于該數(shù)據(jù)集包含多個“記錄”,因此可以安全地假設(shè)我們可能希望循環(huán) item 的每個元素。這就是 {{#each}} 表達(dá)式發(fā)揮作用的地方。它允許您枚舉對象列表。因此,再次記住模板是標(biāo)記和 Handlebars 表達(dá)式的組合,我們可以使用 #each 表達(dá)式來循環(huán)遍歷 Ember 模型對象中可用的每個項目。請記住,Ember 模型是從控制器派生的,控制器通過 Ember 的命名約定與模板關(guān)聯(lián)。
- {{#each item in model}} {{#if item.title}}
- {{item.title}} – {{item.postedAgo}} by {{item.postedBy}}
{{/if}} {{/each}}
這會渲染出類似于以下內(nèi)容的內(nèi)容:
- Tearable Cloth Simulation in JavaScript – 1 hour ago by NathanKP
- Netflix now bigger than HBO – 2 hours ago by edouard1234567
- Fast Database Emerges from MIT Class, GPUs and Student’s Invention – 33 minutes ago by signa11
- Connecting an iPad retina LCD to a PC – 6 hours ago by noonespecial
顯著的優(yōu)勢是 Ember 隱含的觀察者規(guī)范,它將在更新時更新您的數(shù)據(jù)。
如果您的條件表達(dá)式需要更復(fù)雜,您將需要創(chuàng)建一個計算屬性。這允許您基于可以將復(fù)雜代碼條件應(yīng)用于數(shù)據(jù)的方法創(chuàng)建屬性。假設(shè)我只想顯示標(biāo)題為“JavaScript 中的可撕裂布料模擬”的數(shù)據(jù)。我需要設(shè)置幾件事:
- 我需要一個計算屬性來掃描每個項目并告訴我標(biāo)題是否匹配
- 我需要創(chuàng)建一個控制器,可供模板中枚舉的每個項目使用
- 我需要更新模板,以便它為每個項目使用此控制器
我需要做的第一件事是創(chuàng)建新的控制器,它將包裝循環(huán)的每個項目并在其中創(chuàng)建計算屬性:
App.TitleController = Ember.ObjectController.extend({ titleMatch: function() { return this.get('title') === "Tearable Cloth Simulation in JavaScript"; }.Property() });
查看代碼,我們對 Ember.ObjectController 進(jìn)行子類化以創(chuàng)建控制器。這是控制器,它將包裝模板中循環(huán)的每個項目。接下來,我們創(chuàng)建一個名為 titleMatch 的方法,它使用 get() 方法來拉回當(dāng)前標(biāo)題,將其與我定義的文本進(jìn)行比較,然后返回一個布爾值。最后,調(diào)用 Ember property() 方法將 titleMatch 方法定義為計算屬性。
完成此操作后,我們將更新模板的 {{#each}} 表達(dá)式,以使用我們創(chuàng)建的新控制器來表示每個項目。這是通過使用 itemController 指令來完成的。需要理解的關(guān)鍵一點是 itemController 是 Ember 中的一個關(guān)鍵短語,旨在將控制器與模板的項目關(guān)聯(lián)起來。不要將其與實際的控制器名稱混淆(就像我最初所做的那樣)。控制器名稱分配給 itemController,如下所示:
- {{#each item in model itemController=”title”}} {{#if titleMatch}}
- {{foo.title}} – {{foo.postedAgo}} by {{foo.postedBy}}
{{/if}} {{/each}}
同樣,命名約定規(guī)定,在模板中分配名稱時,我們使用小寫。在本例中,我們將 TitleController 分配給 itemController。
現(xiàn)在,當(dāng)循環(huán)每個項目時,計算屬性 titleMatch 用于評估標(biāo)題并在匹配時顯示數(shù)據(jù)。
將數(shù)據(jù)綁定到元素
創(chuàng)建動態(tài)模板不僅僅是吐出文本。有時,ui 的外觀需要受到正在處理的數(shù)據(jù)的影響。顯示圖像或建立鏈接就是很好的例子。
將數(shù)據(jù)綁定到元素需要使用特殊的 Ember 助手來幫助定義屬性的上下文,并確保在數(shù)據(jù)更改時正確更新屬性。對于元素屬性,{{bindAttr}} 幫助器用于填充屬性的值。如果我們需要動態(tài)指定圖像的 URL,我們將使用以下語法:
@@##@@
對于不接收值的屬性也可以這樣做,例如disabled:
<input type="checkbox" disabled>
在這種情況下, isAdminstrator 可以是基于控制器中的方法的計算屬性,或者只是一個普通的對象屬性,為您在定義禁用復(fù)選框的條件方面提供了很大的靈活性。這種靈活性也適用于定義類名。如果我想使用條件語句來定義是否應(yīng)將類應(yīng)用于我的元素,我可以使用以下代碼:
<div class="isUrgent"> Warning! </div>
根據(jù)布爾狀態(tài),我的標(biāo)記將是:
<div class="is-urgent"> Warning! </div>
對于 true 條件,或:
<div> Warning! </div>
對于 false 條件。請注意,當(dāng)我為該類指定 isUrgent 時,Ember 對該名稱進(jìn)行了 dasher 處理,并將該類呈現(xiàn)為 is-urgent。如果您希望根據(jù)結(jié)果指定自己的類,可以使用類似于三元語句的條件表達(dá)式:
<div class="isUrgent:urgent:normal"> <p>這將根據(jù) isUrgent 的條件值返回該類的 urgent 或 normal。</p> <hr> <h2>了解模板</h2> <p>模板將成為用戶界面的基礎(chǔ),因此花時間閱讀 Ember 和 Handlebars 站點上的文檔以充分了解它們的整體功能非常重要。即使您不使用 Ember,Handlebars 也是一個適合您日常使用的出色框架,并且值得投資學(xué)習(xí)如何使用它。</p> <p> Gabriel Manricks 在 Nettuts+ 上編寫了一篇關(guān)于 Handlebars 的精彩教程,您可以使用它來加快框架的速度。</p> <img src="logoUrl" alt="Logo"> </div>