extjs2.0 文件上傳_extjs數據存儲與傳輸

本章內容

qExt.data簡介

qExt.data.Connection

qExt.data.Record

qExt.data.Store

q常用proxy

q常用reader

q高級store

qEXT中的Ajax

q關于scope和createDelegate()

qDWR與EXT整合

10.1Ext.data簡介

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

10.2Ext.data.Connection

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

Ext.data.Connection主要用于在Ext.data.HttpProxy和Ext.data.ScriptTagProxy中執行與后臺交互的任務,它會從指定的URL獲得數據,并把后臺返回的數據交給HttpProxy或ScriptTagProxy處理,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()函數中可以設置success和failure兩個回調函數,分別在請求成功和請求失敗時調用。請求成功時,success函數的參數就是后臺返回的信息。

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

qurl:String:請求url。

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

qmethod:String:請求方法,通常為GET或POST。

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

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

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

qscope:Object:回調函數的作用域。

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

qisUpload:Boolean:是否執行文件上傳。

qheaders:Object:請求首部信息。

qxmlData:Object:XML文檔對象,可以通過URL附加參數的方式發起請求。

qdisableCaching:Boolean:是否禁用緩存,默認為禁用。

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

10.3Ext.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'賦值給name,0賦值給sex。

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

alert(boy.data.name);

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

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

這里涉及Ext.data.Record的data屬性,這是定義在Ext.data.Record中的一個公共屬性,用于保存當前record對象的所有數據。它是一個JSON對象,可以直接從它里面獲得需要的數據。可以通過Ext.data.Record的get()函數方便地從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的屬性數據被修改后,我們可以執行如下幾種操作。

qcommit()(提交):這個函數的效果是設置dirty為false,并刪除modified中保存的原始數據。

qreject()(撤銷):這個函數的效果是將data中已經修改了的屬性值都恢復成modified中保存的原始數據,然后設置dirty為false,并刪除保存原始數據的modified對象。

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

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

Ext.data.Record還提供了用于復制record實例的函數copy()。

var copyBoy = boy.copy();

這樣我們就得到了boy的一個副本,它里面包含了boy的data數據,但copy()函數不會復制dirty和modified等額外的屬性值。

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

10.4Ext.data.Store

Ext.data.Store是EXT中用來進行數據交換和數據交互的標準中間件,無論是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最少需要兩個組件的支持,分別是proxy和reader,proxy用于從某個途徑讀取原始數據,reader用于將原始數據轉換成Record實例。

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

經過轉換之后,store里的數據就可以提供給Grid或ComboBox使用了,這就是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會在向后臺請求數據時自動加入sort和dir兩個參數,分別對應排序的字段和排序的方式,由后臺獲取并處理這兩個參數,在后臺對所需數據進行排序操作。remoteSort:true也會導致每次執行sort()時都要去后臺重新加載數據,而不能只對本地數據進行排序。

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

10.4.3從store中獲取數據

從store中獲取數據有很多種途徑,可以依據不同的要求選擇不同的函數。最直接的方法是根據record在store中的行號獲得對應的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'));

}

如果確實不知道record的id,也可以根據record本身的id從store中獲得對應的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表示從第幾行開始搜索,第四個參數anyMatch為true時,不必從頭開始匹配;第五個參數caseSensitive為true時,會區分大小寫。

如下面的代碼所示:

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中的多個字段,在函數中實現復雜邏輯。

我們還可以使用query和queryBy函數對store中的數據進行查詢。與find和findBy不同的是,query和queryBy返回的是一個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

});

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

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

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

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

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

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

store.baseParams.start = 0;

store.baseParams.limit = 20;

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

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時,返回的結果中可能會包含null、undefined或空字符串,否則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)函數根據record或record的id獲得record對應的行號,如下面的代碼所示。

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

alert(store.indexOfId(1001));

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

store.loadData(data, true);

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

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

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

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

表10-1store提供的事件

事件名

參  數

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 )

至此,store和record等組件已經講解完畢,下面我們主要討論一下常用的proxy和reader組件。

10.5常用proxy

10.5.1MemoryProxy

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

varproxy =?new?Ext.data.MemoryProxy([

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

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

]);

10.5.2HttpProxy

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

varproxy =?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.3ScriptTagProxy

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

varproxy =?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頁面里添加一個標簽,然后把后臺返回的內容添加到這個標簽中,這樣就可以解決跨域訪問數據的問題。為了讓后臺返回的內容可以在動態生成的標簽中運行,EXT會生成一個名為callback的回調函數,并把回調函數的名稱傳遞給后臺,由后臺生成callback(data)形式的響應內容,然后返回給前臺自動運行。

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

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

代碼清單10-2在后臺同時支持HttpProxy和ScriptTagProxy

booleanscriptTag = 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.1ArrayReader

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

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

vardata = [

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

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

];

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

varreader =?new?Ext.data.ArrayReader({

id:1

},[

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

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

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

]);

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

10.6.2JsonReader

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

vardata = {

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數據對應的,如下面的代碼所示。

varreader =?new?Ext.data.JsonReader({

successProperty:?"successproperty",

totalProperty:?"totalProperty",

root:?"root",

id:?"id"

}, [

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

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

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

]);

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

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

vardata = {

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'

}}

]

};

varreader =?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、name和sex等屬性。在JsonReader中可以用mapping把這些嵌套的內部屬性映射出來,賦予對應的record,而其他字段都不變。

10.6.3XmlReader

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

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

1

2

true

1

name1

descn1

2

name2

descn2

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

varreader =?new?Ext.data.XmlReader({

totalRecords:?'totalRecords',

success:?'success'

record:?'record',

id:?"id"

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

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

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

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

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

"" +

"1"+

"2"+

"true"?+

""?+

"1"?+

"name1"?+

"descn1"?+

""?+

""?+

"2"?+

"name2"?+

"descn2"?+

""?+

"";

varxdoc;

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;

}

varproxy =?new?Ext.data.MemoryProxy(xdoc);

varreader =?new?Ext.data.XmlReader({

totalRecords:?'totalRecords',

success:?'success',

record:?'record',

id: "id"

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

vards =?new?Ext.data.Store({

proxy: proxy,

reader: reader

});

10.7高級store

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

qSimpleStore=Store+MemoryProxy+ArrayReader

var?ds = Ext.data.SimpleStore({

data: [

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

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

],

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

});

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

qJsonStore=Store+HttpProxy+JsonReader

var?ds = Ext.data.JsonStore({

url:?'xxx.jsp',

root:?'root',

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

});

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

qExt.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.8EXT中的Ajax

EXT與后臺交換數據時,很大程度上依賴于底層實現的Ajax。所謂底層實現,就是說很可能就是我們之前提到的Prototype、jQuery或YUI中提供的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.Ajax的request函數,它的參數是一個JSON對象,具體如下所示。

qurl參數表示將要訪問的后臺網址。

qsuccess參數表示響應成功后的回調函數。

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

qfailure參數表示響應失敗后的回調函數。

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

qparams參數表示請求時發送到后臺的參數,既可以使用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.2Ext.lib.Ajax是更底層的封裝

其實Ext.Ajax和Ext.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個參數,分別為method、url、callback和params。它們的含義與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關于scope和createDelegate()

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

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

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

functiondoSuccess(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請求之前獲取回調函數中需要操作的對象,有兩種方法可供選擇:scope和createDelegate。

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)

);

在Ajax的callback參數部分添加一個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.10DWR與EXT整合

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

10.10.1在EXT中直接使用DWR

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

publicclass Info {

long id;

String name;

int sex;

String email;

String tel;

Date addTime;

String descn;

}

然后編寫操作POJO的manager類,代碼如下所示。

publicclass?InfoManager {

private List infoList = new ArrayList();

public List getResult(){

return infoList;

}

}

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

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

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

varcm =?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需要哪些屬性。

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

10.10.2DWRProxy

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

注意這個DWRProxy.js一定要放在ext-base.js和ext-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,我們從中獲得需要的參數,比如start和limit。不過HTTP里的參數都是字符串,而我們需要的是數字,所以要對類型進行相應的轉換。根據start和limit兩個屬性從全部數據中截取一部分,放進新建的ListRange中,然后把生成的ListRange返回給前臺,于是一切都解決了。

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

varstore =?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類,也可以自己創建一個類,只要把totalProperty和data兩個屬性與之對應即可。

10.10.3DWRTreeLoader

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

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即可,如下面的代碼所示。

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

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

});

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

10.10.4DWRProxy和ComboBox

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

代碼清單10-8DWRProxy與ComboBox整合

varinfo = 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包、class、XML和JSP,所以示例單獨放在10.store/dwr2/下,請將它們復制到tomcat的webapps下,然后再使用瀏覽器訪問。

10.11localXHR支持本地使用Ajax

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

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

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

q加入了一個forceActiveX屬性,默認是false,它用來控制是否強制使用activex,activex是在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.js對handleTransactionResponse函數進行了簡化。因為增加的getHttpStatus函數很好地封裝了與請求相關的各種狀態信息,所以在handleTransactionResponse函數中我們不會看到讓人頭暈目眩的響應狀態代碼。取而代之的是isError和isOK這些更容易理解的屬性,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。

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

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

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

本章內容

qExt.data簡介

qExt.data.Connection

qExt.data.Record

qExt.data.Store

q常用proxy

q常用reader

q高級store

qEXT中的Ajax

q關于scope和createDelegate()

qDWR與EXT整合

10.1Ext.data簡介

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

10.2Ext.data.Connection

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

Ext.data.Connection主要用于在Ext.data.HttpProxy和Ext.data.ScriptTagProxy中執行與后臺交互的任務,它會從指定的URL獲得數據,并把后臺返回的數據交給HttpProxy或ScriptTagProxy處理,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()函數中可以設置success和failure兩個回調函數,分別在請求成功和請求失敗時調用。請求成功時,success函數的參數就是后臺返回的信息。

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

qurl:String:請求url。

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

qmethod:String:請求方法,通常為GET或POST。

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

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

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

qscope:Object:回調函數的作用域。

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

qisUpload:Boolean:是否執行文件上傳。

qheaders:Object:請求首部信息。

qxmlData:Object:XML文檔對象,可以通過URL附加參數的方式發起請求。

qdisableCaching:Boolean:是否禁用緩存,默認為禁用。

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

10.3Ext.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'賦值給name,0賦值給sex。

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

alert(boy.data.name);

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

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

這里涉及Ext.data.Record的data屬性,這是定義在Ext.data.Record中的一個公共屬性,用于保存當前record對象的所有數據。它是一個JSON對象,可以直接從它里面獲得需要的數據。可以通過Ext.data.Record的get()函數方便地從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的屬性數據被修改后,我們可以執行如下幾種操作。

qcommit()(提交):這個函數的效果是設置dirty為false,并刪除modified中保存的原始數據。

qreject()(撤銷):這個函數的效果是將data中已經修改了的屬性值都恢復成modified中保存的原始數據,然后設置dirty為false,并刪除保存原始數據的modified對象。

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

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

Ext.data.Record還提供了用于復制record實例的函數copy()。

var copyBoy = boy.copy();

這樣我們就得到了boy的一個副本,它里面包含了boy的data數據,但copy()函數不會復制dirty和modified等額外的屬性值。

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

10.4Ext.data.Store

Ext.data.Store是EXT中用來進行數據交換和數據交互的標準中間件,無論是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最少需要兩個組件的支持,分別是proxy和reader,proxy用于從某個途徑讀取原始數據,reader用于將原始數據轉換成Record實例。

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

經過轉換之后,store里的數據就可以提供給Grid或ComboBox使用了,這就是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會在向后臺請求數據時自動加入sort和dir兩個參數,分別對應排序的字段和排序的方式,由后臺獲取并處理這兩個參數,在后臺對所需數據進行排序操作。remoteSort:true也會導致每次執行sort()時都要去后臺重新加載數據,而不能只對本地數據進行排序。

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

10.4.3從store中獲取數據

從store中獲取數據有很多種途徑,可以依據不同的要求選擇不同的函數。最直接的方法是根據record在store中的行號獲得對應的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'));

}

如果確實不知道record的id,也可以根據record本身的id從store中獲得對應的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表示從第幾行開始搜索,第四個參數anyMatch為true時,不必從頭開始匹配;第五個參數caseSensitive為true時,會區分大小寫。

如下面的代碼所示:

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中的多個字段,在函數中實現復雜邏輯。

我們還可以使用query和queryBy函數對store中的數據進行查詢。與find和findBy不同的是,query和queryBy返回的是一個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

});

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

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

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

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

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

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

store.baseParams.start = 0;

store.baseParams.limit = 20;

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

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時,返回的結果中可能會包含null、undefined或空字符串,否則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)函數根據record或record的id獲得record對應的行號,如下面的代碼所示。

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

alert(store.indexOfId(1001));

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

store.loadData(data, true);

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

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

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

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

表10-1store提供的事件

事件名

參  數

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 )

至此,store和record等組件已經講解完畢,下面我們主要討論一下常用的proxy和reader組件。

10.5常用proxy

10.5.1MemoryProxy

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

varproxy =?new?Ext.data.MemoryProxy([

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

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

]);

10.5.2HttpProxy

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

varproxy =?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.3ScriptTagProxy

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

varproxy =?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頁面里添加一個標簽,然后把后臺返回的內容添加到這個標簽中,這樣就可以解決跨域訪問數據的問題。為了讓后臺返回的內容可以在動態生成的標簽中運行,EXT會生成一個名為callback的回調函數,并把回調函數的名稱傳遞給后臺,由后臺生成callback(data)形式的響應內容,然后返回給前臺自動運行。

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

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

代碼清單10-2在后臺同時支持HttpProxy和ScriptTagProxy

booleanscriptTag = 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.1ArrayReader

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

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

vardata = [

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

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

];

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

varreader =?new?Ext.data.ArrayReader({

id:1

},[

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

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

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

]);

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

10.6.2JsonReader

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

vardata = {

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數據對應的,如下面的代碼所示。

varreader =?new?Ext.data.JsonReader({

successProperty:?"successproperty",

totalProperty:?"totalProperty",

root:?"root",

id:?"id"

}, [

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

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

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

]);

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

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

vardata = {

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'

}}

]

};

varreader =?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、name和sex等屬性。在JsonReader中可以用mapping把這些嵌套的內部屬性映射出來,賦予對應的record,而其他字段都不變。

10.6.3XmlReader

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

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

1

2

true

1

name1

descn1

2

name2

descn2

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

varreader =?new?Ext.data.XmlReader({

totalRecords:?'totalRecords',

success:?'success'

record:?'record',

id:?"id"

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

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

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

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

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

"" +

"1"+

"2"+

"true"?+

""?+

"1"?+

"name1"?+

"descn1"?+

""?+

""?+

"2"?+

"name2"?+

"descn2"?+

""?+

"";

varxdoc;

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;

}

varproxy =?new?Ext.data.MemoryProxy(xdoc);

varreader =?new?Ext.data.XmlReader({

totalRecords:?'totalRecords',

success:?'success',

record:?'record',

id: "id"

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

vards =?new?Ext.data.Store({

proxy: proxy,

reader: reader

});

10.7高級store

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

qSimpleStore=Store+MemoryProxy+ArrayReader

var?ds = Ext.data.SimpleStore({

data: [

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

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

],

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

});

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

qJsonStore=Store+HttpProxy+JsonReader

var?ds = Ext.data.JsonStore({

url:?'xxx.jsp',

root:?'root',

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

});

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

qExt.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.8EXT中的Ajax

EXT與后臺交換數據時,很大程度上依賴于底層實現的Ajax。所謂底層實現,就是說很可能就是我們之前提到的Prototype、jQuery或YUI中提供的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.Ajax的request函數,它的參數是一個JSON對象,具體如下所示。

qurl參數表示將要訪問的后臺網址。

qsuccess參數表示響應成功后的回調函數。

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

qfailure參數表示響應失敗后的回調函數。

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

qparams參數表示請求時發送到后臺的參數,既可以使用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.2Ext.lib.Ajax是更底層的封裝

其實Ext.Ajax和Ext.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個參數,分別為method、url、callback和params。它們的含義與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關于scope和createDelegate()

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

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

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

functiondoSuccess(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請求之前獲取回調函數中需要操作的對象,有兩種方法可供選擇:scope和createDelegate。

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)

);

在Ajax的callback參數部分添加一個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.10DWR與EXT整合

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

10.10.1在EXT中直接使用DWR

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

publicclass Info {

long id;

String name;

int sex;

String email;

String tel;

Date addTime;

String descn;

}

然后編寫操作POJO的manager類,代碼如下所示。

publicclass?InfoManager {

private List infoList = new ArrayList();

public List getResult(){

return infoList;

}

}

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

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

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

varcm =?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需要哪些屬性。

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

10.10.2DWRProxy

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

注意這個DWRProxy.js一定要放在ext-base.js和ext-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,我們從中獲得需要的參數,比如start和limit。不過HTTP里的參數都是字符串,而我們需要的是數字,所以要對類型進行相應的轉換。根據start和limit兩個屬性從全部數據中截取一部分,放進新建的ListRange中,然后把生成的ListRange返回給前臺,于是一切都解決了。

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

varstore =?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類,也可以自己創建一個類,只要把totalProperty和data兩個屬性與之對應即可。

10.10.3DWRTreeLoader

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

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即可,如下面的代碼所示。

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

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

});

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

10.10.4DWRProxy和ComboBox

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

代碼清單10-8DWRProxy與ComboBox整合

varinfo = 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包、class、XML和JSP,所以示例單獨放在10.store/dwr2/下,請將它們復制到tomcat的webapps下,然后再使用瀏覽器訪問。

10.11localXHR支持本地使用Ajax

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

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

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

q加入了一個forceActiveX屬性,默認是false,它用來控制是否強制使用activex,activex是在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.js對handleTransactionResponse函數進行了簡化。因為增加的getHttpStatus函數很好地封裝了與請求相關的各種狀態信息,所以在handleTransactionResponse函數中我們不會看到讓人頭暈目眩的響應狀態代碼。取而代之的是isError和isOK這些更容易理解的屬性,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。

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

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

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455164.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455164.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455164.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

盤點key value在各個編程語言中的類型

個人博客點這里 起步 在編程語言中,有這么一種數據類型,我們經常會使用到,他就是類似于json的鍵值對類型 那么今天我們就對比一下,在各大編程語言中的key value類型的特點 main python key value 叫做字典 dict類型 聲明 字典操作 以鍵值對方式存在的無序數據的組合就是…

音頻格式詳解 CD WAV MP3 WMA MIDI RM RA OGG VOF AAC M4A APE FLAC TAK TTA MV RealAudio AIFF AU

以下是常見音頻文件格式的特點。 要在計算機內播放或是處理音頻文件&#xff0c;也就是要對聲音文件進行數、模轉換&#xff0c;這個過程同樣由采樣和量化構成&#xff0c;人耳所能聽到的聲音&#xff0c;最低的頻率是從20Hz起一直到最高頻率20KHZ&#xff0c;20KHz以上人耳是聽…

Spring MVC-表單(Form)標簽-下拉框(Dropdown)示例(轉載實踐)

以下內容翻譯自&#xff1a;https://www.tutorialspoint.com/springmvc/springmvc_dropdown.htm 說明&#xff1a;示例基于Spring MVC 4.1.6。 以下示例顯示如何使用Spring Web MVC框架在表單中使用Dropdown。首先&#xff0c;讓我們使用Eclipse IDE&#xff0c;并按照以下步驟…

vuex的命名空間有哪些_Vuex在vue路由器中訪問命名空間模塊的getter

我試圖通過檢查用戶是否經過身份驗證來保護我的路由&#xff0c;這是示例路由&#xff1a;{path: /intranet,component: search,meta: { requiresAuth: true },props: {tax: type,term: intranet-post,name: Intranet}},我正在這樣設置警衛&#xff1a;router.beforeEach((to, …

阿里巴巴中文站架構設計實踐(何崚)圖書

阿里巴巴中文站架構實踐 何崚阿里巴巴 全文地址:阿里巴巴中文站架構設計實踐(何崚).pdf 更多課件可到:這里查看 后續內容還在更新…

使用sqlite3創建數據庫表的時候須要注意

使用sqlite3創建數據庫表的時候須要注意&#xff1a; 1. 不能使用純數字 2. 不能帶有"-",""等字符。也不能以數字打頭。一、以下是log打出來的信息&#xff1a; DB Error: 1 "unrecognized token: "47464636265757265763393573649"" D…

物聯網世界的承諾與挑戰

最初參與到與物聯網&#xff08;Internet of Things / IoT&#xff09;相關的行業對話時&#xff0c;我對整個行業的發展充滿疑問。“ 物聯網”和 “萬聯網”&#xff08;Internet of Everything&#xff09;有什么區別&#xff1f;這場變革現今是處于啟蒙階段&#xff0c;亦或…

音樂文件基本格式,wave,mod,midi,mp3,wma,flac

經典的WAVE WAVE文件作為最經典的Windows多媒體音頻格式&#xff0c;應用非常廣泛&#xff0c;它使用三個參數來表示聲音&#xff1a;采樣位數、采樣頻率和聲道數。 聲道有單聲道和立體聲之分&#xff0c;采樣頻率一般有11025Hz&#xff08;11kHz&#xff09;、22050Hz&#xf…

bilibili go框架_最好的開發語言一定是那位嗎?Go語言有話說

魯迅先生說過&#xff0c;所有能用 JS 寫的前端項目最終都會被用 JS 重寫一遍&#xff0c;所有能用 Go 寫的后端項目最終也都會被用 Go 重寫一遍。作為一名開發者&#xff0c;周六的我們能做什么呢&#xff1f;是因為產品經理的各種需求在加班嗎&#xff1f;給你說了實現不了實…

關于虛擬機棧的理解

虛擬機棧 虛擬機棧出現的背景 由于跨平臺性的設計,Java的指令都是根據棧來設計的. 不同平臺CPU架構不同,所以不能設置為基于寄存器的 優點是跨平臺,指令集小,編譯器容易實現,缺點是性能下降,實現同樣的功能需要更多的指令. 有不少Java開發人員一提到Java內存結構,就會非常粗…

抓取各個瀏覽器引擎關鍵字,,百度學術關鍵字

百度學術 $list_arr getbaiduxueshu($row[name]); $list_arr explode((,$list_arr); $list_arr explode(),$list_arr[1]); $list_arr json_decode($list_arr[0]); $list_arr $list_arr->s; //百度學術平臺熱詞地址function getbaiduxueshu($keyname 100){ $url http:…

wave格式分析,wave音頻文件格式分析配程序

wav文件格式分析詳解 程序如上一篇博文 一、綜述 WAVE文件作為多媒體中使用的聲波文件格式之一&#xff0c;它是以RIFF格式為標準的。RIFF是英文Resource Interchange File Format的縮寫&#xff0c;每個WAVE文件的頭四個字節便是“RIFF”。 WAVE文件是由若干個Chunk組成…

poi設置word表格單元格寬度_java poi如何設置word的頁面的大小和水平方向?

展開全部你好&#xff0c;試試以下代碼行不行。packagecom.sample;importjava.awt.color;importjava.io.fileoutputstream;importjava.io.ioexception;importcom.lowagie.text.cell;importcom.lowagie.text.document;importcom.lowagie.text.documentexception;importcom.lowag…

時間通用方法

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;import org.apache.commons.lang3.time.DateFormatUtils;/** * 日期工具類, 繼承org.apache.commons.lang.time.DateUtils類 */ public class DateUtils extends org.apache.commo…

Python-MongoDB的驅動安裝、升級

安裝pip&#xff0c;并通過此來安裝pymongo–Python mongodb驅動 1、下載pip安裝包&#xff0c;下載地址&#xff1a;http://pypi.python.org/packages/source/p/pip/pip-1.0.2.tar.gz#md547ec6ff3f6d962696fe08d4c8264ad49 2、解壓安裝&#xff1a; tar -zxf pip.1.0.2.tar.gz…

python5_python5

python2&python31.python2中print可加括號可不加括號&#xff0c;python3中print一定要加括號。2.python2中有range(),也有xrange(),python中只有range()。生成器。3.Python2中raw_input(),python3中input()。,,is賦值比較是否相等is 比較內存地址&#xff0c;id(內容)li1[…

單例設計模式1

單例 所謂單例設計模式,即時采取一定的方法保證在整個軟件系統當中,對于某個類只能存在一個對象實例,并且該類只提供一個其對象實例的方法(靜態方法) 惡漢式 優缺點說明: 優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化.避免了線程同步問題 缺點: 在類裝載的時候就…

SJXXX串口擴展芯片 4串口芯片 UART串口芯片

SJXX串口擴展芯片1 概述SJ000是一款具備I2C總線/SPI總線/UART接口的四通道異步收發器件&#xff0c;通過模式選擇使得該器件工作于以上任何一種主接口模式下。器件的四個通道UART可提供高達2Mbps的數據率&#xff0c;低功耗模式和睡眠電流。每個通道含有一個接收器和一個發送器…

MPLS服務合同到期了,是否該續簽?

當考慮是否要更新現有MPLS服務合同以及續簽多久時&#xff0c;你需要著眼于從價格到部署速度的方方面面。簡而言之&#xff0c;如果你還沒有獲取一些與軟件定義廣域網有關技術的經驗&#xff0c;即使用寬帶或取代MPLS服務&#xff0c;那么你就沒辦法在未來幾個月之內棄用MPLS。…

oracle rds 運維服務_從運維的角度分析使用阿里云數據庫RDS的必要性–你不應該在阿里云上使用自建的MySQL/SQL Server/Oracle/PostgreSQL數據庫...

開宗明義&#xff0c;你不應該在阿里云上使用自建的MySQL or SQL Server數據庫&#xff0c;對了&#xff0c;還有Oracle or PostgreSQL數據庫。云數據庫 RDS(Relational Database Service)是一種穩定可靠、可彈性伸縮的在線數據庫服務。基于飛天分布式系統和全SSD盤高性能存儲&…