简述
员工信息维护界面主要用于维护员工信息表,在HSQL数据库中其定义如下:
CREATE MEMORY TABLE EMPLOYEE( EMPLOYEE_ID VARCHAR(16) NOT NULL PRIMARY KEY, DEPT_ID VARCHAR(16),EMPLOYEE_NAME VARCHAR(16), SEX BOOLEAN NOT NULL, BIRTHDAY TIMESTAMP, MARRIED BOOLEAN NOT NULL, SALARY DECIMAL(10,2), DEGREE VARCHAR(30), EMAIL VARCHAR(50), WEB VARCHAR(50), CMNT VARCHAR(255), IMAGE VARCHAR(50) )
该界面代表了单表维护的典型界面展现风格,利用AJAX技术实现增删改查等四种逻辑操作。员工信息维护(对应为Employee表)的基本开发过程与部门信息维护模块基本一样。在实现对员工信息维护的基础上添加了一些新的功能:
- EXCEL数据导出功能;
- 树形结构的下拉框;
- 员工字段非空校验;
- 日期格式化显示为XXXX年XX月XX日;
- 薪水格式化为#,###.## 元显示;
图例:
 
SqlDataset实现员工信息维护 (T1B)
创建视图模型(View)
在src/hr/manage文件夹下新建Employee视图模型对象:
 
 图表 6 51
创建记录集(Dataset)
在新建的视图模型中选中Datasets节点,点左边工具条上的SqlDataset按钮,系统自动生成一个SqlDataset对象:
 
 图表 6 52
 把默认生成的Dataset的id属性dataset1修改为datasetEmployee。
 设置Dataset的keyFields属性,值为employee_id,该属性在dataset执行批量数据提交时被使用。
 
 图表 6 53
 设置pageSize属性,值为10,通过这个属性,Dataset可以按照每页10条记录来分页。
 
 图表 6 54
 设置sql属性,值为"select employee.*, dept.dept_name from employee, dept where employee.dept_id=dept.dept_id"。
 
 图表 6 55
 选择datasetDept节点,并利用Auto Create Fields快捷菜单自动生成datasetEmployee中的字段:
 
 图表 6 56
 设置各个字段的label属性如下:
| 字段 | label | 
|---|---|
| EMPLOYEE_ID | 员工编号 | 
| DEPT_ID | 部门编号 | 
| EMPLOYEE_NAME | 姓名 | 
| SEX | 性别 | 
| BIRTHDAY | 生日 | 
| MARRIED | 婚否 | 
| SALARY | 薪水 | 
| DEGREE | 学历 | 
| 邮件 | |
| WEB | 网址 | 
| CMNT | 备注 | 
| DEPT_NAME | 部门名称 | 
非空校验
展开datasetEmployee的Fields节点,找到EMPLOYEE_ID字段,并利用快捷按钮,添加RequiredValidator对象:
 
 图表 6 57
 该对象用以给EMPLOYEE_ID字段加上非空校验器。提供非空校验处理。
格式化显示
并设置BIRTHDAY字段的format属性为:"yyyy年MM月dd日"
 
 图表 6 58
 该属性主要用于设定BIRTHDAY字段的格式化显示。
 设置SALARY字段的format属性为:"#,###.## 元",可从下拉列表中选出"#,###.##",并在格式化字符串后添加" 元"
 
 图表 6 59
创建数据表格(DataTable)
Dataset设定结束之后,我们再使用数据表格展示dataset的数据。以及通过数据表格的增删改等操作dataset的数据。
 在当前的视图模型中选择Controls节点,添加DataTable对象,并选中新建的DataTable对象修改其属性:
| 属性 | 值 | 
|---|---|
| dataset | datasetEmployee | 
| fixedColumn | 2 | 
| height | 100% | 
| id | tableEmployee | 
| width | 100% | 
设计视图如下:
 
 图表 6 60
 利用tableEmployee的Auto create fields快捷方式根据datasetEmployee的字段导入column对象:
 
 图表 6 61
 并调整自动生成的DEPT_NAME列的顺序,以及删除自动生成的IMAGE列:
 
 图表 6 62
创建分页导航条(PagePilot)
选中Controls节点,添加组件PagePilot,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | pagepilotEmployee | 
| dataset | datasetEmployee | 
设计视图如下:
 
 图表 6 63
创建数据导航条(DataPilot)
选中Controls节点,添加组件DataPilot,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | datapilotEmployee | 
| dataset | datasetEmployee | 
设计视图如下:
 
 图表 6 64
创建数据表单(AutoForm)
选中Controls节点,添加组件AutoForm,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | formEmployee | 
| dataset | datasetEmployee | 
选择formEmployee节点,点左边工具栏中Auto create elements快捷按钮利用绑定的datasetEmployee中的字段信息自动生成表单元素。
 生成表单元素后,展开formEmployee节点,并选中自动生成的<FormGroup>节点,设置title属性为"员工信息"。并调整<FormGroup>中的节点顺序:
 
 图表 6 65
 设定结束后保存视图模型的所有修改。
创建数据更新命令(UpdateCommand)
选中Controls节点,点左边工具栏中的图标(从上往下数第7个),在列表中选择UpdateCommand。生成后,修改新建命令对象的id为commandSave,并展开commandSave节点,在datasetInfos节点下添加一个新的datasetInfo节点,并设定其dataset属性为datasetEmployee:
 
 图表 6 66
 表示命令执行时自动的将DatasetInfos中包含的datasetEmployee对象提交到服务器。
创建保存按钮(Button)
设定好数据更新命令之后,由于数据更新命令是不可见对象,为了方便用户操作,我们,还需要添加一个按钮,使用户单击该按钮时自动执行commandSave的默认动作。
 选中Controls节点,新增Button对象,并设置其属性如下:
| 属性 | 值 | 
|---|---|
| command | commandSave | 
| id | buttonSave | 
| value | 保存 | 
| width | 60 | 
设计视图如下:
 
 图表 6 67
创建JSP
之后在WebRoot/manage目录下,并利用当前员工维护的Employee视图模型的JSP生成向导在manage目录中生成employee.jsp页面。
 并利用Layout调整employee.jsp的布局设定,最终代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://www.bstek.com/dorado" prefix="d" %> <html> <head> <title></title> </head> <body> <d:View config="hr.manage.Employee"> <d:Layout type="vflow" height="100%"> <d:Pane> <d:AutoForm id="formEmployee" /> </d:Pane> <d:Pane> <d:Layout type="Hflow"> <d:Pane> <d:DataPilot id="datapilotEmployee" /> </d:Pane> <d:Pane> <d:Button id="buttonSave" /> </d:Pane> </d:Layout> </d:Pane> <d:Pane height="100%"> <d:DataTable id="tableEmployee" /> </d:Pane> <d:Pane align="right"> <d:PagePilot id="pagepilotEmployee" /> </d:Pane> </d:Layout> </d:View> </body> </html>
我们在主框架界面的菜单对象初始化的时候(MainViewModel.java中的initControls方法)已经设定好了相关菜单项的连接功能,代码如下:
subItem = item.addItem("employee", "员工信息"); 
subItem.setPath("manage/employee.jsp");
上面的菜单项的path属性定义为"manage/employee.jsp"。
查看运行效果
下面我们通过主框架查看效果。在主框架中选择部门信息菜单项。
 最终效果:
 
 图表 6 68
 现在让我们验证一下员工维护界面的基本功能:
 AutoForm与表格的数据联动功能:你可以选择表格中的不同行,注意观察AutoForm中的数据是否和表格中的当前行保持一致;你也可以修改表格中的数据并注意观察AutoForm中的数据是否能保持一致;
 批量数据更新:利用DataPilot对表格进行增删改的操作,并最终单击保存所有修改按钮完成批量提交;提交后刷新页面验证提交是否成功; 
 分页导航条的翻页功能;
功能改进一:添加自定义部门下拉框
为了实现部门下拉选择,该处我们使用了自定义下拉框(CustomDropDown),CustomDropDown是dorado中用以引用另一个dorado JSP页面的特殊下拉框,通过引用配置,该下拉框能够将另一个JSP页面在下拉框中展示,并通过CustomDropDown的数据处理机制,实现数据输入或数据选择,如下图所属部门字段维护时,通过选择下拉框中的树节点实现,而不是用户直接在编辑框中输入:
 
 图表 6 69
 下拉框中的树,采用了二级树展现部门信息,其中树的一级节点为分公司信息,二级节点为部门信息,在数据库中它们的关联关系如下:
CREATE MEMORY TABLE BRANCH( BRANCH_ID VARCHAR(20) NOT NULL PRIMARY KEY, BRANCH_NAME VARCHAR(50) ) CREATE MEMORY TABLE DEPT( DEPT_ID VARCHAR(16) NOT NULL PRIMARY KEY, BRANCH_ID VARCHAR(16), DEPT_NAME VARCHAR(30) )
Dept表通过branch_id字段与branch表建立关联关系。 
 实现部门树形下拉框的两个关键部分:
- 定义CustomDropDown并引用包含Tree的JSP;
- 创建包含Tree组件的JSP;
下面逐一说明其开发过程。
定义CustomDropDown
为了实现部门的树形下拉框,首先需要使用CustomDropDown实现。
 选中Controls节点,新增CustomDropDown,修改其id属性为dropdownDept。并设置其他的属性:
| 属性 | 值 | 说明 | 
|---|---|---|
| path | 本例为:deptTree.jsp,该页面将在后面创建 | 定义下拉框中显示的内容,可以设置为指向一个页面的路径 | 
| autoDropDown | true | 决定下拉框得到焦点时是否自动触发 | 
| cachable | true | 是否缓存下拉框中的内容 | 
| fixed | true | 决定编辑框中的值是否可编辑 | 
属性设置图示:
 
 图表 6 70
 并设定datasetEmployee的dept_name字段的dropdown属性为dropdownDept:
 
 图表 6 71
创建包含Tree的JSP
在本例中我们对部门列表信息采用树形结构的方式显示。
添加新的视图模型
在src/hr/manager目录下创建一个View,命名为DeptTree。
 
 图表 6 72
添加DataTree
选中Controls文件夹,添加一个组件DataTree。并修改id属性为TreeDept。选择TreeDept,并利用快捷按钮添加Tree中的Level对象,并将name属性修改为levleBranch。选中levelBranch节点,并使用同样的方式在levleBranch上再添加一个子节点。并设置name属性为levelDept。如下图:
 
 图表 6 73
 树的层设计为:树的第一层(levelBranch)显示分公司数据,第二层(levelDept)显示部门数据。下面再作详细设计。
添加datasetBranch,datasetDept
因为treeDept的每一层都需要显示不同的信息:分公司信息或某个分公司下的部门信息。Tree提供的Level对象提供了dataset属性用于绑定Dataset,在系统运行时,Level会自动的根据dataset中的数据初始化Tree的节点。因此下一步我们首先创建这两个Level所对应的SqlDataset,其id分别为datasetBranch和datasetDept。
 datasetBranch的创建:
 新增一个SqlDataset,其基本属性的配置如下:
| 属性 | 值 | 
|---|---|
| id | datasetBranch | 
| sql | select * from branch | 
利用SqlDataset的Auto Create Fields快捷按钮自动生成字段,如下图:
 
 图表 6 74
 datasetDept的创建:
 新增一个SqlDataset,其基本属性的配置如下:
| 属性 | 值 | 
|---|---|
| id | datasetDept | 
| sql | select * from dept where branch_id=:branch_id | 
利用SqlDataset的Auto Create Fields快捷按钮自动生成字段,如下图:
 
 图表 6 75
 其中datasetDept中sql语句中的:branch_id是SqlDataset中参数的定义方式,我们为datasetDept定义一个参数,便于在运行期动态的设定branch_id的值,datasetDept根据新的分公司编号从数据库中查询出相关的部门信息。该参数值我们会利用dataset的MasterLink技术,动态的从datasetBranch中获取。具体如下设置。
设置datasetBranch与datasetDept的关联关系
下面我们将datasetBranch与datasetDept建立主从关联关系,确保datasetDept中:BRANCH_ID的参数值来源于datasetBranch。在dorado中通过dataset的MasterLink对象建立这种关联关系。
 选中datasetDept的MasterLink,点属性masterDataset的编辑框,会出现一个按钮,点这个向导按钮。
 
 图表 6 76
 进入MasterLink编辑向导窗口:
 
 图表 6 77
 选择Fields from Master Dataset中的BRANCH_ID,以及Fields from Detial Dataset中的BRANCH_ID,点下面的Add按钮。
 系统将会在Join Relations编辑框中出现 "BRANCH_ID=BRANCH_ID"数据行。
 这样就通过MasterLink的配置给两个Dataset之间定义了一个主从关联关系。datasetDept会自动的通过MasterLink对象的配置动态的从datasetBranch中获取BRANCH_ID的值并作为datasetDept的参数:BRANCH_ID的值。
Level的dataset绑定
选中创建的treeDept,选中levelBranch:
- labelField属性设置为BRANCH_NAME,用于树节点的标题显示。
- dataset属性设置为datasetBranch。

 图表 6 78
 接下来,选中levelDept
- labelField属性设置为DEPT_NAME,用于树节点的标题显示。
- dataset属性设置为datasetDept。
- hasChild设置为false,表示无子节点,即本层是叶子节点。

 图表 6 79
创建下拉框JSP
在视图模型DeptTree中选择View根节点,并利用向导自动创建JSP页面,命名为deptTree.jsp,并保存在WebRoot/hr/manage目录中
 JSP页面代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://www.bstek.com/dorado" prefix="d" %> <html> <head> <title></title> </head> <body> <d:View config="hr.manage.DeptTree"> <d:DataTree id="treeDept" /> </d:View> </body> </html>
浏览修改效果
现在我们已经可以浏览基本的执行效果了,浏览器中重新输入:
界面效果如下:
 
 图表 6 80
继续完善下拉框的选择功能
在视图模型DeptTree中,选中treeDept组件对象,切换到事件视图(选择窗口最下方的Events Inspector按钮),在treeDept的onClick事件中增加代码:
if(tree.getCurrentNode().getLevel() != 1) DropDown.closeFrame(tree.getCurrentNode().getRecord());
以上代码的意思是,当选择节点的层次不是第一层时,也就是说不是分公司节点时,返回选中节点的附属Record对象,并且通过DropDown的closeFrame函数关闭下拉框。由于在treeDept的设计中,我们规定了第一层节点显示分公司的信息,通过这样处理,就可以确保只有在用户选择了部门节点之后,下拉框才会关闭。
 回到视图模型Employee中,选中dropdownDept对象,设置其中的readFields和writerFields的属性都为DEPT_ID,DEPT_NAME。两个属性保持一致。如下图:
 
 图表 6 81
 这两个属性在用户选中下拉框的一个部门节点后使用,dropdownDept会自动的从下拉框中返回的record中获取DEPT_ID,DEPT_NAME的信息并设置到主界面上。
 完成以上的设定之后,重新刷新employee.jsp页面,并打开下拉框操作,选择不同的部门后,可以看到主界面上的所属部门信息也发生了变化。
功能改进二:excel数据导出
参考快速入门中系列一中的excel数据导出。
功能改进三:AJAX交互时Server端的编程方式
一个业务界面通常会包含很多的业务逻辑操作,如下图:
 
 图表 6 82
 上图页面中的项目合同管理中状态控制,支付检查等操作。这些都需要与Server交互,在传统的页面流技术中,我们通常执行一个业务操作就需要跳转一次,刷新本页面或跳转到另一个页面,而采用了AJAX处理技术之后,我们就没有必要这么麻烦了。当用户单击页面上的各个按钮时,都由系统内部自动的向Server发出AJAX请求,并将反馈信息显示到当前页面上,在这整个业务操作中,页面是不需要转向的。
 因此开发上来说我们只要在Server端提供各种Services接受当前页面向Server发出的AJAX请求即可。
 而dorado中的视图模型实现类就是这样的一个Service。ViewModel的实现类提供了组件和dataset的初始化事件,负责接收客户端UpdateCommand的数据提交请求和RPCCommand发出RPC请求。以及客户端获取数据的请求。
 因此在多数情况下我们建议使用ViewModel的实现类作为后台业务逻辑的调用接口。
 为了说明视图模型实现类的用法,下面我们在员工维护界面上添加一些业务功能。这些请求我们都会使用dorado内部集成的AJAX技术向视图模型的实现类发出服务请求,并由实现类负责接收这些请求,并最终完成业务逻辑操作后将反馈信息返回到当前的操作界面上。而在这整个过程中用户是不需要刷新本页面的。
查询(FlushData方式)
该种实现方式在前文中已经有很多的应用了,在本文前面的各种查询实现方式中我们都采用了Listener实现,由dataset的loadData动作触发listener的事件。
 实际上我们也可以定义一个视图模型实现类,通过实现类接受客户端发出的数据查询请求,并获取客户端发出查询请求相关的信息,如pageIndex,查询条件等。
 下面我们实现datasetEmployee的查询,查询设定基本上与部门信息维护中查询的设定方式相同。
 本文作简要说明:
定义视图模型的实现类
我们利用dorado studio的视图模型实现类向导为Employee创建一个实现类:
 
 图表 6 83
 选择View根节点,并单击左侧的  图标,打开视图模型实现类的设计向导:
 图标,打开视图模型实现类的设计向导:
 
 图表 6 84
 保持默认的设定不变,将Options列表中的doLoadData_forSingleDataset方法选中。单击OK按钮,关闭向导窗口,系统自动生成EmployeeViewModel.java的代码如下:
package hr.manage;
import com.bstek.dorado.common.*;
import com.bstek.dorado.data.*;
import com.bstek.dorado.view.*;
import com.bstek.dorado.view.data.*;
import com.bstek.dorado.view.control.*;
/**
 * EmployeeViewModel
 */
public class EmployeeViewModel extends DefaultViewModel {
    protected void doLoadData(ViewDataset dataset) throws Exception {
        // Add your code here
        super.doLoadData(dataset);
    }
}
其中doLoadData方法是视图模型接受客户端dataset的数据请求时会自动触发的事件。参数dataset既为发出数据请求的dataset对象,本例为datasetEmployee。下面我们修改doLoadData方法。注意看下图:
 
 图表 6 85
 如果我们将视图模型切换到下方Soruce Editor时,我们会发现SqlDataset类型datasetEmployee的xml申明中,其真实类型是Wrapper,这是dataset中的一种包装器技术,用于方便的将各种类型的dataset封装到一起使用。在Listener调用事件中,dorado会自动处理包装器对象,并将原始的dataset传递给Listener中的事件使用。但是如果我们在视图模型实现类中直接使用,则我们默认获取的就是Wrapper包装过的dataset对象。而为了获取其包装的原始dataset对象。我们可以通过包装器Java类DatasetWrapper.java提供的方法getWrappedDataset()获取。参考6.1.2.12中的代码实现,本处实现查询的代码修改如下:
protected void doLoadData(ViewDataset dataset) throws Exception {
        if ("datasetEmployee".equals(dataset.getId())){
            SqlDataset datasetEmployee = (SqlDataset)((DatasetWrapper)dataset).getWrappedDataset();
            String employeeId = dataset.parameters().getString("employee_id");
            String deptId = dataset.parameters().getString("dept_id");
            String employeeName = dataset.parameters().getString("employee_name");
    
            String sql = "select * from employee";
            String where = "";
            if (StringHelper.isNotEmpty(employeeId)){
                where = " (employee.employee_id like '%"+employeeId"%')";
            }
            if (StringHelper.isNotEmpty(deptId)){
                where = ((StringHelper.isEmpty(where))?"":" and ")" (employee.dept_id like '%"deptId"%')";
            }
            if (StringHelper.isNotEmpty(employeeName)){
                where = ((StringHelper.isEmpty(where))?"":" and ")" (employee.employee_name like '%"employeeName"%')";
            }
    
            datasetEmployee.setSql(sql + ((StringHelper.isEmpty(where))?" ":" where ") + where);
        }
        super.doLoadData(dataset);
}
以上代码中通过getWrappedDataset获取原始Dataset对象:
SqlDataset datasetEmployee = (SqlDataset)((DatasetWrapper)dataset).getWrappedDataset();
获取原始的dataset之后我们就可以利用其setSql方法重新设置其sql语句。
 以上代码中还需要注意两点:
- 先做判断,再编写逻辑代码: - if ("datasetEmployee".equals(dataset.getId())){ 
 ....
 }
- 修改SqlDataset属性的逻辑代码必须写在super.doLoadData方法之前,确保sql语句的修改在dataset执行查询之前完成。便于生效。
通过以上的实现类的doLoadData方法的定义,我们已经可以使datasetEmployee支持查询处理了,可以说万事俱备只差客户端的AJAX查询请求了。
修改界面以便发出查询请求
下一步我们要做的就是给dataset的parameters赋值,并利用dataset的flushData并向服务器重新请求数据。flushData执行时会自动的将parameters中的值传递到服务器的监听器中。
 flushData方法的基本说明如下:
 flushData是dataset提供的客户端方法,所有dataset都具有该方法。该方法的主要作用是重新从服务器端下载数据集的数据来填充当前dataset。执行此方法时dataset中原有的记录将被全部清除。
 在客户端可以直接调用flushData()方法向服务器发出请求:
//范例1:范例说明:利用dataset的flushData()方法,上传dept_id参数信息,并重新获取数据/
dataset.parameters().setValue("dept_id", "D11");dataset.flushData();
更详细的内容请参考<<dorado 5 用户指南 v1.1 .doc>>。
 上述代码范例中的参数值是通过代码写在程序中的,我们的范例希望由用户可以直接在界面上输入查询条件,并单击查询按钮得到查询结果。下面我们就按照这种方式实现datasetDept的查询功能。
 界面的基本风格如下:
 
 图表 6 86
 上图的最上方区域为用户输入查询条件的区域,并最终单击查询按钮实现员工信息的查询功能。
 设计步骤如下:
 步骤一:Employee视图模型中添加新的FormDataset,并命名为datasetCondition。同时新增三个字段属性设置如下图:
 
 图表 6 87
 步骤二:添加AutoForm对象formQuery,并绑定到datasetCondition上,如下:
 
 图表 6 88
 步骤三:新增Button对象buttonQuery,属性设置如下:
| 属性 | 值 | 
|---|---|
| id | buttonQuery | 
| value | 查询 | 
| width | 60 | 
步骤四:新建QueryCommand对象,命名为commandQuery,并设定conditionDataset为datasetCondition。queryDataset属性设置为datasetEmployee。如下图:
 
 图表 6 89
 步骤五:并将buttonQuery的command属性设定为commandQuery。通过commandQuery自动的帮我们完成datasetCondition中的值向datasetEmployee的parameters中赋值的动作。
 步骤六:利用AutoForm的Auto create elements快捷按钮,导入datasetCondition的字段信息。并设定<FormGroup>的title属性为"查询条件",以及利用AutoForm的Custom Element对象将buttonQuery引入到formQuery中。如下图:
 
 图表 6 90
 步骤七:在employee.jsp上修改代码,将新增的formQuery添加到JSP中:
| <d:Layout type="vflow" height="100%">  | 
其中的粗体代码为新增内容
 保存所有修改。
 (查询设置完成)
 体验查询结果
 重新启动服务后,刷新页面,在查询条件中输入部门编号为"D1",并单击查询按钮:
 
 图表 6 91
实现员工加薪(UpdateCommand方式)
上例使用flushData技术调用视图模型实现类的处理方法实现AJAX交互,下面我们再利用UpdateCommand实现dorado中的update操作。Update操作在dorado中被独立为一个AJAX处理的分类。它在dorado开发当中起着非常重要的作用。与前面的flushData技术不同:
 flushData技术中的请求参数以dataset的parameters()为主,由客户端发送到server(在本节中我们约定server特指视图模型实现类或监听器对象)。并由视图模型实现类或监听器对象分析参数信息以及dataset的pageSize和pageIndex信息,将新的查询结果返回到client端。
 而在update处理技术中,有几个大的特征:
- 可以指定一个到多个dataset对象,并将client端的dataset中的数据发送到视图模型实现类;
- 可以通过视图模型对UpdateCommand的method分派技术支持,指定调用视图模型实现类的某一个具体方法;
- UpdateCommand提供了数据回写的功能,提交到实现类中的数据,在实现类中被修改和删除后,在UpdateCommand执行结束之后,系统会自动的将数据同步到Client端;
下面我们对员工维护界面实现员工加薪的开发。界面效果如下:
 
 图表 6 92
 这种业务功能的实现方式一就是我们可以直接在表格中修改薪水字段,让用户将原来的薪水添加500。但是这种处理方式显然存在很多不确定因素。如用户输入错误。
 实现方式二就是,我们在加薪按钮的onClick事件中加入如下的代码:
var record = datasetEmployee.getCurrent(); 
var salary = record.getValue("salary"); 
record.setValue("salary", salary+500); 
dataset.postRecord();
这种实现方式将加薪的业务逻辑直接放在客户端的JS代码中,也不是很合适,例如:网络通信中被人截取其中的信息,并修改其中的数字。
 更为安全的做法是Client端告诉Server谁需要实现加薪操作,而由Server端的业务Java类负责加薪操作,这样我们就可以在实现类中加入校验机制:如判断该员工是否满足加薪条件,以及应该加薪多少等。
 本例主要目的在于阐述如何利用实现类实现业务逻辑操作,并且如何同dorado的客户端实现AJAX方式的交互,因此在实际开发中会略过各种校验的实现。
 由于我们希望实现一种界面操作效果:当员工加薪成功后,表格中的数据可以自动与数据库同步,则我们就需要利用UpdateCommand的数据回写功能。
 下面我们就利用UpdateCommand技术,实现员工加薪。
 步骤如下:
步骤一:添加加薪的业务方法
在实现类EmployeeViewModel.java代码中添加一个新的方法raiseSalary实现员工加薪:
public void raiseSalary(ParameterSet parameter, ParameterSet outParameters)
            throws Exception {
        Dataset datasetEmployee = getDataset("datasetEmployee");
        if (datasetEmployee.getCurrent() != null) {
            String employeeId = datasetEmployee.getString("employee_id");
            // @todo 添加业务逻辑判断,如根据工号判断该员工是否允许加薪,本处省略....
            float salary = datasetEmployee.getFloat("salary") + 500;
            datasetEmployee.setFloat("salary", salary);
            MessageHelper.addMessage(DoradoContext.getContext(), "["
                    + datasetEmployee.getString("employee_name") + "]已加薪至"
                    + salary + "!");// 利用MessageHelper添加系统提示信息,告诉操作员操作结果。
        }
        super.doUpdateData(parameter, outParameters);
}
业务方法的定义需要注意的几点:
- 方法的作用域需要定义为public,便于客户端的UpdateCommand可以调用该方法;
- 本例中的加薪实现利用了SqlDataset本身的数据保存功能,因此注意方法的最后一定要保留代码: - super.doUpdateData(parameter, outParameters); 
- 修改薪水时调用了datasetEmployee的setFloat方法,注意该方法只是修改dataset的内部数据,最终是需要通过super.doUpdateData方法提交的。
步骤二:添加UpdateCommand对象调用实现类的业务方法
这一步,我们在Employee视图模型的Controls节点下新增一个UpdateCommand对象,并命名为commandRaiseSalary,同时设定其DatasetInfos中提交的dataset对象为datasetEmployee。
 
 图表 6 93
 特别注意1:submitScope
 上图中我们设定DatasetInfos中被提交的datasetEmployee的submitScope属性的设定。
 在UpdateCommand提交处理机制中,默认情况下会提交DatasetInfos中指定的dataset对象的所有发生数据变动的记录。这样使我们将业务逻辑的操作集中在这少部分数据上,另外也可以在很大程度上减少网络的数据流量。
 这种默认处理机制是可以改变的,它就是由submitScope属性决定,目前可选的有:
| submitScope的取值 | 含义 | 
|---|---|
| all | 提交所有的记录 | 
| all-change | 提交所有发生数据变动的记录,该值也使系统的默认值 | 
| all-visible | 提交所有被删除记录以外的其他可见记录 | 
| current | 提交dataset光标所在行记录 | 
| selected | 提交所有选中行的记录,一般用于多选处理 | 
本例中,我们就将submitScope的值设置为current,即表示选中表格中的某一个员工,单击加薪按钮时,就提交当前选中行的记录,对该员工加薪。
 特别注意2:设置commandRaiseSalary的method属性
 View支持实现UpdateCommand的方法调用分派工作,例如我们在上例中的EmployeeViewModel.java中定义了raiseSalary方法,则我们就可以在commandRaiseSalary中设定其method方法为raiseSalary:
 
 图表 6 94
 这样commandRaiseSalary命令执行时,视图模型会自动的将它的请求转发到EmployeeViewModel.java的raiseSalary方法中,并由raiseSalary方法实现具体的业务操作。
步骤三:添加加薪按钮
在Controls节点下新增Button对象,并设定其属性如下:
| 属性 | 值 | 
|---|---|
| command | commandRaiseSalary | 
| id | buttonRaiseSalary | 
| value | 加薪 | 
| width | 60 | 
设定buttonRaiseSalary的command属性为commandRaiseSalary。用以实现绑定。
 最后我们在employee.jsp中添加buttonRaiseSalary按钮,添加位置参考以下的蓝色代码:
| <d:Layout type="Hflow">  | 
预览运行效果
刷新该页面,并单击加薪按钮测试。
工号唯一校验(RPCCommand方式)
在员工信息维护中要求新增记录时,员工的工号不能重复,由于dataset只包含查询结果的一部分记录,因此我们无法通过dataset内部的员工列表判断工号是否重复。通常情况下系统都是在进行数据提交的时候由server端再作统一判断。
 在本例中我们通过一个RPCCommand对象,实现用户输入的即时判断。运行效果就是用户一但输入一个重复的工号,系统就自动的给出提示信息,并提醒用户重新输入。
 RPCCommand对象的用法,与UpdateCommand对象较为类似。RPCCommand对象无法指定提交client端的dataset对象。但是我们可以将需要发送到server的信息用RPCCommand对象的parameters()参数集合封装。并通过RPCCommand的method属性告诉视图模型将请求转发给实现类的具体方法。实现类业务方法调用结束后可以通过outParameters集合将参数返回到client端。
 实现步骤如下:
步骤一:实现类中添加工号重复判断的业务方法
在实现类EmployeeViewModel.java代码中添加一个新的方法checkEmployee实现员工工号判断:
public void checkEmployee(ParameterSet parameter, ParameterSet outParameters)
            throws Exception {
        outParameters.setBoolean("success", false);
        String employeeId = parameter.getString("employeeId");
        if (StringHelper.isNotEmpty(employeeId)) {
            DBStatement stmt = new DBStatement();
            try {
                stmt.setSql(DBStatement.SELECT, "employee");
                stmt.parameters().setString("employee_id", employeeId);
                VariantSet vs = stmt.query();
                outParameters
                        .setBoolean("success", (vs != null) ? true : false);
            } finally {
                stmt.close();
            }
        }
}
员工查询代码中我们用了DBStatement工具类作工号查询,如果你对此不太熟悉也可以直接通过dorado提供的ConnectionHelper获取DataSource配置中的相应Connection处理查询,获取Connection的方法如下:
public static Connection ConnectionHelper.getConnection(String datasource);
其中的datasource既为项目dorado资源文件夹,即home文件夹下的DataSource.xml文件中配置的datasource的name。
 根据判断结果设置outParameters参数集合,该集合在请求结束后会返回到client端。
步骤二:定义RPCCommand对象调用业务方法
在视图模型的Controls中添加一个RPCCommand对象,属性定义如下:
| 属性 | 说明 | 
|---|---|
| id | commandCheckEmployee | 
| method | checkEmployee | 
| showLoadingTip | false | 
其中method属性用以告诉视图模型该命令执行时,将请求转发到EmployeeViewModel.java的checkEmployee方法中。
步骤三:定义datasetEmployee的beforeChange事件,调用RPCCommand对象
在datasetEmployee的beforeChange事件中加入如下的代码:
switch (field.getName()) { 
	 case "EMPLOYEE_ID": 
	 	if (record.getState()=="new") { 
		 commandCheckEmployee.parameters().setValue("employeeId", value); 
		 commandCheckEmployee.execute(); 
		 var success = commandCheckEmployee.outParameters().getValue("success"); 
		 if (success) 
			 return new DoradoException("工号重复错误,请重新输入!"); 
		 } 
	break; 
}
由于beforeChange事件在用户修改记录和新增记录时都会触发,因此代码中添加了对field的name判断,保证这一段代码只在用户修改EMPLOYEE_ID字段的时候执行。另外我们还增加了对记录状态的判断,记录发生修改和新增操作时,记录的状态是不一样的,记录一共有五种状态:
| 状态 | 说明 | 
|---|---|
| none | 无状态. 此状态为记录的默认状态. | 
| new | 新增状态. 表示该记录刚刚被添加到数据集,并且尚未得到验证和确认.如果此时我们执行了对该记录的撤销,那么该记录将被从数据集中移除. | 
| insert | 已添加状态. 表示这是一条新增的并且已经经过验证和确认的记录. | 
| modify | 已添加状态. 表示这是一条数据已被修改的并且已经经过验证和确认的记录. | 
| delete | 已删除状态. 表示这是一条被标记为已删除的记录.默认形式下此种记录是不可见的,我们对数据集的遍历操作也不会得到该记录. | 
预览运行结果
编译整个项目,并重新运行,查看效果:
 
 图表 6 95
CustomDataset实现员工信息维护 (T1B)
创建视图模型(View)
在src/hr/manage文件夹下新建Employee1视图模型对象:
 
 图表 6 96
创建记录集(Dataset)
在新建的视图模型中选中Datasets节点,点左边工具条上的CustomDataset按钮,系统自动生成一个CustomDataset对象:
 
 图表 6 97
 把默认生成的Dataset的id属性dataset1修改为datasetEmployee。
 设置pageSize属性,值为10,通过这个属性,Dataset可以按照每页10条记录来分页。
 
 图表 6 98
 设置clazz属性,值为"hr.manage.domain.Employee"。Employee.java的定义如下:
package hr.manage.domain;
import java.util.Date;
public class Employee {
    private String employeeId;
    private Dept dept;
    private String employeeName;
    private boolean sex;
    private Date birthday;
    private boolean married;
    private float salary;
    private String degree;
    private String email;
    private String web;
    private String cmnt;
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getCmnt() {
        return cmnt;
    }
    public void setCmnt(String cmnt) {
        this.cmnt = cmnt;
    }
    public String getDegree() {
        return degree;
    }
    public void setDegree(String degree) {
        this.degree = degree;
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getEmployeeId() {
        return employeeId;
    }
    public void setEmployeeId(String employeeId) {
        this.employeeId = employeeId;
    }
    public String getEmployeeName() {
        return employeeName;
    }
    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }
    public boolean isMarried() {
        return married;
    }
    public void setMarried(boolean married) {
        this.married = married;
    }
    public float getSalary() {
        return salary;
    }
    public void setSalary(float salary) {
        this.salary = salary;
    }
    public boolean isSex() {
        return sex;
    }
    public void setSex(boolean sex) {
        this.sex = sex;
    }
    public String getWeb() {
        return web;
    }
    public void setWeb(String web) {
        this.web = web;
    }
}
其中还包含了dept对象。
 并在datasetEmployee中新增12个字段,分别为:
 employeeId,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 员工编号 | 
| name | employeeId | 
| property | employeeId | 
employeeName,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 姓名 | 
| name | employeeName | 
| property | employeeName | 
deptId,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 部门编号 | 
| name | deptId | 
| property | dept.deptId | 
deptName,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 部门名称 | 
| name | deptName | 
| property | dept.deptName | 
sex,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | boolean | 
| label | 性别 | 
| name | sex | 
| property | sex | 
birthday,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | date | 
| label | 生日 | 
| name | birthday | 
| property | birthday | 
married,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | boolean | 
| label | 婚否 | 
| name | married | 
| property | married | 
salary,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | float | 
| label | 薪水 | 
| name | salary | 
| property | salary | 
degree,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 学历 | 
| name | degree | 
| property | degree | 
email,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 邮件 | 
| name | |
| property | 
web,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 网址 | 
| name | web | 
| property | web | 
cmnt,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 备注 | 
| name | cmnt | 
| property | cmnt | 
其中的property属性与datasetEmployee的objectClazz属性对应的POJO对象的属性相对应,例如employeeId字段的定义就表示它与Employee.java中的employeeId属性相对应。
 另外Field与POJO的属性映射还存在一种默认的约定,如以上12个字段的设定中name与Employee.java中的属性名称完全相同,则我们可以在Field的设置中省略property属性的设定。只有在Field的名称与POJO不一致时,我们才有必要设定Field的property属性,去处property属性设置后,datasetEmployee的设计视图就如下:
 
 图表 6 99
 另外对于Employee.java中dept信息的获取,我们在设定field的时候。设置其property为dept.deptId和dept.deptName的方式建立映射关系。当dataset与employee对象数据转换的时候会自动处理。
非空校验
展开datasetEmployee的Fields节点,找到employeeId字段,并利用快捷按钮,添加RequiredValidator对象:
 
 图表 6 100
 该对象用以给employeeId字段加上非空校验器。提供非空校验处理。
格式化显示
并设置birthday字段的format属性为:"yyyy年MM月dd日"
 
 图表 6 101
 该属性主要用于设定birthday字段的格式化显示。
 设置salary字段的format属性为:"#,###.## 元",可从下拉列表中选出"#,###.##",并在格式化字符串后添加" 元"
 
 图表 6 102
创建数据表格(DataTable)
Dataset设定结束之后,我们再使用数据表格展示dataset的数据。以及通过数据表格的增删改等操作dataset的数据。
 在当前的视图模型中选择Controls节点,添加DataTable对象,并选中新建的DataTable对象修改其属性:
| 属性 | 值 | 
|---|---|
| dataset | datasetEmployee | 
| fixedColumn | 2 | 
| height | 100% | 
| id | tableEmployee | 
| width | 100% | 
设计视图如下:
 
 图表 6 103
 利用tableEmployee的Auto create fields快捷方式根据datasetEmployee的字段导入column对象,并调整自动生成的deptName列的顺序:
 
 图表 6 104
添加分页导航条(PagePilot)
选中Controls节点,添加组件PagePilot,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | pagepilotEmployee | 
| dataset | datasetEmployee | 
设计视图如下:
 
 图表 6 105
创建数据导航条(DataPilot)
选中Controls节点,添加组件DataPilot,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | datapilotEmployee | 
| dataset | datasetEmployee | 
设计视图如下:
 
 图表 6 106
创建数据表单(AutoForm)
选中Controls节点,添加组件AutoForm,并修改属性如下:
| 属性 | 值 | 
|---|---|
| id | formEmployee | 
| dataset | datasetEmployee | 
选择formEmployee节点,点左边工具栏中Auto create elements快捷按钮利用绑定的datasetEmployee中的字段信息自动生成表单元素。
 生成表单元素后,展开formEmployee节点,并选中自动生成的<FormGroup>节点,设置title属性为"员工信息"。并调整<FormGroup>中的节点顺序:
 
 图表 6 107
 设定结束后保存视图模型的所有修改。
创建数据更新命令(UpdateCommand)
选中Controls节点,点左边工具栏中的图标(从上往下数第7个),在列表中选择UpdateCommand。生成后,修改新建命令对象的id为commandSave,并展开commandSave节点,在datasetInfos节点下添加一个新的datasetInfo节点,并设定其dataset属性为datasetEmployee:
 
 图表 6 108
 表示命令执行时自动的将DatasetInfos中包含的datasetEmployee对象提交到服务器。
创建保存按钮(Button)
设定好数据更新命令之后,由于数据更新命令是不可见对象,为了方便用户操作,我们,还需要添加一个按钮,使用户单击该按钮时自动执行commandSave的默认动作。
 选中Controls节点,新增Button对象,并设置其属性如下:
| 属性 | 值 | 
|---|---|
| command | commandSave | 
| id | buttonSave | 
| value | 保存 | 
| width | 60 | 
设计视图如下:
 
 图表 6 109
创建JSP
之后在WebRoot/manage目录下,并利用当前员工维护的Employee视图模型的JSP生成向导在manage目录中生成employee1.jsp页面。
 并利用Layout调整employee.jsp的布局设定,最终代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://www.bstek.com/dorado" prefix="d" %> <html> <head> <title></title> </head> <body> <d:View config="hr.manage.Employee1"> <d:Layout type="vflow" height="100%"> <d:Pane> <d:AutoForm id="formEmployee" /> </d:Pane> <d:Pane> <d:Layout type="Hflow"> <d:Pane> <d:DataPilot id="datapilotEmployee" /> </d:Pane> <d:Pane> <d:Button id="buttonSave" /> </d:Pane> </d:Layout> </d:Pane> <d:Pane height="100%"> <d:DataTable id="tableEmployee" /> </d:Pane> <d:Pane align="right"> <d:PagePilot id="pagepilotEmployee" /> </d:Pane> </d:Layout> </d:View> </body> </html>
我们在主框架界面的菜单对象初始化方法中添加新的菜单项,在MainViewModel.java的initControls方法中加入代码,代码如下:
| subItem = item.addItem("employee", "员工信息");  | 
上面的蓝色代码为新插入的代码,用以关联刚才新建的employee1.jsp页面。
查看运行效果
下面我们通过主框架查看效果。在主框架中选择员工信息1菜单项。
 最终效果:
 
 图表 6 110
 在以上的界面中还没有看到部门数据。下一步我们将初步完善该页面的基本功能。
功能改进一:添加Listener实现数据查询,并实现查询结果Employee与datasetEmployee的数据转换
新建datasetEmployee的listener对象,代码如下:
| Employee1_datasetEmployeeListener.java | 
package hr.manage;
import com.bstek.dorado.data.*;
import com.bstek.dorado.common.*;
/**
 * Employee1_datasetEmployeeListener
 */
public class Employee1_datasetEmployeeListener extends AbstractDatasetListener {
    public boolean beforeLoadData(Dataset dataset) throws Exception {
        return true;
    }
}
其中包含了执行数据加载动作的datasetEmployee对象,即方法中的dataset参数。则我们就可以在这儿将查询出的Employee对象与datasetEmployee转换。将数据交给dataset。
public void afterLoadData(Dataset dataset) throws Exception {
	    EmployeeDao dao = new EmployeeDao();
        List employeeList = dao.getAll(dataset.parameters());
        dataset.fromDO(employeeList);
}
我们通过一个自定义的EmployeeDao对象获取一个部门列表信息,并通过dataset的fromDO将部门列表转化到dataset中。
 上述代码中使用的EmployeeDao对象的相关代码参考附录一。
 现在来重新浏览刚才的JSP页面,可以看到如下的界面:
 
 图表 6 111
功能改进二:添加自定义部门下拉框
为了实现部门下拉选择,该处我们使用了自定义下拉框(CustomDropDown),CustomDropDown是dorado中用以引用另一个dorado JSP页面的特殊下拉框,通过引用配置,该下拉框能够将另一个JSP页面再下拉框展示,并通过CustomDropDown的数据处理机制,实现数据输入或数据选择,如下图所属部门字段维护时,通过选择下拉框中的树节点实现,而不是用户直接在编辑框中输入:
 
 图表 6 112
 下拉框中的树,采用了二级树展现部门信息,其中树的一级节点为分公司信息,二级节点为部门信息,在数据库中他们的关联关系如下:
CREATE MEMORY TABLE BRANCH( BRANCH_ID VARCHAR(20) NOT NULL PRIMARY KEY, BRANCH_NAME VARCHAR(50) ) CREATE MEMORY TABLE DEPT( DEPT_ID VARCHAR(16) NOT NULL PRIMARY KEY, BRANCH_ID VARCHAR(16), DEPT_NAME VARCHAR(30) )
Dept表通过branch_id字段与branch表建立关联关系。 
 实现部门树形下拉框的两个关键部分:
- 定义CustomDropDown并引用包含Tree的JSP;
- 创建包含Tree组件JSP;
下面逐一说明其开发过程。
定义CustomDropDown
为了实现部门的树形下拉框,首先需要使用CustomDropDown实现。
 选中Controls节点,新增CustomDropDown,修改其id属性为dropdownDept。并设置其他的属性:
| 属性 | 值 | 说明 | 
|---|---|---|
| path | 本例为:deptTree1.jsp,该页面将在后面创建 | 定义下拉框中显示的内容,可以设置为指向一个页面的路径 | 
| autoDropDown | true | 决定下拉框得到焦点时是否自动触发 | 
| cachable | true | 是否缓存下拉框中的内容 | 
| fixed | true | 决定编辑框中的值是否可编辑 | 
属性设置图示:
 
 图表 6 113
 并设定datasetEmployee的dept_name字段的dropdown属性为dropdownDept:
 
 图表 6 114
创建包含Tree的JSP
在本例中我们对部门列表信息采用树形结构的方式显示。
添加新的视图模型
在src/hr/manager目录下创建一个View,命名为DeptTree1:
 
 图表 6 115
添加DataTree
选中Controls文件夹,添加一个组件DataTree。并修改ID属性为TreeDept。选择TreeDept,并利用快捷按钮添加Tree中的Level对象,并将name属性修改为levleBranch。选中levelBranch节点,并使用同样的方式在levleBranch上再添加一个子节点。并设置name属性为levelDept。如下图:
 
 图表 6 116
 树的层设计为:树的第一层(levelBranch)显示分公司数据,第二层(levelDept)显示部门数据。下面再作详细设计。
添加datasetBranch,datasetDept
因为treeDept的每一层都需要显示不同的信息:分公司信息或某个分公司下的部门信息。Tree提供的Level对象提供了dataset属性用于绑定Dataset,在系统运行时,Level会自动的根据dataset中的数据初始化Tree的节点。因此下一步我们首先创建这两个Level所对应的CustomDataset,其id分别为datasetBranch和datasetDept。
 新增CustomDataset并修改id为datasetBranch:
 设定datasetBranch的objectClazz为"hr.manage.domain.Branch"。(该类在前面的例子中已经创建)
 并在datasetBranch中新增两个字段,分别为:
 branchId,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 分公司编号 | 
| name | branchId | 
branchName,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 分公司名称 | 
| name | branchName | 
datasetBranch的数据我们依然使用listener方式实现加载,在这儿我们利用前面定义好的hr.manage.Dept1_datasetBranchListener。定义datasetBranch的listener属性为hr.manage.Dept1_datasetBranchListener即可。
 新增CustomDataset并修改id为datasetDept:
 设定datasetDept的objectClazz为"hr.manage.domain.Dept"。(该类在前面的例子中已经创建)
 并在datasetDept中新增两个字段,分别为:
 deptId,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 部门编号 | 
| name | deptId | 
deptName,属性设置如下:
| 属性 | 值 | 
|---|---|
| dataType | string | 
| label | 部门名称 | 
| name | deptName | 
如下图:
 
 图表 6 117
 datasetDept的数据我们依然使用listener方式实现加载,在这儿我们利用前面定义好的hr.manage.Dept1_datasetDeptListener。该监听器内部调用了DeptDao对象取出部门信息:
DeptDao dao = new DeptDao(); List deptList = dao.getAll(dataset.parameters());
DeptDao.java的getAll方法支持分公司编号的过滤查询:
public List getAll(ParameterSet parameters) throws Exception {
        String deptId = parameters.getString("deptId");
        String branchId = parameters.getString("branchId");
        String deptName = parameters.getString("deptName");
        List result = new ArrayList();
        DBStatement stmt = this.getStatement(DBStatement.SELECT, "dept");
        try {
            ParameterSet p = stmt.parameters();
            if (StringHelper.isNotEmpty(deptId)) {
                p.setString("dept_id", deptId);
            }
            if (StringHelper.isNotEmpty(branchId)) {
                p.setString("branch_id", branchId);
            }
            if (StringHelper.isNotEmpty(deptName)) {
                p.setString("dept_name", deptName);
            }
            List list = stmt.queryForList();
            for (int i = 0; i < list.size(); i++) {
                VariantSet entity = (VariantSet) list.get(info);
                result.add(this.convert(entity));
            }
        } finally {
            stmt.close();
        }
        return result;
}
因此我们直接定义datasetDept的listener属性为hr.manage.Dept1_datasetDeptListener即可。
设置datasetBranch与datasetDept的关联关系
下面我们将datasetBranch与datasetDept建立主从关联关系,我们通过dataset的MasterLink对象建立这种关联关系。
 选中datasetDept的MasterLink属性。配置如下:
| 属性 | 说明 | 
|---|---|
| detailKeyParameters | branchId | 
| masterKeyFields | branchId | 
| masterDataset | datasetBranch | 
如下图:
 
 图表 6 118
 这样就通过MasterLink的配置给两个Dataset之间定义了一个主从关联关系。datasetDept会自动的通过MasterLink对象的配置动态的从datasetBranch中获取branchId的值并作为datasetDept的参数branchId的值。
Level的dataset绑定
选中创建的treeDept,选中levelBranch:
- labelField属性设置为branchName,用于树节点的标题显示。
- dataset属性设置为datasetBranch。

 图表 6 119
 接下来,选中levelDept
- labelField属性设置为deptName,用于树节点的标题显示。
- dataset属性设置为datasetDept。
- hasChild设置为false,表示无子节点,即本层是叶子节点。

 图表 6 120
创建下拉框JSP
在视图模型DeptTree1中选择View根节点,并利用向导自动创建JSP页面,命名为deptTree1.jsp,并保存在WebRoot/hr/manage目录中
 JSP页面代码如下:
<%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://www.bstek.com/dorado" prefix="d" %> <html> <head> <title></title> </head> <body> <d:View config="hr.manage.DeptTree1"> <d:DataTree id="treeDept" /> </d:View> </body> </html>
浏览修改效果
现在我们已经可以浏览基本的执行效果了,浏览器中重新输入:
界面效果如下:
 
 图表 6 121
继续完善下拉框的选择功能
在视图模型DeptTree1中,选中treeDept组件对象,切换到事件视图(选择窗口最下方的Events Inspector按钮),在treeDept的onClick事件中增加代码:
if(tree.getCurrentNode().getLevel() != 1) DropDown.closeFrame(tree.getCurrentNode().getRecord());
以上代码的意思是,当选择节点的层次不是第一层时,也就是说不是分公司节点时,返回选中节点的附属Record对象,并且通过DropDown的closeFrame函数关闭下拉框。由于在treeDept的设计中,我们规定了第一层节点显示分公司的信息,通过这样处理,就可以确保只有在用户选择了部门节点之后,下拉框才会关闭。
 回到视图模型Employee中,选中dropdownDept对象,设置其中的readFields和writerFields的属性都为deptId,deptName。两个属性保持一致。如下图:
 
 图表 6 122
 这两个属性在用户选中下拉框的一个部门节点后使用,dropdownDept会自动的从下拉框中返回的record中获取deptId,deptName的信息并设置到主界面上。
 完成以上的设定之后,重新刷新employee1.jsp页面,并打开下拉框操作,选择不同的部门后,可以看到主界面上的所属部门信息也发生了变化。
功能改进三:excel数据导出
参考快速入门中系列一中的excel数据导出。
功能改进四:AJAX交互时Server端的编程方式
同6.2.2.13。请参考实现。
Attachments:
 worddava75c1ddc2ed51cc6f719581929eb6868.png (image/png)
                                worddava75c1ddc2ed51cc6f719581929eb6868.png (image/png)
                                 worddav02e33b3639e13ca4f71cdc3b19d5155b.png (image/png)
                                worddav02e33b3639e13ca4f71cdc3b19d5155b.png (image/png)
                                 worddav840be24b30f4030170fb4b4427b23e89.png (image/png)
                                worddav840be24b30f4030170fb4b4427b23e89.png (image/png)
                                 worddav10dc274452004b6887f7b056bd467210.png (image/png)
                                worddav10dc274452004b6887f7b056bd467210.png (image/png)
                                 worddav5c4a17d9f9f17d7b4e775202c8b6b9cf.png (image/png)
                                worddav5c4a17d9f9f17d7b4e775202c8b6b9cf.png (image/png)
                                 worddav1268d77f48689e121e15ad9d286b28aa.png (image/png)
                                worddav1268d77f48689e121e15ad9d286b28aa.png (image/png)
                                 worddavea05591eaf6e83f97a794a9ef1ae2c3b.png (image/png)
                                worddavea05591eaf6e83f97a794a9ef1ae2c3b.png (image/png)
                                 worddavf1277c8ba4633a0f10dc1896db8b23ee.png (image/png)
                                worddavf1277c8ba4633a0f10dc1896db8b23ee.png (image/png)
                                 worddavd2282fb50a634bf7086005a7895e839d.png (image/png)
                                worddavd2282fb50a634bf7086005a7895e839d.png (image/png)
                                 worddavc8412e1bc3228140ceca730675ccfb3b.png (image/png)
                                worddavc8412e1bc3228140ceca730675ccfb3b.png (image/png)
                                 worddav84886549a0e61f7d27f16d97ba942744.png (image/png)
                                worddav84886549a0e61f7d27f16d97ba942744.png (image/png)
                                 worddav38598b3a2bfc2e56079c6aced3a20c4e.png (image/png)
                                worddav38598b3a2bfc2e56079c6aced3a20c4e.png (image/png)
                                 worddav81949710d76a4018f163b83766d4f8a3.png (image/png)
                                worddav81949710d76a4018f163b83766d4f8a3.png (image/png)
                                 worddav4a555bcd5e839b9e80a7373a8dfa8151.png (image/png)
                                worddav4a555bcd5e839b9e80a7373a8dfa8151.png (image/png)
                                 worddav172a0b429e75737b39de0e92c48b4958.png (image/png)
                                worddav172a0b429e75737b39de0e92c48b4958.png (image/png)
                                 worddav93782f25c7e5a74ad84476c4cd1cd0e4.png (image/png)
                                worddav93782f25c7e5a74ad84476c4cd1cd0e4.png (image/png)
                                 worddavf2126b2e365899a1680f0ba4a68cf7bc.png (image/png)
                                worddavf2126b2e365899a1680f0ba4a68cf7bc.png (image/png)
                                 worddav6123fc649e375b870ce259446780d2dd.png (image/png)
                                worddav6123fc649e375b870ce259446780d2dd.png (image/png)
                                 worddavc8b6cf47275616b8832d7de1bfa67693.png (image/png)
                                worddavc8b6cf47275616b8832d7de1bfa67693.png (image/png)
                                 worddavb6f8e677bd659fa047b4d81ceb4f33fd.png (image/png)
                                worddavb6f8e677bd659fa047b4d81ceb4f33fd.png (image/png)
                                 worddav124ab45818198d2cf63b554cb4ad5b85.png (image/png)
                                worddav124ab45818198d2cf63b554cb4ad5b85.png (image/png)
                                 worddav41f532799356d86dd607502061ecb85f.png (image/png)
                                worddav41f532799356d86dd607502061ecb85f.png (image/png)
                                 worddav08bf440b4fbe2c0c8fd935fa12df6bdf.png (image/png)
                                worddav08bf440b4fbe2c0c8fd935fa12df6bdf.png (image/png)
                                 worddav253edcda5791f7d70f73c1524114e9f6.png (image/png)
                                worddav253edcda5791f7d70f73c1524114e9f6.png (image/png)
                                 worddav325c5c71cd5c952ee141071491209363.png (image/png)
                                worddav325c5c71cd5c952ee141071491209363.png (image/png)
                                 worddavb739bf82513d304db911b2a1099a3a94.png (image/png)
                                worddavb739bf82513d304db911b2a1099a3a94.png (image/png)
                                 worddav3b57efa5bf2abc276f981a0edcd67fde.png (image/png)
                                worddav3b57efa5bf2abc276f981a0edcd67fde.png (image/png)
                                 worddav49d8bbf7aafbeb1e4e3b89f9440c9946.png (image/png)
                                worddav49d8bbf7aafbeb1e4e3b89f9440c9946.png (image/png)
                                 worddavb294adc36757c1684c1d86bc71f78fb2.png (image/png)
                                worddavb294adc36757c1684c1d86bc71f78fb2.png (image/png)
                                 worddav3f3aa00df018b6135fb1de77a3c00460.png (image/png)
                                worddav3f3aa00df018b6135fb1de77a3c00460.png (image/png)
                                 worddav4a8703aa354d047f261875c26bc9921e.png (image/png)
                                worddav4a8703aa354d047f261875c26bc9921e.png (image/png)
                                 worddavab14ccd1d306bd6545d15d57a288dc9e.png (image/png)
                                worddavab14ccd1d306bd6545d15d57a288dc9e.png (image/png)
                                 worddava65af567e335f0237613ca60024cb61a.png (image/png)
                                worddava65af567e335f0237613ca60024cb61a.png (image/png)
                                 worddav1c05ed228fd926e4ad2c662b4c9ded25.png (image/png)
                                worddav1c05ed228fd926e4ad2c662b4c9ded25.png (image/png)
                                 worddavd62a22b8149c5449d1902b8a542c1c81.png (image/png)
                                worddavd62a22b8149c5449d1902b8a542c1c81.png (image/png)
                                 worddav87d965b6162ea5a81b01584d43b62a08.png (image/png)
                                worddav87d965b6162ea5a81b01584d43b62a08.png (image/png)
                                 worddavea1c7f11c8ff56b9caed45baf5150883.png (image/png)
                                worddavea1c7f11c8ff56b9caed45baf5150883.png (image/png)
                                 worddavca08ed161b48579f9552c6b0348cf7c8.png (image/png)
                                worddavca08ed161b48579f9552c6b0348cf7c8.png (image/png)
                                 worddavd7575f58525760a62226172871a46fd3.png (image/png)
                                worddavd7575f58525760a62226172871a46fd3.png (image/png)
                                 worddav83eb1e269ed1d4a2d43729190c9b48f2.png (image/png)
                                worddav83eb1e269ed1d4a2d43729190c9b48f2.png (image/png)
                                 worddavc74e6789d7b764d70a25c481e7c22969.png (image/png)
                                worddavc74e6789d7b764d70a25c481e7c22969.png (image/png)
                                 worddav31108343de518a216f9af477ec18fc21.png (image/png)
                                worddav31108343de518a216f9af477ec18fc21.png (image/png)
                                 worddav573de24ec56b277946638f1c380a2958.png (image/png)
                                worddav573de24ec56b277946638f1c380a2958.png (image/png)
                                 worddav59788a7c8fc5815e586fee6031d1b53f.png (image/png)
                                worddav59788a7c8fc5815e586fee6031d1b53f.png (image/png)
                                 worddav4fb866493183d1cdd1bf254e0d9a813e.png (image/png)
                                worddav4fb866493183d1cdd1bf254e0d9a813e.png (image/png)
                                 worddav19a0c2a0fab3ba7ea9cd5ee7ad1f61af.png (image/png)
                                worddav19a0c2a0fab3ba7ea9cd5ee7ad1f61af.png (image/png)
                                 worddavb7f50bf08bea644228c2c9899a3636ff.png (image/png)
                                worddavb7f50bf08bea644228c2c9899a3636ff.png (image/png)
                                 worddav75978d2fbfed1f91ef83f0a828f23953.png (image/png)
                                worddav75978d2fbfed1f91ef83f0a828f23953.png (image/png)
                                 worddav948ed9a89d6d2bc0da91927e572f7685.png (image/png)
                                worddav948ed9a89d6d2bc0da91927e572f7685.png (image/png)
                                 worddav48163d9ae09a087dc230c6761d776da7.png (image/png)
                                worddav48163d9ae09a087dc230c6761d776da7.png (image/png)
                                 worddavaf39475f6111a1b5efd7c7b69eddf98f.png (image/png)
                                worddavaf39475f6111a1b5efd7c7b69eddf98f.png (image/png)
                                 worddav71477c0bb8bcaf6578e93f8e2b198163.png (image/png)
                                worddav71477c0bb8bcaf6578e93f8e2b198163.png (image/png)
                                 worddav8da15fef73bb8557a014049bde506dbf.png (image/png)
                                worddav8da15fef73bb8557a014049bde506dbf.png (image/png)
                                 worddavef0a2b549de840bc23ebdd6d11cddc8a.png (image/png)
                                worddavef0a2b549de840bc23ebdd6d11cddc8a.png (image/png)
                                 worddavb226e198fe8969eda63b4e30626cc021.png (image/png)
                                worddavb226e198fe8969eda63b4e30626cc021.png (image/png)
                                 worddav866fb979b01bdfe8271b2e581ffa3daa.png (image/png)
                                worddav866fb979b01bdfe8271b2e581ffa3daa.png (image/png)
                                 worddav31f17f17b0cb48e430a805bb1874810e.png (image/png)
                                worddav31f17f17b0cb48e430a805bb1874810e.png (image/png)
                                 worddav98eaa2824f1f29e04a38b957bdb680ac.png (image/png)
                                worddav98eaa2824f1f29e04a38b957bdb680ac.png (image/png)
                                 worddav83ace977f5c4edf47133b8f49364659a.png (image/png)
                                worddav83ace977f5c4edf47133b8f49364659a.png (image/png)
                                 worddavcaa0d2153e5ba9547185e76e3967ad93.png (image/png)
                                worddavcaa0d2153e5ba9547185e76e3967ad93.png (image/png)
                                 worddavc88ee481790169da2ef0728607664214.png (image/png)
                                worddavc88ee481790169da2ef0728607664214.png (image/png)
                                 worddav4da0a99a44d33e7d55232680f8525ff3.png (image/png)
                                worddav4da0a99a44d33e7d55232680f8525ff3.png (image/png)
                                 worddav8199bd1df96334014612a3e086560cf4.png (image/png)
                                worddav8199bd1df96334014612a3e086560cf4.png (image/png)
                                 worddav919e0ebe30ae9ab4facc349b4a0cdf7d.png (image/png)
                                worddav919e0ebe30ae9ab4facc349b4a0cdf7d.png (image/png)