Ember.js 的視圖層
本指導(dǎo)會(huì)詳盡闡述 Ember.js 視圖層的細(xì)節(jié)。為想成為熟練 Ember 開發(fā)者準(zhǔn)備,且包 含了對(duì)于入門 Ember 不必要的細(xì)節(jié)。
Ember.js 有一套復(fù)雜的用于創(chuàng)建、管理并渲染連接到瀏覽器 DOM 上的層級(jí)視圖的系 統(tǒng)。視圖負(fù)責(zé)響應(yīng)諸如點(diǎn)擊、拖拽以及滾動(dòng)等的用戶事件,也在視圖底層數(shù)據(jù)變更時(shí)更 新 DOM 的內(nèi)容。
視圖層級(jí)通常由求值一個(gè) Handlebars 模板創(chuàng)建。當(dāng)模板求值后,會(huì)添加子視圖。當(dāng) 那些 子視圖求值后,會(huì)添加它們的子視圖,如此遞推,直到整個(gè)層級(jí)被創(chuàng)建。
即使你并沒有在 Handlebars 模板中顯式地創(chuàng)建子視圖,Ember.js 內(nèi)部仍使用視圖系 統(tǒng)更新綁定的值。例如,每個(gè) Handlebars 表達(dá)式 {{value}} 幕后創(chuàng)建一個(gè)視圖, 這個(gè)視圖知道當(dāng)值變更時(shí)如何更新綁定值。
你也可以在應(yīng)用運(yùn)行時(shí)用 Ember.ContainerView 類對(duì)視圖層級(jí)做出修改。一個(gè)容器 視圖暴露一個(gè)可以手動(dòng)修改的子視圖實(shí)例數(shù)組,而非模板驅(qū)動(dòng)。
視圖和模板串聯(lián)工作提供一套用于創(chuàng)建任何你夢(mèng)寐以求的用戶界面的穩(wěn)健系統(tǒng)。最終用 戶應(yīng)從諸如當(dāng)渲染和事件傳播是的計(jì)時(shí)事件之類的復(fù)雜東西中隔離開。應(yīng)用開發(fā)者應(yīng)可 以一次性把他們的 UI 描述成 Handlebars 標(biāo)記字符串,然后繼續(xù)完成他們的應(yīng)用,而 不必?zé)烙诖_保它一直是最新的。
它解決了什么問題?
子視圖
在典型的客戶端應(yīng)用中,視圖同時(shí)在本身和 DOM 中表示嵌套的元素。在解決這個(gè)問題 的天真方案中,獨(dú)立的視圖對(duì)象表示單個(gè) DOM 元素,專門的引用解決不同種類的視圖 保持對(duì)概念中嵌套在它們內(nèi)部的視圖的跟蹤。
這里是一個(gè)簡單的例子,表示一個(gè)應(yīng)用主視圖,里面有一集合嵌套視圖,且獨(dú)立的元素在集合內(nèi)嵌套。

這個(gè)系統(tǒng)第一眼看上去毫無異樣,但是想象我們要在上午 8 點(diǎn)而不是上午 9 點(diǎn)開放喬 的七鰓鰻小屋。在這種情況下,我們會(huì)想要重新渲染應(yīng)用視圖。因?yàn)殚_發(fā)者需要構(gòu)建指 向在一個(gè)特殊基礎(chǔ)上的子視圖的引用,這個(gè)重渲染過程存在若干問題。
為了重新渲染應(yīng)用視圖,應(yīng)用視圖也必須手動(dòng)重新渲染子視圖并重新把它們插入到應(yīng)用 視圖的元素中。如果實(shí)現(xiàn)得完美,這個(gè)過程會(huì)正常工作,但它依賴于一個(gè)完美的,專門 的視圖層級(jí)實(shí)現(xiàn)。如果任何一個(gè)視圖沒有精確地實(shí)現(xiàn)它,整個(gè)重新渲染過程會(huì)失敗。
為了避免這些問題,Ember 的視圖層級(jí)從概念上就帶有子視圖的烙印。
   
當(dāng)應(yīng)用時(shí)圖重新渲染時(shí),Ember 而不是應(yīng)用代碼負(fù)責(zé)重新渲染并插入子視圖。這也意味 著 Ember 可以為你執(zhí)行任何內(nèi)存管理,比如清理觀察者和綁定。
這不僅在一定程度上消滅了樣板代碼,也破除了有瑕疵的視圖層級(jí)實(shí)現(xiàn)帶來的未期失敗的可能。
#p#
事件委派
在過去,web 開發(fā)者已經(jīng)用在獨(dú)立的單個(gè)元素上添加事件監(jiān)聽器來獲知什么時(shí)候用戶與 它們交互。例如,你會(huì)有一個(gè) <div> 元素,其上注冊(cè)了一個(gè)當(dāng)用戶點(diǎn)擊它時(shí)觸發(fā)的 函數(shù)。
盡管如此,這個(gè)途徑在處理大數(shù)量交互元素上不會(huì)縮放。比如,想象一個(gè)帶有 100 個(gè) <li> 的 <ul> ,每個(gè)項(xiàng)目后都有一個(gè)刪除按鈕。既然所有的這些項(xiàng)目行為都是一 致的,為每個(gè)刪除按鈕創(chuàng)建共計(jì) 100 個(gè)事件監(jiān)聽器無疑是低效的。   
 
 要解決這個(gè)問題,開發(fā)者發(fā)現(xiàn)了一種名為“事件委派”的技術(shù)。你可以在容器元素上注冊(cè) 一個(gè)監(jiān)聽器并使用 event.target 來識(shí)別哪個(gè)元素是用戶點(diǎn)擊的,而不是為問題中的 每個(gè)項(xiàng)目創(chuàng)建一個(gè)監(jiān)聽器。
 
實(shí)現(xiàn)這有一些微妙,因?yàn)橐恍┦录ū热?focus 、 blur 和 change )不會(huì)冒 泡。幸運(yùn)的是,jQuery 已經(jīng)徹底解決了這個(gè)問題;用 jQuery 的 on 方法可以可靠 地處理所有原生瀏覽器事件。
其它 JavaScript 框架用兩種方法中的其一來處理這個(gè)問題。第一種是,它們要你自己 實(shí)現(xiàn)原生解決方案,為每個(gè)項(xiàng)目創(chuàng)建獨(dú)立的視圖。當(dāng)你創(chuàng)建視圖,它在視圖的元素上設(shè) 置一個(gè)監(jiān)聽器。如果你有一個(gè)含有 500 個(gè)項(xiàng)目的列表,你會(huì)創(chuàng)建 500 個(gè)視圖并且每個(gè) 視圖都會(huì)在它自己的元素上設(shè)置一個(gè)監(jiān)聽器。
第二種方法是,框架在視圖層內(nèi)置事件委派。當(dāng)創(chuàng)建一個(gè)視圖,你可以提供一個(gè)事件列 表來在事件發(fā)生時(shí)委派一個(gè)方法來調(diào)用。這只剩下識(shí)別接受事件的方法的點(diǎn)擊上下文 (比如,列表中的哪個(gè)項(xiàng)目)。
你現(xiàn)在要面對(duì)兩個(gè)令人不安的選擇:為每個(gè)項(xiàng)目創(chuàng)建一個(gè)新視圖,這樣會(huì)喪失事件委派 的優(yōu)勢(shì),或是為所有項(xiàng)目創(chuàng)建單個(gè)視圖,這樣必須存儲(chǔ) DOM 中底層 JavaScript 的信息。
要解決這個(gè)問題,Ember 用 jQuery 把所有事件委派到應(yīng)用的根元素(通常是文檔的 body )。當(dāng)一個(gè)事件發(fā)生,Ember 識(shí)別出最近的處理事件視圖并調(diào)用它的事件處理 器。這意味著你可以創(chuàng)建視圖來保存一個(gè) JavaScript 上下文,但仍然從事件委派上受 益。
進(jìn)一步地,因?yàn)?Ember 只為整個(gè) Ember 應(yīng)用注冊(cè)一個(gè)事件,創(chuàng)建新視圖永遠(yuǎn)都不需要 設(shè)置事件監(jiān)聽器,這使得重渲染高效且免于出錯(cuò)。當(dāng)視圖有一個(gè)子視圖,這也意味著不 需要手動(dòng)取消委派重新渲染過程中替換掉的視圖。
渲染管道
大多數(shù) web 應(yīng)用用特殊的模板語言標(biāo)記來指定它們的用戶界面。對(duì)于 Ember.js,我們 已經(jīng)完成用可在值修改的時(shí)候自動(dòng)更新模板的 Handlebars 模板語言來編寫模板。
雖然顯示模板的過程對(duì)開發(fā)者是自動(dòng)的,但其遮蓋了把原始模板轉(zhuǎn)換為最終模板、生成 用戶可見的 DOM 表示的一系列必要步驟。
這是 Ember 視圖的近似生命周期:
 
1. 模板編譯
應(yīng)用的模板通過網(wǎng)絡(luò)加載或以字符串形式作為應(yīng)用的載荷。當(dāng)應(yīng)用加載時(shí),它發(fā)送模板 字符串到 Handlebars 來編譯成函數(shù)。一經(jīng)編譯,模板函數(shù)會(huì)被保存,且可以被多個(gè)視 圖重復(fù)使用,每次都它們都需重新編譯。
這個(gè)步驟會(huì)在應(yīng)用中服務(wù)器預(yù)編譯模板的地方發(fā)出。在那些情況下,模板不作為原始的 人類可讀的模板傳輸,而是編譯后的代碼。
因?yàn)?Ember 負(fù)責(zé)模板編譯,你不需要做任何額外的工作來保證編譯后的模板可以重用。
#p#
2. 字符串的連接
當(dāng)應(yīng)用在視圖上調(diào)用 append 或 appendTo 時(shí),一個(gè)視圖渲染過程會(huì)被啟動(dòng)。 append 或 appendChild 調(diào)用 安排 視圖渲染并在之后插入。這允許應(yīng)用中的 延遲邏輯(譬如綁定同步)在渲染元素之前執(zhí)行。
要開始渲染過程,Ember 創(chuàng)建一個(gè) RenderBuffer 并把它呈遞給視圖來把視圖的內(nèi)容 附加到上面。在這個(gè)過程中,視圖可以創(chuàng)建并渲染子視圖。當(dāng)它這么做時(shí),父視圖創(chuàng)建 并分配一個(gè) RenderBuffer 給子視圖,并把它連接到父視圖的 RenderBuffer 上。
Ember 在渲染每個(gè)視圖前刷新綁定同步隊(duì)列。這樣,Ember 保障不會(huì)渲染需要立即替換 的過期數(shù)據(jù)。
一旦主視圖完成渲染,渲染過程會(huì)創(chuàng)建一個(gè)視圖樹(即“視圖層級(jí)”),連接到緩沖區(qū)樹 上。通過向下遍歷緩沖區(qū)樹并把它們轉(zhuǎn)換為字符串,我們就有了一個(gè)可以插入到 DOM 的字符串。
這里是一個(gè)簡單的例子:
 
除子節(jié)點(diǎn)之外(字符串和其它 RenderBuffer ), RenderBuffer 也會(huì)封裝元素標(biāo) 簽名稱、id、class、樣式和其它屬性。這使得渲染過程修改這些屬性(例如樣式)成 為可能,即使在子字符串已經(jīng)渲染完畢。因?yàn)檫@些屬性的許多都可以通過綁定(例如用 bindAttr )控制,這使得渲染過程穩(wěn)健且透明。
3. 元素的創(chuàng)建和插入
在渲染過程的最后,根視圖向 RenderBuffer 請(qǐng)求它的元素。 RenderBuffer 獲得 它的完整字符串并用 jQuery 把它轉(zhuǎn)換成一個(gè)元素。視圖把那個(gè)元素分配到它的 element 屬性并把把它放置到 DOM 中正確的位置( appendTo 指定的位置,如果 應(yīng)用使用 append 即是應(yīng)用的根元素)。
雖然父視圖直接分配它的元素,但每個(gè)子視圖惰性查找它的元素。它通過查找 id 匹 配它的 elementId 屬性的元素來完成這。除非顯式提供,渲染過程生成一個(gè) elementId 屬性比你更分配它的值給視圖的 RenderBuffer ,RenderBuffer 允 許視圖按需查找它的元素。
4. 重新渲染
在視圖把自己插入到 DOM 后,Ember 和應(yīng)用都會(huì)要重新渲染視圖。它們可以在視圖上 調(diào)用 rerender 方法來出發(fā)一次重渲染。
重新渲染會(huì)重復(fù)上面的步驟 2 和步驟 3,有兩點(diǎn)例外:
rerender用新元素替換已有的元素,而不是把元素插入到顯式定義的位置。- 除了渲染新元素,它也刪除舊元素并銷毀它的子元素。這允許 Ember 在重新渲染視 圖時(shí)自動(dòng)處理撤銷合適的綁定和觀察者。這使得路徑上的觀察者可行,因?yàn)樽?cè)和撤銷 注冊(cè)所有的嵌套觀察者都是自動(dòng)的。
 
最常見的導(dǎo)致視圖重新渲染的原因是當(dāng)綁定到 Handlebars 表達(dá)式( {{foo}} )變 更。Ember 內(nèi)部為每個(gè)表達(dá)式創(chuàng)建一個(gè)簡單的視圖,并且在路徑上注冊(cè)一個(gè)觀察者。當(dāng) 路徑變更時(shí),Ember 用新值更新那個(gè)區(qū)域的 DOM。
另一個(gè)常見的情況是一個(gè) {{#if}} 或 {{#with}} 塊。當(dāng)渲染一個(gè)模板時(shí),Ember 為這些塊輔助標(biāo)創(chuàng)建虛擬的視圖。這些虛擬的視圖不會(huì)出現(xiàn)在公共可訪問的視圖層級(jí)里 (當(dāng)從視圖獲取 parentView 和 childViews 時(shí)),但它們的存在啟用了一致的重 渲染。
當(dāng)傳遞到 {{#if}} 或 {{#with}} 的路徑變更,Ember 自動(dòng)重新渲染虛擬視圖替換 它的內(nèi)容,重要的是,也會(huì)銷毀所有的子視圖來釋放內(nèi)存。
除了這些情景,應(yīng)用有時(shí)也會(huì)要顯式地重新渲染視圖(通常是一個(gè) ContainerView ,見下)。在這種情況下,應(yīng)用可以直接調(diào)用 rerender ,且 Ember 會(huì)把一項(xiàng)重渲染工作加入隊(duì)列,用相同的語義元素。
這個(gè)過程像是這樣:
 
#p#
視圖層級(jí)
父與子
當(dāng) Ember 渲染一個(gè)模板化的視圖,它會(huì)生成一個(gè)視圖層級(jí)。讓我們假設(shè)已有一個(gè)模板 form 。
原文鏈接:
- {{view App.Search placeholder="Search"}}
 - {{#view Ember.Button}}Go!{{/view}}
 
然后我們像這樣把它插入到 DOM 中:
- var view = Ember.View.create({
 - templateName: 'form'
 - }).append();
 
這會(huì)創(chuàng)建一個(gè)如下小巧的視圖等級(jí):
 
你可以用 parentView 和 childViews 屬性在視圖層級(jí)中游走。
- var children = view.get('childViews') // [ <App.Search>, <Ember.Button> ]
 - children.objectAt(0).get('parentView') // 視圖
 
一個(gè)常見的 parentView 使用方法是在子視圖的實(shí)例里。
- App.Search = Ember.View.extend({
 - didInsertElement: function() {
 - // this.get('parentView') 指向 `view`
 - }
 - })
 
生命周期鉤子
為了容易地在視圖的生命周期的不同點(diǎn)上執(zhí)行行為,有若干你可以實(shí)現(xiàn)的鉤子。
willInsertElement: 這個(gè)鉤子在視圖渲染后插入 DOM 之前調(diào)用。它不提供對(duì)視圖的element的訪問。didInsertElement: 這個(gè)鉤子在視圖被插入到 DOM 后立即調(diào)用。它提供到視圖的element的訪問,且對(duì)集成到外部庫非常有用。任何顯式的 DOM 設(shè)置代碼應(yīng)限于這個(gè)鉤子。willDestroyElement: 這個(gè)鉤子在元素從 DOM 移除前立即調(diào)用。這提供了銷毀任何與 DOM 節(jié)點(diǎn)關(guān)聯(lián)的外部狀態(tài)的機(jī)會(huì)。像didInsertElement一樣,它對(duì)于集成外部庫非常有用。willRerender: 這個(gè)鉤子在視圖被重新渲染前立即調(diào)用。如果你想要在視圖被重新渲染前執(zhí)行一些銷毀操作,這會(huì)很有用。becameVisible: 這個(gè)鉤子在視圖的isVisible或它的祖先之一的isVisible變?yōu)檎嬷担谊P(guān)聯(lián)的元素也變?yōu)榭梢姾笳{(diào)用。注意這個(gè)鉤子只在所有可見性由isVisible屬性控制的時(shí)候可靠。becameHidden: 這個(gè)鉤子在視圖的isVisible或它的祖先之一的isVisible變?yōu)榧僦担谊P(guān)聯(lián)的元素也變?yōu)殡[藏后調(diào)用。注意這個(gè)鉤子只在所有可見性由isVisible屬性控制的時(shí)候可靠。
應(yīng)用可以通過在視圖上定義一個(gè)與鉤子同名的方法來實(shí)現(xiàn)鉤子?;蛘?,在視圖上為鉤子 注冊(cè)一個(gè)監(jiān)聽器也是可行的。
- view.on('willRerender', function() {
 - // do something with view
 - });
 
虛擬視圖
正如上文所述,Handlebars 在視圖層級(jí)內(nèi)創(chuàng)建視圖來表現(xiàn)綁定值。每次你使用 Handlebars 表達(dá)式,無論是一個(gè)簡單值還是一個(gè)諸如 {{#with}} 或 {{#if}} 的 塊表達(dá)式,Handlebars 會(huì)創(chuàng)建一個(gè)新視圖。
因?yàn)?Ember 只把這些視圖用于內(nèi)部簿記,它們對(duì)于視圖的公共 parentView 和 childViews API 是隱藏的。公共視圖層級(jí)只反射用 {{view}} 輔助標(biāo)記或通過 ContainerView 創(chuàng)建的視圖(見下)。
例如,考慮下面的 Handlebars 模板:
- <h1>Joe's Lamprey Shack</h1>
 - {{controller.restaurantHours}}
 - {{#view App.FDAContactForm}}
 - 如果你在喬的七鰓鰻小屋用餐后不適,請(qǐng)用下面的表格向 FDA 提交申訴。
 - {{#if controller.allowComplaints}}
 - {{view Ember.TextArea valueBinding="controller.complaint"}}
 - <button {{action submitComplaint}}>提交</button>
 - {{/if}}
 - {{/view}}
 
渲染這個(gè)模板會(huì)創(chuàng)建這樣的層級(jí):

幕后,Ember 跟蹤為 Handlebars 表達(dá)式創(chuàng)建的額外的虛擬視圖:
     
#p#
在TextArea 中, parentView 會(huì)指向 FDAContactForm ,并且 FDAContactForm 的 childViews 會(huì)是一個(gè)只包含 TextArea 的數(shù)組。
你可以通過 _parentView 和 _childViews 來查看內(nèi)部視圖層級(jí),這會(huì)包含虛擬視 圖:
- var _childViews = view.get('_childViews');
 - console.log(_childViews.objectAt(0).toString());
 - //> <Ember._HandlebarsBoundView:ember1234>
 
警告! 你不應(yīng)該在應(yīng)用代碼中依賴于這些內(nèi)部 API。它們會(huì)在任何時(shí)候更改并且 沒有任何公共合約。返回值也不能被觀察或被綁定。它可能不是 Ember 對(duì)象。如果覺 得有使用它們的需求,請(qǐng)聯(lián)系我們,這樣我們可以為你的使用需求暴露一個(gè)更好的公共 API。
底線:這個(gè) API 就像是 XML。如果你覺得你需要用到它,那么你很可能沒有足夠理解 問題。三思!
事件冒泡
視圖的一個(gè)任務(wù)是響應(yīng)原始用戶事件并把它們翻譯成對(duì)你應(yīng)用而言有語義的事件。
例如,一個(gè)刪除按鈕把原始的 click 事件翻譯成應(yīng)用特定的“把這個(gè)元素從數(shù)組中刪 除”。
為了響應(yīng)用戶事件,創(chuàng)建一個(gè)視圖的子類來把事件實(shí)現(xiàn)為方法:
- App.DeleteButton = Ember.View.create({
 - click: function(event) {
 - var stateManager = this.getPath('controller.stateManager');
 - var item = this.get('content');
 - stateManager.send('deleteItem', item);
 - }
 - });
 
當(dāng)你創(chuàng)建一個(gè)新的 Ember.Application 實(shí)例,它用 jQuery 的事件委派 API 給每個(gè) 原生瀏覽器事件注冊(cè)一個(gè)事件處理器。當(dāng)用戶觸發(fā)一個(gè)事件,應(yīng)用事件分配器會(huì)找出離 事件最近的視圖并實(shí)現(xiàn)那個(gè)事件。
一個(gè)視圖通過定義與事件同名的方法來實(shí)現(xiàn)事件。當(dāng)事件名稱由多個(gè)詞組成(如 mouseup )方法名會(huì)用 Camel 命名法把事件名作為方法名( mousUp )。
事件會(huì)在視圖層級(jí)中冒泡,直到事件到達(dá)根視圖。一個(gè)事件處理器可以用與常規(guī) jQuery 事件處理器相同的技術(shù)來停止事件傳播:
- 在視圖中 
return false event.stopPropagation
例如,假設(shè)你已經(jīng)定義了如下的視圖類:
- App.GrandparentView = Ember.View.extend({
 - click: function() {
 - console.log('Grandparent!');
 - }
 - });
 - App.ParentView = Ember.View.extend({
 - click: function() {
 - console.log('Parent!');
 - return false;
 - }
 - });
 - App.ChildView = Ember.View.extend({
 - click: function() {
 - console.log('Child!');
 - }
 - });
 
這是使用它們的 Handlebars 模板。
- {{#view App.GrandparentView}}
 - {{#view App.ParentView}}
 - {{#view App.ChildView}}
 - <h1>點(diǎn)擊這里!</h1>
 - {{/view}}
 - {{/view}}
 - {{/view}}
 
如果你點(diǎn)擊 <h1> ,你會(huì)在瀏覽器控制臺(tái)里看見下面的輸出:
- Child!
 - Parent!
 
你可以看出 Ember 在接受事件的最深層級(jí)視圖上調(diào)用了處理器。事件繼續(xù)上浮到 ParentView ,但不會(huì)到達(dá) GrandparentView 因?yàn)?ParentView 從它的事件處理 器中返回了 false 。
你可以使用常規(guī)事件冒泡技術(shù)來實(shí)現(xiàn)常見的模式。例如,你可以實(shí)現(xiàn)一個(gè)帶有 submit 方法的 FormView 。因?yàn)闉g覽器在用戶向文本域輸入回車的時(shí)候會(huì)觸發(fā) submit 事件,在表單視圖上定義一個(gè) submit 方法會(huì)“剛好完成任務(wù)”。
- App.FormView = Ember.View.extend({
 - tagName: "form",
 - submit: function(event) {
 - // 會(huì)在任何用戶觸發(fā)瀏覽器的
 - // `submit` 方法時(shí)被調(diào)用
 - }
 - });
 
- {{#view App.FormView}}
 - {{view Ember.TextFieldView valueBinding="controller.firstName"}}
 - {{view Ember.TextFieldView valueBinding="controller.lastName"}}
 - <button type="submit">確定</button>
 - {{/view}}
 
#p#
添加新事件
Ember 內(nèi)置了如下原生瀏覽器事件的支持:
| 
             事件名  | 
            
             方法名  | 
        
|---|---|
| touchstart | touchStart | 
| touchmove | touchMove | 
| touchend | touchEnd | 
| touchcancel | touchCancel | 
| keydown | keyDown | 
| keyup | keyUp | 
| keypress | keyPress | 
| mousedown | mouseDown | 
| mouseup | mouseUp | 
| contextmenu | contextMenu | 
| click | click | 
| dblclick | doubleClick | 
| mousemove | mouseMove | 
| 
             事件名  | 
            
             方法名  | 
        
|---|---|
| focusin | focusIn | 
| focusout | focusOut | 
| mouseenter | mouseEnter | 
| mouseleave | mouseLeave | 
| submit | submit | 
| change | change | 
| dragstart | dragStart | 
| drag | drag | 
| dragenter | dragEnter | 
| dragleave | dragLeave | 
| dragover | dragOver | 
| drop | drop | 
| dragend | dragEnd | 
當(dāng)你創(chuàng)建一個(gè)新應(yīng)用時(shí),你可以向事件分配器添加額外的事件:
- App = Ember.Application.create({
 - customEvents: {
 - // 添加 loadedmetadata 媒體播放器事件
 - 'loadedmetadata': "loadedMetadata"
 - }
 - });
 
要使這能對(duì)自定義事件奏效,HTML5 規(guī)范必須定義事件為“bubbling”,否則 jQuery 必 須為這個(gè)事件提供一個(gè)事件委派折中方案。
模板化視圖
如同迄今你在本指導(dǎo)中所見,你在應(yīng)用中會(huì)用的大多數(shù)視圖是依靠模板的。當(dāng)使用模板 時(shí),你不需要編寫你的視圖層級(jí),因?yàn)槟0鍟?huì)為你創(chuàng)建它。
渲染時(shí),視圖模板可以把視圖附加到它的子視圖數(shù)組中。模板的 {{view}} 輔助標(biāo)記 內(nèi)部會(huì)調(diào)用視圖的 appendChild 方法。
調(diào)用 appendChild 會(huì)做兩件事:
- 把視圖添加到 
childViews數(shù)組。 - 立即渲染子視圖并把它添加到父視圖的渲染緩沖區(qū)。
 

appendChild 。模板渲染出“混合內(nèi)容”(包含 視圖和純文本),所以當(dāng)渲染過程完成后,父視圖不知道到底把新的子視圖插入到哪 里。
 在上例中,想象試圖把一個(gè)新視圖插入到父視圖的 childViews 數(shù)組中。它應(yīng)該立即 放在 App.MyView 的閉合標(biāo)簽 </div> 后?還是在整個(gè)視圖的閉合標(biāo)簽 </div> 后?這個(gè)答案不總是正確的。
因?yàn)檫@種含糊性,創(chuàng)建視圖層級(jí)的唯一方法就是用模板的 {{view}} 輔助標(biāo)記,它總 是把視圖插入到相對(duì)任何純文本的正確位置。
雖然這個(gè)機(jī)制對(duì)大多數(shù)情景奏效,偶爾你也會(huì)想要直接程序控制一個(gè)視圖的子視圖。在 這種情況下,你可以用 Ember.ContainerView ,它顯式地暴露了實(shí)現(xiàn)此目的的 API。
#p#
容器視圖
容器視圖不包含純文本。它們完全由子視圖(可能依靠模板)構(gòu)成。
ContainerView 暴露兩個(gè)用于修改本身內(nèi)容的公共 API:
- 一個(gè)可寫的 
childViews數(shù)組,你可以把Ember.View實(shí)例插入到其中。 - 一個(gè) 
currentView屬性,設(shè)置時(shí)會(huì)把新值插入到子視圖數(shù)組。如果存在早先的currentView值,它會(huì)被從childViews數(shù)組刪除。 
這里是一個(gè)用 childViews API 創(chuàng)建新視圖的例子,由假想的 DescriptionView 開始,并可以在任何時(shí)候用 addButton 方法添加一個(gè)新按鈕:
- App.ToolbarView = Ember.ContainerView.create({
 - init: function() {
 - var childViews = this.get('childViews');
 - var descriptionView = App.DescriptionView.create();
 - childViews.pushObject(descriptionView);
 - this.addButton();
 - return this._super();
 - },
 - addButton: function() {
 - var childViews = this.get('childViews');
 - var button = Ember.ButtonView.create();
 - childViews.pushObject(button);
 - }
 - });
 
如你在上例中所見,我們以兩個(gè)視圖初始化 ContainerView ,并且可以在運(yùn)行時(shí)添 加額外的視圖。存在一個(gè)方便的捷徑來設(shè)置視圖,而不用覆蓋 init 方法:
- App.ToolbarView = Ember.ContainerView.create({
 - childViews: ['descriptionView', 'buttonView'],
 - descriptionView: App.DescriptionView,
 - buttonView: Ember.ButtonView,
 - addButton: function() {
 - var childViews = this.get('childViews');
 - var button = Ember.ButtonView.create();
 - childViews.pushObject(button);
 - }
 - });
 
如上,當(dāng)用這個(gè)速記方法時(shí),你把 childViews 指定為一個(gè)字符串?dāng)?shù)組。在初始化 時(shí),每個(gè)字符串會(huì)作為在查找視圖實(shí)例或類的關(guān)鍵字。那個(gè)視圖會(huì)被自動(dòng)實(shí)例化,如果 必要,會(huì)加入到 childViews 數(shù)組中。  
- {{#if controller.isAuthenticated}}
 - <h1>歡迎 {{controller.name}}</h1>
 - {{/if}}
 - {{#with controller.user}}
 - <p>你有 {{notificationCount}} 條通知。</p>
 - {{/with}}
 
在上面的模板中,當(dāng) isAuthenticated 屬性從 false 變?yōu)?true 時(shí),Ember 會(huì) 重新渲染這個(gè)塊,用原始的外部作用域作為它的上下文。
{{#with}} 輔助標(biāo)記把它的塊的上下文修改為當(dāng)前控制器的 user 屬性。當(dāng) user 屬性被修改。Ember 重新渲染塊,并用 controller.user 的新值作為它的上 下文。
#p#
視圖作用域
除了 Handlebars 上下文,Ember 中的模板也有當(dāng)前視圖的概念。無論當(dāng)前上下文是什 么, view 屬性總是引用到最近的視圖。
注意 view 屬性不會(huì)引用由 {{#if}} 之類的塊表達(dá)式創(chuàng)建的內(nèi)部視圖。這允許你 區(qū)分 Handlebars 上下文,在 Handlebars 中和在視圖層級(jí)中的工作方式是一樣的。
因?yàn)?view 指向一個(gè) Ember.View 實(shí)例,你可以用 view.propertyName 之類的 表達(dá)式訪問視圖上的任何屬性。你可以用 view.parentView 訪問視圖的父視圖。
例如,想象你有一個(gè)帶有如下屬性的視圖:
- App.MenuItemView = Ember.View.create({
 - templateName: 'menu_item_view',
 - bulletText: '*'
 - });
 
……和下面的模板:
- {{#with controller}}
 - {{view.bulletText}} {{name}}
 - {{/with}}
 
盡管 Handlebars 上下文已經(jīng)變?yōu)楫?dāng)前的控制器,你仍然可以用 view.bulletText 訪問視圖的 bulletText 。
模板變量
迄今為止,我們已經(jīng)在 Handlebars 模板中邂逅了 controller 屬性。它是從哪來的呢?
Ember 中的 Handlebars 上下文可以繼承它們的父上下文中的變量。在 Ember 在當(dāng)前 上下文中查找變量之前,它首先檢查它的模板變量。當(dāng)一個(gè)視圖創(chuàng)建了一個(gè)新的 Handlebars 作用域,它們自動(dòng)繼承它們父作用域的變量。
Ember 定義了這些 view 和 controller 變量,所以當(dāng)一個(gè)表達(dá)式使用 view 或 controller 變量名,它們總是最先被找到。
如上所述,Ember 設(shè)置了 Handlebars 上下文中的 view 變量,無論何時(shí)模板中使用 了 {{#view}} 輔助標(biāo)記。起初,Ember 把 view 變量設(shè)置為正在渲染模板的視 圖。
Ember 設(shè)置了 Handlebars 上下文中的 controller 變量,無論已渲染的視圖是否存 在 controller 屬性。如果視圖沒有 controller 屬性,它從時(shí)間上最近的擁有該 屬性的視圖上繼承 controller 變量。
其它變量
Ember 中的 Handlebars 輔助標(biāo)記也會(huì)指定變量。例如, {{#with controller.person as tom}} 形式指定一個(gè) tom 變量,它的后代作用域 是可訪問的。即使一個(gè)子上下文有 tom 屬性,這個(gè) tom 變量會(huì)廢除它。
這個(gè)形式的最大好處是,它允許你簡寫長路徑,而不喪失對(duì)父作用域的訪問權(quán)限。
在 {{#each}} 輔助標(biāo)記中,提供 {{#each person in people}} 形式尤其重要。 在這個(gè)形式中,后代上下文可以訪問 person 變量,但在模板調(diào)用 each 的地方 保留相同的作用域。
- {{#with controller.preferences}}
 - <h1>Title</h1>
 - <ul>
 - {{#each person in controller.people}}
 - {{! prefix here is controller.preferences.prefix }}
 - <li>{{prefix}}: {{person.fullName}}</li>
 - {{/each}}
 - <ul>
 - {{/with}}
 
注意這些變量繼承了 ContainerView 中的那些,即使它們不是 Handlebars 上下文 層級(jí)中的一部分。
從視圖中訪問模板變量
在大多數(shù)情況下,你會(huì)需要從模板中訪問這些模板變量。在一些不尋常的情景下,你會(huì) 想要在視圖的 JavaScript 代碼中訪問范圍內(nèi)的變量。
你可以訪問視圖的 templateVariables 屬性來達(dá)成此目的,它會(huì)返回一個(gè)包含當(dāng)視 圖渲染后存在于其作用于的變量的 JavaScript 對(duì)象。 ContainerView 也可以訪問 這個(gè)屬性,它指向時(shí)間上最近的模板依賴的視圖的模板變量。
目前,你不能觀察或綁定一個(gè)包含 templateVariables 的路徑。
原文鏈接:http://emberjs.torriacg.org/guides/view_layer/#toc_















 
 
 









 
 
 
 