简介
DataSet为客户端的一个不可见的数据容器,其中的数据可以通过JS直接访问或修改,也可以将其它可见控件绑定到DataSet上,以展现其中的数据和提供界面接口方便用户修改和调整。
基本概念详细说明请参考:
基本使用
请参考:05. 产品表的增删改和查询
详细属性说明
id
在一个View中DataSet的id必须唯一,且id的命名规则参考Java变量名的命名规则,避免用中文或数字命名,避免特殊符号等。
如下的一些命名规则都是不推荐的:
不良的name命名示例 | 说明 |
---|---|
部门表格 | 中文命名 |
dept.id | 含点符号 |
$deptId | 含特殊符号 |
dept^id | 含特殊符号 |
dept@id | 含特殊符号 |
dept-201 | 中间含横线 |
20010101 | 数字命名 |
201DSDept | 数字作为前缀 |
listenrer
DataSet提供了listener属性,这样便于你利用Listener处理机制,在服务器端利用Java代码动态设置DataSet的属性,listener属性值编写范例:
有关监听器的详细说明请参考:基础教程中的 10. 对象监听器(SEFC)
cacheable
是否要根据parameter参数将每次装载的数据缓存起来。
chaheable使用场景介绍:
场景一:相互关联下拉框
参考范例(联动下拉框):http://bsdn.org/projects/dorado7/deploy/sample-center/com.bstek.dorado.sample.Main.d#94900
如上范例,我们希望实现懒装载的联动下拉框(非懒装载就用不着后面的讨论了),即选择了具体的产品分类后,利用AJAX技术动态的从后台获取对应产品分类的产品列表,并初始化到产品名称对应的下拉框中。
如果你掌握一点Dorado中的AJAX技术,实现这个功能并不复杂。就是在用户每次选择一个产品分类后,在产品名称下拉框打开之前动态调用如下的JS代码:
var categoryId = dsForm.get("data.categoryId"); if (categoryId) { dsProducts.set("parameter", categoryId); dsProducts.flushAsync(); }
在上面的JS代码中动态设置dsProducts的parameter,并调用其flushAsync()重新获取数据。这样我们就以及实现了具有懒加载特征的联动下拉框。
如果前后两次打开产品下拉框的时候的参数值没有变化,上述代码的flushAsync()处理机制都会重复产生AJAX请求。对网络资源进行无谓的消耗。
聪明的你很快就能想到优化的办法:
var oldCategoryId = dsProducts.get("parameter"); var categoryId = dsForm.get("data.categoryId"); if (categoryId && categoryId!=oldCategoryId) { dsProducts.set("parameter", categoryId); dsProducts.flushAsync(); }
这样你就轻易化解了上面性能问题的尴尬。
我们再设想一种场景:产品分类参数值的顺序依次为:"1","2","1".
那么上面那段代码就无法起到性能优化的作用。
勤快的你或许还有一招,就是将每次查询的数据缓存起来,并用一个类似Map的数据结构缓存产品分类ID和对应的产品数据列表,这样我们就能最终解决这个问题。—但这儿的编码工作量似乎大了一些。
因此下面推荐你最优解决办法:设置DataSet的chaheable为true,就能帮助上面那个勤快的程序员所做的所有工作。
场景二:代码自动回填功能
还有一种场景,如下图:
用户希望在产品分类ID输入ID后敲回车自动将分类名称从数据库中查询出来,并自动回填到分类名称编辑框中。其实现机制与相互关联下拉框类似,但它也同样会遇到根据产品分类ID查询产品名称的AJAX请求重复执行的后果。解决的办法是一样:
设置对应DataSet的chaheable为true。
总之,利用cacheable特性我们可以在parameter不发生变化的情况下避免无谓的AJAX数据请求,从而提高界面操作效率。
dataProvider
详细说明参考:
03. DataProvider和DataResolver(SEFC)
4. DataProvider和DataResolver (CEUG)
另外通常情况下DataProvider相关的Java方法的参数传递是通过DataSet的parameter定义的,详细说明参考:parameter
dataType
DataSet是一个数据容器,其中的数据由DataProvider提供,假如对应的DataProvider方法:
@DataProvider public Collection<Product> getAll() { return productDao.getAll(); }
DataSet要解析返回的数据,就需要提供其返回数据的格式声明,类似XML解析器需要你提供XML格式说明一样。DataSet的格式声明是由其dataType属性设定的,如我们返回一个Product类型的数据,就需要定义一个Product类型的DataType,并设定DataSet的dataType为[Product],如果返回的是Category类型的数据,就需要定义其dataType属性为[Category],这样DataSet就能正确的解析DataProvider返回的数据。
有关DataType的详细说明请参考:
另外需要说明的是,DataSet中定义的dataType属性有两种格式,带中括号和不带中括号的,如:
上图中dataType是不带中括号的,表示DataProvider返回的是单个Category对象,对应的服务器端的Java方法(注意放回值为单个Category):
@DataProvider public Category getCategory(long id) { return categoryDao.get(id); }
如果DataProvider放回的是一个Collection数组,如:
@DataProvider public Collection<Category> getAll() { return categoryDao.getAll(); }
那么我们就需要相应的调整DataSet的dataType属性,使之带中括号:
ingored
同Control的ignored,参考:Control(DCUG)#支持ignored属性设定
loadMode
数据集中数据的自动装载方式。
该属性具有下列两中可能的取值,默认情况下系统将按照lazy的方式来进行处理:
- preload - 预加载,也就是数据随同HTML代码一起下载
- onReady - 当数据集的状态变为ready时自动开始装载数据。注意:此处所指的的异步装载方式。
- lazy - 当数据集的getData()或getDataAsync()方法第一次被调用时开始装载数据。
- manual - 当数据集的flush()或flushAsync()方法被调用时才开始装载数据。
onReady
onReady模式加载机制下,打开页面的时候,可以看到Grid的右上角有一个装载数据的提示信息:
这是在页面打开后出现的,表示这是一个异步的数据请求。现在我们观察一下HTML的源码:
右键单击查看网页源代码,查看到的内容如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=IE8"> <title>Product Maintain</title> <script language="javascript" type="text/javascript" charset="UTF-8" src="/sample-center/dorado/client/boot.dpkg?cacheBuster=1352335375179"></script> <script language="javascript" type="text/javascript"> $import("widget"); </script> <script language="javascript" type="text/javascript"> dorado.onInit(function(){ AUTO_APPEND_TO_TOPVIEW=false; var view=new dorado.widget.View({ "id":"viewMain", "name":"com.bstek.dorado.sample.data.SimpleCRUD" }); function f(view){view.set("children",[ { "$type":"DataSet", "dataProvider":dorado.DataProvider.create("simpleCRUD#getAll"), "pageSize":10, "id":"dsProducts", "dataType":"v:com.bstek.dorado.sample.data.SimpleCRUD$[v:com.bstek.dorado.sample.data.SimpleCRUD$ProductType]" }, { "$type":"UpdateAction", "id":"actionSave", "icon":"url(skin>common\/icons.gif) -140px -20px", "updateItems":[ { "$type":"UpdateItem", "submitOldData":true, "dataSet":view.getComponentReference("dsProducts") } ], "executingMessage":null, "caption":"Save", "dataResolver":dorado.DataResolver.create("simpleCRUD#saveAll"), "successMessage":"\u4FDD\u5B58\u6210\u529F\uFF01" }, { "$type":"ToolBar", "items":[ { "$type":"DataPilot", "itemCodes":"pages,|,+,-,x,pageSize", "dataSet":view.getComponentReference("dsProducts") }, { "$type":"Separator" }, { "$type":"ToolBarButton", "action":view.getComponentReference("actionSave") } ], "layoutConstraint":{ "type":"top" } }, { "$type":"DataGrid", "selectionMode":"multiRows", "droppable":true, "dataSet":view.getComponentReference("dsProducts"), "dragTags":"a", "droppableTags":"a", "draggable":true } ]);} view.get("dataTypeRepository").parseJsonData([ { "propertyDefs":[ { "dataType":"long", "readOnly":true, "name":"id" }, { "dataType":"String", "name":"productName", "required":true }, { "dataType":"Long", "name":"categoryId" }, { "dataType":"String", "name":"quantityPerUnit" }, { "dataType":"float", "name":"unitPrice" }, { "dataType":"int", "name":"unitsInStock" }, { "dataType":"int", "name":"unitsOnOrder" }, { "dataType":"int", "name":"reorderLevel" }, { "dataType":"boolean", "name":"discontinued" } ], "id":"v:com.bstek.dorado.sample.data.SimpleCRUD$ProductType", "name":"ProductType" } ]); f(view); AUTO_APPEND_TO_TOPVIEW=true; var doradoView = document.getElementById("doradoView"); if (doradoView) view.replace(doradoView); }); $import("widget,common,base-widget,grid,debugger"); </script> </head> <body scroll="no" style="margin:0px; overflow:hidden"> <label id="doradoView" style="display:none" /> </body> </html>
上面的代码可以看出其中并未包含任何的产品数据。其产品数据是通过一个AJAX请求专门加载的,监控HTTP请求可以看到:
上图中可以看到加载页面和加载数据是两个独立的不同的请求,数据是通过一个名称为view-service的AJAX请求专门下载下来的。
这种加载机制的好处是可以当页面数据量较大的时候,可以先将页面渲染好,再加载数据,提高用户体验。
preload
preload与onReady的处理机制是相反的,它是在加载页面的时候顺便把数据一起下载下来,查看网页源代码:
就可以看到(注意其中包含一个data节点):
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=IE8"> <title>Product Maintain</title> <script language="javascript" type="text/javascript" charset="UTF-8" src="/sample-center/dorado/client/boot.dpkg?cacheBuster=1352335375179"></script> <script language="javascript" type="text/javascript"> $import("widget"); </script> <script language="javascript" type="text/javascript"> dorado.onInit(function(){ AUTO_APPEND_TO_TOPVIEW=false; var view=new dorado.widget.View({ "id":"viewMain", "name":"com.bstek.dorado.sample.data.SimpleCRUD" }); function f(view){view.set("children",[ { "$type":"DataSet", "dataProvider":dorado.DataProvider.create("simpleCRUD#getAll"), "pageSize":10, "id":"dsProducts", "dataType":"v:com.bstek.dorado.sample.data.SimpleCRUD$[v:com.bstek.dorado.sample.data.SimpleCRUD$ProductType]", "loadMode":"preload", "data":{ "$isWrapper":true, "data":[ { "id":1, "productName":"Chai", "categoryId":1, "quantityPerUnit":"10 boxes x 20 bags", "unitPrice":18.0, "unitsInStock":39, "unitsOnOrder":0, "reorderLevel":12, "discontinued":false }, { "id":2, "productName":"Chang", "categoryId":1, "quantityPerUnit":"24 - 12 oz bottles", "unitPrice":19.0, "unitsInStock":17, "unitsOnOrder":40, "reorderLevel":25, "discontinued":true }, { "id":3, "productName":"Aniseed Syrup", "categoryId":2, "quantityPerUnit":"12 - 550 ml bottles", "unitPrice":10.0, "unitsInStock":13, "unitsOnOrder":70, "reorderLevel":66, "discontinued":false }, { "id":4, "productName":"Chef Anton\'s Cajun Seasoning", "categoryId":2, "quantityPerUnit":"48 - 6 oz jars", "unitPrice":22.0, "unitsInStock":53, "unitsOnOrder":0, "reorderLevel":0, "discontinued":false }, { "id":5, "productName":"Chef Anton\'s Gumbo Mix", "categoryId":2, "quantityPerUnit":"36 boxes", "unitPrice":21.35, "unitsInStock":0, "unitsOnOrder":0, "reorderLevel":0, "discontinued":true }, { "id":6, "productName":"Grandma\'s Boysenberry Spread", "categoryId":2, "quantityPerUnit":"12 - 8 oz jars", "unitPrice":25.0, "unitsInStock":120, "unitsOnOrder":0, "reorderLevel":25, "discontinued":true }, { "id":7, "productName":"Uncle Bob\'s Organic Dried Pears", "categoryId":7, "quantityPerUnit":"12 - 1 lb pkgs.", "unitPrice":30.0, "unitsInStock":15, "unitsOnOrder":0, "reorderLevel":10, "discontinued":false }, { "id":8, "productName":"Northwoods Cranberry Sauce", "categoryId":2, "quantityPerUnit":"12 - 12 oz jars", "unitPrice":40.0, "unitsInStock":6, "unitsOnOrder":0, "reorderLevel":0, "discontinued":false }, { "id":9, "productName":"Mishi Kobe Niku", "categoryId":6, "quantityPerUnit":"18 - 500 g pkgs.", "unitPrice":97.0, "unitsInStock":29, "unitsOnOrder":0, "reorderLevel":0, "discontinued":true }, { "id":10, "productName":"Ikura", "categoryId":8, "quantityPerUnit":"12 - 200 ml jars", "unitPrice":31.0, "unitsInStock":31, "unitsOnOrder":0, "reorderLevel":0, "discontinued":false } ], "pageSize":10, "pageNo":1, "pageCount":8, "entityCount":78 } }, { "$type":"UpdateAction", "id":"actionSave", "icon":"url(skin>common\/icons.gif) -140px -20px", "updateItems":[ { "$type":"UpdateItem", "submitOldData":true, "dataSet":view.getComponentReference("dsProducts") } ], "executingMessage":null, "caption":"Save", "dataResolver":dorado.DataResolver.create("simpleCRUD#saveAll"), "successMessage":"\u4FDD\u5B58\u6210\u529F\uFF01" }, { "$type":"ToolBar", "items":[ { "$type":"DataPilot", "itemCodes":"pages,|,+,-,x,pageSize", "dataSet":view.getComponentReference("dsProducts") }, { "$type":"Separator" }, { "$type":"ToolBarButton", "action":view.getComponentReference("actionSave") } ], "layoutConstraint":{ "type":"top" } }, { "$type":"DataGrid", "selectionMode":"multiRows", "droppable":true, "dataSet":view.getComponentReference("dsProducts"), "dragTags":"a", "droppableTags":"a", "draggable":true } ]);} view.get("dataTypeRepository").parseJsonData([ { "propertyDefs":[ { "dataType":"long", "readOnly":true, "name":"id" }, { "dataType":"String", "name":"productName", "required":true }, { "dataType":"Long", "name":"categoryId" }, { "dataType":"String", "name":"quantityPerUnit" }, { "dataType":"float", "name":"unitPrice" }, { "dataType":"int", "name":"unitsInStock" }, { "dataType":"int", "name":"unitsOnOrder" }, { "dataType":"int", "name":"reorderLevel" }, { "dataType":"boolean", "name":"discontinued" } ], "id":"v:com.bstek.dorado.sample.data.SimpleCRUD$ProductType", "name":"ProductType" } ]); f(view); AUTO_APPEND_TO_TOPVIEW=true; var doradoView = document.getElementById("doradoView"); if (doradoView) view.replace(doradoView); }); $import("widget,common,base-widget,grid,debugger"); </script> </head> <body scroll="no" style="margin:0px; overflow:hidden"> <label id="doradoView" style="display:none" /> </body> </html>
上面源码查看中我们可以看出HTML代码中已经包含了相关的产品数据。
采用preload机制可以减少HTTP请求的次数,对于网路负荷较重的环境下可以改善用户的体验。
当然采用prelaod机制后,onReady中的AJAX请求就没有了。
manual
采用manual模式的DataSet默认不会加载数据,除非你通过类似代码调用DataSet的flush()或flushAsync()方法或其他导致DataSet需要加载数据的方法的时候,才会从客户端发出AJAX请求去加载数据。
lazy
lazy与onReady机制有些类似,都是懒转载数据模式。其区别是onReady模式在DataSet触发了onReady事件的时候就一定会执行,而lazy模式只有在你需要使用它的时候才会触发,相当而言lazy是更懒的一种数据加载机制。因此它也是默认的装载模式。
与onReady模式区别举例:
场景一
DataSet在客户端没有任何可见的数据绑定控件,该DataSet只在客户端某些逻辑触发的时候才需要访问其内部数据。这种场景下采用lazy模式优于onReady,假设用户只是打开这个页面看一看的话采用lazy不会导致这个DataSet的数据加载,从而降低网络数据加载的负担,减少网络数据流量。
场景二
标签页场景,多个DataSet,不同的标签页的数据绑定控件绑定不同的DataSet:
其中标签页2中的DataGrid的dataSet属性设置为dsProducts2.
页面效果如:
如上图,虽然我们打开这个页面,但是由于标签页2还未显示,其中的DataGrid还未访问过dsProducts2的getData()方法,因此这个时候dsProducts2中的数据还不需要加载。
这个时候如果将标签页切换到第二个,我们就可以在界面的右上角看到数据加载的系统提示信息:
这表示由于需要渲染DataGrid,从而导致DataGrid去访问dsProducts2中的getData()方法,从而导致异步的AJAX数据加载。
metaData
同Control的metaData,参考:Control(DCUG)#支持metaData属性设定
pageSize
装载数据时使用的分页大小,即按照每页多少条记录来进行分页装载。该属性会影响DataProvider对应Java方法的方法签名,详细说明参考:Page对象使用说明
parameter
装载数据时使用的参数,及传递给数据提供器(DataProvider)的参数。
DataSet中的数据都是由DataProvider提供的,也就是我们说的数据提供器。大多数情况下这些数据提供器都是向web应用服务器发出ajax请求,由服务器返回指定的数据给客户端的DataSet。
在请求数据的过程中,一些额外的请求参数都是通过parameter设定的,如根据产品分类查询产品名称,获取当前代办任务等。
parameter定义方式有多种,以下详细说明:
通过IDE可视化窗口完成配置
通过IDE视图可以直接在parameter中设置值,如下设置parameter的值为:"C01"
或者通过其向导窗口将parameter设置为一个对象类型的参数:
步骤一:激活parameter编辑框可以看到右侧有下拉小图标:
步骤二:单击上面的小图标,打开设计窗口:
在parameter节点下添加Entity对象,并添加多个Property,同时分别设置他们的name与value属性
通过以上两个步骤我们就将parameter设置为一个对象型的参数了。
当然这个parameter还可以定义为一个Collection结构,如:
通过JS动态设定
parameter在浏览器客户端可通过JS访问,因此事实上我们也可以通过JS代码动态设置,如:
单值参数设定:
dataSet.set("parameter", "C01");
对象型参数设定:
dataSet.set("parameter", { param1: "xxxx", param2: "xxxx", param3: "xxxx" });
Collection类型参数设定:
dataSet.set("parameter", [{ param1: "xxxx", param2: "xxxx", param3: "xxxx" },{ param1: "xxxx", param2: "xxxx", param3: "xxxx" },{ param1: "xxxx", param2: "xxxx", param3: "xxxx" }]);
通过JS动态设定的方式可以大大增强编程的灵活性。
服务器端访问参数的办法
以上面JS动态设定参数的不同方式分别说明
单值参数设定:
dataSet.set("parameter", "C01");
服务器端DataProvider的Java代码对应为:
@DataProvider public Collection<Product> getProducts(String parameter) { ... }
当然如果你想将参数业务化一些,根据智能方法中的类型适配原则可以改写为:
@DataProvider public Collection<Product> getProducts(String categoryId) { ... }
对象型参数设定:
dataSet.set("parameter", { param1: "xxxx", param2: "xxxx", param3: "xxxx" });
服务器端DataProvider的Java代码对应为:
@DataProvider public Collection<Product> getProducts(Map<String, String> parameter) { String param1 = parameter.get("param1"); String param2 = parameter.get("param2"); String param3 = parameter.get("param3"); }
根据智能方法中的类型适配原则上面的方法改写为如下的结构,也可以获取客户端上传上来的参数:
@DataProvider public Collection<Product> getProducts(String param1, String param2, String param3) {//注意参数名与JS中的要保持一致 ... }
Collection类型参数设定:
dataSet.set("parameter", [{ param1: "xxxx", param2: "xxxx", param3: "xxxx" },{ param1: "xxxx", param2: "xxxx", param3: "xxxx" },{ param1: "xxxx", param2: "xxxx", param3: "xxxx" }]);
服务器端DataProvider的Java代码对应为:
@DataProvider public Collection<Product> getProducts(Collection<Map<String, String>> parameter) { Iterator<Map<String, String>> it = parameter.iterator(); while (it.hasNext()){ Map<String, String> params = it.next(); String param1 = params.get("param1"); String param2 = params.get("param2"); String param3 = params.get("param3"); } ... }
有关智能方法适配的文章请参考:08. 智能方法适配(SEFC)
有关DataProvider的定义参考:dataProvider
readOnly
数据集是否只读。数据集的只读特性会影响绑定在上面的数据展现控件是否可编辑,如:Grid, TextEditor, RadioGroup和CheckBox等等。
根据DataSet的基本概念我们知道DataSet支持立体数据结构,如下的两个Grid是通过立体数据模型定义出来的,只定义了一个DataSet:
如果我们将对应的DataSet的readOnly设置为true,那么最终在浏览器中两个Grid都会处于只读不可编辑的状态。
技巧说明
如下图的一个界面
如果我们希望将其中所有的编辑框和Grid都设置只读,则只要设置DataSet的readOnly属性为true就可以。
另外在动态编程中,我们也可以通过JS动态控制DataSet的readOnly属性:
dataSet.set("readOnly", true/false);
tags
同Control的tags,参考:Control(DCUG)#支持标签设定(tags)
userData
同Control的userData,参考:Control(DCUG)#支持userData特性
Attachments:
ParameterDesign.PNG (image/png)
ParameterWizard1.png (image/png)
ParameterWizard2.png (image/png)
ParameterWizard3.png (image/png)
DataTypeSingelBean.PNG (image/png)
DataTypeCollection.PNG (image/png)
AutoForm.PNG (image/png)
SimpleCRUD.PNG (image/png)
onReady1.png (image/png)
onReady2.png (image/png)
onReady3.png (image/png)
TabSetDesign.PNG (image/png)
TabSetPreview.PNG (image/png)
TabSetLoading.PNG (image/png)
LinkageDropDowns2.PNG (image/png)
LinkageDropDowns1.PNG (image/png)