免费建工作室网站,青岛网站建设效果,前端开发有哪些,页面模板在公号什么地方显示本章内容qExt.data简介qExt.data.ConnectionqExt.data.RecordqExt.data.Storeq常用proxyq常用readerq高级storeqEXT中的Ajaxq关于scope和createDelegate()qDWR与EXT整合10.1Ext.data简介Ext.data在命名空间中定义了一系列store、reader和proxy。Grid和ComboxBox都是以Ext.data为…本章内容qExt.data简介qExt.data.ConnectionqExt.data.RecordqExt.data.Storeq常用proxyq常用readerq高级storeqEXT中的Ajaxq关于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.ConnectionExt.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.Connectionvar 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:ObjectXML文档对象可以通过URL附加参数的方式发起请求。qdisableCaching:Boolean是否禁用缓存默认为禁用。Ext.data.Connection还提供了abort([Number transactionId])函数当同时有多个请求发生时根据指定的事务id放弃其中的某一个请求。如果不指定事务id就会放弃最后一个请求。isLoading([Number transactionId])函数的用法与abort()类似可以根据事务id判断对应的请求是否完成。如果未指定事务id就判断最后一个请求是否完成。10.3Ext.data.RecordExt.data.Record就是一个设定了内部数据类型的对象它是Ext.data.Store的最基本组成部分。如果把Ext.data.Store看作是一张二维表那么它的每一行就对应一个Ext.data. Record实例。Ext.data.Record的主要功能是保存数据并且在内部数据发生改变时记录修改的状态它还可以保留修改之前的原始值。我们使用Ext.data.Record时通常都是由create()函数开始首先用create()函数创建一个自定义的Record类型如下面的代码所示。var PersonRecord Ext.data.Record.create([{name: name, type: string},{name: sex, type: int}]);PersonRecord就是我们定义的新类型包含字符串类型的name和整数类型的sex两个属性然后我们使用new关键字创建PersonRecord的实例如下面的代码所示。var boy new PersonRecord({name: boy,sex: 0});创建对象时可以直接通过构造方法为对象赋予初始值将boy赋值给name0赋值给sex。现在我们得到了PersonRecord的实例boy如何才能得到它的属性呢以下三种方式都可以获得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.StoreExt.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和readerproxy用于从某个途径读取原始数据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] ) : NumberfindBy()函数允许用户使用自定义函数对内部数据进行搜索。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] ) : voidfilter()函数的用法与之前谈到的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] ) : Arraycollect函数获得指定的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常用proxy10.5.1MemoryProxyMemoryProxy只能从JavaScript对象获得数据可以直接把数组或JSON和XML格式的数据交给它处理如下面的代码所示。varproxy new Ext.data.MemoryProxy([[id1,name1,descn1],[id2,name2,descn2]]);10.5.2HttpProxyHttpProxy使用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.3ScriptTagProxyScriptTagProxy的用法几乎和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和ScriptTagProxybooleanscriptTag 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常用Reader10.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.3XmlReaderXML是非常通用的数据传输格式XmlReader使用的XML格式的数据如代码清单10-4所示。代码清单10-4XmlReader使用的XML格式的数据12true1name1descn12name2descn2这里一定要用dataset作为XML根元素。再让我们看一下如何对XmlReader进行配置从而读取上面示例中的XML数据如下面的代码所示。varreader new Ext.data.XmlReader({totalRecords: totalRecords,success: successrecord: 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 version1.0 encodingutf-8? 12true 1 name1 descn1 2 name2 descn2 ;varxdoc;if(typeof(DOMParser) undefined){xdoc new ActiveXObject(Microsoft.XMLDOM);xdoc.asyncfalse;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为我们提供了几种可选择的整合方案。qSimpleStoreStoreMemoryProxyArrayReadervar ds Ext.data.SimpleStore({data: [[id1,name1,descn1],[id2,name2,descn2]],fields: [id,name,descn]});SimpleStore是专为简化读取本地数组而设计的设置上MemoryProxy需要的data和ArrayReader需要的fields就可以使用了。qJsonStoreStoreHttpProxyJsonReadervar 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中的AjaxEXT与后台交换数据时很大程度上依赖于底层实现的Ajax。所谓底层实现就是说很可能就是我们之前提到的Prototype、jQuery或YUI中提供的Ajax功能。为了统一接口EXT在它们的基础上进行了封装让我们可以用同一种写法“游走”于各种不同的底层实现之间。10.8.1最容易看到的Ext.AjaxExt.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对象也可以直接使用namevalue形式的字符串。上面的示例可以在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调用DWRvarcm 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和ComboBoxDWRProxy既然可以用在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支持本地使用AjaxAjax是不能在本地文件系统中使用的必须把数据放到服务器上。无论是IIS、Apache、Tomcat还是你熟悉的其他服务器只要支持HTTP协议就可以使用EXT中的Ajax。至于本地为何不能用Ajax主要是因为Ajax要判断HTTP响应返回的状态只有status200时才认为这次请求是成功的。所以localXHR做的就是强行修改响应状态让Ajax可以继续下去。下面我们来分析一下localXHR的源代码。q加入了一个forceActiveX属性默认是false它用来控制是否强制使用activexactivex是在IE下专用的。q修改createXhrObject函数只是在最开始处加了一条判断语句如下所示。if(Ext.isIE7 !!this.forceActiveX){throw(IE7forceActiveX);}q增加了getHttpStatus函数这是为了处理HTTP的响应状态如代码清单10-9所示。代码清单10-9处理HTTP响应状态getHttpStatus: function(reqObj){var statObj {status:0,statusText:,isError:false,isLocal:false,isOK:false};try {if(!reqObj)throw(noobj);statObj.status reqObj.status || 0;statObj.isLocal !reqObj.status location.protocol file: ||Ext.isSafari reqObj.status undefined;statObj.statusText reqObj.statusText || ;statObj.isOK (statObj.isLocal ||(statObj.status 199 statObj.status 300) ||statObj.status 304);} catch(e){//status may not avail/valid yet.statObj.isError true;}return statObj;},它为状态增添了更多语义status表示状态值statusText表示状态描述isError表示是否有错误isLocal表示是否在本地进行Ajax访问isOK表示操作是否成功。判断isLocal是否为本地的有两种方法reqObj没有status而且请求协议是file:浏览器是Safari而且reqObj.status没有定义。statObj中的isOK属性用来判断此次请求是否成功。判断请求是否成功的条件很多例如isLocal的属性为true、响应状态值在199~300之间、响应状态值是304等。如果处理过程中出现了异常就会将isError属性设置为true最后会把配置好的statObj对象返回等待下一个步骤的处理。localXHR.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、storeSimpleStore和JsonStore以及它们的应用场景。最后我们介绍了扩展插件localXHR.js它可以解决EXT中Ajax无法访问本地文件的问题。本章内容qExt.data简介qExt.data.ConnectionqExt.data.RecordqExt.data.Storeq常用proxyq常用readerq高级storeqEXT中的Ajaxq关于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.ConnectionExt.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.Connectionvar 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:ObjectXML文档对象可以通过URL附加参数的方式发起请求。qdisableCaching:Boolean是否禁用缓存默认为禁用。Ext.data.Connection还提供了abort([Number transactionId])函数当同时有多个请求发生时根据指定的事务id放弃其中的某一个请求。如果不指定事务id就会放弃最后一个请求。isLoading([Number transactionId])函数的用法与abort()类似可以根据事务id判断对应的请求是否完成。如果未指定事务id就判断最后一个请求是否完成。10.3Ext.data.RecordExt.data.Record就是一个设定了内部数据类型的对象它是Ext.data.Store的最基本组成部分。如果把Ext.data.Store看作是一张二维表那么它的每一行就对应一个Ext.data. Record实例。Ext.data.Record的主要功能是保存数据并且在内部数据发生改变时记录修改的状态它还可以保留修改之前的原始值。我们使用Ext.data.Record时通常都是由create()函数开始首先用create()函数创建一个自定义的Record类型如下面的代码所示。var PersonRecord Ext.data.Record.create([{name: name, type: string},{name: sex, type: int}]);PersonRecord就是我们定义的新类型包含字符串类型的name和整数类型的sex两个属性然后我们使用new关键字创建PersonRecord的实例如下面的代码所示。var boy new PersonRecord({name: boy,sex: 0});创建对象时可以直接通过构造方法为对象赋予初始值将boy赋值给name0赋值给sex。现在我们得到了PersonRecord的实例boy如何才能得到它的属性呢以下三种方式都可以获得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.StoreExt.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和readerproxy用于从某个途径读取原始数据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] ) : NumberfindBy()函数允许用户使用自定义函数对内部数据进行搜索。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] ) : voidfilter()函数的用法与之前谈到的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] ) : Arraycollect函数获得指定的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常用proxy10.5.1MemoryProxyMemoryProxy只能从JavaScript对象获得数据可以直接把数组或JSON和XML格式的数据交给它处理如下面的代码所示。varproxy new Ext.data.MemoryProxy([[id1,name1,descn1],[id2,name2,descn2]]);10.5.2HttpProxyHttpProxy使用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.3ScriptTagProxyScriptTagProxy的用法几乎和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和ScriptTagProxybooleanscriptTag 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常用Reader10.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.3XmlReaderXML是非常通用的数据传输格式XmlReader使用的XML格式的数据如代码清单10-4所示。代码清单10-4XmlReader使用的XML格式的数据12true1name1descn12name2descn2这里一定要用dataset作为XML根元素。再让我们看一下如何对XmlReader进行配置从而读取上面示例中的XML数据如下面的代码所示。varreader new Ext.data.XmlReader({totalRecords: totalRecords,success: successrecord: 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 version1.0 encodingutf-8? 12true 1 name1 descn1 2 name2 descn2 ;varxdoc;if(typeof(DOMParser) undefined){xdoc new ActiveXObject(Microsoft.XMLDOM);xdoc.asyncfalse;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为我们提供了几种可选择的整合方案。qSimpleStoreStoreMemoryProxyArrayReadervar ds Ext.data.SimpleStore({data: [[id1,name1,descn1],[id2,name2,descn2]],fields: [id,name,descn]});SimpleStore是专为简化读取本地数组而设计的设置上MemoryProxy需要的data和ArrayReader需要的fields就可以使用了。qJsonStoreStoreHttpProxyJsonReadervar 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中的AjaxEXT与后台交换数据时很大程度上依赖于底层实现的Ajax。所谓底层实现就是说很可能就是我们之前提到的Prototype、jQuery或YUI中提供的Ajax功能。为了统一接口EXT在它们的基础上进行了封装让我们可以用同一种写法“游走”于各种不同的底层实现之间。10.8.1最容易看到的Ext.AjaxExt.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对象也可以直接使用namevalue形式的字符串。上面的示例可以在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调用DWRvarcm 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和ComboBoxDWRProxy既然可以用在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支持本地使用AjaxAjax是不能在本地文件系统中使用的必须把数据放到服务器上。无论是IIS、Apache、Tomcat还是你熟悉的其他服务器只要支持HTTP协议就可以使用EXT中的Ajax。至于本地为何不能用Ajax主要是因为Ajax要判断HTTP响应返回的状态只有status200时才认为这次请求是成功的。所以localXHR做的就是强行修改响应状态让Ajax可以继续下去。下面我们来分析一下localXHR的源代码。q加入了一个forceActiveX属性默认是false它用来控制是否强制使用activexactivex是在IE下专用的。q修改createXhrObject函数只是在最开始处加了一条判断语句如下所示。if(Ext.isIE7 !!this.forceActiveX){throw(IE7forceActiveX);}q增加了getHttpStatus函数这是为了处理HTTP的响应状态如代码清单10-9所示。代码清单10-9处理HTTP响应状态getHttpStatus: function(reqObj){var statObj {status:0,statusText:,isError:false,isLocal:false,isOK:false};try {if(!reqObj)throw(noobj);statObj.status reqObj.status || 0;statObj.isLocal !reqObj.status location.protocol file: ||Ext.isSafari reqObj.status undefined;statObj.statusText reqObj.statusText || ;statObj.isOK (statObj.isLocal ||(statObj.status 199 statObj.status 300) ||statObj.status 304);} catch(e){//status may not avail/valid yet.statObj.isError true;}return statObj;},它为状态增添了更多语义status表示状态值statusText表示状态描述isError表示是否有错误isLocal表示是否在本地进行Ajax访问isOK表示操作是否成功。判断isLocal是否为本地的有两种方法reqObj没有status而且请求协议是file:浏览器是Safari而且reqObj.status没有定义。statObj中的isOK属性用来判断此次请求是否成功。判断请求是否成功的条件很多例如isLocal的属性为true、响应状态值在199~300之间、响应状态值是304等。如果处理过程中出现了异常就会将isError属性设置为true最后会把配置好的statObj对象返回等待下一个步骤的处理。localXHR.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、storeSimpleStore和JsonStore以及它们的应用场景。最后我们介绍了扩展插件localXHR.js它可以解决EXT中Ajax无法访问本地文件的问题。