Dorado 9 : DataSet(DCUG)

简介

DataSet为客户端的一个不可见的数据容器,其中的数据可以通过JS直接访问或修改,也可以将其它可见控件绑定到DataSet上,以展现其中的数据和提供界面接口方便用户修改和调整。

基本概念详细说明请参考:

基本使用

请参考:05. 产品表的增删改和查询

详细属性说明

id

在一个View中DataSet的id必须唯一,且id的命名规则参考Java变量名的命名规则,避免用中文或数字命名,避免特殊符号等。

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参数将每次装载的数据缓存起来。

注意如果你设置了DataSet的pageSize属性,则cacheable的设置将被忽略,也就是说只有不设置pageSize属性的DataSet才支持cacheable设置

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属性,使之带中括号:

特别注意:DataSet将根据dataType属性定义中是否带中括号来判断DataProvider返回的是一个单实体对象还是一个集合类型的实体对象。

ingored

同Control的ignored,参考:Control(DCUG)#支持ignored属性设定

loadMode

数据集中数据的自动装载方式。

该属性具有下列两中可能的取值,默认情况下系统将按照lazy的方式来进行处理:

  • preload - 预加载,也就是数据随同HTML代码一起下载
  • onReady - 当数据集的状态变为ready时自动开始装载数据。注意:此处所指的的异步装载方式。
  • lazy - 当数据集的getData()或getDataAsync()方法第一次被调用时开始装载数据。
  • manual - 当数据集的flush()或flushAsync()方法被调用时才开始装载数据。
下面将详细说明这些模式之间的区别,在阅读下面模式区别之前请最好先掌握Chrome的开发者工具的基本使用,学会用Chrome的开发者工具查看网页源码,AJAX请求等。
我们以sample-center中提供的SimpleCRUD页面为例说明:
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数据加载。

场景二,并不仅仅限于标签页场景,其它如Dialog等等这些只要不会提前触发DataSet的getData()方法的页面形式都会使DataSet的数据加载被推迟。

 

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:

MasterDetail.PNG (image/png)
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)