天天看點

一起談.NET技術,使用Dijit實作界面元件化開發

  對于元件化的軟體工程設計,很多開發人員都比較熟悉。元件化的設計适合于複雜的軟體系統和團隊協作開發。把軟體系統劃分成若幹個元件,元件之間通過預先定義好的接口和協定進行通訊和協作,共同完成整個軟體系統的職責。團隊中的開發人員可以各自負責不同的元件。元件化的思想在桌面應用和Web應用背景開發中比較流行,相關的技術和實踐都比較成熟。

  而在Web應用的前端部分,元件化一直進展得比較緩慢。這其中的原因有很多,最主要的是Web應用的前端在開始的時候比較簡單,沒有元件化和設計的必要。随着Ajax應用的流行,Web前端部分越發複雜,使用者對Web應用的要求不斷向桌面應用靠攏。HTML語言的基本界面元素不能單獨地滿足這樣的需求。菜單、樹形控件、對話框和進度條等元件,在現在的Ajax應用中十分常見,但是并不是HTML預設提供的。HTML 5規範中引入了一些新的元素,但還是不夠。元件化對于Web應用本身的代碼共享和團隊分工也是很有意義的。

  Web 應用前端元件化的發展也是漸進的。開始的時候,隻是一些簡單的HTML、CSS加上JavaScript的代碼示例。比如當需要實作一個多級菜單的時候,就下載下傳相關的代碼示例,就根據自己的需要進行修改。這樣的元件比較難以複用。後來JavaScript架構開始流行的時候,有些架構本身就提供了元件的支援,包括Ext JS、jQuery UI和Dojo等。不過不同架構提供的元件模型不盡相同。

  Web 應用的前端元件的定義比較寬泛。一個元件占據Web頁面上的某個區域,并負責完成某項具體的任務。Web元件有時候也被稱為小部件(widget)。在 Dijit元件模型中,一個Dijit元件是一個JavaScript類,可以在頁面上通過new操作符來建立元件的執行個體。每個元件執行個體都需要與頁面上的某個DOM元素綁定在一起。這個DOM元素就是該元件的根節點。在Dijit元件的邏輯中,就可以對該根節點進行操縱來建構使用者界面。元件 JavaScript類暴露出來的屬性和方法就是該元件的接口。

  Dijit 元件的使用方式非常簡單。首先需要在頁面上加載元件的JavaScript代碼,這通過dojo.require函數就可以完成。接着在頁面上找到或建立一個DOM元素作為該元件的根節點。最後通過new操作符建立即可。如new dijit.form.ComboBox({}, node)就可以用node作為根元素建立一個dijit.form.ComboBox元件,即一個下拉清單選擇框。可以看到建立Dijit元件的時候,使用了兩個參數:第二個參數是元件的根元素,如果建立的時候不指定該根元素,會自動建立一個新的DIV元素作為根元素。

  不過該新建立的根元素一般沒有加入到目前的文檔樹中,可以通過元件的placeAt方法來設定該元件在頁面文檔樹中的位置。第一個元素則是一個JavaScript對象,包含了元件的配置屬性。通常來說,一個Dijit元件是可以複用的。是以一般都會提供一些屬性供使用者進行配置。通過這個參數,就可以修改這些配置。

  上面提到的是程式式的方式建立Dijit元件,還有另外的一種方式來進行建立,即通過在HTML代碼中以聲明式的方式建立,如

<div dojoType="dijit.Dialog" id="myDialog" title="示例對話框">

<h3>對話框标題</h3>

<div>對話框内容</div>

</div>

  聲明式的方式在一定程度上簡化了開發人員使用Dijit元件的方式。聲明式的方式與編寫HTML代碼的形式類似,隻需要在一般的HTML元素上添加一些額外的屬性就可以把HTML片段轉換成Dijit元件。這對于隻熟悉HTML語言的人來說非常友善,相當于在HTML語言的基本元素之上,增加了更多的可用元件。

  所有的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中添加。很多容器類元件都使用該方法來對其子節點進行布局。

  如果隻是使用dijit._Widget的話,編寫Dijit元件會比較繁瑣。比如在建構使用者界面的時候,可能會需要很多的DOM操作,編寫起來也不友善。 Dijit提供了dijit._Templated用來使用HTML片段來定義元件的内容。HTML片段是作為元件的内容模闆。如:

dojo.declare("TempWidget", [dijit._Widget, dijit._Templated], {

templateString : "<div><span>Hello</span></div>"

});

  TempWidget繼承了兩個JavaScript類,除了必需的dijit._Widget之外,還有dijit._Templated的。需要保證 dijit._Widget是父類數組的第一個元素,隻有它是真正意義上的父類,其餘的是混入類。dijit._Templated類已經覆寫了 buildRendering方法來從HTML模闆中建立元件内容的DOM元素,并作為元件的this.domNode的值。在HTML模闆中,除了可以使用基本的HTML元素和屬性之外,還有一些附加的實用功能:

在HTML模闆中直接引用元件中的屬性。比如元件中有個屬性叫title,在HTML模闆中想引用該屬性的值,可以直接寫<span>${title}</span>。如果屬性title的值是"Hello",那麼上述模闆在運作時刻會變成<span>Hello</span>。

通過dojoAttachPoint來聲明在元件對象中可見的DOM節點。當需要在元件中引用某個内部的DOM節點時,不需要再次進行查詢,通過 dojoAttachPoint即可。如聲明<div><span dojoAttachPoint="myNode"></span></div>,就可以在元件對象中通過 this.myNode來引用該SPAN元素。

通過dojoAttachEvent來進行事件綁定。這種方式比先手工查詢DOM節點,再通過dojo.connect來綁定要簡單得多。如聲明<div><button dojoAttachEvent="onclick:test">Test</button></div>,就意味着将元件的test方法綁定到按鈕的onclick事件上。

  dijit._Templated的模闆機制的這些實用功能減少了建構使用者界面時的一些繁瑣代碼。

  如果元件是作為其它元件的容器來使用的話,就可以混入dijit._Container類。該類提供了對子元件的基本管理功能,包括查詢、添加和删除等。使用該類的時候,需要在元件中聲明一個containerNode的屬性作為子元件的父節點。建立出Dijit元件之後,就可以通過addChild方法來添加子元件了。

  元件在建立并運作之後,就可能需要被銷毀。銷毀一個Dijit元件很簡單,隻需要調用destroyRecursive方法即可。該方法會負責銷毀目前Dijit元件及其包含的子元件。當一個元件被銷毀的時候,其uninitialize方法會被調用,類似于析構函數。是以可以把元件特有的銷毀邏輯添加在uninitialize方法中。

  前面提到,元件之間通過設計好的接口和協定進行通訊。對于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元件,在運作時刻也是通過程式式的方式來進行建立的,由dojo.parser.parse方法來完成。是以,聲明式的方式更像一種文法糖衣。不過聲明式方式的一個好處是可以實作優雅地退化(graceful degradation),即當JavaScript不被支援的時候,仍然可以在頁面上顯示出部分内容。

  對于簡單的和容器類的元件來說,聲明式建立的方式比較好。簡單的元件用聲明式的方式比較簡潔。而容器類的元件在建立的時候一般都需要指定所包含的内容。使用聲明式的時候,元件的子節點會自動作為容器類元件的子元件來添加。而如果以程式式的方式來完成的話,需要手工建立子元件,并通過addChild方法來逐個添加。代碼會比較繁瑣。

  在開發Dijit元件中,經常會遇到的一個場景是根據元件内部的狀态變化改變其外觀樣式。比如對于一個單選按鈕元件來說,選中和未被選中的外觀樣式是不同的。典型的做法是通過切換不同的CSS樣式名稱來轉換外觀。比如未被選中适合的CSS樣式名稱可能是MyRadioButton,被選中之後則變成 MyRadioButtonChecked。Dijiti提供了dijit._CssStateMixin混入類來抽象這種行為。

  開發人員的元件隻需要繼承此類,并通過屬性this.baseClass 設定基礎的CSS樣式名稱,就具備根據狀态的變化動态修改CSS樣式名稱的能力。比如設定了baseClass為myWidget,當滑鼠移動到該元件上的時候,其根元素的CSS樣式名稱會自動變成myWidgetHover。該類所支援的狀态變化包括滑鼠進入/離開、焦點擷取/失去、選中/未選中、啟用 /禁用等。

繼續閱讀