简介
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特性