Ajax一統天下之Dojo整合篇_dojo
來源:互聯網
上載者:User
隨著Ajax應用越來越多,各種Ajax Library(Prototype),Ajax Framework(DWR),Ajax Toolkit(Dojo,YUI)也日漸豐富起來,有沒有辦法將這些結合起來呢?類似Spring的做法,當然我沒法整出一個IoC的微核心將各種Ajax“粘合”起來,但是將這些Ajax可重用的組件加以整合應該是沒有問題的,這樣即可以避免重複發明輪子,還可以針對各種Ajax進行揚長避短,形成一套比較全面的Ajax解決方案。同時也增加了開發人員選擇自己熟悉Ajax組件的靈活性。
目前我們公司已經形成一套基於Ajax的完整的產品,封裝了自己的Ajax前後台通訊機制以及提供了可重用的用戶端組件,我嘗試了一下將我們的產品與Dojo Toolkit進行整合。下面是我的做法,整合的是Dojo ComboBox Widget,它實際上是一個Auto Completion組件,類似Google Suggest。
從Dojo提供的測試類別test_ComboBox.html入手,加debugger進行跟蹤調試,理清Dojo Widget的載入流程。
經過跟蹤調試,對Dojo的Widget有了一個大致的瞭解:首先是載入當前需要的JavaScript檔案,然後對整個html頁面進行解析。在頁面上使用widget有三種方式:一種就是在html元素上添加一些dojo能解析的屬性,如
<select dojoType="combobox" style="width: 300px;" name="foo.bar1" autocomplete="false"
onValueChanged="setVal1"
>
其中的dojoType,autocomplete, onValueChanged都是dojo能夠識別的屬性,這個有些類似typestry的做法。第二種就是使用DojoML的寫法:
<dojo:combobox style="width: 300px;" name="foo.bar1" autocomplete="false"
onValueChanged="setVal1"
/>
這種寫法有些變態,跟jsp中的自訂標籤基本就是一回事,只是把解析的過程從後台移到了前台來做,後來看到有些架構也這麼幹,也就沒話說了。
還有一種寫法是使用javascript在頁面載入完成之後,在指定的html元素建立widget:
var combo;
dojo.addOnLoad(init);
function init(){
combo = dojo.widget.createWidget("dojo:ComboBox", {name:"prog",autocomplete:false,dataUrl:"comboBoxData.js"}, dojo.byId("progCombo"));
}
在對元素解析建立的時候同時利用dojo定義的combobox html模版以及css模版完成在頁面中插入最終的combobox控制項的目的。
接下來看看Dojo ComboBox如何通過ajax與後台通訊,Dojo ComboBox提供了兩種自動完成方式:一種是將所有的資料下載到前台緩衝,然後在前台根據使用者輸入的資料從緩衝中匹配出自動完成所需要的資料列表。另外一種就是根據使用者每次輸入的資料即時向後台發送請求獲得要自動完成的資料,當然這個資料也會以使用者輸入的內容為key,以得到的資料為value進行緩衝。對於兩種方式,Dojo通過不同的DataProvider來實現(dojo.widget.incrementalComboBoxDataProvider和dojo.widget.basicComboBoxDataProvider),這一點非常精妙,讓我非常佩服。而這兩個類都是通過dojo.declare(“className”, “parentClassName”, constructor, declarationBody)這種方式來做的,這個也和我們以往的做法有別。總之就是比較精妙啦!
Dojo向後台發送請求的過程封裝在dojo.io.bind()這個方法中,而我們有自己的一套前後台通訊機制,因為必須想辦法將dojo.io.bind()替換成我們的做法來達到最終整合的目的,因為Dojo ComboBox的資料互動都是封裝在DataProvider裡面的,因為我們只需要實現自己的DataProvider就可以搞定了,這樣我們無須修改Dojo的源,而且還可以使用Dojo的繼承機制,從已有的DataProvider整合複寫掉我需要替換的方法,這讓我有了寫Java的感覺。
dojo.declare(
"dojo.widget.incrementalDoradoComboBoxDataProvider",
dojo.widget.incrementalComboBoxDataProvider,
null,
{
//要替換的方法,使用自己的通訊機制
startSearch: function(/*String*/ searchStr, /*Function*/ callback){
if(this._inFlight){
// FIXME: implement backoff!
}
var cmd = getControl(this.searchUrl);
cmd.parameters().setValue("searchString", searchStr);
var _this = this;
EventManager.addDoradoEvent(cmd, "onSuccess", function(command){
_this._inFlight = false;
//convention:
//1.the key must be "result"
//2.the data format must be [["Alabama","AL"],["Alaska","AK"]] or [{"Alabama":"AL"},{"Alaska":"AK"}]
var data = dj_eval(command.outParameters().getValue("result"));
if(!dojo.lang.isArray(data)){
var arrData = [];
for(var key in data){
arrData.push([data[key], key]);
}
data = arrData;
}
_this._addToCache(searchStr, data);
callback(data);
}
);
cmd.execute();
this._inFlight = true;
}
}
);
通過上面的處理,就可以使用我們自己的前後台通訊機制來完成請求資料的目的。
接下來就是產生我們的頁面,添加dojo載入js的指令碼:
<script type="text/javascript" src="./dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.widget.ComboBox");
// 注意這裡有一個定位的問題,尋找路徑必須加"..",
// 因為dojo在尋找DoradoComboBox.js的時候會從"/dojo"而不是"/"目錄開始尋找
// 最終使用xmlhttp載入的路徑是/dojo/../adapter/dojo/widget/DoradoComboBox.js
dojo.setModulePrefix("adapter.dojo.widget","../adapter/dojo/widget");
dojo.require("adapter.dojo.widget.DoradoComboBox");
</script>
下面要載入的控制項部分html:
<input dojoType="ComboBox"
dataUrl="cmdComboboxSearch"
dataProviderClass = "dojo.widget.incrementalDoradoComboBoxDataProvider"
style="width: 200px;"
name="sample2"
autocomplete="false"
>
這樣我們的整合工作就完成了,對了還有檔案的目錄結構:
Webapp
|--adapter(存放所有用於整合的js檔案)
|------dojo
|---------widget
|-----------DoradoComboBox.js
|--dojo(dojo的所有js檔案)
|------src
|------dojo.js
|--js(我們自己組件庫的js檔案)
|--WEB-INF
結論
通過擴充之後還是發現了不少問題:
1、由於整合的兩套東西都會在Object.prototype, Array.prototype, Function.prototype上加一些自己的東西,因此這樣非常容易帶來命名上的衝突,已經碰到這個問題。
2、由於二者都會使用一些全域性的函數,變數等,這樣也會存在潛在的衝突,不過目前還沒有碰到。
3、多套js庫要同時載入,用戶端的壓力是不是大了些?效能可以接受嗎?目前還沒有測試不得而知。