extjs數據存儲與傳輸

 

本章內容

q   Ext.data簡介

q   Ext.data.Connection

q   Ext.data.Record

q   Ext.data.Store

q   常用proxy

q   常用reader

q   高級store

q   EXT中的Ajax

q   關於scopecreateDelegate()

q   DWREXT整合

10.1 Ext.data簡介

Ext.data在命名空間中定義了一系列storereaderproxyGridComboxBox都是以Ext.data爲媒介獲取數據的,它包含異步加載、類型轉換、分頁等功能。Ext.data默認支持ArrayJSONXML等數據格式,可以通過MemoryHTTPScriptTag等方式獲得這些格式的數據。如果要實現新的協議和新的數據結構,只需要擴展readerproxy即可。DWRProxy就實現了自身的proxyreader,讓EXT可以直接從DWR獲得數據。

10.2 Ext.data.Connection

Ext.data.Connection是對Ext.lib.Ajax的封裝,它提供了配置使用Ajax的通用方式,它在內部通過Ext.lib.Ajax實現與後臺的異步調用。與底層的Ext.lib.Ajax相比,Ext.data. Connection提供了更簡潔的配置方式,使用起來更方便。

Ext.data.Connection主要用於在Ext.data.HttpProxyExt.data.ScriptTagProxy中執行與後臺交互的任務,它會從指定的URL獲得數據,並把後臺返回的數據交給HttpProxyScriptTagProxy處理,Ext.data.Connection的使用方式如代碼清單10-1所示。

代碼清單10-1 使用Ext.data.Connection

var conn = new Ext.data.Connection({

    autoAbort: false,

    defaultHeaders: {

        referer: 'http://localhost:8080/'

    },

    disableCaching : false,

    extraParams : {

        name: 'name'

    },

    method : 'GET',

    timeout : 300,

    url : '01-01.txt'

});

 

在使用Ext.data.Connection之前,都要像上面這樣創建一個新的Ext.Connection實例。我們可以在構造方法裏配置對應的參數,比如autoAbort表示鏈接是否會自動斷開、default- Headers參數表示請求的默認首部信息、disableCaching參數表示請求是否會禁用緩存、extraParams參數代表請求的額外參數、method參數表示請求方法、timeout參數表示連接的超時時間、url參數表示請求訪問的網址等。

在創建了conn之後,可以調用request()函數發送請求,處理返回的結果,如下面的代碼所示。

 

conn.request({

    success: function(response) {

        Ext.Msg.alert('info', response.responseText);

    },

    failure: function() {

        Ext.Msg.alert('warn', 'failure');

    }

});

 

Request()函數中可以設置successfailure兩個回調函數,分別在請求成功和請求失敗時調用。請求成功時,success函數的參數就是後臺返回的信息。

我們再來看一下request函數中的其他參數。

q   url:String:請求url

q   params:Object/String/Function:請求傳遞的參數。

q   method:String:請求方法,通常爲GETPOST

q   callback:Function:請求完成後的回調函數,無論是成功還是失敗,都會執行。

q   success:Function:請求成功時的回調函數。

q   failure:Function:請求失敗時的回調函數

q   scope:Object:回調函數的作用域。

q   form:Object/String:綁定的form表單。

q   isUpload:Boolean:是否執行文件上傳。

q   headers:Object:請求首部信息。

q   xmlData:ObjectXML文檔對象,可以通過URL附加參數的方式發起請求。

q   disableCaching:Boolean:是否禁用緩存,默認爲禁用。

Ext.data.Connection還提供了abort([Number transactionId])函數,當同時有多個請求發生時,根據指定的事務id放棄其中的某一個請求。如果不指定事務id,就會放棄最後一個請求。isLoading([Number transactionId])函數的用法與abort()類似,可以根據事務id判斷對應的請求是否完成。如果未指定事務id,就判斷最後一個請求是否完成。

10.3 Ext.data.Record

Ext.data.Record就是一個設定了內部數據類型的對象,它是Ext.data.Store的最基本組成部分。如果把Ext.data.Store看作是一張二維表,那麼它的每一行就對應一個Ext.data. Record實例。

Ext.data.Record的主要功能是保存數據,並且在內部數據發生改變時記錄修改的狀態,它還可以保留修改之前的原始值。

我們使用Ext.data.Record時通常都是由create()函數開始,首先用create()函數創建一個自定義的Record類型,如下面的代碼所示。

 

var PersonRecord = Ext.data.Record.create([

    {name: 'name', type: 'string'},

    {name: 'sex', type: 'int'}

]);

 

PersonRecord就是我們定義的新類型,包含字符串類型的name和整數類型的sex兩個屬性,然後我們使用new關鍵字創建PersonRecord的實例,如下面的代碼所示。

 

var boy = new PersonRecord({

    name: 'boy',

    sex: 0

});

 

創建對象時,可以直接通過構造方法爲對象賦予初始值,將'boy'賦值給name0賦值給sex

現在,我們得到了PersonRecord的實例boy,如何才能得到它的屬性呢?以下三種方式都可以獲得boyname屬性的數據,如下面的代碼所示。

 

alert(boy.data.name);

alert(boy.data['name']);

alert(boy.get('name'));

 

這裏涉及Ext.data.Recorddata屬性,這是定義在Ext.data.Record中的一個公共屬性,用於保存當前record對象的所有數據。它是一個JSON對象,可以直接從它裏面獲得需要的數據。可以通過Ext.data.Recordget()函數方便地從data屬性中獲得指定的屬性值。

如果我們需要修改boy中的數據,請不要使用以下方式直接操作data,如下面的代碼所示。

 

    boy.data.name = 'boy name';

    boy.data['name'] = 'boy name';

而應該使用set()函數,如下面的代碼所示。

 

    boy.set('name', 'body name');

 

set()函數會判斷屬性值是否發生了改變,如果改變了,就要將當前對象的dirty屬性設置爲true,並將修改之前的原始值放入modified對象中,供其他函數使用。如果直接操作data中的值,record就無法記錄屬性數據的修改情況。

Record的屬性數據被修改後,我們可以執行如下幾種操作。

q   commit()(提交):這個函數的效果是設置dirtyfalse,並刪除modified中保存的原始數據。

q   reject()(撤銷):這個函數的效果是將data中已經修改了的屬性值都恢復成modified中保存的原始數據,然後設置dirtyfalse,並刪除保存原始數據的modified對象。

q   getChanges()獲得修改的部分:這個函數會把data中經過修改的屬性和數據放在一個JSON對象裏並返回。例如上例中,getChanges()返回的結果是{name:’body name’}

q   我們還可以調用isModified()判斷當前record中的數據是否被修改。

      Ext.data.Record還提供了用於複製record實例的函數copy()

 

  var copyBoy = boy.copy();

 

這樣我們就得到了boy的一個副本,它裏面包含了boydata數據,但copy()函數不會複製dirtymodified等額外的屬性值。

Ext.data.Record中其他的參數大多與Ext.data.Store有關,請參考與Ext.data.Store相關的討論。

10.4 Ext.data.Store

Ext.data.StoreEXT中用來進行數據交換和數據交互的標準中間件,無論是Grid還是ComboBox,都是通過它實現數據讀取、類型轉換、排序分頁和搜索等操作的。

Ext.data.Store中有一個Ext.data.Record數組,所有數據都存放在這些Ext.data. Record實例中,爲後面的讀取和修改操作做準備。

10.4.1 基本應用

在使用之前,首先要創建一個Ext.data.Store的實例,如下面的代碼所示。

 

var data = [

    ['boy', 0],

    ['girl', 1]

];

 

var store = new Ext.data.Store({

    proxy: new Ext.data.MemoryProxy(data),

    reader: new Ext.data.ArrayReader({}, PersonRecord)

});

store.load();

 

每個store最少需要兩個組件的支持,分別是proxyreaderproxy用於從某個途徑讀取原始數據,reader用於將原始數據轉換成Record實例。

這裏我們使用的是Ext.data.MemoryProxyExt.data.ArrayReader,將data數組中的數據轉換成對應的幾個PersonRecord實例,然後放入store中。store創建完畢之後,執行store.load()實現這個轉換過程。

經過轉換之後,store裏的數據就可以提供給GridComboBox使用了,這就是Ext.data. Store的最基本用法。

10.4.2 對數據進行排序

Ext.data.Store提供了一系列屬性和函數,利用它們對數據進行排序操作。

可以在創建Ext.data.Store時使用sortInfo參數指定排序的字段和排序方式,如下面的代碼所示。

 

var store = new Ext.data.Store({

    proxy: new Ext.data.MemoryProxy(data),

    reader: new Ext.data.ArrayReader({}, PersonRecord),

    sortInfo: {field: 'name', direction: 'DESC'}

});

 

這樣,在store加載數據之後,就會自動根據name字段進行降序排列。對store使用store.setDefaultSort('name','DESC');也會達到同樣效果。

也可以在任何時候調用sort()函數,比如store.sort('name', 'DESC');,對store中的數據進行排序。

如果我們希望獲得store的排序信息,可以調用getSortState()函數,返回的是類似{field: "name", direction: " DESC"}JSON對象。

與排序相關的參數還有remoteSort,這個參數是用來實現後臺排序功能的。當設置爲remoteSort:true時,store會在向後臺請求數據時自動加入sortdir兩個參數,分別對應排序的字段和排序的方式,由後臺獲取並處理這兩個參數,在後臺對所需數據進行排序操作。remoteSort:true也會導致每次執行sort()時都要去後臺重新加載數據,而不能只對本地數據進行排序。

詳細的用法可以參考第2章。

10.4.3 store中獲取數據

store中獲取數據有很多種途徑,可以依據不同的要求選擇不同的函數。最直接的方法是根據recordstore中的行號獲得對應的record,得到了record就可以使用get()函數獲得裏面的數據了,如下面的代碼所示。

 

store.getAt(0).get('name')

 

通過這種方式,我們可以遍歷store中所有的record,依次得到它們的數據,如下面的代碼所示。

 

for (var i = 0; i < store.getCount(); i++) {

    var record = store.getAt(i);

    alert(record.get('name'));

}

 

Store.getCount()返回的是store中的所有數據記錄,然後使用for循環遍歷整個store,從而得到每條記錄。

除了使用getCount()的方法外,還可以使用each()函數,如下面的代碼所示。

 

store.each(function(record) {

    alert(record.get('name'));

});

 

Each()可以接受一個函數作爲參數,遍歷內部record,並將每個record作爲參數傳遞給function()處理。如果希望停止遍歷,可以讓function()返回false

也可以使用getRange()函數連續獲得多個record,只需要指定開始和結束位置的索引值,如下面的代碼所示。

 

var records = store.getRange(0, 1);

for (var i = 0; i < records.length; i++) {

    var record = records[i];

    alert(record.get('name'));

}

 

如果確實不知道recordid,也可以根據record本身的idstore中獲得對應的record,如下面的代碼所示。

 

store.getById(1001).get('name')

 

EXT還提供了函數find()findBy(),可以利用它們對store中的數據進行搜索,如下面的代碼所示。

 

find( String property, String/RegExp value, [Number startIndex], [Boolean anyMatch],

[Boolean caseSensitive] )

 

在這5個參數中,只有前兩個是必須的。第一個參數property代表搜索的字段名;第二個參數value是匹配用字符串或正則表達式;第三個參數startIndex表示從第幾行開始搜索,第四個參數anyMatchtrue時,不必從頭開始匹配;第五個參數caseSensitivetrue時,會區分大小寫。

如下面的代碼所示:

 

var index = store.find('name','g');

alert(store.getAt(index).get('name'));

 

find()函數對應的findBy()函數的定義格式如下:

 

findBy( Function fn, [Object scope], [Number startIndex] ) : Number

 

findBy()函數允許用戶使用自定義函數對內部數據進行搜索。fn返回true時,表示查找成功,於是停止遍歷並返回行號。fn返回false時,表示查找失敗(即未找到),繼續遍歷,如下面的代碼所示。

 

index = store.findBy(function(record, id) {

    return record.get('name') == 'girl' && record.get('sex') == 1;

});

alert(store.getAt(index).get('name'));

 

通過findBy()函數,我們可以同時判斷record中的多個字段,在函數中實現複雜邏輯。

我們還可以使用queryqueryBy函數對store中的數據進行查詢。與findfindBy不同的是,queryqueryBy返回的是一個MixCollection對象,裏面包含了搜索得到的數據,如下面的代碼所示。

 

alert(store.query('name', 'boy'));

    alert(store.queryBy(function(record) {

        return record.get('name') == 'girl' && record.get('sex') == 1;

    }));

10.4.4 更新store中的數據

可以使用add(Ext.data.Record[] records)store末尾添加一個或多個record,使用的參數可以是一個record實例,如下面的代碼所示。

 

store.add(new PersonRecord({

    name: 'other',

    sex: 0

}));

 

Add()的也可以添加一個record數組,如下面的代碼所示:

 

store.add([new PersonRecord({

    name: 'other1',

    sex: 0

}), new PersonRecord({

    name: 'other2',

    sex: 0

})]);

 

Add()函數每次都會將新數據添加到store的末尾,這就有可能破壞store原有的排序方式。如果希望根據store原來的排序方式將新數據插入到對應的位置,可以使用addSorted()函數。它會在添加新數據之後立即對store進行排序,這樣就可以保證store中的數據有序地顯示,如下面的代碼所示。

 

store.addSorted(new PersonRecord({

    name: 'lili',

    sex: 1

}));

 

store會根據排序信息查找這條record應該插入的索引位置,然後根據得到的索引位置插入數據,從而實現對整體進行排序。這個函數需要預先爲store設置本地排序,否則會不起作用。

如果希望自己指定數據插入的索引位置,可以使用insert()函數。它的第一個參數表示插入數據的索引位置,可以使用record實例或record實例的數組作爲參數,插入之後,後面的數據自動後移,如下面的代碼所示。

 

store.insert(3, new PersonRecord({

    name: 'other',

    sex: 0

}));

 

store.insert(3, [new PersonRecord({

    name: 'other1',

    sex: 0

}), new PersonRecord({

    name: 'other2',

    sex: 0

})]);

 

刪除操作可以使用remove()removeAll()函數,它們分別可以刪除指定的record和清空整個store中的數據,如下面的代碼所示。

 

store.remove(store.getAt(0));

store.removeAll();

 

store中沒有專門提供修改某一行record的操作,我們需要先從store中獲取一個record。對這個record內部數據的修改會直接反映到store上,如下面的代碼所示。

 

store.getAt(0).set('name', 'xxxx');

 

修改record的內部數據之後有兩種選擇:執行rejectChanges()撤銷所有修改,將修改過的record恢復到原來的狀態;執行commitChanges()提交數據修改。在執行撤銷和提交操作之前,可以使用getModifiedRecords()獲得store中修改過的record數組。

與修改數據相關的參數是pruneModifiedRecords,如果將它設置爲true,當每次執行刪除或reload操作時,都會清空所有修改。這樣,在每次執行刪除或reload操作之後,getModifiedRecords()返回的就是一個空數組,否則仍然會得到上次修改過的record記錄。

10.4.5 加載及顯示數據

store創建好後,需要調用load()函數加載數據,加載成功後才能對store中的數據進行操作。load()調用的完整過程如下面的代碼所示。

 

store.load({

    params: {start:0,limit:20},

    callback: function(records, options, success){

        Ext.Msg.alert('info', '加載完畢');

    },

    scope: store,

    add: true

});

 

q   params是在store加載時發送的附加參數。

q   callback是加載完畢時執行的回調函數,它包含3個參數:records參數表示獲得的數據,options表示執行load()時傳遞的參數,success表示是否加載成功。

q   Scope用來指定回調函數執行時的作用域。

q   Addtrue時,load()得到的數據會添加在原來的store數據的末尾,否則會先清除之前的數據,再將得到的數據添加到store中。

一般來說,爲了對store中的數據進行初始化,load()函數只需要執行一次。如果用params參數指定了需要使用的參數,以後再次執行reload()重新加載數據時,store會自動使用上次load()中包含的params參數內容。

如果有一些需要固定傳遞的參數,也可以使用baseParams參數執行,它是一個JSON對象,裏面的數據會作爲參數發送給後臺處理,如下面的代碼所示。

 

store.baseParams.start = 0;

store.baseParams.limit = 20;

 

store加載數據之後,有時不需要把所有數據都顯示出來,這時可以使用函數filterfilterBystore中的數據進行過濾,只顯示符合條件的部分,如下面的代碼所示。

 

filter( String field, String/RegExp value, [Boolean anyMatch],
[Boolean caseSensitive] ) : void

 

filter()函數的用法與之前談到的find()相似,如下面的代碼所示。

 

store.filter('name', 'boy');

 

對應的filterBy()findBy()類似,也可以在自定義的函數中實現各種複雜判斷,如下面的代碼所示。

 

store.filterBy(function(record) {

    return record.get('name') == 'girl' && record.get('sex') == 1;

});

 

如果想取消過濾並顯示所有數據,那麼可以調用clearFilter()函數,如下面的代碼所示。

 

store.clearFilter();

 

如果想知道store上是否設置了過濾器,可以通過isFiltered()函數進行判斷。

10.4.6 其他功能

除了上面提到的數據獲取、排序、更新、顯示等功能外,store還提供了其他一些功能函數。

 

collect( String dataIndex, [Boolean allowNull], [Boolean bypassFilter] ) : Array

 

collect函數獲得指定的dataIndex對應的那一列的數據,當allowNull參數爲true時,返回的結果中可能會包含nullundefined或空字符串,否則collect函數會自動將這些空數據過濾掉。當bypassFilter參數爲true時,collect的結果不會受查詢條件的影響,無論查詢條件是什麼都會忽略掉,返回的信息是所有的數據,如下面的代碼所示。

 

alert(store.collect('name'));

 

這樣會獲得所有name列的值,示例中返回的是包含了'boy''girl'的數組。

getTotalCount()用於在翻頁時獲得後臺傳遞過來的數據總數。如果沒有設置翻頁,get- TotalCount()的結果與getCount()相同,都是返回當前的數據總數,如下面的代碼所示。

 

alert(store.getTotalCount());

 

indexOf(Ext.data.Record record)indexOfId(String id)函數根據recordrecordid獲得record對應的行號,如下面的代碼所示。

 

alert(store.indexOf(store.getAt(1)));

alert(store.indexOfId(1001));

 

loadData(object data, [Boolean append])從本地JavaScript變量中讀取數據,appendtrue時,將讀取的數據附加到原數據後,否則執行整體更新,如下面的代碼所示。

 

store.loadData(data, true);

 

Sum(String property, Number start, Number end):Number用於計算某一個列從startend的總和,如下面的代碼所示。

 

alert(store.sum('sex'));

 

如果省略參數startend,就計算全部數據的總和。

store還提供了一系列事件(見表10-1),讓我們可以爲對應操作設定操作函數。

10-1 store提供的事件

事件名

參  數

add

( Store this, Ext.data.Record[] records, Number index )

beforelaod

( Store this, Object options )

clear

( Store this )

datachanged

( Store this )

load

( Store this, Ext.data.Record[] records, Object options )

loadexception

()

metachange

( Store this, Object meta. )

remove

( Store this, Ext.data.Record record, Number index )

update

( Store this, Ext.data.Record record, String operation )

至此,storerecord等組件已經講解完畢,下面我們主要討論一下常用的proxyreader組件。

10.5 常用proxy

10.5.1 MemoryProxy

MemoryProxy只能從JavaScript對象獲得數據,可以直接把數組,或JSONXML格式的數據交給它處理,如下面的代碼所示。

 

var proxy = new Ext.data.MemoryProxy([

    ['id1','name1','descn1'],

    ['id2','name2','descn2']

]);               

10.5.2 HttpProxy

HttpProxy使用HTTP協議,通過Ajax去後臺取數據,構造它時需要設置url:'xxx.jsp'參數。這裏的url可以替換成任何一個合法的網址,這樣HttpProxy才知道去哪裏獲取數據,如下面的代碼所示。

 

var proxy = new Ext.data.HttpProxy({url:'xxx.jsp'});               

 

後臺需要返回EXT所需要的JSON格式的數據,下面的內容就是後臺使用JSP的一個範例,如下面的代碼所示。

 

response.setContentType("application/x-json");

Writer out = response.getWriter();

out.print("[" +

        "['id1','name1','descn1']" +

        "['id2','name2','descn2']" +

    "]");               

 

請注意,這裏的HttpProxy不支持跨域,它只能從同一域中獲得數據。如果想跨域,請參考下面的ScriptTagProxy

10.5.3 ScriptTagProxy

ScriptTagProxy的用法幾乎和HttpProxy一樣,如下面的代碼所示。

 

var proxy = new Ext.data.ScriptTagProxy({url:'xxx.jsp'});               

 

從這裏也看不出來它是如何支持跨域的,我們還需要在後臺進行相應的處理,如下面的代碼所示。

 

String cb = request.getParameter("callback");

response.setContentType("text/javascript");

Writer out = response.getWriter();

out.write(cb + "(");

out.print("[" +

        "['id1','name1','descn1']" +

        "['id2','name2','descn2']" +

    "]");

out.write(");");

 

其中的關鍵就在於從請求中獲得的callback參數,這個參數叫做回調函數。ScriptTag- Proxy會在當前的HTML頁面裏添加一個<script type="text/javascript"src="xxx.jsp"> </script>標籤,然後把後臺返回的內容添加到這個標籤中,這樣就可以解決跨域訪問數據的問題。爲了讓後臺返回的內容可以在動態生成的標籤中運行,EXT會生成一個名爲callback的回調函數,並把回調函數的名稱傳遞給後臺,由後臺生成callback(data)形式的響應內容,然後返回給前臺自動運行。

雖然上述處理過程比較難理解,但是我們只需要瞭解ScriptTagProxy的用法就足夠了。如果還想進一步瞭解ScriptTagProxy的運行過程,可以使用Firebug查看動態生成的HTML以及響應的JSON內容。

最後我們來分析一下EXTAPI文檔中提供的示例,這段後臺代碼會自動判斷請求的類型,返回支持ScriptTagProxyHttpProxy的數據,如代碼清單10-2所示。

代碼清單10-2 在後臺同時支持HttpProxyScriptTagProxy

boolean scriptTag = false;

String cb = request.getParameter("callback");

if (cb != null) {

    scriptTag = true;

    response.setContentType("text/javascript");

} else {

    response.setContentType("application/x-json");

}

Writer out = response.getWriter();

if (scriptTag) {

    out.write(cb + "(");

}

out.print(dataBlock.toJsonString());

if (scriptTag) {

    out.write(");");

}               

 

代碼中通過判斷請求中是否包含callback參數來決定返回何種數據類型。如果包含,就返回ScriptTagProxy需要的數據;否則,就當作HttpProxy處理。

10.6 常用Reader

10.6.1 ArrayReader

proxy中讀取的數據需要進行解析,這些數據轉換成Record數組後才能提供給Ext.data. Store使用。

ArrayReader的作用是從二維數組裏依次讀取數據,然後生成對應的Record。默認情況下是按列順序讀取數組中的數據,不過你也可以考慮用mapping指定record與原始數組對應的列號。ArrayReader的用法很簡單,但缺點是不支持分頁。使用二維數組的方式如下面的代碼所示。

 

var data = [

    ['id1','name1','descn1'],

    ['id2','name2','descn2']

];

 

對應的ArrayReader如下面的代碼所示。

 

var reader = new Ext.data.ArrayReader({

    id:1

},[

    {name:'name',mapping:1},

    {name:'descn',mapping:2},

    {name:'id',mapping:0},

]);

 

我們演示的是字段順序不一致的情況,如果字段順序和列順序一致,就不用額外配置mapping

10.6.2 JsonReader

JavaScript中,JSON是一種非常重要的數據格式,key:value的形式比XML那種複雜的標籤結構更容易理解,代碼量也更小,很多人傾向於使用它作爲EXT的數據交換格式。爲Json- Reader準備的JSON數據如下面的代碼所示。

 

var data = {

    id:0,

    totalProperty:2,

    successProperty:true,

    root:[

        {id:'id1',name:'name1',descn:'descn1'},

        {id:'id2',name:'name2',descn:'descn2'}

    ]

};

 

與數組相比,JSON的最大優點就是支持分頁,我們可以使用totalProperty參數表示數據的總量。successProperty參數是可選的,可以用它判斷當前請求是否執行成功,進而判斷是否進行數據加載。在不希望JsonReader處理響應數據時,可以把successProperty設置成false

現在來討論一下JsonReader,看看它是如何與上面的JSON數據對應的,如下面的代碼所示。

 

var reader = new Ext.data.JsonReader({

    successProperty: "successproperty",

    totalProperty: "totalProperty",

    root: "root",

    id: "id"

}, [

    {name:'id',mapping:'id'},

    {name:'name',mapping:'name'},

    {name:'descn',mapping:'descn'}

]);

 

上例中的對應方式不夠簡潔,因爲namemapping部分的內容是相同的,其實這裏的mapping可以省略,默認會用name參數從JSON中獲得對應的數據。如果不想與JSON裏的名字一樣,也可以使用mapping修改。不過,mapping在這裏還有其他用途,如代碼清單10-3所示。

代碼清單10-3 爲JsonReader設置mapping進行數據映射

var data = {

    id:0,

    totalProperty:2,

    successProperty:true,

    root:[

        {id:'id1',name:'name1',descn:'descn1',person:{

            id:1,name:'man',sex:'male'

        }},

        {id:'id2',name:'name2',descn:'descn2',person:{

            id:2,name:'woman',sex:'female'

        }}

    ]

};

var reader = new Ext.data.JsonReader({

    successProperty: "successproperty",

    totalProperty: "totalProperty",

    root: "root",

    id: "id"

}, [

    'id','name','descn',

    {name:'person_name',mapping:'person.name'},

    {name:'person_sex',mapping:'person.sex'}

]);

 

在上面的代碼中,我們使用JSON支持更復雜的嵌套結構,其中的person對象自身就擁有id namesex等屬性。在JsonReader中可以用mapping把這些嵌套的內部屬性映射出來,賦予對應的record,而其他字段都不變。

10.6.3 XmlReader

XML是非常通用的數據傳輸格式,XmlReader使用的XML格式的數據如代碼清單10-4所示。

代碼清單10-4 XmlReader使用的XML格式的數據

<?xml version="1.0" encoding="utf-8"?>

<dataset>

    <id>1</id>

    <totalRecords>2</totalRecords>

    <success>true</success>

    <record>

        <id>1</id>

        <name>name1</name>

        <descn>descn1</descn>

    </record>

    <record>

        <id>2</id>

        <name>name2</name>

        <descn>descn2</descn>

    </record>

</dataset>

 

這裏一定要用dataset作爲XML根元素。再讓我們看一下如何對XmlReader進行配置,從而讀取上面示例中的XML數據,如下面的代碼所示。

 

var reader = new Ext.data.XmlReader({

   totalRecords: 'totalRecords',

   success: 'success'

   record: 'record',

   id: "id"

}, ['id','name','descn']);               

 

XmlReader使用的參數與之前介紹的JsonReader有些不同,我們可以看到這裏用到了totalRecordsrecord兩個參數,其中totalRecords用來指定從’totalRecords’標籤裏獲得後臺數據總數,record則表示XML中放在record標籤裏的數據是我們需要顯示的結果數據。其他兩個參數successid的含義和JsonReader中對應的參數相似,分別用來判斷操是否成功和這次返回的id。因爲XML中的標籤和reader裏需要的名字是相同的,所以簡化了配置,將[{name:’id’},{name:’name’},{name:’descn’}]直接寫成了[‘id’,’name’,’descn’]

因爲XmlReader不能將JavaScript中的字符串自動解析成XML格式的數據,因此我們需要利用其他方法進行演示。參考localXHR.js中構造XML的方式,我們有了下面的解決方案,如代碼清單10-5所示。

代碼清單10-5 通過本地字符串構造XML對象

var data = "<?xml version='1.0' encoding='utf-8'?>" +

    "<dataset>" +

        "<id>1</id>" +

        "<totalRecords>2</totalRecords>" +

        "<success>true</success>" +

        "<record>" +

            "<id>1</id>" +

            "<name>name1</name>" +

            "<descn>descn1</descn>" +

        "</record>" +

        "<record>" +

            "<id>2</id>" +

            "<name>name2</name>" +

            "<descn>descn2</descn>" +

        "</record>" +

    "</dataset>";

 

var xdoc;

 

if(typeof(DOMParser) == 'undefined'){

    xdoc = new ActiveXObject("Microsoft.XMLDOM");

    xdoc.async="false";

    xdoc.loadXML(data);

}else{

    var domParser = new DOMParser();

    xdoc = domParser.parseFromString(data, 'application/xml');

    domParser = null;

}

 

var proxy = new Ext.data.MemoryProxy(xdoc);

 

var reader = new Ext.data.XmlReader({

    totalRecords: 'totalRecords',

    success: 'success',

    record: 'record',

    id: "id"

}, ['id','name','descn']);

 

var ds = new Ext.data.Store({

    proxy: proxy,

    reader: reader

});

10.7 高級store

實際開發時,並不需要每次都對proxyreaderstore這三個對象進行配置,EXT爲我們提供了幾種可選擇的整合方案。

q   SimpleStore = Store + MemoryProxy + ArrayReader

 

  var ds = Ext.data.SimpleStore({

      data: [

            ['id1','name1','descn1'],

            ['id2','name2','descn2']

      ],

      fields: ['id','name','descn']

  });

 

SimpleStore是專爲簡化讀取本地數組而設計的,設置上MemoryProxy需要的dataArrayReader需要的fields就可以使用了。

q   JsonStore = Store + HttpProxy + JsonReader

 

  var ds = Ext.data.JsonStore({

      url: 'xxx.jsp',

      root: 'root',

      fields: ['id','name','descn']

  });

 

JsonStoreJsonReaderHttpProxy整合在一起,提供了一種從後臺讀取JSON信息的簡便方法,大多數情況下可以考慮直接使用它從後臺讀取數據。

q   Ext.data.GroupingStore對數據進行分組

Ext.data.GroupingStore繼承自Ext.data.Store,它的主要功能是可以對內部的數據進行分組,我們可以在創建Ext.data.GroupingStore時指定根據某個字段進行分組,也可以在創建實例後調用它的groupBy()函數對內部數據重新分組,如下面的代碼所示。

 

    var ds = new Ext.data.GroupingStore({

        data: [

            ['id1','name1','female','descn1'],

            ['id2','name2','male','descn2'],

            ['id3','name3','female','descn3'],

            ['id4','name4','male','descn4'],

            ['id5','name5','female','descn5']

        ],

        reader: new Ext.data.ArrayReader({

            fields: ['id','name','sex','descn']

        }),

        groupField: 'sex',

        groupOnSort: true

    });

 

上例中,我們使用groupField作爲參數,爲Ext.data.Grouping設置了分組字段,另外還設置了groupOnSort參數,這個參數可以保證只有在進行分組時纔會對Ext.data.Grouping- Store內部的數據進行排序。如果採用默認值,就需要手工指定sortInfo參數,從而指定默認的排序字段和排序方式,否則就會出現錯誤。

創建Ext.data.GroupingStore的實例之後,我們還可以調用groupBy()函數重新對數據進行分組。因爲我們設置了groupOnSort:true,所以在重新分組時,EXT會使用分組的字段對內部數據進行排序。如果不希望對數據進行分組,也可以調用clearGrouping()函數清除分組信息,如下面的代碼所示。

 

    ds.groupBy('id');

    ds.clearGrouping();

10.8 EXT中的Ajax

EXT與後臺交換數據時,很大程度上依賴於底層實現的Ajax。所謂底層實現,就是說很可能就是我們之前提到的 PrototypejQueryYUI中提供的Ajax功能。爲了統一接口,EXT在它們的基礎上進行了封裝,讓我們可以用同一種寫法“遊走”於各種不同的底層實現之間。

10.8.1 最容易看到的Ext.Ajax

Ext.Ajax的基本用法如下所示。

 

Ext.Ajax.request({

    url: '07-01.txt',

    success: function(response) {

        Ext.Msg.alert('成功', response.responseText);

    },

    failure: function(response) {

        Ext.Msg.alert('失敗', response.responseText);

    },

    params: { name: 'value' }

});

 

這裏調用的是Ext.Ajaxrequest函數,它的參數是一個JSON對象,具體如下所示。

q   url參數表示將要訪問的後臺網址。

q   success參數表示響應成功後的回調函數。

上例中我們直接從response取得返回的字符串,用Ext.Msg.alert顯示出來。

q   failure參數表示響應失敗後的回調函數。

注意,這裏的響應失敗並不是指數據庫操作之類的業務性失敗,而是指HTTP返回404500錯誤,請不要把HTTP響應錯誤與業務錯誤混淆在一起。

q   params參數表示請求時發送到後臺的參數,既可以使用JSON對象,也可以直接使用"name=value"形式的字符串。

上面的示例可以在10.store/07-01.html中找到。

Ext.Ajax直接繼承自Ext.data.Connection,不同的是,它是一個單例,不需要用new創建實例,可以直接使用。在使用Ext.data.Connection前需要先創建實例,因爲Ext.data. Connection是爲了給Ext.data中的各種proxy提供Ajax功能,分配不同的實例更有利於分別管理。Ext.Ajax爲用戶提供了一個簡易的調用接口,實際使用時,可以根據自己的需要進行選擇。

10.8.2 Ext.lib.Ajax是更底層的封裝

其實Ext.AjaxExt.data.Connection的內部功能實現都是依靠Ext.lib.Ajax來完成的,在Ext.lib.Ajax下面就是各種底層庫的Ajax了。

如果使用Ext.lib.Ajax實現以上的功能,就需要寫成下面的形式,如下面的代碼所示。

 

Ext.lib.Ajax.request(

    'POST',

    '07-01txt',

    {success: function(response){

        Ext.Msg.alert('成功', response.responseText);

    },failure: function(){

        Ext.Msg.alert('失敗', response.responseText);

    }},

    'data=' + encodeURIComponent(Ext.encode({name:'value'}))

);

 

我們可以看到,使用Ext.lib.Ajax時需要傳遞4個參數,分別爲methodurlcallbackparams。它們的含義與Ext.Ajax中的參數都是一一對應的,唯一沒有提到過的method參數表示請求HTTP的方法,它也可以在Ext.Ajax中使用method:'POST'的方式設置。

相對於Ext.Ajax來說,Ext.lib.Ajax有如下幾個缺點。

q   參數的順序被定死了,第一個參數是method,第二個參數是url,第三個參數是回調函數callback,第四個參數是params。這樣既不容易記憶,也無法省略其中某個不需要的參數。Ext.Ajax中用JSON對象來定義參數,使用起來更靈活。

q   params部分,Ext.lib.Ajax必須使用字符串形式,顯得有些笨重。Ext.Ajax則可以在JSON對象和字符串之間隨意選擇,非常靈活。

比與Ext.Ajax相比,Ext.lib.Ajax的唯一優勢就是它可以在EXT 1.x中使用。如果你使用的是EXT 2.0或更高的版本,那麼就放心大膽地使用Ext.Ajax吧,它會帶給你更多的驚喜。

該示例在10.store/07-02.html中。

10.9 關於scopecreateDelegate()

關於JavaScriptthis的使用,這是一個由來已久的問題了。我們這裏就不介紹它的發展歷史了,只結合具體的例子,告訴大家可能會遇到什麼問題,在遇到這些問題時EXT是如何解決的。在使用EXT時,最常碰到的就是使用Ajax回調函數時出現的問題,如下面的代碼所示。

 

<input type="text" name="text" id="text">

<input type="button" name="button" id="button" value="button">

 

現在的HTML 頁面中有一個text輸入框和一個按鈕。我們希望按下這個按鈕之後,能用Ajax去後臺讀取數據,然後把後臺響應的數據放到text中,實現過程如代碼清單10-6所示。

代碼清單10-6 Ajax中使用回調函數

function doSuccess(response) {

    text.dom.value = response.responseText;

}

 

Ext.onReady(function(){

    Ext.get('button').on('click', function(){

        var text = Ext.get('text');

        Ext.lib.Ajax.request(

            'POST',

            '08.txt',

            {success:doSuccess},

            'param=' + encodeURIComponent(text.dom.value)

        );

    });

});

 

在上面的代碼中,Ajax已經用Ext.get('text')獲得了text,以爲後面可以直接使用,沒想到回調函數success不會按照你寫的順序去執行。當然,也不會像你所想的那樣使用局部變量text。實際上,如果什麼都不做,僅僅只是使用回調函數,你不得不再次使用Ext.get('text')重新獲得元素,否則瀏覽器就會報text未定義的錯誤。

在此使用Ext.get('text')重新獲取對象還比較簡單,在有些情況下不容易獲得需要處理的對象,我們要在發送Ajax請求之前獲取回調函數中需要操作的對象,有兩種方法可供選擇:scopecreateDelegate

q   Ajax設置scope

 

  function doSuccess(response) {

      this.dom.value = response.responseText;

  }

  Ext.lib.Ajax.request(

      'POST',

      '08.txt',

      {success:doSuccess,scope:text},

      'param=' + encodeURIComponent(text.dom.value)

  );               

 

Ajaxcallback參數部分添加一個scope:text,把回調函數的scope指向text,它的作用就是把doSuccess函數裏的this指向text對象。然後再把doSuccess裏改成this.dom. value,這樣就可以了。如果想再次在回調函數裏用某個對象,必須配上scope,這樣就能在回調函數中使用this對它進行操作了。

q   success添加createDelegate()

 

  function doSuccess(response) {

      this.dom.value = response.responseText;

  }

 

  Ext.lib.Ajax.request(

      'POST',

      '08.txt',

      {success:doSuccess.createDelegate(text)},

      'param=' + encodeURIComponent(text.dom.value)

  );

 

createDelegate只能在function上調用,它把函數裏的this強行指向我們需要的對象,然後我們就可以在回調函數doSuccess裏直接通過this來引用createDelegate()中指定的這個對象了。它可以作爲解決this問題的一個備選方案。

如果讓我選擇,我會盡量選擇scope,因爲createDelegate是要對原來的函數進行封裝,重新生成function對象。簡單環境下,scope就夠用了,倒是createDelegate還有其他功能,比如修改調用參數等。

示例在10.store/08.html中。

10.10 DWREXT整合

據不完全統計,從事Ajax開發的Java程序員有一大半都使用DWR。我們下面來介紹一下如何在EXT中使用DWR與後臺交互。

10.10.1 EXT中直接使用DWR

因爲DWR在前臺的表現形式和普通的JavaScript完全一樣,所以我們不需要特地去做些什麼,直接使用EXT調用DWR生成的JavaScript函數即可。以Grid爲例,比如現在我們要顯示一個通訊錄的信息,後臺記錄的數據有:idnamesexemailteladdTimedescn。編寫對應的POJO,代碼如下所示。

 

public class Info {

    long id;

    String name;

    int sex;

    String email;

    String tel;

    Date addTime;

    String descn;

}

 

然後編寫操作POJOmanager類,代碼如下所示。

 

public class InfoManager {

    private List infoList = new ArrayList();

 

    public List getResult() {

        return infoList;

    }

}

 

代碼部分有些刪減,我們只保留了其中的關鍵部分,就這樣把這兩個類配置到dwr.xml中,讓前臺可以對這些類進行調用。

下面是EXTDWR交互的關鍵部分,我們要對JavaScript部分做如下修改,如代碼清單10-7所示。

代碼清單10-7 使用EXT調用DWR

var cm = new Ext.grid.ColumnModel([

    {header:'編號',dataIndex:'id'},

    {header:'名稱',dataIndex:'name'},

    {header:'性別',dataIndex:'sex'},

    {header:'郵箱',dataIndex:'email'},

    {header:'電話',dataIndex:'tel'},

    {header:'添加時間',dataIndex:'addTime'},

    {header:'備註',dataIndex:'descn'}

]);

 

var store = new Ext.data.JsonStore({

    fields: ["id","name","sex",'email','tel','addTime','descn']

});

 

// 調用DWR取得數據

infoManager.getResult(function(data) {

    store.loadData(data);

});

 

var grid = new Ext.grid.GridPanel({

    renderTo: 'grid',

    store: store,

    cm: cm

});

 

注意,執行infoManager.getResult()函數時,DWR就會使用Ajax去後臺取數據了,操作成功後調用我們定義的匿名回調函數。在這裏我們只做一件事,那就是將返回的data直接注入到ds中。

DWR返回的data可以被JsonStore直接讀取,我們需要設置對應的fields參數,以告訴JsonReader需要哪些屬性。

在這裏,EXTDWR兩者之間沒有任何關係,將它們任何一方替換掉都可以。實際上它們只是在一起運行,並沒有整合。我們給出的這個示例也是說明了一種鬆耦合的可能性,實際操作中完全可以使用這種方式。

10.10.2 DWRProxy

要結合使用EXTDWR,不需要對後臺程序進行任何修改,可以直接讓前後臺數據進行交互。不過還要考慮很多細節,比如Grid分頁、刷新、排序、搜索等常見的操作。EXT的官方網站上已經有人放上了DWRProxy,藉助它可以讓DWREXT連接得更加緊密。不過,需要在後臺添加DWRProxy所需要的Java類,這可能不是最好的解決方案。但我們相信,通過對它的內在實現的討論,我們可以有更多的選擇和想象空間。

注意     這個DWRProxy.js一定要放在ext-base.jsext-all.js後面,否則會出錯。

我們現在就用DWRProxy來實現一個分頁的示例。除了準備好插件DWRProxy.js外,還要在後臺準備一個專門用於分頁的封裝類。因爲不僅要告訴前臺顯示哪些數據,還要告訴前臺一共有多少條數據。現在我們來重點看一下ListRange.java,如下面的代碼所示。

 

public class ListRange {

    Object[] data;

    int totalSize;

}

 

其實ListRange非常簡單,只有兩個屬性:提供數據的data和提供數據總量的totalSize。再看一下InfoManager.java,爲了實現分頁,我們專門編寫了一個getItems方法,代碼如下所示。

 

public ListRange getItems(Map conditions) {

    int start = 0;

    int pageSize = 10;

    int pageNo = (start / pageSize) + 1;

 

    try {

        start = Integer.parseInt(conditions.get("start").toString());

        pageSize = Integer.parseInt(conditions.get("limit").toString());

        pageNo = (start / pageSize) + 1;

    } catch (Exception ex) {

        ex.printStackTrace();

    }

    List list = infoList.subList(start, start + pageSize);

    return new ListRange(list.toArray(), infoList.size());

}

 

getItems()的參數是Map,我們從中獲得需要的參數,比如startlimit。不過HTTP裏的參數都是字符串,而我們需要的是數字,所以要對類型進行相應的轉換。根據startlimit兩個屬性從全部數據中截取一部分,放進新建的ListRange中,然後把生成的ListRange返回給前臺,於是一切都解決了。

重頭戲要上演了,我們就要使用傳說中的Ext.data.DWRProxy了,還有Ext.data.List- RangeReader。通過這兩個擴展,EXT完全可以支持DWR的數據傳輸協議。實際上,這正是EXT要把數據和顯示分離設計的原因,這樣你只需要添加自定義的proxyreader,不需要修改EXT的其他部分,就可以實現從特定途徑獲取數據的功能。後臺還是DWR,所以至少在Grid部分,我們可以很好地使用它們的結合,主要代碼如下所示。

var store = new Ext.data.Store({

    proxy: new Ext.data.DWRProxy(infoManager.getItems, true),

    reader: new Ext.data.ListRangeReader({

        totalProperty: 'totalSize',

        root: 'data',

        id: 'id'

    }, info),

    remoteSort: true

});

 

與我們上面說的一樣,我們修改了proxy,也修改了reader,其他地方都不需要進行修改,Grid已經可以正常運行了。需要提醒的是DWRProxy的用法,其中包括兩個參數:第一個是dwr- Call,它把一個DWR函數放進去,它對應的是後臺的getItems方法;第二個參數是paging- AndSort,這個參數控制DWR是否需要分頁和排序。

ListRangeReader部分與後臺的ListRange.java對應。totalProperty表示後臺數據總數,我們通過它指定從ListRange中讀取totalSize屬性的值來作爲後臺數據總數。還需要指定root參數,以告訴它在ListRange中的數據變量的名稱爲data,隨後DWRProxy會從ListRange中的data屬性中獲取數據並顯示到頁面上。如果不想使用我們提供的ListRange.java,也可以自己創建一個類,只要把totalPropertydata兩個屬性與之對應即可。

10.10.3 DWRTreeLoader

我們現在來嘗試一下讓樹形也支持DWR。有了前面的基礎,整合DWRtree就更簡單了。在後臺,我們需要樹形節點對應的TreeNode.java。目前,只要idtextleaf三項就可以了。

 

public class TreeNode {

    String id;

    String text;

    boolean leaf;

}

 

id是節點的唯一標記,知道了id就能知道是在觸發哪個節點了。text是顯示的標題,leaf比較重要,它用來標記這個節點是不是葉子。

這裏還是用異步樹,TreeNodeManager.java裏的getTree()方法將獲得一個節點的id作爲參數,然後返回這個節點下的所有子節點。我們這裏沒有限制生成的樹形的深度,你可以根據自己的需要進行設置。TreeNodeManager.java的代碼如下所示。

 

public List getTree(String id) {

    List list = new ArrayList();

    String seed1 = id + 1;

    String seed2 = id + 2;

    String seed3 = id + 3;

    list.add(new TreeNode(seed1, "" + seed1, false));

    list.add(new TreeNode(seed2, "" + seed2, false));

    list.add(new TreeNode(seed3, "" + seed3, true));

 

    return list;

}

 

上面的代碼並不複雜,它實現的效果與在Java中使用List或數組是相同的,因爲返回給前臺的數據都是JSON格式的。前臺使用JavaScript處理返回信息的部分更簡單,先引入DWRTree- Loader.js,然後把TreeLoader替換成DWRTreeLoder即可,如下面的代碼所示。

 

var tree = new Ext.tree.TreePanel('tree', {

    loader: new Ext.tree.DWRTreeLoader({dataUrl: treeNodeManager.getTree})

});

 

參數依然是dataUrl,它的值treeNodeManager.getTree代表的是一個DWR函數,我們不需要對它進行深入研究,它的內部會自動處理數據之間的對應關係。DWR有時真的很方便。

10.10.4 DWRProxyComboBox

DWRProxy既然可以用在Ext.data.Store中,那麼它也可以爲ComboBox服務,如代碼清單10-8所示。

代碼清單10-8 DWRProxyComboBox整合

var info = Ext.data.Record.create([

    {name: 'id', type: 'int'},

    {name: 'name', type: 'string'}

]);

 

var store = new Ext.data.Store({

    proxy: new Ext.data.DWRProxy(infoManager.getItems, true),

    reader: new Ext.data.ListRangeReader({

        totalProperty: 'totalSize',

        root: 'data',

        id: 'id'

    }, info)

});

 

var combo = new Ext.form.ComboBox({

    store: store,

    displayField: 'name',

    valueField: 'id',

    triggerAction: 'all',

    typeAhead: true,

    mode: 'remote',

    emptyText: '請選擇',

    selectOnFocus: true

});

combo.render('combo');

 

我們既可以用mode:'remote'triggerAction:'all'在第一次選擇時讀取數據,也可以設置mode:'local',然後手工操作store.load()並讀取數據。

DWR要比Json-lib方便得多,而且DWR返回的數據可以直接作爲JSON使用,使用Json-lib時還要面對無休無止的循環引用。

這次的示例稍微複雜一些,因爲包括依賴jar包、classXMLJSP,所以示例單獨放在10.store/dwr2/下,請將它們複製到tomcatwebapps下,然後再使用瀏覽器訪問。

10.11 localXHR支持本地使用Ajax

Ajax是不能在本地文件系統中使用的,必須把數據放到服務器上。無論是IISApache Tomcat,還是你熟悉的其他服務器,只要支持HTTP協議,就可以使用EXT中的Ajax

至於本地爲何不能用Ajax,主要是因爲Ajax要判斷HTTP響應返回的狀態,只有status=200時才認爲這次請求是成功的。所以,localXHR做的就是強行修改響應狀態,讓Ajax可以繼續下去。

下面我們來分析一下localXHR的源代碼。

q   加入了一個forceActiveX屬性,默認是false,它用來控制是否強制使用activexactivex是在IE下專用的。

q   修改createXhrObject函數,只是在最開始處加了一條判斷語句,如下所示。

 

  if(Ext.isIE7 && !!this.forceActiveX){throw("IE7forceActiveX");}               

 

q   增加了getHttpStatus函數,這是爲了處理HTTP的響應狀態,如代碼清單10-9所示。

代碼清單10-9 處理HTTP響應狀態

getHttpStatus: function(reqObj){

    var statObj = {

        status:0

        ,statusText:''

        ,isError:false

        ,isLocal:false

        ,isOK:false

    };

    try {

        if(!reqObj)throw('noobj');

        statObj.status = reqObj.status || 0;

 

        statObj.isLocal = !reqObj.status && location.protocol == "file:" ||

                           Ext.isSafari && reqObj.status == undefined;

 

        statObj.statusText = reqObj.statusText || '';

 

        statObj.isOK = (statObj.isLocal ||

                        (statObj.status > 199 && statObj.status < 300) ||

                         statObj.status == 304);

 

    } catch(e){

        //status may not avail/valid yet.

        statObj.isError = true;

    }

 

    return statObj;

},

 

它爲狀態增添了更多語義,status表示狀態值,statusText表示狀態描述,isError表示是否有錯誤,isLocal表示是否在本地進行Ajax訪問,isOK表示操作是否成功。

判斷isLocal是否爲本地的有兩種方法:reqObj沒有status,而且請求協議是file:;瀏覽器是Safari,而且reqObj.status沒有定義。

statObj中的isOK屬性用來判斷此次請求是否成功。判斷請求是否成功的條件很多,例如:isLocal的屬性爲true、響應狀態值在199~300之間、響應狀態值是304等。如果處理過程中出現了異常,就會將isError屬性設置爲true,最後會把配置好的statObj對象返回,等待下一個步驟的處理。

localXHR.jshandleTransactionResponse函數進行了簡化。因爲增加的getHttpStatus函數很好地封裝了與請求相關的各種狀態信息,所以在handleTransactionResponse函數中我們不會看到讓人頭暈目眩的響應狀態代碼。取而代之的是isErrorisOK這些更容易理解的屬性,localXHR.js直接使用這些屬性來處理響應。

createResponseObject函數被大大強化了。其實前半部分都是一樣的,localXHR.js中對isLocal做了大量的處理,響應中的responseText可以從連接中獲得。如果需要XML,它就使用ActiveXObject("Microsoft.XMLDOM")new DOMParser()responseText解析成XML放到response裏,響應狀態也是重新計算的,這樣就能讓Ajax正常調用了。

最後處理的是asyncRequest函數,如果在異步請求時出現異常,就調用handleTransac- tionResponse返回響應,然後根據各種情況稍微修改header屬性。

我們來看看下面這行代碼:

 

Ext.lib.Ajax.forceActiveX = (document.location.protocol == 'file:');               

 

如果協議是file:,就強制使用activex

10.12 本章小結

本章系統地討論了Ext.data包中的各個類的功能和使用方式,還涉及如何將EXTDWR通過自定義的proxy相結合的示例。我們介紹瞭如何使用Ext.data.Connection與後臺進行數據交互,還專門介紹了它的子類Ext.Ajax,並討論了EXTAjax的應用以及在回調函數中使用scopecreateDelegate()解決this的問題。

接着詳細介紹了類Ext.data.RecordExt.data.Store的功能和使用方法,這兩個類結合起來形成了Ext.data中的主體數據模型,很多組件(包括GridComboBox)都是建立在它們之上的。除此之外,還討論了常用的proxyreaderstoreSimpleStoreJsonStore,以及它們的應用場景。

最後我們介紹了擴展插件localXHR.js,它可以解決EXTAjax無法訪問本地文件的問題。

 

 

本章內容

q   Ext.data簡介

q   Ext.data.Connection

q   Ext.data.Record

q   Ext.data.Store

q   常用proxy

q   常用reader

q   高級store

q   EXT中的Ajax

q   關於scopecreateDelegate()

q   DWREXT整合

10.1 Ext.data簡介

Ext.data在命名空間中定義了一系列storereaderproxyGridComboxBox都是以Ext.data爲媒介獲取數據的,它包含異步加載、類型轉換、分頁等功能。Ext.data默認支持ArrayJSONXML等數據格式,可以通過MemoryHTTPScriptTag等方式獲得這些格式的數據。如果要實現新的協議和新的數據結構,只需要擴展readerproxy即可。DWRProxy就實現了自身的proxyreader,讓EXT可以直接從DWR獲得數據。

10.2 Ext.data.Connection

Ext.data.Connection是對Ext.lib.Ajax的封裝,它提供了配置使用Ajax的通用方式,它在內部通過Ext.lib.Ajax實現與後臺的異步調用。與底層的Ext.lib.Ajax相比,Ext.data. Connection提供了更簡潔的配置方式,使用起來更方便。

Ext.data.Connection主要用於在Ext.data.HttpProxyExt.data.ScriptTagProxy中執行與後臺交互的任務,它會從指定的URL獲得數據,並把後臺返回的數據交給HttpProxyScriptTagProxy處理,Ext.data.Connection的使用方式如代碼清單10-1所示。

代碼清單10-1 使用Ext.data.Connection

var conn = new Ext.data.Connection({

    autoAbort: false,

    defaultHeaders: {

        referer: 'http://localhost:8080/'

    },

    disableCaching : false,

    extraParams : {

        name: 'name'

    },

    method : 'GET',

    timeout : 300,

    url : '01-01.txt'

});

 

在使用Ext.data.Connection之前,都要像上面這樣創建一個新的Ext.Connection實例。我們可以在構造方法裏配置對應的參數,比如autoAbort表示鏈接是否會自動斷開、default- Headers參數表示請求的默認首部信息、disableCaching參數表示請求是否會禁用緩存、extraParams參數代表請求的額外參數、method參數表示請求方法、timeout參數表示連接的超時時間、url參數表示請求訪問的網址等。

在創建了conn之後,可以調用request()函數發送請求,處理返回的結果,如下面的代碼所示。

 

conn.request({

    success: function(response) {

        Ext.Msg.alert('info', response.responseText);

    },

    failure: function() {

        Ext.Msg.alert('warn', 'failure');

    }

});

 

Request()函數中可以設置successfailure兩個回調函數,分別在請求成功和請求失敗時調用。請求成功時,success函數的參數就是後臺返回的信息。

我們再來看一下request函數中的其他參數。

q   url:String:請求url

q   params:Object/String/Function:請求傳遞的參數。

q   method:String:請求方法,通常爲GETPOST

q   callback:Function:請求完成後的回調函數,無論是成功還是失敗,都會執行。

q   success:Function:請求成功時的回調函數。

q   failure:Function:請求失敗時的回調函數

q   scope:Object:回調函數的作用域。

q   form:Object/String:綁定的form表單。

q   isUpload:Boolean:是否執行文件上傳。

q   headers:Object:請求首部信息。

q   xmlData:ObjectXML文檔對象,可以通過URL附加參數的方式發起請求。

q   disableCaching:Boolean:是否禁用緩存,默認爲禁用。

Ext.data.Connection還提供了abort([Number transactionId])函數,當同時有多個請求發生時,根據指定的事務id放棄其中的某一個請求。如果不指定事務id,就會放棄最後一個請求。isLoading([Number transactionId])函數的用法與abort()類似,可以根據事務id判斷對應的請求是否完成。如果未指定事務id,就判斷最後一個請求是否完成。

10.3 Ext.data.Record

Ext.data.Record就是一個設定了內部數據類型的對象,它是Ext.data.Store的最基本組成部分。如果把Ext.data.Store看作是一張二維表,那麼它的每一行就對應一個Ext.data. Record實例。

Ext.data.Record的主要功能是保存數據,並且在內部數據發生改變時記錄修改的狀態,它還可以保留修改之前的原始值。

我們使用Ext.data.Record時通常都是由create()函數開始,首先用create()函數創建一個自定義的Record類型,如下面的代碼所示。

 

var PersonRecord = Ext.data.Record.create([

    {name: 'name', type: 'string'},

    {name: 'sex', type: 'int'}

]);

 

PersonRecord就是我們定義的新類型,包含字符串類型的name和整數類型的sex兩個屬性,然後我們使用new關鍵字創建PersonRecord的實例,如下面的代碼所示。

 

var boy = new PersonRecord({

    name: 'boy',

    sex: 0

});

 

創建對象時,可以直接通過構造方法爲對象賦予初始值,將'boy'賦值給name0賦值給sex

現在,我們得到了PersonRecord的實例boy,如何才能得到它的屬性呢?以下三種方式都可以獲得boyname屬性的數據,如下面的代碼所示。

 

alert(boy.data.name);

alert(boy.data['name']);

alert(boy.get('name'));

 

這裏涉及Ext.data.Recorddata屬性,這是定義在Ext.data.Record中的一個公共屬性,用於保存當前record對象的所有數據。它是一個JSON對象,可以直接從它裏面獲得需要的數據。可以通過Ext.data.Recordget()函數方便地從data屬性中獲得指定的屬性值。

如果我們需要修改boy中的數據,請不要使用以下方式直接操作data,如下面的代碼所示。

 

    boy.data.name = 'boy name';

    boy.data['name'] = 'boy name';

而應該使用set()函數,如下面的代碼所示。

 

    boy.set('name', 'body name');

 

set()函數會判斷屬性值是否發生了改變,如果改變了,就要將當前對象的dirty屬性設置爲true,並將修改之前的原始值放入modified對象中,供其他函數使用。如果直接操作data中的值,record就無法記錄屬性數據的修改情況。

Record的屬性數據被修改後,我們可以執行如下幾種操作。

q   commit()(提交):這個函數的效果是設置dirtyfalse,並刪除modified中保存的原始數據。

q   reject()(撤銷):這個函數的效果是將data中已經修改了的屬性值都恢復成modified中保存的原始數據,然後設置dirtyfalse,並刪除保存原始數據的modified對象。

q   getChanges()獲得修改的部分:這個函數會把data中經過修改的屬性和數據放在一個JSON對象裏並返回。例如上例中,getChanges()返回的結果是{name:’body name’}

q   我們還可以調用isModified()判斷當前record中的數據是否被修改。

      Ext.data.Record還提供了用於複製record實例的函數copy()

 

  var copyBoy = boy.copy();

 

這樣我們就得到了boy的一個副本,它裏面包含了boydata數據,但copy()函數不會複製dirtymodified等額外的屬性值。

Ext.data.Record中其他的參數大多與Ext.data.Store有關,請參考與Ext.data.Store相關的討論。

10.4 Ext.data.Store

Ext.data.StoreEXT中用來進行數據交換和數據交互的標準中間件,無論是Grid還是ComboBox,都是通過它實現數據讀取、類型轉換、排序分頁和搜索等操作的。

Ext.data.Store中有一個Ext.data.Record數組,所有數據都存放在這些Ext.data. Record實例中,爲後面的讀取和修改操作做準備。

10.4.1 基本應用

在使用之前,首先要創建一個Ext.data.Store的實例,如下面的代碼所示。

 

var data = [

    ['boy', 0],

    ['girl', 1]

];

 

var store = new Ext.data.Store({

    proxy: new Ext.data.MemoryProxy(data),

    reader: new Ext.data.ArrayReader({}, PersonRecord)

});

store.load();

 

每個store最少需要兩個組件的支持,分別是proxyreaderproxy用於從某個途徑讀取原始數據,reader用於將原始數據轉換成Record實例。

這裏我們使用的是Ext.data.MemoryProxyExt.data.ArrayReader,將data數組中的數據轉換成對應的幾個PersonRecord實例,然後放入store中。store創建完畢之後,執行store.load()實現這個轉換過程。

經過轉換之後,store裏的數據就可以提供給GridComboBox使用了,這就是Ext.data. Store的最基本用法。

10.4.2 對數據進行排序

Ext.data.Store提供了一系列屬性和函數,利用它們對數據進行排序操作。

可以在創建Ext.data.Store時使用sortInfo參數指定排序的字段和排序方式,如下面的代碼所示。

 

var store = new Ext.data.Store({

    proxy: new Ext.data.MemoryProxy(data),

    reader: new Ext.data.ArrayReader({}, PersonRecord),

    sortInfo: {field: 'name', direction: 'DESC'}

});

 

這樣,在store加載數據之後,就會自動根據name字段進行降序排列。對store使用store.setDefaultSort('name','DESC');也會達到同樣效果。

也可以在任何時候調用sort()函數,比如store.sort('name', 'DESC');,對store中的數據進行排序。

如果我們希望獲得store的排序信息,可以調用getSortState()函數,返回的是類似{field: "name", direction: " DESC"}JSON對象。

與排序相關的參數還有remoteSort,這個參數是用來實現後臺排序功能的。當設置爲remoteSort:true時,store會在向後臺請求數據時自動加入sortdir兩個參數,分別對應排序的字段和排序的方式,由後臺獲取並處理這兩個參數,在後臺對所需數據進行排序操作。remoteSort:true也會導致每次執行sort()時都要去後臺重新加載數據,而不能只對本地數據進行排序。

詳細的用法可以參考第2章。

10.4.3 store中獲取數據

store中獲取數據有很多種途徑,可以依據不同的要求選擇不同的函數。最直接的方法是根據recordstore中的行號獲得對應的record,得到了record就可以使用get()函數獲得裏面的數據了,如下面的代碼所示。

 

store.getAt(0).get('name')

 

通過這種方式,我們可以遍歷store中所有的record,依次得到它們的數據,如下面的代碼所示。

 

for (var i = 0; i < store.getCount(); i++) {

    var record = store.getAt(i);

    alert(record.get('name'));

}

 

Store.getCount()返回的是store中的所有數據記錄,然後使用for循環遍歷整個store,從而得到每條記錄。

除了使用getCount()的方法外,還可以使用each()函數,如下面的代碼所示。

 

store.each(function(record) {

    alert(record.get('name'));

});

 

Each()可以接受一個函數作爲參數,遍歷內部record,並將每個record作爲參數傳遞給function()處理。如果希望停止遍歷,可以讓function()返回false

也可以使用getRange()函數連續獲得多個record,只需要指定開始和結束位置的索引值,如下面的代碼所示。

 

var records = store.getRange(0, 1);

for (var i = 0; i < records.length; i++) {

    var record = records[i];

    alert(record.get('name'));

}

 

如果確實不知道recordid,也可以根據record本身的idstore中獲得對應的record,如下面的代碼所示。

 

store.getById(1001).get('name')

 

EXT還提供了函數find()findBy(),可以利用它們對store中的數據進行搜索,如下面的代碼所示。

 

find( String property, String/RegExp value, [Number startIndex], [Boolean anyMatch],

[Boolean caseSensitive] )

 

在這5個參數中,只有前兩個是必須的。第一個參數property代表搜索的字段名;第二個參數value是匹配用字符串或正則表達式;第三個參數startIndex表示從第幾行開始搜索,第四個參數anyMatchtrue時,不必從頭開始匹配;第五個參數caseSensitivetrue時,會區分大小寫。

如下面的代碼所示:

 

var index = store.find('name','g');

alert(store.getAt(index).get('name'));

 

find()函數對應的findBy()函數的定義格式如下:

 

findBy( Function fn, [Object scope], [Number startIndex] ) : Number

 

findBy()函數允許用戶使用自定義函數對內部數據進行搜索。fn返回true時,表示查找成功,於是停止遍歷並返回行號。fn返回false時,表示查找失敗(即未找到),繼續遍歷,如下面的代碼所示。

 

index = store.findBy(function(record, id) {

    return record.get('name') == 'girl' && record.get('sex') == 1;

});

alert(store.getAt(index).get('name'));

 

通過findBy()函數,我們可以同時判斷record中的多個字段,在函數中實現複雜邏輯。

我們還可以使用queryqueryBy函數對store中的數據進行查詢。與findfindBy不同的是,queryqueryBy返回的是一個MixCollection對象,裏面包含了搜索得到的數據,如下面的代碼所示。

 

alert(store.query('name', 'boy'));

    alert(store.queryBy(function(record) {

        return record.get('name') == 'girl' && record.get('sex') == 1;

    }));

10.4.4 更新store中的數據

可以使用add(Ext.data.Record[] records)store末尾添加一個或多個record,使用的參數可以是一個record實例,如下面的代碼所示。

 

store.add(new PersonRecord({

    name: 'other',

    sex: 0

}));

 

Add()的也可以添加一個record數組,如下面的代碼所示:

 

store.add([new PersonRecord({

    name: 'other1',

    sex: 0

}), new PersonRecord({

    name: 'other2',

    sex: 0

})]);

 

Add()函數每次都會將新數據添加到store的末尾,這就有可能破壞store原有的排序方式。如果希望根據store原來的排序方式將新數據插入到對應的位置,可以使用addSorted()函數。它會在添加新數據之後立即對store進行排序,這樣就可以保證store中的數據有序地顯示,如下面的代碼所示。

 

store.addSorted(new PersonRecord({

    name: 'lili',

    sex: 1

}));

 

store會根據排序信息查找這條record應該插入的索引位置,然後根據得到的索引位置插入數據,從而實現對整體進行排序。這個函數需要預先爲store設置本地排序,否則會不起作用。

如果希望自己指定數據插入的索引位置,可以使用insert()函數。它的第一個參數表示插入數據的索引位置,可以使用record實例或record實例的數組作爲參數,插入之後,後面的數據自動後移,如下面的代碼所示。

 

store.insert(3, new PersonRecord({

    name: 'other',

    sex: 0

}));

 

store.insert(3, [new PersonRecord({

    name: 'other1',

    sex: 0

}), new PersonRecord({

    name: 'other2',

    sex: 0

})]);

 

刪除操作可以使用remove()removeAll()函數,它們分別可以刪除指定的record和清空整個store中的數據,如下面的代碼所示。

 

store.remove(store.getAt(0));

store.removeAll();

 

store中沒有專門提供修改某一行record的操作,我們需要先從store中獲取一個record。對這個record內部數據的修改會直接反映到store上,如下面的代碼所示。

 

store.getAt(0).set('name', 'xxxx');

 

修改record的內部數據之後有兩種選擇:執行rejectChanges()撤銷所有修改,將修改過的record恢復到原來的狀態;執行commitChanges()提交數據修改。在執行撤銷和提交操作之前,可以使用getModifiedRecords()獲得store中修改過的record數組。

與修改數據相關的參數是pruneModifiedRecords,如果將它設置爲true,當每次執行刪除或reload操作時,都會清空所有修改。這樣,在每次執行刪除或reload操作之後,getModifiedRecords()返回的就是一個空數組,否則仍然會得到上次修改過的record記錄。

10.4.5 加載及顯示數據

store創建好後,需要調用load()函數加載數據,加載成功後才能對store中的數據進行操作。load()調用的完整過程如下面的代碼所示。

 

store.load({

    params: {start:0,limit:20},

    callback: function(records, options, success){

        Ext.Msg.alert('info', '加載完畢');

    },

    scope: store,

    add: true

});

 

q   params是在store加載時發送的附加參數。

q   callback是加載完畢時執行的回調函數,它包含3個參數:records參數表示獲得的數據,options表示執行load()時傳遞的參數,success表示是否加載成功。

q   Scope用來指定回調函數執行時的作用域。

q   Addtrue時,load()得到的數據會添加在原來的store數據的末尾,否則會先清除之前的數據,再將得到的數據添加到store中。

一般來說,爲了對store中的數據進行初始化,load()函數只需要執行一次。如果用params參數指定了需要使用的參數,以後再次執行reload()重新加載數據時,store會自動使用上次load()中包含的params參數內容。

如果有一些需要固定傳遞的參數,也可以使用baseParams參數執行,它是一個JSON對象,裏面的數據會作爲參數發送給後臺處理,如下面的代碼所示。

 

store.baseParams.start = 0;

store.baseParams.limit = 20;

 

store加載數據之後,有時不需要把所有數據都顯示出來,這時可以使用函數filterfilterBystore中的數據進行過濾,只顯示符合條件的部分,如下面的代碼所示。

 

filter( String field, String/RegExp value, [Boolean anyMatch],
[Boolean caseSensitive] ) : void

 

filter()函數的用法與之前談到的find()相似,如下面的代碼所示。

 

store.filter('name', 'boy');

 

對應的filterBy()findBy()類似,也可以在自定義的函數中實現各種複雜判斷,如下面的代碼所示。

 

store.filterBy(function(record) {

    return record.get('name') == 'girl' && record.get('sex') == 1;

});

 

如果想取消過濾並顯示所有數據,那麼可以調用clearFilter()函數,如下面的代碼所示。

 

store.clearFilter();

 

如果想知道store上是否設置了過濾器,可以通過isFiltered()函數進行判斷。

10.4.6 其他功能

除了上面提到的數據獲取、排序、更新、顯示等功能外,store還提供了其他一些功能函數。

 

collect( String dataIndex, [Boolean allowNull], [Boolean bypassFilter] ) : Array

 

collect函數獲得指定的dataIndex對應的那一列的數據,當allowNull參數爲true時,返回的結果中可能會包含nullundefined或空字符串,否則collect函數會自動將這些空數據過濾掉。當bypassFilter參數爲true時,collect的結果不會受查詢條件的影響,無論查詢條件是什麼都會忽略掉,返回的信息是所有的數據,如下面的代碼所示。

 

alert(store.collect('name'));

 

這樣會獲得所有name列的值,示例中返回的是包含了'boy''girl'的數組。

getTotalCount()用於在翻頁時獲得後臺傳遞過來的數據總數。如果沒有設置翻頁,get- TotalCount()的結果與getCount()相同,都是返回當前的數據總數,如下面的代碼所示。

 

alert(store.getTotalCount());

 

indexOf(Ext.data.Record record)indexOfId(String id)函數根據recordrecordid獲得record對應的行號,如下面的代碼所示。

 

alert(store.indexOf(store.getAt(1)));

alert(store.indexOfId(1001));

 

loadData(object data, [Boolean append])從本地JavaScript變量中讀取數據,appendtrue時,將讀取的數據附加到原數據後,否則執行整體更新,如下面的代碼所示。

 

store.loadData(data, true);

 

Sum(String property, Number start, Number end):Number用於計算某一個列從startend的總和,如下面的代碼所示。

 

alert(store.sum('sex'));

 

如果省略參數startend,就計算全部數據的總和。

store還提供了一系列事件(見表10-1),讓我們可以爲對應操作設定操作函數。

10-1 store提供的事件

事件名

參  數

add

( Store this, Ext.data.Record[] records, Number index )

beforelaod

( Store this, Object options )

clear

( Store this )

datachanged

( Store this )

load

( Store this, Ext.data.Record[] records, Object options )

loadexception

()

metachange

( Store this, Object meta. )

remove

( Store this, Ext.data.Record record, Number index )

update

( Store this, Ext.data.Record record, String operation )

至此,storerecord等組件已經講解完畢,下面我們主要討論一下常用的proxyreader組件。

10.5 常用proxy

10.5.1 MemoryProxy

MemoryProxy只能從JavaScript對象獲得數據,可以直接把數組,或JSONXML格式的數據交給它處理,如下面的代碼所示。

 

var proxy = new Ext.data.MemoryProxy([

    ['id1','name1','descn1'],

    ['id2','name2','descn2']

]);               

10.5.2 HttpProxy

HttpProxy使用HTTP協議,通過Ajax去後臺取數據,構造它時需要設置url:'xxx.jsp'參數。這裏的url可以替換成任何一個合法的網址,這樣HttpProxy才知道去哪裏獲取數據,如下面的代碼所示。

 

var proxy = new Ext.data.HttpProxy({url:'xxx.jsp'});               

 

後臺需要返回EXT所需要的JSON格式的數據,下面的內容就是後臺使用JSP的一個範例,如下面的代碼所示。

 

response.setContentType("application/x-json");

Writer out = response.getWriter();

out.print("[" +

        "['id1','name1','descn1']" +

        "['id2','name2','descn2']" +

    "]");               

 

請注意,這裏的HttpProxy不支持跨域,它只能從同一域中獲得數據。如果想跨域,請參考下面的ScriptTagProxy

10.5.3 ScriptTagProxy

ScriptTagProxy的用法幾乎和HttpProxy一樣,如下面的代碼所示。

 

var proxy = new Ext.data.ScriptTagProxy({url:'xxx.jsp'});               

 

從這裏也看不出來它是如何支持跨域的,我們還需要在後臺進行相應的處理,如下面的代碼所示。

 

String cb = request.getParameter("callback");

response.setContentType("text/javascript");

Writer out = response.getWriter();

out.write(cb + "(");

out.print("[" +

        "['id1','name1','descn1']" +

        "['id2','name2','descn2']" +

    "]");

out.write(");");

 

其中的關鍵就在於從請求中獲得的callback參數,這個參數叫做回調函數。ScriptTag- Proxy會在當前的HTML頁面裏添加一個<script type="text/javascript"src="xxx.jsp"> </script>標籤,然後把後臺返回的內容添加到這個標籤中,這樣就可以解決跨域訪問數據的問題。爲了讓後臺返回的內容可以在動態生成的標籤中運行,EXT會生成一個名爲callback的回調函數,並把回調函數的名稱傳遞給後臺,由後臺生成callback(data)形式的響應內容,然後返回給前臺自動運行。

雖然上述處理過程比較難理解,但是我們只需要瞭解ScriptTagProxy的用法就足夠了。如果還想進一步瞭解ScriptTagProxy的運行過程,可以使用Firebug查看動態生成的HTML以及響應的JSON內容。

最後我們來分析一下EXTAPI文檔中提供的示例,這段後臺代碼會自動判斷請求的類型,返回支持ScriptTagProxyHttpProxy的數據,如代碼清單10-2所示。

代碼清單10-2 在後臺同時支持HttpProxyScriptTagProxy

boolean scriptTag = false;

String cb = request.getParameter("callback");

if (cb != null) {

    scriptTag = true;

    response.setContentType("text/javascript");

} else {

    response.setContentType("application/x-json");

}

Writer out = response.getWriter();

if (scriptTag) {

    out.write(cb + "(");

}

out.print(dataBlock.toJsonString());

if (scriptTag) {

    out.write(");");

}               

 

代碼中通過判斷請求中是否包含callback參數來決定返回何種數據類型。如果包含,就返回ScriptTagProxy需要的數據;否則,就當作HttpProxy處理。

10.6 常用Reader

10.6.1 ArrayReader

proxy中讀取的數據需要進行解析,這些數據轉換成Record數組後才能提供給Ext.data. Store使用。

ArrayReader的作用是從二維數組裏依次讀取數據,然後生成對應的Record。默認情況下是按列順序讀取數組中的數據,不過你也可以考慮用mapping指定record與原始數組對應的列號。ArrayReader的用法很簡單,但缺點是不支持分頁。使用二維數組的方式如下面的代碼所示。

 

var data = [

    ['id1','name1','descn1'],

    ['id2','name2','descn2']

];

 

對應的ArrayReader如下面的代碼所示。

 

var reader = new Ext.data.ArrayReader({

    id:1

},[

    {name:'name',mapping:1},

    {name:'descn',mapping:2},

    {name:'id',mapping:0},

]);

 

我們演示的是字段順序不一致的情況,如果字段順序和列順序一致,就不用額外配置mapping

10.6.2 JsonReader

JavaScript中,JSON是一種非常重要的數據格式,key:value的形式比XML那種複雜的標籤結構更容易理解,代碼量也更小,很多人傾向於使用它作爲EXT的數據交換格式。爲Json- Reader準備的JSON數據如下面的代碼所示。

 

var data = {

    id:0,

    totalProperty:2,

    successProperty:true,

    root:[

        {id:'id1',name:'name1',descn:'descn1'},

        {id:'id2',name:'name2',descn:'descn2'}

    ]

};

 

與數組相比,JSON的最大優點就是支持分頁,我們可以使用totalProperty參數表示數據的總量。successProperty參數是可選的,可以用它判斷當前請求是否執行成功,進而判斷是否進行數據加載。在不希望JsonReader處理響應數據時,可以把successProperty設置成false

現在來討論一下JsonReader,看看它是如何與上面的JSON數據對應的,如下面的代碼所示。

 

var reader = new Ext.data.JsonReader({

    successProperty: "successproperty",

    totalProperty: "totalProperty",

    root: "root",

    id: "id"

}, [

    {name:'id',mapping:'id'},

    {name:'name',mapping:'name'},

    {name:'descn',mapping:'descn'}

]);

 

上例中的對應方式不夠簡潔,因爲namemapping部分的內容是相同的,其實這裏的mapping可以省略,默認會用name參數從JSON中獲得對應的數據。如果不想與JSON裏的名字一樣,也可以使用mapping修改。不過,mapping在這裏還有其他用途,如代碼清單10-3所示。

代碼清單10-3 爲JsonReader設置mapping進行數據映射

var data = {

    id:0,

    totalProperty:2,

    successProperty:true,

    root:[

        {id:'id1',name:'name1',descn:'descn1',person:{

            id:1,name:'man',sex:'male'

        }},

        {id:'id2',name:'name2',descn:'descn2',person:{

            id:2,name:'woman',sex:'female'

        }}

    ]

};

var reader = new Ext.data.JsonReader({

    successProperty: "successproperty",

    totalProperty: "totalProperty",

    root: "root",

    id: "id"

}, [

    'id','name','descn',

    {name:'person_name',mapping:'person.name'},

    {name:'person_sex',mapping:'person.sex'}

]);

 

在上面的代碼中,我們使用JSON支持更復雜的嵌套結構,其中的person對象自身就擁有id namesex等屬性。在JsonReader中可以用mapping把這些嵌套的內部屬性映射出來,賦予對應的record,而其他字段都不變。

10.6.3 XmlReader

XML是非常通用的數據傳輸格式,XmlReader使用的XML格式的數據如代碼清單10-4所示。

代碼清單10-4 XmlReader使用的XML格式的數據

<?xml version="1.0" encoding="utf-8"?>

<dataset>

    <id>1</id>

    <totalRecords>2</totalRecords>

    <success>true</success>

    <record>

        <id>1</id>

        <name>name1</name>

        <descn>descn1</descn>

    </record>

    <record>

        <id>2</id>

        <name>name2</name>

        <descn>descn2</descn>

    </record>

</dataset>

 

這裏一定要用dataset作爲XML根元素。再讓我們看一下如何對XmlReader進行配置,從而讀取上面示例中的XML數據,如下面的代碼所示。

 

var reader = new Ext.data.XmlReader({

   totalRecords: 'totalRecords',

   success: 'success'

   record: 'record',

   id: "id"

}, ['id','name','descn']);               

 

XmlReader使用的參數與之前介紹的JsonReader有些不同,我們可以看到這裏用到了totalRecordsrecord兩個參數,其中totalRecords用來指定從’totalRecords’標籤裏獲得後臺數據總數,record則表示XML中放在record標籤裏的數據是我們需要顯示的結果數據。其他兩個參數successid的含義和JsonReader中對應的參數相似,分別用來判斷操是否成功和這次返回的id。因爲XML中的標籤和reader裏需要的名字是相同的,所以簡化了配置,將[{name:’id’},{name:’name’},{name:’descn’}]直接寫成了[‘id’,’name’,’descn’]

因爲XmlReader不能將JavaScript中的字符串自動解析成XML格式的數據,因此我們需要利用其他方法進行演示。參考localXHR.js中構造XML的方式,我們有了下面的解決方案,如代碼清單10-5所示。

代碼清單10-5 通過本地字符串構造XML對象

var data = "<?xml version='1.0' encoding='utf-8'?>" +

    "<dataset>" +

        "<id>1</id>" +

        "<totalRecords>2</totalRecords>" +

        "<success>true</success>" +

        "<record>" +

            "<id>1</id>" +

            "<name>name1</name>" +

            "<descn>descn1</descn>" +

        "</record>" +

        "<record>" +

            "<id>2</id>" +

            "<name>name2</name>" +

            "<descn>descn2</descn>" +

        "</record>" +

    "</dataset>";

 

var xdoc;

 

if(typeof(DOMParser) == 'undefined'){

    xdoc = new ActiveXObject("Microsoft.XMLDOM");

    xdoc.async="false";

    xdoc.loadXML(data);

}else{

    var domParser = new DOMParser();

    xdoc = domParser.parseFromString(data, 'application/xml');

    domParser = null;

}

 

var proxy = new Ext.data.MemoryProxy(xdoc);

 

var reader = new Ext.data.XmlReader({

    totalRecords: 'totalRecords',

    success: 'success',

    record: 'record',

    id: "id"

}, ['id','name','descn']);

 

var ds = new Ext.data.Store({

    proxy: proxy,

    reader: reader

});

10.7 高級store

實際開發時,並不需要每次都對proxyreaderstore這三個對象進行配置,EXT爲我們提供了幾種可選擇的整合方案。

q   SimpleStore = Store + MemoryProxy + ArrayReader

 

  var ds = Ext.data.SimpleStore({

      data: [

            ['id1','name1','descn1'],

            ['id2','name2','descn2']

      ],

      fields: ['id','name','descn']

  });

 

SimpleStore是專爲簡化讀取本地數組而設計的,設置上MemoryProxy需要的dataArrayReader需要的fields就可以使用了。

q   JsonStore = Store + HttpProxy + JsonReader

 

  var ds = Ext.data.JsonStore({

      url: 'xxx.jsp',

      root: 'root',

      fields: ['id','name','descn']

  });

 

JsonStoreJsonReaderHttpProxy整合在一起,提供了一種從後臺讀取JSON信息的簡便方法,大多數情況下可以考慮直接使用它從後臺讀取數據。

q   Ext.data.GroupingStore對數據進行分組

Ext.data.GroupingStore繼承自Ext.data.Store,它的主要功能是可以對內部的數據進行分組,我們可以在創建Ext.data.GroupingStore時指定根據某個字段進行分組,也可以在創建實例後調用它的groupBy()函數對內部數據重新分組,如下面的代碼所示。

 

    var ds = new Ext.data.GroupingStore({

        data: [

            ['id1','name1','female','descn1'],

            ['id2','name2','male','descn2'],

            ['id3','name3','female','descn3'],

            ['id4','name4','male','descn4'],

            ['id5','name5','female','descn5']

        ],

        reader: new Ext.data.ArrayReader({

            fields: ['id','name','sex','descn']

        }),

        groupField: 'sex',

        groupOnSort: true

    });

 

上例中,我們使用groupField作爲參數,爲Ext.data.Grouping設置了分組字段,另外還設置了groupOnSort參數,這個參數可以保證只有在進行分組時纔會對Ext.data.Grouping- Store內部的數據進行排序。如果採用默認值,就需要手工指定sortInfo參數,從而指定默認的排序字段和排序方式,否則就會出現錯誤。

創建Ext.data.GroupingStore的實例之後,我們還可以調用groupBy()函數重新對數據進行分組。因爲我們設置了groupOnSort:true,所以在重新分組時,EXT會使用分組的字段對內部數據進行排序。如果不希望對數據進行分組,也可以調用clearGrouping()函數清除分組信息,如下面的代碼所示。

 

    ds.groupBy('id');

    ds.clearGrouping();

10.8 EXT中的Ajax

EXT與後臺交換數據時,很大程度上依賴於底層實現的Ajax。所謂底層實現,就是說很可能就是我們之前提到的 PrototypejQueryYUI中提供的Ajax功能。爲了統一接口,EXT在它們的基礎上進行了封裝,讓我們可以用同一種寫法“遊走”於各種不同的底層實現之間。

10.8.1 最容易看到的Ext.Ajax

Ext.Ajax的基本用法如下所示。

 

Ext.Ajax.request({

    url: '07-01.txt',

    success: function(response) {

        Ext.Msg.alert('成功', response.responseText);

    },

    failure: function(response) {

        Ext.Msg.alert('失敗', response.responseText);

    },

    params: { name: 'value' }

});

 

這裏調用的是Ext.Ajaxrequest函數,它的參數是一個JSON對象,具體如下所示。

q   url參數表示將要訪問的後臺網址。

q   success參數表示響應成功後的回調函數。

上例中我們直接從response取得返回的字符串,用Ext.Msg.alert顯示出來。

q   failure參數表示響應失敗後的回調函數。

注意,這裏的響應失敗並不是指數據庫操作之類的業務性失敗,而是指HTTP返回404500錯誤,請不要把HTTP響應錯誤與業務錯誤混淆在一起。

q   params參數表示請求時發送到後臺的參數,既可以使用JSON對象,也可以直接使用"name=value"形式的字符串。

上面的示例可以在10.store/07-01.html中找到。

Ext.Ajax直接繼承自Ext.data.Connection,不同的是,它是一個單例,不需要用new創建實例,可以直接使用。在使用Ext.data.Connection前需要先創建實例,因爲Ext.data. Connection是爲了給Ext.data中的各種proxy提供Ajax功能,分配不同的實例更有利於分別管理。Ext.Ajax爲用戶提供了一個簡易的調用接口,實際使用時,可以根據自己的需要進行選擇。

10.8.2 Ext.lib.Ajax是更底層的封裝

其實Ext.AjaxExt.data.Connection的內部功能實現都是依靠Ext.lib.Ajax來完成的,在Ext.lib.Ajax下面就是各種底層庫的Ajax了。

如果使用Ext.lib.Ajax實現以上的功能,就需要寫成下面的形式,如下面的代碼所示。

 

Ext.lib.Ajax.request(

    'POST',

    '07-01txt',

    {success: function(response){

        Ext.Msg.alert('成功', response.responseText);

    },failure: function(){

        Ext.Msg.alert('失敗', response.responseText);

    }},

    'data=' + encodeURIComponent(Ext.encode({name:'value'}))

);

 

我們可以看到,使用Ext.lib.Ajax時需要傳遞4個參數,分別爲methodurlcallbackparams。它們的含義與Ext.Ajax中的參數都是一一對應的,唯一沒有提到過的method參數表示請求HTTP的方法,它也可以在Ext.Ajax中使用method:'POST'的方式設置。

相對於Ext.Ajax來說,Ext.lib.Ajax有如下幾個缺點。

q   參數的順序被定死了,第一個參數是method,第二個參數是url,第三個參數是回調函數callback,第四個參數是params。這樣既不容易記憶,也無法省略其中某個不需要的參數。Ext.Ajax中用JSON對象來定義參數,使用起來更靈活。

q   params部分,Ext.lib.Ajax必須使用字符串形式,顯得有些笨重。Ext.Ajax則可以在JSON對象和字符串之間隨意選擇,非常靈活。

比與Ext.Ajax相比,Ext.lib.Ajax的唯一優勢就是它可以在EXT 1.x中使用。如果你使用的是EXT 2.0或更高的版本,那麼就放心大膽地使用Ext.Ajax吧,它會帶給你更多的驚喜。

該示例在10.store/07-02.html中。

10.9 關於scopecreateDelegate()

關於JavaScriptthis的使用,這是一個由來已久的問題了。我們這裏就不介紹它的發展歷史了,只結合具體的例子,告訴大家可能會遇到什麼問題,在遇到這些問題時EXT是如何解決的。在使用EXT時,最常碰到的就是使用Ajax回調函數時出現的問題,如下面的代碼所示。

 

<input type="text" name="text" id="text">

<input type="button" name="button" id="button" value="button">

 

現在的HTML 頁面中有一個text輸入框和一個按鈕。我們希望按下這個按鈕之後,能用Ajax去後臺讀取數據,然後把後臺響應的數據放到text中,實現過程如代碼清單10-6所示。

代碼清單10-6 Ajax中使用回調函數

function doSuccess(response) {

    text.dom.value = response.responseText;

}

 

Ext.onReady(function(){

    Ext.get('button').on('click', function(){

        var text = Ext.get('text');

        Ext.lib.Ajax.request(

            'POST',

            '08.txt',

            {success:doSuccess},

            'param=' + encodeURIComponent(text.dom.value)

        );

    });

});

 

在上面的代碼中,Ajax已經用Ext.get('text')獲得了text,以爲後面可以直接使用,沒想到回調函數success不會按照你寫的順序去執行。當然,也不會像你所想的那樣使用局部變量text。實際上,如果什麼都不做,僅僅只是使用回調函數,你不得不再次使用Ext.get('text')重新獲得元素,否則瀏覽器就會報text未定義的錯誤。

在此使用Ext.get('text')重新獲取對象還比較簡單,在有些情況下不容易獲得需要處理的對象,我們要在發送Ajax請求之前獲取回調函數中需要操作的對象,有兩種方法可供選擇:scopecreateDelegate

q   Ajax設置scope

 

  function doSuccess(response) {

      this.dom.value = response.responseText;

  }

  Ext.lib.Ajax.request(

      'POST',

      '08.txt',

      {success:doSuccess,scope:text},

      'param=' + encodeURIComponent(text.dom.value)

  );               

 

Ajaxcallback參數部分添加一個scope:text,把回調函數的scope指向text,它的作用就是把doSuccess函數裏的this指向text對象。然後再把doSuccess裏改成this.dom. value,這樣就可以了。如果想再次在回調函數裏用某個對象,必須配上scope,這樣就能在回調函數中使用this對它進行操作了。

q   success添加createDelegate()

 

  function doSuccess(response) {

      this.dom.value = response.responseText;

  }

 

  Ext.lib.Ajax.request(

      'POST',

      '08.txt',

      {success:doSuccess.createDelegate(text)},

      'param=' + encodeURIComponent(text.dom.value)

  );

 

createDelegate只能在function上調用,它把函數裏的this強行指向我們需要的對象,然後我們就可以在回調函數doSuccess裏直接通過this來引用createDelegate()中指定的這個對象了。它可以作爲解決this問題的一個備選方案。

如果讓我選擇,我會盡量選擇scope,因爲createDelegate是要對原來的函數進行封裝,重新生成function對象。簡單環境下,scope就夠用了,倒是createDelegate還有其他功能,比如修改調用參數等。

示例在10.store/08.html中。

10.10 DWREXT整合

據不完全統計,從事Ajax開發的Java程序員有一大半都使用DWR。我們下面來介紹一下如何在EXT中使用DWR與後臺交互。

10.10.1 EXT中直接使用DWR

因爲DWR在前臺的表現形式和普通的JavaScript完全一樣,所以我們不需要特地去做些什麼,直接使用EXT調用DWR生成的JavaScript函數即可。以Grid爲例,比如現在我們要顯示一個通訊錄的信息,後臺記錄的數據有:idnamesexemailteladdTimedescn。編寫對應的POJO,代碼如下所示。

 

public class Info {

    long id;

    String name;

    int sex;

    String email;

    String tel;

    Date addTime;

    String descn;

}

 

然後編寫操作POJOmanager類,代碼如下所示。

 

public class InfoManager {

    private List infoList = new ArrayList();

 

    public List getResult() {

        return infoList;

    }

}

 

代碼部分有些刪減,我們只保留了其中的關鍵部分,就這樣把這兩個類配置到dwr.xml中,讓前臺可以對這些類進行調用。

下面是EXTDWR交互的關鍵部分,我們要對JavaScript部分做如下修改,如代碼清單10-7所示。

代碼清單10-7 使用EXT調用DWR

var cm = new Ext.grid.ColumnModel([

    {header:'編號',dataIndex:'id'},

    {header:'名稱',dataIndex:'name'},

    {header:'性別',dataIndex:'sex'},

    {header:'郵箱',dataIndex:'email'},

    {header:'電話',dataIndex:'tel'},

    {header:'添加時間',dataIndex:'addTime'},

    {header:'備註',dataIndex:'descn'}

]);

 

var store = new Ext.data.JsonStore({

    fields: ["id","name","sex",'email','tel','addTime','descn']

});

 

// 調用DWR取得數據

infoManager.getResult(function(data) {

    store.loadData(data);

});

 

var grid = new Ext.grid.GridPanel({

    renderTo: 'grid',

    store: store,

    cm: cm

});

 

注意,執行infoManager.getResult()函數時,DWR就會使用Ajax去後臺取數據了,操作成功後調用我們定義的匿名回調函數。在這裏我們只做一件事,那就是將返回的data直接注入到ds中。

DWR返回的data可以被JsonStore直接讀取,我們需要設置對應的fields參數,以告訴JsonReader需要哪些屬性。

在這裏,EXTDWR兩者之間沒有任何關係,將它們任何一方替換掉都可以。實際上它們只是在一起運行,並沒有整合。我們給出的這個示例也是說明了一種鬆耦合的可能性,實際操作中完全可以使用這種方式。

10.10.2 DWRProxy

要結合使用EXTDWR,不需要對後臺程序進行任何修改,可以直接讓前後臺數據進行交互。不過還要考慮很多細節,比如Grid分頁、刷新、排序、搜索等常見的操作。EXT的官方網站上已經有人放上了DWRProxy,藉助它可以讓DWREXT連接得更加緊密。不過,需要在後臺添加DWRProxy所需要的Java類,這可能不是最好的解決方案。但我們相信,通過對它的內在實現的討論,我們可以有更多的選擇和想象空間。

注意     這個DWRProxy.js一定要放在ext-base.jsext-all.js後面,否則會出錯。

我們現在就用DWRProxy來實現一個分頁的示例。除了準備好插件DWRProxy.js外,還要在後臺準備一個專門用於分頁的封裝類。因爲不僅要告訴前臺顯示哪些數據,還要告訴前臺一共有多少條數據。現在我們來重點看一下ListRange.java,如下面的代碼所示。

 

public class ListRange {

    Object[] data;

    int totalSize;

}

 

其實ListRange非常簡單,只有兩個屬性:提供數據的data和提供數據總量的totalSize。再看一下InfoManager.java,爲了實現分頁,我們專門編寫了一個getItems方法,代碼如下所示。

 

public ListRange getItems(Map conditions) {

    int start = 0;

    int pageSize = 10;

    int pageNo = (start / pageSize) + 1;

 

    try {

        start = Integer.parseInt(conditions.get("start").toString());

        pageSize = Integer.parseInt(conditions.get("limit").toString());

        pageNo = (start / pageSize) + 1;

    } catch (Exception ex) {

        ex.printStackTrace();

    }

    List list = infoList.subList(start, start + pageSize);

    return new ListRange(list.toArray(), infoList.size());

}

 

getItems()的參數是Map,我們從中獲得需要的參數,比如startlimit。不過HTTP裏的參數都是字符串,而我們需要的是數字,所以要對類型進行相應的轉換。根據startlimit兩個屬性從全部數據中截取一部分,放進新建的ListRange中,然後把生成的ListRange返回給前臺,於是一切都解決了。

重頭戲要上演了,我們就要使用傳說中的Ext.data.DWRProxy了,還有Ext.data.List- RangeReader。通過這兩個擴展,EXT完全可以支持DWR的數據傳輸協議。實際上,這正是EXT要把數據和顯示分離設計的原因,這樣你只需要添加自定義的proxyreader,不需要修改EXT的其他部分,就可以實現從特定途徑獲取數據的功能。後臺還是DWR,所以至少在Grid部分,我們可以很好地使用它們的結合,主要代碼如下所示。

var store = new Ext.data.Store({

    proxy: new Ext.data.DWRProxy(infoManager.getItems, true),

    reader: new Ext.data.ListRangeReader({

        totalProperty: 'totalSize',

        root: 'data',

        id: 'id'

    }, info),

    remoteSort: true

});

 

與我們上面說的一樣,我們修改了proxy,也修改了reader,其他地方都不需要進行修改,Grid已經可以正常運行了。需要提醒的是DWRProxy的用法,其中包括兩個參數:第一個是dwr- Call,它把一個DWR函數放進去,它對應的是後臺的getItems方法;第二個參數是paging- AndSort,這個參數控制DWR是否需要分頁和排序。

ListRangeReader部分與後臺的ListRange.java對應。totalProperty表示後臺數據總數,我們通過它指定從ListRange中讀取totalSize屬性的值來作爲後臺數據總數。還需要指定root參數,以告訴它在ListRange中的數據變量的名稱爲data,隨後DWRProxy會從ListRange中的data屬性中獲取數據並顯示到頁面上。如果不想使用我們提供的ListRange.java,也可以自己創建一個類,只要把totalPropertydata兩個屬性與之對應即可。

10.10.3 DWRTreeLoader

我們現在來嘗試一下讓樹形也支持DWR。有了前面的基礎,整合DWRtree就更簡單了。在後臺,我們需要樹形節點對應的TreeNode.java。目前,只要idtextleaf三項就可以了。

 

public class TreeNode {

    String id;

    String text;

    boolean leaf;

}

 

id是節點的唯一標記,知道了id就能知道是在觸發哪個節點了。text是顯示的標題,leaf比較重要,它用來標記這個節點是不是葉子。

這裏還是用異步樹,TreeNodeManager.java裏的getTree()方法將獲得一個節點的id作爲參數,然後返回這個節點下的所有子節點。我們這裏沒有限制生成的樹形的深度,你可以根據自己的需要進行設置。TreeNodeManager.java的代碼如下所示。

 

public List getTree(String id) {

    List list = new ArrayList();

    String seed1 = id + 1;

    String seed2 = id + 2;

    String seed3 = id + 3;

    list.add(new TreeNode(seed1, "" + seed1, false));

    list.add(new TreeNode(seed2, "" + seed2, false));

    list.add(new TreeNode(seed3, "" + seed3, true));

 

    return list;

}

 

上面的代碼並不複雜,它實現的效果與在Java中使用List或數組是相同的,因爲返回給前臺的數據都是JSON格式的。前臺使用JavaScript處理返回信息的部分更簡單,先引入DWRTree- Loader.js,然後把TreeLoader替換成DWRTreeLoder即可,如下面的代碼所示。

 

var tree = new Ext.tree.TreePanel('tree', {

    loader: new Ext.tree.DWRTreeLoader({dataUrl: treeNodeManager.getTree})

});

 

參數依然是dataUrl,它的值treeNodeManager.getTree代表的是一個DWR函數,我們不需要對它進行深入研究,它的內部會自動處理數據之間的對應關係。DWR有時真的很方便。

10.10.4 DWRProxyComboBox

DWRProxy既然可以用在Ext.data.Store中,那麼它也可以爲ComboBox服務,如代碼清單10-8所示。

代碼清單10-8 DWRProxyComboBox整合

var info = Ext.data.Record.create([

    {name: 'id', type: 'int'},

    {name: 'name', type: 'string'}

]);

 

var store = new Ext.data.Store({

    proxy: new Ext.data.DWRProxy(infoManager.getItems, true),

    reader: new Ext.data.ListRangeReader({

        totalProperty: 'totalSize',

        root: 'data',

        id: 'id'

    }, info)

});

 

var combo = new Ext.form.ComboBox({

    store: store,

    displayField: 'name',

    valueField: 'id',

    triggerAction: 'all',

    typeAhead: true,

    mode: 'remote',

    emptyText: '請選擇',

    selectOnFocus: true

});

combo.render('combo');

 

我們既可以用mode:'remote'triggerAction:'all'在第一次選擇時讀取數據,也可以設置mode:'local',然後手工操作store.load()並讀取數據。

DWR要比Json-lib方便得多,而且DWR返回的數據可以直接作爲JSON使用,使用Json-lib時還要面對無休無止的循環引用。

這次的示例稍微複雜一些,因爲包括依賴jar包、classXMLJSP,所以示例單獨放在10.store/dwr2/下,請將它們複製到tomcatwebapps下,然後再使用瀏覽器訪問。

10.11 localXHR支持本地使用Ajax

Ajax是不能在本地文件系統中使用的,必須把數據放到服務器上。無論是IISApache Tomcat,還是你熟悉的其他服務器,只要支持HTTP協議,就可以使用EXT中的Ajax

至於本地爲何不能用Ajax,主要是因爲Ajax要判斷HTTP響應返回的狀態,只有status=200時才認爲這次請求是成功的。所以,localXHR做的就是強行修改響應狀態,讓Ajax可以繼續下去。

下面我們來分析一下localXHR的源代碼。

q   加入了一個forceActiveX屬性,默認是false,它用來控制是否強制使用activexactivex是在IE下專用的。

q   修改createXhrObject函數,只是在最開始處加了一條判斷語句,如下所示。

 

  if(Ext.isIE7 && !!this.forceActiveX){throw("IE7forceActiveX");}               

 

q   增加了getHttpStatus函數,這是爲了處理HTTP的響應狀態,如代碼清單10-9所示。

代碼清單10-9 處理HTTP響應狀態

getHttpStatus: function(reqObj){

    var statObj = {

        status:0

        ,statusText:''

        ,isError:false

        ,isLocal:false

        ,isOK:false

    };

    try {

        if(!reqObj)throw('noobj');

        statObj.status = reqObj.status || 0;

 

        statObj.isLocal = !reqObj.status && location.protocol == "file:" ||

                           Ext.isSafari && reqObj.status == undefined;

 

        statObj.statusText = reqObj.statusText || '';

 

        statObj.isOK = (statObj.isLocal ||

                        (statObj.status > 199 && statObj.status < 300) ||

                         statObj.status == 304);

 

    } catch(e){

        //status may not avail/valid yet.

        statObj.isError = true;

    }

 

    return statObj;

},

 

它爲狀態增添了更多語義,status表示狀態值,statusText表示狀態描述,isError表示是否有錯誤,isLocal表示是否在本地進行Ajax訪問,isOK表示操作是否成功。

判斷isLocal是否爲本地的有兩種方法:reqObj沒有status,而且請求協議是file:;瀏覽器是Safari,而且reqObj.status沒有定義。

statObj中的isOK屬性用來判斷此次請求是否成功。判斷請求是否成功的條件很多,例如:isLocal的屬性爲true、響應狀態值在199~300之間、響應狀態值是304等。如果處理過程中出現了異常,就會將isError屬性設置爲true,最後會把配置好的statObj對象返回,等待下一個步驟的處理。

localXHR.jshandleTransactionResponse函數進行了簡化。因爲增加的getHttpStatus函數很好地封裝了與請求相關的各種狀態信息,所以在handleTransactionResponse函數中我們不會看到讓人頭暈目眩的響應狀態代碼。取而代之的是isErrorisOK這些更容易理解的屬性,localXHR.js直接使用這些屬性來處理響應。

createResponseObject函數被大大強化了。其實前半部分都是一樣的,localXHR.js中對isLocal做了大量的處理,響應中的responseText可以從連接中獲得。如果需要XML,它就使用ActiveXObject("Microsoft.XMLDOM")new DOMParser()responseText解析成XML放到response裏,響應狀態也是重新計算的,這樣就能讓Ajax正常調用了。

最後處理的是asyncRequest函數,如果在異步請求時出現異常,就調用handleTransac- tionResponse返回響應,然後根據各種情況稍微修改header屬性。

我們來看看下面這行代碼:

 

Ext.lib.Ajax.forceActiveX = (document.location.protocol == 'file:');               

 

如果協議是file:,就強制使用activex

10.12 本章小結

本章系統地討論了Ext.data包中的各個類的功能和使用方式,還涉及如何將EXTDWR通過自定義的proxy相結合的示例。我們介紹瞭如何使用Ext.data.Connection與後臺進行數據交互,還專門介紹了它的子類Ext.Ajax,並討論了EXTAjax的應用以及在回調函數中使用scopecreateDelegate()解決this的問題。

接着詳細介紹了類Ext.data.RecordExt.data.Store的功能和使用方法,這兩個類結合起來形成了Ext.data中的主體數據模型,很多組件(包括GridComboBox)都是建立在它們之上的。除此之外,還討論了常用的proxyreaderstoreSimpleStoreJsonStore,以及它們的應用場景。

最後我們介紹了擴展插件localXHR.js,它可以解決EXTAjax無法訪問本地文件的問題。

 

 

發佈了20 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章