文章目錄
對於組件化的軟體工程設計,很多開發人員都比較熟悉。組件化的設計適合於複雜的軟體系統和團隊協作開發。把軟體系統劃分成若干個組件,組件之間通過預先定義好的介面和協議進行通訊和協作,共同完成整個軟體系統的職責。團隊中的開發人員可以各自負責不同的組件。組件化的思想在案頭應用和Web應用後台開發中比較流行,相關的技術和實踐都比較成熟。而在Web應用的前端部分,組件化一直進展得比較緩慢。這其中的原因有很多,最主要的是Web應用的前端在開始的時候比較簡單,沒有組件化和設計的必要。隨著Ajax應用的流行,Web前端部分越發複雜,使用者對Web應用的要求不斷向案頭應用靠攏。HTML語言的基本介面元素不能單獨地滿足這樣的需求。菜單、樹形控制項、對話方塊和進度條等組件,在現在的Ajax應用中十分常見,但是並不是HTML預設提供的。HTML 5規範中引入了一些新的元素,但還是不夠。組件化對於Web應用本身的代碼共用和團隊分工也是很有意義的。
Web 應用程式前端組件化的發展也是漸進的。開始的時候,只是一些簡單的HTML、CSS加上JavaScript的程式碼範例。比如當需要實現一個多級菜單的時候,就下載相關的程式碼範例,就根據自己的需要進行修改。這樣的組件比較難以複用。後來JavaScript架構開始流行的時候,有些架構本身就提供了組件的支援,包括Ext JS、jQuery UI和Dojo等。不過不同架構提供的組件模型不盡相同。
Dijit組件模型概述
Web 應用程式的前端組件的定義比較寬泛。一個組件佔據Web頁面上的某個地區,並負責完成某項具體的任務。Web組件有時候也被稱為小組件(widget)。在 Dijit組件模型中,一個Dijit組件是一個JavaScript類,可以在頁面上通過new操作符來建立組件的執行個體。每個組件執行個體都需要與頁面上的某個DOM元素繫結在一起。這個DOM元素就是該組件的根節點。在Dijit組件的邏輯中,就可以對該根節點進行操縱來構建使用者介面。組件 JavaScript類暴露出來的屬性和方法就是該組件的介面。
Dijit組件的使用
Dijit 組件的使用方式非常簡單。首先需要在頁面上載入組件的JavaScript代碼,這通過dojo.require函數就可以完成。接著在頁面上找到或建立一個DOM元素作為該組件的根節點。最後通過new操作符建立即可。如new dijit.form.ComboBox({}, node)就可以用node作為根項目建立一個dijit.form.ComboBox組件,即一個下拉式清單選擇框。可以看到建立Dijit組件的時候,使用了兩個參數:第二個參數是組件的根項目,如果建立的時候不指定該根項目,會自動建立一個新的DIV元素作為根項目。不過該新建立的根項目一般沒有加入到當前的文檔樹中,可以通過組件的placeAt方法來設定該組件在頁面文檔樹中的位置。第一個元素則是一個JavaScript對象,包含了組件的配置屬性。通常來說,一個Dijit組件是可以複用的。因此一般都會提供一些屬性供使用者進行配置。通過這個參數,就可以修改這些配置。
上面提到的是程式式的方式建立Dijit組件,還有另外的一種方式來進行建立,即通過在HTML代碼中以聲明式的方式建立,如
對話方塊標題 對話方塊內容
聲明式的方式在一定程度上簡化了開發人員使用Dijit組件的方式。聲明式的方式與編寫HTML代碼的形式類似,只需要在一般的HTML元素上添加一些額外的屬性就可以把HTML片段轉換成Dijit組件。這對於只熟悉HTML語言的人來說非常方便,相當於在HTML語言的基本元素之上,增加了更多的可用組件。
Dijit深入分析Dijit組件基本類
所有的Dijit組件都繼承自dijit._Widget類。dijit._Widget類中定義了與組件相關的一系列方法。這些方法中有一些是與組件生命週期相關的,有一些則是所有組件都需要的通用方法。瞭解Dijit組件的生命週期有利於理解Dijit組件的運行方式,從而更好的使用已有的組件或開發出自己的組件。
建立Dijit組件的過程開始於dijit._Widget類中的create方法。create方法採用了典型的模板方法設計模式,即在該方法中封裝了建立組件的基本流程。該方法會執行一些重要的操作,並依次調用其它的方法來完成整個建立過程。具體的流程包括:
- 把建立時的配置參數混入(mix-in)到組件中。比如在建立組件的時候使用的方式是var myWidget = new TestWidget({prop : "Hello"}, node);,那麼在建立完成之後就可以通過myWidget.prop來擷取到"Hello",在組件中也可以通過this.prop來訪問。
- 調用生命週期方法:postMixInProperties。該方法在配置參數混入之後調用,可以對混入的參數進行修改。
- 把該新建立出來的組件添加到全域的組件對象註冊表中。Dijit組件都會被分配一個惟一的標識符。添加到註冊表中之後,就可以用dijit.byId來根據標識符擷取組件對象。
- 調用生命週期方法:buildRendering。該方法用來完成構建組件的使用者介面。該方法負責設定this.domNode的值,表示的是建立完成的組件的根項目。
- 調用生命週期方法:postCreate。該方法在使用者介面構建完成之後被調用。一般是組件內部行為邏輯的起點,類似HTML頁面中的onload方法。
對於Dijit組件開發人員來說,建立一個新的Dijit非常簡單。只需要用dojo.declare聲明一個JavaScript類並繼承自 dijit._Widget,在該類中覆寫感興趣的JavaScript方法即可。最簡單的情況是覆寫postCreate方法並添加組件的邏輯。
對於用來包含其它子組件的容器類組件來說,一般會覆寫startup方法來讓其調用者顯式的啟動這個組件。這是因為在postCreate被調用的時候,只是保證了組件的DOM節點已經被建立成功了,但是這些DOM節點可能並沒有被添加到當前文檔樹中,因此不能在postCreate中包含與DOM 節點大小和位置相關的代碼。如果要添加這樣的代碼,應該在startup中添加。很多容器類組件都使用該方法來對其子節點進行布局。
使用HTML模板
如果只是使用dijit._Widget的話,編寫Dijit組件會比較繁瑣。比如在構建使用者介面的時候,可能會需要很多的DOM操作,編寫起來也不方便。 Dijit提供了dijit._Templated用來使用HTML片段來定義組件的內容。HTML片段是作為組件的內容範本。如:
dojo.declare("TempWidget", [dijit._Widget, dijit._Templated], { templateString : "Hello"});
TempWidget繼承了兩個JavaScript類,除了必需的dijit._Widget之外,還有dijit._Templated的。需要保證 dijit._Widget是父類數組的第一個元素,只有它是真正意義上的父類,其餘的是混入類。dijit._Templated類已經覆寫了 buildRendering方法來從HTML模板中建立組件內容的DOM元素,並作為組件的this.domNode的值。在HTML模板中,除了可以使用基本的HTML元素和屬性之外,還有一些附加的實用功能:
dijit._Templated的模板機制的這些實用功能減少了構建使用者介面時的一些繁瑣代碼。
作為容器
如果組件是作為其它組件的容器來使用的話,就可以混入dijit._Container類。該類提供了對子組件的基本管理功能,包括查詢、添加和刪除等。使用該類的時候,需要在組件中聲明一個containerNode的屬性作為子組件的父節點。建立出Dijit組件之後,就可以通過 addChild方法來添加子組件了。
銷毀過程
組件在建立並運行之後,就可能需要被銷毀。銷毀一個Dijit組件很簡單,只需要調用destroyRecursive方法即可。該方法會負責銷毀當前Dijit組件及其包含子群組件。當一個組件被銷毀的時候,其uninitialize方法會被調用,類似於解構函式。因此可以把組件特有的銷毀邏輯添加在uninitialize方法中。
Dijit組件的介面與互動
前面提到,組件之間通過設計好的介面和協議進行通訊。對於Dijit組件來說,它所提供的介面一般有下面這幾類:
- 公開的屬性和方法。這些屬性和方法類似於Java類中的公開的域和方法,在擷取到組件對象之後可以直接使用。
- 通過dojo.connect進行串連。有些組件提供了一些預留位置方法用來允許其使用者監聽其內部狀態的變化,類似於DOM事件的處理。
當組件之間進行通訊和協作的時候,一般有下面幾種互動的模式:
- 傳遞組件對象的引用。這種做法一般是在建立新群組件的時候,將其需要引用的組件對象傳遞進去,如var anotherWidget = new MyWidget({parent : oneWidget}, node);。
- 不傳遞對象引用,而是進行尋找。這種情況適用於所依賴的組件的ID已知的情況。可以通過dijit.byId來直接進行尋找。
- 使用Dojo提供的全域通訊機制:dojo.publish和dojo.subscribe。一個組件通過dojo.publish來發布訊息,另外一個組件則通過dojo.subscribe來監聽相關的訊息並做出處理。
一般來說,比較推薦的做法是第一種,即通過傳遞組件對象的引用來完成。不過當組件之間的關係比較複雜的時候,有可能需要將一個對象的引用進行多次傳遞。這個時候也可以考慮後兩種做法。
Dijit開發最佳實務
編寫Dijit組件並不是一件複雜的事情,只需要按照一般的流程依次完成即可。不過Dijit組件本身的設計和實現比較複雜,包含了比較多的內容。下面對一些重點的地方進行討論。
編程式和聲明式的建立方式選擇
這兩種方式的區別只是在於開發人員的使用方式上。用聲明式方式聲明的Dijit組件,在運行時刻也是通過程式式的方式來進行建立的,由dojo.parser.parse方法來完成。因此,聲明式的方式更像一種文法糖衣。不過聲明式方式的一個好處是可以實現優雅地退化(graceful degradation),即當JavaScript不被支援的時候,仍然可以在頁面上顯示出部分內容。對於簡單的和容器類的組件來說,聲明式建立的方式比較好。簡單的組件用聲明式的方式比較簡潔。而容器類的組件在建立的時候一般都需要指定所包含的內容。使用聲明式的時候,組件的子節點會自動作為容器類組件的子組件來添加。而如果以程式式的方式來完成的話,需要手工建立子組件,並通過addChild方法來逐個添加。代碼會比較繁瑣。
組件狀態變遷與外觀樣式
在開發Dijit組件中,經常會遇到的一個情境是根據組件內部的狀態變化改變其外觀樣式。比如對於一個選項按鈕組件來說,選中和未被選中的外觀樣式是不同的。典型的做法是通過切換不同的CSS樣式名稱來轉換外觀。比如未被選中適合的CSS樣式名稱可能是MyRadioButton,被選中之後則變成 MyRadioButtonChecked。Dijiti提供了dijit._CssStateMixin混入類來抽象這種行為。開發人員的組件只需要繼承此類,並通過屬性this.baseClass 設定基礎的CSS樣式名稱,就具備根據狀態的變化動態修改CSS樣式名稱的能力。比如設定了baseClass為myWidget,當滑鼠移動到該組件上的時候,其根項目的CSS樣式名稱會自動變成myWidgetHover。該類所支援的狀態變化包括滑鼠進入/離開、焦點擷取/失去、選中/未選中、啟用 /禁用等。
本文已經首發於InfoQ中文站,著作權,原文為《使用Dijit實現介面組件化開發》,如需轉載,請務必附帶本聲明,謝謝。
InfoQ中文站是一個面向中高端技術人員的線上獨立社區,為Java、.NET、Ruby、SOA、敏捷、架構等領域提供及時而有深度的資訊、高端技術大會如QCon 、線下技術交流活動QClub、免費迷你書下載如《架構師》等。