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.mzph.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盘高性能存储&…