Aurora之DataSet
1.DataSet的直观认识
在Aurora框架中,DataSet是web前端与服务器直接进行数据交换的介质,是Aurora框架前端AuroraUI中最核心的部分。DataSet提供了对数据的操作和导航,对数据的操作会触发相应的事件(event),所有绑定(bind)到DataSet有UI组件会根据事件做出响应。
换句话说就是,Aurora框架中web页的操作基本都是围绕DataSet 进行操作的,是核心组件。框架将数据封装为DataSet之后,我们直接操作DataSet就可以实现相应的功能,专注于数据逻辑的操作,而对页面的相关组件不必去关心。
向服务器发送请求后,返回的页面中DataSet的创建脚本形式如下:
new Aurora.DataSet(
{“datas”:[{“user_id”:”1”,”user_name”:”HECADMIN”, “encrypted_foundation_password”:”……”}, //一条record
{“user_id”:”2”,”user_name”:”ADMIN”, “encrypted_foundation_password”:”……”},
……
],
“listeners”:{“update”,onUpdate, //注册的事件
“submitsuccess”: onModifyUserSuccess,
……
},
“fields”:[{“name”:”user_id”, "databasetype":"BIGINT","datatype":"java.lang.Long","isprimarykey":true}, //field
{"name":"user_name","databasetype":"VARCHAR","datatype":"java.lang.String",
"prompt":"SYS_USER.ACCOUNT","readonly":true},
{"name":"encrypted_foundation_password","databasetype":"VARCHAR","datatype":"java.lang.String",
"insertexpression":"sys_login_pkg.md5(${@user_password})"},
……],
“id”:”sys_user_edit_ds”,
“pagesize”:10,
……
}
);
DataSet是web前端的组件之一,其中包括数据(datas),事件(listeners),字段级别的元数据(fields),id以及其他的信息组成。
- datas中是DataSet初始化时的数据中的数据,是由多条数据记录(record)组成。DataSet将后台数据库查询出来的数据集统一封装到DataSet中。
- listeners中记录了当前dataSet中注册的事件。发生特定动作时会响应特定的事件。调用特定的事件或者自己定义的事件。
- fields中记录的是字段级的元数据信息,每个字段的名称,数据类型,提示(prompt)等属性。Field中的信息集中了DataSet定义以及bm中的field、ref-field中的信息的总和。
- DataSet其他的信息,如id,pageSize,querurl等等。
2.定义DataSet
DataSet是由多条record组成的,并且可以注册不同的事件。典型的DataSet的定义如下:
<a:dataSets>
<a:dataSet model="sys.sys_user" id="sys_user_edit_ds">
//model绑定的数据源.bm id DataSet的标识
<a:fields>//数据集
<a:field name="user_name" readOnly="true"/>
<a:field name="start_date" required="true"/>
<a:field name="end_date"/>
<a:field name="description" required="true"/>
<a:field name="frozen_date" readOnly="true"/>
</a:fields>
<events>//注册事件
<a:event name="submitsuccess" handler="onModifyUserSuccess"/>
<a:event name="update" handler="onUpdate"/>
</events>
</a:dataSet>
</a:dataSets>
dataSets中可以定义多个dataSet,在screen文件中,fields中一般声明的是具有特殊属性的field(例如readOnly,requird),否则可以省略不写,若没有具有特殊属性的field,则可省略fileds。在这里定义的field都是字段级别的元数据信息,若需要注册事件,可在events中可以给dataSet注册事件。
个人建议页面上面涉及到的字段数据,应该都在DataSet里面进行声明,既方便阅读,后期加prompt也方便。
3.DataSet的数据来源方式
DataSet中数据的来源方式主要有以下四种:
- 直接在dataSet中写入数据记录:
<a:dataSet id="users_ds">
<a:datas>
<a:record id="1" user_name="ADMIN" description="系统管理员"/>
<a:record id="2" user_name="HAPADMIN" description="费控系统管理_hand公司"/>
</a:datas>
</a:dataSet>
该方式一般用于定义下拉列表中的选项的值,但这样写不便于管理以及维护,因此不建议这样使用,建议将这样的一些值保存至数据库当中,在页面文件中定义一个dataSet,通过查询,从数据库中获取,再在页面中显示,以提高可维护性。ps:不建议,low
- 通过mode-query加载数据:
<a:init-procedure>
<model-query model="sys.sys_user" rootPath="user_model" defaultWhereClause=”user_name like ‘AD%’” queryOrderBy=”user_id”/>
</a:init-procedure>
<a:dataSets>
<a:dataSet model="sys.sys_user" id="sys_user_ds">
<a:datas dataSource="/model/user_model"/>
</a:dataSet>
</a:dataSets>
通过这种方式加载数据时,是在服务器端进行查询后与请求的页面一起发送到客户端,数据是一次性全部加载的。因些不可以进行分页显示。这种方式加载数据时可以通过在model-query中加入defaultWhereClause属性限定查询条件,加入queryOrderBy限定排序字段。ps:用的不多
- 使用loadData加载数据:
<a:dataSets>
<a:dataSet model="sys.sys_user" id="sys_user_ds" loadData="true"/>
</a:dataSets>
是方式2的一种简写形式,与方式2加载数据的实质是一样,会在后台自动转换为方式2的形式,但是使用loadData不能够限定查询条件和排序方式。ps:更简便,逻辑更加清晰。主流用法。
- 通过ajax请求数据:
<a:dataSet model= "sys.sys_user" id="sys_user_result_ds"
queryDataSet="sys_user_query_ds"/>
若增加autoQeury属性,则是在页面加载完成后,再发起ajax请求,从服务器获取数据,可以进行分页,若没有加入autoQuery属性,则可以在javaScript中通过脚本请求数据:
var dataSet = $(‘sys_user_result_ds’);
dataSet.query();
其中$(‘object_id’)
,是页面文件中组件id为object_id的对象,$(‘sys_user_result_ds’)
,获取id为sys_user_result_ds的对象,即id为sys_user_result_ds的DataSet,dataSet.query()调用DataSet的query方法,发起Ajax请求,查询数据。ps:在js中发起请求,在对应的DataSet中注册query事件。
4.DataSet中数据校验
DataSet中的数据校验分为两个形式,一种是DataSet内置的数据校验,另一种是自定义的数据校验
DataSet内置的数据校验,主要就是required校验(必输校验)。
自定义校验:如果校验通过需要返回true,否则返回一个字符串(为校验不通过的提示信息)。例如需要“开始日期”不能小于“结束日期”的校验,就需要自己写一个校验方法,并将dataSet中相应的Field的校验器指定为自己写的校验方法:
在需要校验的field中指定校验器:
<a:dataSet id="sys_user_edit_ds" model="sys.sys_user">
<a:fields>
<a:field name="start_date" required="true" validator="dateValidator"/>
<a:field name="end_date" validator="dateValidator"/>//校验时间
</a:fields>
<events>
<a:event handler="onUpdate" name="update"/>//注册update事件
</events>
</a:dataSet>
自定义校验器:
function dateValidator(record, name, value){//日期校验方法
if(name == 'start_date' || name == 'end_date'){//页面点击事件,点击开始日期或者结束日期
var start_date = record.get('start_date');//获取字段数值
var end_date = record.get('end_date');
if(!!end_date){//由于结束日期非必填,只有在结束日期填了才进行比较
if(!compareDate(start_date, end_date)){
//1.
return '开始时间不能大于结束时间';//校验不通过返回字符串,不建议这样写,在系统中创建系统描述使用系统描述的写法
//update事件触发会发起校验,也就是点击保存或者提交的时候会报错
//2.
Aurora.showMessage('${l:PROMPT}', '${l:EXP_REPORT_MAINTAIN.DATE_VALIDATE}');
//两个日期都选择之后就会发起校验,显示报错
return;
}
}
return true;//校验通过返回true
}
}
function compareDate(start, end){
if(start > end){
return false;
}
return true;
}
这样便可实现自定义校验,但是以上的校验存在一个问题:由于校验都是在当前的Record的相应字段进行更改之后才进行校验的,所以如果不输入开始时间,只输入结束时间,那么校验会直接通过,此时的解决方法是当更新的字段为“开始时间”或是“结束时间时”,通过向dataSet注册update事件,再调用一次校验即可:
function onUpdate(ds,record,name,value){
if(name == 'start_date' || name == 'end_date'){
record.validate((name == 'start_date') ? 'end_date' : 'start_date');
}
}
另外,required和readOnly、以及数值类型的allowDecimals、allowNegative校验应当写在field里面,否则不会经过DataSet进行校验,虽然readOnly、allowDecimals、allowNegative可以从组件上进行相关的输入控制。
5.向DataSet上绑定UI组件
DataSet的设计思想是数据的展示与数据分离解藕,这样可以更好地将注意力集中在数据的逻辑处理上,而不需要关心UI组件的变化,这样更有利于提高开发效率。
虽然将数据与展示分离开,但必须有通过某种方式将它们联系起来,在Aurora前端中的做法是将UI组件绑定到DataSet上,当对DataSet的数据进行操作时,会触发相应的事件绑定到DataSet上的UI组件会根据事件做出响应。例如DataSet上的值发生改变时,相应UI组件的值也会随之发生改变,当相应的Field元数据发生改变时(从可输入变为只读、从必输变为非必输),UI组件也会发生相应的变化。
ps:框架集成好的组件,我们直接拿来使用就行,将需要的组件绑定到相应的DataSet上面,使用name属性进行统一。
UI组件的绑定方法
若要将UI组件绑定的DataSet上,只要在组件上的bindTarget属性值指向要绑定的DataSet的id即可。如”sys_user_ds”的字段”user_name”使用文本编辑器(textField):
<a:dataSet id="sys_user_ds" model="sys.sys_user">
<a:fields>
<a:field name="user_name" required="true"/>
</a:fields>
</a:dataSet>
将textField绑定到”sys_user_ds”上,name指定对哪个字段进行编辑,bindTarget指定绑定的DataSet的id。
<a:textField bindTarget="sys_user _ds" name="user_name"/>
头行绑定
当对头行结构的数据进行操作时,例如当选中头表中的一条数据时,可能想查询所有的所选中数据的相应行表中的数据,这时候就可以将行表的DataSet绑定到头表对应的DataSet上去。
// WEB-INF/view/csh/CSH1015/csh_bank.screen
<a:dataSets>
<a:dataSet id="CSH1015_bankQuery_ds" baseUrl="/csh/bank">
//查询集
</a:dataSet>
<a:dataSet id="CSH1015_bankResult_ds" baseUrl="/csh/bank" queryDataSet="CSH1015_bankQuery_ds">
//结果集 头表
</a:dataSet>
<a:dataSet id="CSH1015_cnapsResult_ds" bindTarget="CSH1015_bankResult_ds" baseUrl="/csh/cnaps" queryDataSet="CSH1015_bankResult_ds">
//结果集 行表 绑定头结果集,通过查询头结果集返回所有数据
//baseUrl数据集来源
</a:dataSet>
</a:dataSets>
<span>
<a:queryForm bindTarget="CSH1015_bankQuery_ds" resultTarget="CSH1015_bankResult_ds">
//查询框,查询数据。绑定查询集,结果返回到头结果集
</a:queryForm>
</span>
<a:form>
<a:grid id="CSH1015_bankResult_grid" bindTarget="CSH1015_bankResult_ds">
//grid组件,展示行数据。绑定头结果集
</a:grid>
</a:form>
<a:form>
<a:grid id="CSH1015_cnapResult_grid" bindTarget="CSH1015_cnapsResult_ds">
//grid组件,展示行数据。绑定行结果集
</a:grid>
</a:form>
<a:dataSet id=“sys_code_head_ds” model=“sys.sys_codes”/ >
<a:dataSet id=“sys_code_line_ds” bindTarget=“sys_code_head_ds”
bindName= “sub_ds” model= “sys.sys_code_values”/>
//典型的头行绑定,行表绑定头表的DataSet。
将 sys_code_line_ds 绑定到 sys_code_head_ds 上,其内部实际上是sys_code_line_ds 做为一个 filed 添加到sys_code_head_ds中, 其中 field 的name值为bindName属性值 “sub_ds”,field的type为"dataSet",其它的field的type都没有值。
将一个dataSet绑定到另外一个上时,使用bindTarget指定绑定到哪个dataSet上,bindName指定绑定后的名称,其查询数据时的处理方式是:当头表数据中改变选中行时,会将选中的头表中的record数据整体做为行表的查询参数发送给服务器进行查询。
6.DataSet的属性列表
dataSet有以下属性:
属性名 | 作用 |
---|---|
model | 指定dataSet对应的bm,对应组件的数据来源 |
autoCreate | 指定创建dataSet时是否创建一条空记录(默认为false false:不创建 true:创建) |
autoQuery | 页面加载完成后,是否自动进行一次ajax请求查询数据(默认为false false:不查询 true:自动查询) |
fetchAll | 指定是否一次查询所有的数据(默认为false false:不全加载 true:一次加载) |
pageSize | 指定一次查询出数据的条数(默认10条) |
autoCount | 查询是否进行统计(默认为true false:不进行统计 true:统计查询的总条数、页数信息) |
canQuery | 是否可查询(默认true false:不可查询 true:可查询) |
canSubmit | 是否可提交(默认true false:不可提交 true:可提交) |
loadData | 是否服务器端加载数据(默认false false:不在服务器端加载 true:在服务器端加载) |
selectable | 是否可选择(默认false false:不可选择 true:可选择),选择框 |
selectionModel | 选择模式(默认multiple multiple:多选 single:单选),选择框样式 |
queryUrl | 查询的url地址 |
submitUrl | 提交的url地址 |
bindTarget | 绑定到目标dataSet,作为目标dataSet的是一个子DataSet |
bindName | 绑定后的名称 |
lookupCode | 值列表的code,使用系统代码的时候可以直接使用这个属性来获取对应的值,主要用于combox |
model: 指定dataSet对应的bm,查询、提交的url都对应到该bm上,若想要改变默认的查询、提交的url则需要设置queryUrl、submitUrl。多表或者头行表同时提交或者插入,更新使用svc进行操作。
autoCreate: 当dataSet中没有数据时,即没有记录(record)时,此时如果对record进行操作时,可能会达不到预期的效果。例如查询所用的DataSet中,有必输条件,则须要使autoCreate=”true”,以创建一条空的record。否则,查询条件中没有输入任何值,校验会直接通过。
loadData: 如果一次要查询出所有结果,并且除bm中已有的查询条件不需要其他查询条件,那么可以将loadData设置为true。否则应该通过model-query的方式,在defaultWhereClause中限定查询条件。
queryUrl: 设定查询的url地址,默认是dataSet的model对应bm的地址,若要改变默认的查询地址或是要添加请求参数时,可以重设url地址,例如可以设为如 …/autocrud/sys.sys_user/query?user_id=1 的形式。
submitUrl: 设置提交数据的url,默认是dataSet的model对应bm的地址,要改变默认url时,设置该参数。
fetchAll、pageSize、autoCount配合使用,以进行分页,fetchAll指定是否一次查询出所有数据,即是否进行分页查询,为true时一次性查询所有数据,false时一次查询pageSize条数据,autoCount指定是否对查询条数、页面数进行统计。
7.DataSet事件
DataSet对数据的操作,多数都是通过向DataSet中注册相关事件完成的,其中用的最多是update事件,事件注册的方式参考 “定义DataSet”。
其他常用的事件有:update、valid、indexchange、submitsuccess、submit等事件。
DataSet的事件列表:
8.Lov以及Combox的写法
Lov及ComboBox都可以通过field进行配置完成,例如选择角色,分别用Lov和ComboBox实现:
- Lov实现:
//DataSet
<a:dataSet id="sys_user_role_assign" model="sys.sys_user_role_assign">
<a:fields>
//lovService lov数据来源,可以通过查询sql,或者相应的bm获取
<a:field name="role_code" title="HAP_SELECT_ROLE" lovService="sys.sys_role" lovWidth="340" lovGridHeight="340" lovHeight="460" required="true">
<mapping>
<map from="role_id" to="role_id"/>
<map from="role_code" to="role_code"/>
<map from="role_name" to="role_name"/>
</mapping>
</a:field>
</a:fields>
</a:dataSet>
//UI的写法非常简单
<a:lov name="role_code" bindTarget=" sys_user_role_assign"/>
- ComboBox实现:
//DataSet
<a:dataSet id="sys_user_role_assign" model="sys.sys_user_role_assign">//model数据来源,可以是查询sql,通过查询创建的bm文件,或者lookupcode获取系统代码定义的combox数据。
<a:fields>
<a:field name="role_code" options="sys_role_ds" required="true">//获取多个值写法
<mapping>
<map from="role_id" to="role_id"/>
<map from="role_code" to="role_code"/>
<map from="role_name" to="role_name"/>
</mapping>
</a:field>
</a:fields>
</a:dataSet>
<a:dataset id="sys_combox_ds" lookupcode="PAYMENT_OBJECT"/>//PAYMENT_OBJECT 系统代码
//ComboBox
<a:comboBox name="role_code" bindTarget="sys_user_role_assign"/>
另外一种方式
//Field:
<a:field name="role_code" displayField="role_code" options="sys_role_ds" valueField="role_id" returnField="role_id"/>//获取一个值
//displayField 用于页面展示的字段名,即系统代码里面的XXX_name字段,
//name 展示在页面的字段名
//options 数据来源 dataset
//valueField 需要存入数据库的字段的值
//returnField 需要存入数据库的字段名
//ComboBox
<a:comboBox name="role_code" bindTarget="sys_user_role_assign"/>
9.DataSet的优缺点
优点:
- DataSet使得数据和展示分离开了,从而可以集中注意力在数据的逻辑处理,而不用关心UI组件的变化。
- 对于很多页面逻辑的实现,只需要对DataSet的元数据进行操作,而不需要对UI直接操作,如此可以减少繁琐的操作。不会因为许多组件使用一个字段而写大量重复代码。
不足:
DataSet中暴露了太多的数据操作细节了,例如insertExpression,数据类型等。
DataSet以loadData方式加载数据时,如果又写了autoQuery时,那通过autoQuery查询出来的数据就会覆盖通过loadData查询出来的数据。最好在schema文件中进行定义 或者在工具中就对其进行限制,或者是在解析时,遇到这种情况则报错。
queryUrl,如果不写queryUrl时,有默认的url 地址,而且地址一般都比较长,但写上的url地址一般都是在默认地址后面添加参数,前面的地址其实都没有必要写,可以用另外一个属性专门写地址后面的那些参数,如此,能够简化页面编写,而且便于查看。
DataSet是数据流转的核心。bm通过数据库表或者sql查询,获取到相应的数据,screen页面中DataSet对这些数据封装,通过调用DataSet的下的相应的组件,实现也页面上的数据流转操作。
养成习惯涉及到数据处理或者逻辑处理的尽量在包里进行操作,页面直接调用相应的存储过程就行,减少在页面进行数据处理。
开始的懵懵懂懂,一段时间的练习回头来看,体悟更深。虽然有些还是不懂,但是都有了自己的理解。