Dijit 組件(widget)是 Dojo 提供的圖形用戶界面組件庫。它提供了 Ajax 應用開發(fā)中會用到的常用組件,可以幫助開發(fā)人員快速的構建 Ajax 應用。本文并不會介紹 Dojo 默認提供的組件,而是側重于介紹 Dijit 組件的編程模型和最佳實踐,其目的是幫助開發(fā)人員更好的開發(fā)自己的 Dijit 組件。下面首先對 Dijit 做概要介紹。
Dijit 概述
Dijit 組件的存在是 Dojo 框架區(qū)別于其它 JavaScript 框架的一個重要特性。在桌面應用開發(fā)中,開發(fā)人員大量使用圖形用戶界面組件庫來提高開發(fā)效率。而在 Web 應用開發(fā)中,HTML 語言本身僅提供了少數(shù)基本的控件,如按鈕、單選框、復選框、文本輸入框和下拉列表等。而對于在 Web 應用開發(fā)中常見的一些復雜組件,如對話框、菜單、工具欄、進度條、富文本編輯器和樹等,并沒有提供原生的支持。在這種情況下,開發(fā)人員往往需要自己開發(fā)這樣的復雜組件,這就造成了更長的開發(fā)周期和更高的開發(fā)和維護的成本。
Dojo 提供了一個種類多樣的組件庫。開發(fā)人員只需要簡單的定制就可以在自己的應用中使用這些組件。除此之外,Dojo 還提供了完善的組件編程模型。如果默認提供的組件都不能滿足需求,可以自己來開發(fā)所需的組件。遵循這個統(tǒng)一的編程模型,會比從頭開始創(chuàng)建組件要容易得多。有了組件的概念之后,開發(fā)人員在設計 Web 應用的時候,就可以從比較高的抽象層次來對應用的各個部分進行劃分。定義好清晰的組件接口之后,團隊成員就可以各司其職,并行開發(fā),從而提高開發(fā)效率。
在開發(fā) Dijit 組件的時候,需要注意下面幾個基本的問題。
- 組件的粒度問題。一般來說,功能比較復雜的組件不利于復用,也不利于團隊開發(fā)時的分工合作。但是過多小組件在頁面上的時候,會消耗比較多的系統(tǒng)資源,影響性能。而性能對 Web 應用來說是一個非常重要的因素。因此需要進行一定的權衡。比較好的做法是從較大的組件開始,當發(fā)現(xiàn)存在代碼重復的時候,再把重復的代碼提取出來,重構成新的組件。這樣就把劃分成小組件的決策推遲到了真正需要的時候,避免過度設計。
- 組件的接口問題。組件的接口定義了代碼中的其它部分如何使用該組件。一般來說,組件可以提供三類的接口:公共屬性、公共方法和事件綁定點。公共屬性指的是組件提供的可以公開訪問的簡單數(shù)據(jù)類型屬性。一般在創(chuàng)建組件的時候使用,用來對組件進行定制;公共方法指的是可以公開訪問的 JavaScript 方法。一般在組件創(chuàng)建完成之后使用,用來改變組件的行為;事件綁定點是組件暴露出來的占位方法。一般由組件使用者通過
dojo.connect() 來綁定到該方法上。組件使用該方法來通知使用者其內部狀態(tài)的變化。這類方法一般以 on 作為名稱前綴。開發(fā)人員應該根據(jù)需要定義合適的接口,避免一些不好的實踐。比如公共屬性的值在組件創(chuàng)建之后,一般不推薦使用者設置其值。如果設置該屬性的值是一個合理的場景的話,最好提供相應的公共方法,并以文檔的形式告訴使用者正確的用法。
- 組件之間的交互問題。組件之間如果需要相互通訊的話,最好使用組件的對象引用來完成。比如某個組件在創(chuàng)建另外一個組件的時候,可以把自己的對象引用作為參數(shù)傳遞給其創(chuàng)建出來的組件。后者就可以使用此對象引用來調用前者的方法。另外一種做法是通過
dojo.publish() 和 dojo.subscribe() 方法來完成。這種做法的使用比較簡單,可以避免層次較深的對象引用傳遞。不好的地方是組件之間的關聯(lián)關系不夠清晰,也比較難維護。推薦的做法是優(yōu)先使用第一種方式。
上面對開發(fā) Dijit 組件的一些通用問題進行了討論。下面開始介紹 Dijit 組件的編程模型。在編程模型的介紹過程中,會穿插介紹相關的最佳實踐。首先從 Dijit 組件的核心類 dijit._Widget 開始。
回頁首
dijit._Widget
dijit._Widget 是所有 Dijit 組件的父類。Dijit 默認提供的組件以及自己開發(fā)的組件都需要繼承自此類。dijit._Widget 所提供的方法涉及組件的生命周期、屬性設置和獲取、事件處理和其它輔助功能等。深入了解該類的這些方法的用法和實現(xiàn)細節(jié),是開發(fā)自己的 Dijit 組件的基礎。下面分別對 dijit._Widget 提供的方法進行分類討論。
組件生命周期
dijit._Widget 提供了對組件生命周期的完整管理,包括組件的創(chuàng)建和銷毀。Dijit 組件的生命周期管理在實現(xiàn)的時候,使用了模板方法(Template Method)設計模式。dijit._Widget 類的 create() 方法定義了 Dijit 組件創(chuàng)建時的生命周期的默認模板。該方法會在合適的時機調用模板中包含的其它方法。這些不同的方法構成了組件生命周期中的各個階段。開發(fā)自己的組件的時候,可以覆寫其中的某些方法,從而在感興趣的階段添加自己的處理邏輯。開發(fā)人員也可以覆寫 create() 方法來提供一套完全不同的生命周期實現(xiàn)。不過這種做法風險太大,不建議使用。絕大多數(shù)情況下,覆寫默認模板提供的方法就足夠了。dijit._Widget 中定義的組件生命周期中的創(chuàng)建階段如 圖 1 所示。
圖 1. Dijit 組件創(chuàng)建過程
如 圖 1 所示,創(chuàng)建 Dijit 組件時的過程中包含如下幾個步驟:
- 以聲明式或是編程式的方式創(chuàng)建 Dijit 組件。
- 創(chuàng)建時傳入的參數(shù)被混入(mixin)到當前 Dijit 組件對象中?;烊胪瓿芍?,
postMixInProperties() 方法被調用。
- 如果沒有為此組件對象提供 ID 的話,則自動生成一個惟一的 ID。把此對象添加到全局的組件注冊表中。完成之后,
buildRendering() 方法被調用。
- 設置 Dijit 組件的屬性。完成之后,
postCreate() 方法被調用。
- 當組件被添加到頁面上之后,顯式調用
startup() 方法。
- 組件創(chuàng)建完成之后,開始正常工作。
圖 1 中給出了 4 個方法的名稱,其中橢圓形中的 postMixInProperties() 、buildRendering() 和 postCreate() 是 dijit._Widget 提供的組件生命周期中的擴展點。自己開發(fā)的組件應該通過覆寫這些方法來實現(xiàn)自己的邏輯。圓角矩形中的 startup() 方法并不是創(chuàng)建過程中的一部分,需要在 Dijit 組件創(chuàng)建完成之后顯式的調用。下面對這 4 個方法進行詳細的說明。
postMixInProperties() :在創(chuàng)建 Dijit 組件的時候,可以通過參數(shù)來傳入一個包含各種屬性的 JavaScript 對象。這些屬性被混入到 Dijit 組件中,在代碼中可以通過 this 來引用。當混入完成之后,在創(chuàng)建組件的界面之前,可能還需要執(zhí)行一些處理。這些處理的邏輯可以添加在 postMixInProperties() 方法中。
buildRendering() :該方法用來創(chuàng)建 Dijit 組件的用戶界面,即 DOM 節(jié)點。該方法完成之后,Dijit 組件的 this.domNode 指向的是創(chuàng)建完成的 DOM 節(jié)點。
postCreate() :當 Dijit 組件的 DOM 節(jié)點創(chuàng)建完成之后,此方法被調用。需要注意的是,這個時候組件的 DOM 節(jié)點可能還沒有被添加到當前頁面文檔樹中。
startup() :當 Dijit 組件及其子組件被創(chuàng)建完成并添加到當前頁面文檔樹中之后,顯式的調用此方法。該方法對于那些包含子組件的 Dijit 組件來說非常有用,可以用來控制子組件和進行布局。startup() 方法只需要調用一次即可。調用完成之后,組件的屬性 _started 的值為 true ,可以用來判斷 startup() 方法是否已經(jīng)被調用過。
與創(chuàng)建 Dijit 組件對應的組件的銷毀過程。銷毀過程比較復雜,涉及到 5 個方法,如 圖 2 所示。圖 2 中的箭頭表示的是方法之間的調用關系。
圖 2. Dijit 組件銷毀過程
如 圖 2 所示,最常用的銷毀一個 Dijit 組件的方法是 destroyRecursive() 。該方法用來銷毀一個 Dijit 組件及其包含的子組件。該方法會首先調用 destroyDescendants() 方法來銷毀子組件。由于子組件也可能包含自己的子組件,destroyDescendants() 也會調用 destroyRecursive() 方法來刪除其子組件,從而形成一個遞歸的銷毀過程。當子組件銷毀完成之后,destroyRecursive() 會調用 destroy() 方法來銷毀自己。destroy() 方法會首先調用 uninitialize() ,接著執(zhí)行內部的清理工作,最后調用 destroyRendering() 方法來銷毀組件的 DOM 節(jié)點。需要注意的是,destroy() 方法會銷毀在 Dijit 組件模板中定義的子 Dijit 組件。這 5 個方法中除了 uninitialize() 之外的其它 4 個方法,都有一個參數(shù) preserveDom 用來表明是否保留 Dijit 組件的 DOM 節(jié)點,不過對從模板創(chuàng)建出的 Dijit 組件無效。
在深入理解了 Dijit 組件的生命周期之后,就可以更好的利用 Dijit 庫提供的支持來開發(fā)自己 Dijit 組件。下面介紹一些與組件生命周期相關的最佳實踐。
- 靈活覆寫 Dijit 組件生命周期相關的方法來添加自己的處理邏輯。一般來說,在
postCreate() 方法中添加組件相關的處理邏輯即可。如果需要添加與 DOM 節(jié)點大小和位置相關的邏輯,應該放在 startup() 方法中,并要求組件的使用者顯式調用。在覆寫方法的時候,要通過 this.inherited(arguments); 來調用 dijit._Widget 類的原始方法。
- 如果自己的 Dijit 組件在銷毀的時候需要執(zhí)行額外的處理,應該把相關的邏輯添加在
uninitialize() 方法中,并且只包含當前組件相關的邏輯,不需要考慮子組件。destroyRecursive() 方法會負責處理子組件的銷毀。
- 總是使用
destroyRecursive() 來銷毀一個 Dijit 組件。這樣可以確保子組件總是被正常銷毀,避免內存泄露。
- 盡量不要覆寫
destroyRecursive() 、destroyDescendants() 、destroy() 和 destroyRendering() 等 4 個方法。這 4 個方法封裝了完整的 Dijit 組件銷毀邏輯。一般來說,覆寫 uninitialize() 方法就已經(jīng)足夠了。
屬性獲取與設置
Dijit 組件中可能包含各種不同的屬性,允許使用者對這些屬性進行獲取和設置。下面以一個顯示電子郵件地址的 Dijit 組件來進行說明。該組件應該允許使用者獲取和設置顯示的電子郵件地址。一般來說,需要提供 getEmail() 和 setEmail(email) 兩個方法來實現(xiàn)。另外,為了防止暴露郵件地址,一般需要用特殊的字符替換掉電子郵件地址中的“@”符號,如 admin@example.org 被替換成 admin#example.org 。這樣的話就需要提供另外的兩個方法。為了簡化屬性的獲取和設置操作,dijit._Widget 類中提供了 attr(name, value) 方法來統(tǒng)一完成屬性的獲取和設置。當只傳入一個參數(shù)的時候,如果該參數(shù)是一個字符串,則表示獲取屬性的值;如果參數(shù)是一個 JavaScript 對象,則用該對象中的屬性和值來設置組件的屬性。當傳入兩個參數(shù)的時候,兩個參數(shù)分別表示為屬性的名稱和要設置的值。對于示例 Dijit 組件來說,可以通過 attr("email", "alex@example.com") 來設置要顯示的電子郵件地址。
對于 Dijit 組件自定義的屬性,默認情況下是保持在組件對象實例中的。attr() 方法直接讀取和設置組件對象中對應屬性的值即可。除此之外,還可以通過 attributeMap 來提供從屬性到 DOM 節(jié)點之間的映射。即通過改變屬性的值,就可以修改 DOM 節(jié)點。如果希望在獲取和設置屬性的時候添加額外的處理邏輯,則需要提供滿足命名規(guī)范的對應方法。該命名規(guī)范是在首字母大寫的屬性名稱上添加特定的前綴和后綴。如對于屬性 email 來說,自定義的獲取和設置屬性的方法分別是 _getEmailAttr() 和 _setEmailAttr() 。自定義方法的優(yōu)先級高于默認的方法。attr() 會首先嘗試在 Dijit 組件對象中查找是否存在自定義的方法。代碼清單 1 中給出了一個獲取和設置屬性的示例。
清單 1. 屬性獲取和設置示例
dojo.declare("emailDisplayer", dijit._Widget, {
replaceString : "",
_setEmailAttr : function(value) {
if (!value) {
return;
}
var originalValue = value;
var displayValue = value;
if (this.replaceString) {
displayValue = displayValue.replace(/@/, this.replaceString);
}
this.domNode.innerHTML = displayValue;
this.email = originalValue;
}
});
var n = dojo.create("div", null, dojo.body());
var displayer = new emailDisplayer({}, n);
displayer.attr("replaceChar", "#");
displayer.attr("email", "alex@example.org");
displayer.attr("email");
|
如 代碼清單 1 所示,Dijit 組件 emailDisplayer 定義了兩個屬性 email 和 replaceString 。對于屬性 email 提供了自定義的設置方法 _setEmailAttr() 。對于屬性 replaceString 則使用的是默認的實現(xiàn)。
對于統(tǒng)一的 attr() 方法接口和自定義的屬性獲取和設置方法,Dijit 組件既可以方便使用者的使用,又給了開發(fā)人員足夠的靈活性。在組件開發(fā)中,盡量避免提供 Dijit 組件自己的屬性獲取和設置方法,而是充分利用 Dijit 組件提供的支持。
其它方法
除了上面提到的兩類方法之外,dijit._Widget 還提供了其它一些方法。
- 盡量使用
connect() 方法來綁定事件處理方法。其好處是在 Dijit 組件被銷毀的時候,會自動調用 disconnect() 來取消事件綁定。
- 盡量使用
subscribe() 方法來監(jiān)聽事件通知。其好處是在 Dijit 組件被銷毀的時候,會自動調用 unsubscribe() 來取消事件監(jiān)聽。
在介紹完 dijit._Widget 之后,下面介紹另外一個核心類 dijit._Templated 。
回頁首
dijit._Templated
在介紹 Dijit 組件的生命周期的時候,提到過 dijit._Widget 類中的 buildRendering() 方法用來創(chuàng)建 Dijit 組件的用戶界面。如果通過 DOM 操作來創(chuàng)建用戶界面的話,一般來說會比較復雜,維護起來也比較麻煩。dijit._Templated 提供了一種從 HTML 模板中創(chuàng)建 Dijit 組件用戶界面的方式。dijit._Templated 一般是作為一個混入類的方式來使用的。它提供了自己的 buildRendering() 用來從模板字符串或是文件中創(chuàng)建 DOM 節(jié)點。在使用 dijit._Templated 的時候,有下面幾點需要注意。
- 通過
dojo.declare() 方法定義新的組件的時候,dijit._Widget 需要作為基類,而 dijit._Templated 只能作為混入類。也就是說在父類聲明中,dijit._Widget 需要作為第一個出現(xiàn);否則的話會出現(xiàn)錯誤。
- 可以通過
templateString 和 templatePath 兩種方式來指定所使用的模板。從 Dojo 1.4 開始,建議使用 templateString 來指定模板字符串。不過在 Dijit 組件開發(fā)過程中,把模板存放在單獨的 HTML 文件中更加便于開發(fā)和調試。因此在開發(fā)過程中可以使用 templatePath 。在發(fā)布的時候,則需要通過 Dojo 的構建過程把 HTML 文件的內容內聯(lián)到組件代碼中,通過 templateString 來表示。
- 通過設置屬性
widgetsInTemplate 的值為 true 可以聲明該 Dijit 組件中包含其它的組件。這些包含的組件在 destroy() 方法中會被銷毀。
在模板中可以使用 dojoAttachPoint 和 dojoAttachEvent 兩個特殊的 DOM 節(jié)點的屬性。dojoAttachPoint 屬性的值被轉換成組件對象中的一個屬性的名稱,該屬性的值是當前的 DOM 節(jié)點。dojoAttachEvent 屬性的值被轉換成通過 dojo.connect() 完成的事件處理綁定。如 <div dojoAttachPoint="myDiv" dojoAttachEvent="onclick:show" /> 聲明了該 DOM 節(jié)點可以在組件對象中通過 myDiv 來引用,點擊該節(jié)點會調用 show() 方法。這兩個屬性的存在帶來了編程上的簡便。在組件對象的方法中,如果需要引用某個 DOM 節(jié)點的話,一般需要通過 DOM 查詢或是 dojo.query() 來完成,這樣的話會比較繁瑣。通過 dojoAttachPoint 就避免了查詢操作,使用起來更加簡單。如果需要綁定事件處理的話,使用 dojoAttachEvent 就免去了對 dojo.connect() 方法的顯式調用。不過使用這兩個屬性的話,會造成變量的聲明和使用在不同的地方,會在一定程度上影響代碼的可讀性。比較好的實踐如下:
- 為了方便區(qū)分組件對象中通過
dojoAttachPoint 聲明的屬性和一般的屬性,最好為 dojoAttachPoint 聲明的屬性名稱添加統(tǒng)一的后綴,如 Node 或是 Container 。其好處是開發(fā)人員在發(fā)現(xiàn)帶某個后綴的屬性時,會明白要去模板中查找相關的聲明。
- 在引用 DOM 節(jié)點和綁定事件處理方法的時候,盡量使用統(tǒng)一的方式??梢越y(tǒng)一使用
dojoAttachPoint 和 dojoAttachEvent ,也可以統(tǒng)一使用 DOM 查詢和 dojo.connect() 。最好不要兩種方式混用。項目開發(fā)團隊應該根據(jù)團隊的意見,制定出相關的代碼編寫規(guī)范。
在介紹完 dijit._Templated 之后,下面介紹另外一個核心類 dijit._Container 。
回頁首
dijit._Container
有些 Dijit 組件是作為其它組件的容器而存在的,如與頁面布局相關的組件。作為容器的組件需要對其包含的子組件進行管理,包括查詢、添加和刪除子組件等。dijit._Container 混入類提供了這些管理子組件的功能,自己開發(fā)的組件可以直接混入此類。dijit._Container 所提供的方法如下所示:
addChild(widget, insertIndex) :該方法用來添加一個新的子組件到給定位置上。參數(shù) widget 表示的是子組件,insertIndex 表示的是添加的位置。
removeChild(widget) :該方法用來移除一個子組件。參數(shù) widget 既可以是子組件的引用,也可以是子組件的序號。
getChildren() :該方法返回一個包含所有子組件的數(shù)組。
hasChildren() :該方法用來判斷是否包含子組件。
getIndexOfChild(widget) :該方法用來返回一個子組件的序號。
通過上面提到的這些方法,就可以完成對子組件的管理。在使用 dijit._Container 的時候,有下面幾點需要注意:
dijit._Container 中只能包含 Dijit 組件,也就是必須繼承自 dijit._Widget 。不能包含普通的 DOM 節(jié)點。對于 DOM 節(jié)點,可以用一個 dijit.layout.ContentPane 封裝之后,再添加到 dijit._Container 中。
dijit._Container 的子組件的 DOM 節(jié)點都是屬性 containerNode 所表示的 DOM 節(jié)點的子節(jié)點。而 containerNode 的值與 domNode 不一定相同。只有 containerNode 中包含的子組件才會在 destroyDescendants() 方法中被銷毀。一般來說,在 Dijit 組件的 domNode 下指定一個 DOM 節(jié)點作為 containerNode 。
- 由于
dijit._Container 負責管理其中包含的子組件,其 startup() 方法會負責調用子組件的 startup() 方法。對于通過 addChild() 方法動態(tài)添加的子組件,如果子組件的 startup() 方法沒有被調用過,則會調用此 startup() 方法。
removeChild() 只是將子組件移除,使其不再受 dijit._Container 的管理,并不會銷毀該子組件。
在介紹完 dijit._Container 之后,下面介紹如何對 Dijit 組件進行管理。
回頁首
Dijit 組件管理
Dojo 會負責維護當前頁面上所有 Dijit 組件的一個注冊表,里面包含了所有的 Dijit 組件對象。該注冊表可以通過 dijit.registry 來訪問,它是一個 dijit.WidgetSet 類的實例。dijit.WidgetSet 實際上是一個 Dijit 組件的 ID 及其對象對應的查找表。通過組件的 ID 就可以查詢到組件對象。dijit.WidgetSet 所包含的方法用來對此查找表進行操作。這些方法包括:
add(widget) 用來添加新組件 widget ;remove(id) 用來根據(jù) ID 移除組件。
byId(id) 用來通過 ID 查找組件;byClass(cls) 用來根據(jù)類名來查找組件,如 dijit.registry.byClass("dijit.form.Button") 用來查找頁面上所有的 dijit.form.Button 組件。該方法的返回值是一個新的 dijit.WidgetSet 對象。
toArray() 返回一個包含所有組件對象的數(shù)組。
forEach() 、filter() 、map() 、every() 和 some() :這些方法的含義與用法與 Dojo 基本庫中處理數(shù)組的同名方法的含義與用法是相同的,都是用來對所包含的組件對象進行處理。
除了全局的組件注冊表 dijit.registry 之外,Dijit 庫還提供了其它的方法。這些方法包括:
dijit.byId(id) 用來根據(jù) ID 在頁面上查找組件。
dijit.getUniqueId(widgetType) 用來為指定組件類別 widgetType 中的新組件生成惟一的 ID。
dijit.findWidgets(root) 用來查找指定 DOM 節(jié)點 root 中所包含的 Dijit 組件。但是不包括嵌套的 Dijit 組件。
dijit.getEnclosingWidget(node) 用來查找包含 DOM 節(jié)點 node 的 Dijit 組件。
在管理 Dijit 組件的時候,有下面幾個問題需要注意:
- 當需要管理一些 Dijit 組件的時候,可以創(chuàng)建自己的
dijit.WidgetSet 對象。這樣就可以利用 dijit.WidgetSet 所提供的管理功能。
dijit.registry 是通過組件的 ID 來進行查找的,因此要求 ID 是全局惟一的。對于系統(tǒng)生成的 ID,是可以保證其惟一性的。不過開發(fā)人員也可以為組件提供自己的 ID,這個時候就需要格外注意 ID 的惟一性。如果試圖創(chuàng)建一個 ID 為 myId 的組件,但是頁面上已經(jīng)存在 ID 相同的組件,Dojo 會拋出一個異常 "Tried to register widget with id==myId but that id is already registered" 。造成這種情況的原因比較多:第一種可能是由于編程失誤造成了 ID 重復,這種情況下只需要修改重復的 ID 即可;另外的可能是準備復用某個 ID,但是前一個 Dijit 組件的銷毀不徹底,并沒有從全局組件注冊表 dijit.registry 中移除掉自己。當再次使用此 ID 創(chuàng)建組件的時候就出現(xiàn)了錯誤。在 dijit._Widget 類的 destroy() 方法中包含了從 dijit.registry 中刪除當前組件的實現(xiàn)。如果自己開發(fā)的組件覆寫了 destroy() 方法,而沒有通過 this.herited(arguments) 來調用父類的邏輯的話,就很容易出現(xiàn)這樣的錯誤。
在介紹完 Dijit 組件管理之后,下面介紹實例化 Dijit 組件的兩種方式。
回頁首
實例化 Dijit 組件
一般來說,實例化 Dijit 組件有兩種方式:聲明式和編程式。聲明式的方式指的是在 HTML 代碼中以描述的方式來定義 Dijit 組件,由 Dojo 在運行時刻把這些聲明的組件實例化。編程式的方式是開發(fā)人員在代碼中顯式的通過 new 操作符來實例化某個 Dijit 組件。實例化一個 Dijit 組件需要兩個參數(shù),第一個是混入到組件實例中的包含配置屬性的 JavaScript 對象,第二個則是組件所使用的 DOM 節(jié)點。這兩種方式的不同之處在于如何提供這兩個參數(shù)的值。下面首先介紹聲明式的方式。
聲明式
使用聲明式的時候,需要在 DOM 節(jié)點上添加屬性 dojoType ,其值是 Dijit 組件類的全名。這樣就聲明了在運行時刻會從此 DOM 節(jié)點上創(chuàng)建一個新的 Dijit 組件。如 <div dojoType="dijit.form.Button" /> 聲明了會從此 div 元素上創(chuàng)建一個 dijit.form.Button 組件。而對于混入到組件實例中的屬性,則是通過此 DOM 節(jié)點上的屬性值來聲明的。由于在 HTML 代碼中聲明屬性的時候只能使用字符串,而 Dijit 組件中的屬性是有數(shù)據(jù)類型的,因此需要一個轉換的過程。了解此過程有助解決一些屬性無法設置的問題。這個轉換過程具體如下:
- 對于通過
dojoType 聲明的組件類,遍歷該類的 prototype 對象中的屬性,去掉以“_”開頭的和 Object.prototype 中包含的屬性,其余的就是可以在聲明 Dijit 組件的時候使用的屬性。這些屬性的值的數(shù)據(jù)類型也會被記錄下來。
- 接著對于上一步中得到的每個屬性,查看 DOM 節(jié)點是否包含有同名屬性。如果有的話,則得到此屬性的值,并根據(jù)數(shù)據(jù)類型進行轉換。
- 完成類型轉換之后的值被作為最終的結果。代碼清單 2 中給出了一個示例。
清單 2. 聲明式創(chuàng)建 Dijit 組件時屬性類型轉換示例
dojo.declare("myWidget", dijit._Widget, {
count : 1,
valid : false,
names : ["Alex", "Bob"]
});
<div dojoType="myWidget" count="20" names="John, Jason" />
|
在 代碼清單 2 給出的例子中,myWidget 中定義了 3 個屬性:count 、valid 和 names ,其數(shù)據(jù)類型分別是數(shù)字、布爾型和數(shù)組。在聲明 Dijit 組件的時候,在 DOM 節(jié)點上添加了屬性 count 和 names 。屬性 count 的值被轉換成數(shù)字,而屬性 names 的值被轉換成數(shù)組。
除了通過 DOM 節(jié)點的屬性來設置 Dijit 組件的屬性之外,還可以通過 <script> 子元素來聲明方法。<script> 元素的內容就是方法體本身。支持的聲明方式有三種:
<script type="dojo/method" event="myHandler"> :聲明的 JavaScript 方法與其它簡單屬性一樣,被混入到組件對象中。屬性名稱是 event 的值。
<script type="dojo/method" > :聲明的 JavaScript 方法在組件實例化之后會被立即執(zhí)行。
<script type="dojo/connect" event="onClick" > :聲明的 JavaScript 方法在組件實例化之后會通過 dojo.connect() 綁定到 event 所指明的事件上。
對于第一種方式,也是可以通過 DOM 節(jié)點上的屬性來進行聲明的。如 <div dojoType="myWidget" myHandler="alert('Hello World!');" /> 。不過 <script> 元素提供了更多的靈活性,比如可以通過屬性 args 來聲明參數(shù),屬性 with 來使用 JavaScript 中的 with 表達式。在 <script> 元素內部編寫方法體也更加清晰,可以使用代碼縮進。代碼清單 3 給出了一個使用 <script type="dojo/method" > 的示例。
清單 3. <script type="dojo/method" > 使用示例
<div dojoType="emailDisplayer" jsId="myEmailDisplayer">
<script type="dojo/method" event="setEmailByName" args="name">
var email = name + "@example.org";
this.attr("email", email);
</script>
</div>
|
代碼清單 3 中使用了 代碼清單 1 中給出的顯示電子郵件地址的 Dijit 組件,并通過 <script type="dojo/method" > 定義了一個新的 JavaScript 方法 setEmailByName 。通過屬性 jsId 可以為組件設置一個全局的引用名稱。當組件實例化之后,就可以通過 myEmailDisplayer.setEmailByName("Alex") 來調用方法 setEmailByName() ,組件會顯示出 Alex@example.org 。
在聲明了 Dijit 組件之后,需要通過 dojo.parser.parse(rootNode, args) 方法來完成組件的實例化工作。該方法可以遍歷 rootNode 的子孫節(jié)點,根據(jù)是否包含屬性 dojoType 來判斷是否為 Dijit 組件。如果是的話,就實例化該組件。該方法會返回一個實例化出來的組件列表。在實例化一個 Dijit 組件的時候,默認的方式是通過 new 操作符來完成的。dojo.parser.parse() 方法也允許開發(fā)人員自定義組件實例化的行為。通過在組件類或是其 prototype 對象上定義 markupFactory 方法就可以完成。dojo.parser.parse() 方法會優(yōu)先檢查是否存在 markupFactory 方法。如果有的話,就把此方法的返回值作為實例化出來的組件對象。
編程式
與聲明式的方式相比,編程式的方式相對簡單。只需要通過 new 操作符,并傳入合適的參數(shù)即可。如 new dijit.form.Button({label : "Press Me!"}) 就創(chuàng)建了一個新的 dijit.form.Button 組件。
兩種方式的比較
聲明式和編程式兩種方式是密切相關的。只不過使用聲明式的情況下,是由 Dojo 庫在后臺以編程的方式來實現(xiàn) Dijit 組件的實例化的。兩種方式從使用上來說,聲明式方式比較易懂,開發(fā)人員不需要了解太多 JavaScript 語言的細節(jié),就可以用直觀的方式來定義出所需要創(chuàng)建的組件;編程式的方式則功能更加強大,給了開發(fā)人員更多的靈活性。如果創(chuàng)建組件時所需要的參數(shù)是在運行時刻動態(tài)計算出來的話,使用聲明式就無法實現(xiàn),只能以編程的方式來創(chuàng)建。
一般來說,對于簡單的和容器類的組件,最好使用聲明式的方式來創(chuàng)建。簡單的組件用聲明式的方式非常簡潔;而容器類的組件,即混入了 dijit._Container 的組件,如 dijit.layout.TabContainer 和 dijit.layout.BorderContainer ,使用聲明式的方式可以很直觀的定義容器的子組件,而使用編程式的方式則需要通過 addChild() 來逐個添加子組件,過程比較繁瑣。如果創(chuàng)建組件時可以允許的屬性比較復雜,或是屬性的值需要動態(tài)計算,則最好使用編程式的方式來創(chuàng)建。開發(fā)人員可以根據(jù)實際情況來選擇最適合自己的組件創(chuàng)建方式。
回頁首
總結
Dojo 默認提供的 Dijit 組件庫中包含的都是一些通用的組件。在 Ajax 應用開發(fā)中,經(jīng)常會需要根據(jù)業(yè)務邏輯的需要開發(fā)出自己的組件。在開發(fā)自己組件的時候,深入了解 Dijit 組件的編程模型和相關的最佳實踐是大有益處的。本文介紹了 Dijit 組件編程模型中的核心類 dijit._Widget 、dijit._Templated 和 dijit._Container ,以及管理和實例化 Dijit 組件相關的內容。在介紹的過程中,穿插了對最佳實踐的說明,可以幫助開發(fā)人員更好的開發(fā)自己的 Dijit 組件。
回頁首
聲明
本人所發(fā)表的內容僅為個人觀點,不代表 IBM 公司立場、戰(zhàn)略和觀點。
參考資料
學習
討論
|