下面部分内容摘自《Dorado5产品白皮书》:
图 2.1:Dorado 5原理图
本图描述了Dorado5展现中间件的运行机制。其中全图可分为Server端和Client端两个区域。分别展示了Dorado5在Server端和Client端的实现机制。
对于图中出现的几个新名词的解释如下:
- ViewModel:视图模型。一种用于封装界面逻辑和操作逻辑的对象。即视图中包含哪些数据、这些数据以什么方式展现、视图中包含哪些控件、这些控件会激发什么操作等等。 ViewModel一般不用于定义各种控件最终在显示的布局,控件布局应通过其它方式进行定义,例如JSP或者Html。
- Dataset: 数据集。 数据集是Dorado5架构的核心,用来管理一组数据。结构类似于关系型数据库中的表或视图。 Dataset在运行时会有Server端和Client端两种实例。 Dataset不可以直接显示,一般须通过数据控件来展示其中的数据。
- Control: 各种可视化的控件。 包含数据控件和非数据控件。 其中非数据控件一般与数据操作没有直接关系(例如:按钮,菜单等)。 而数据控件是指可以直接用于显示或编辑数据的控件(例如:数据表格,编辑框,树状列表等)。 数据控件可以直接与Dataset进行绑定并自动对Dataset中的数据进行展现。
- BRich客户端: 即Browser Rich Client。是指运行与纯浏览器环境中的具有富客户端表现能力的客户端。
图中的数字标示处的详细介绍如下:
- 首先Dorado5将根据用户的配置和定义创建ViewModel对象, ViewModel中包含了各种Control和Dataset并且将根据配置自动的建立其Control和Dataset之间的绑定关系。 ViewModel可通过两种途径将自己输出到客户端, 详见3和4。
- 此处的外部数据既可以是直接来自于数据库中的数据,例如ResultSet; 也可以是来自于业务逻辑层的数据,例如DTO(Data Transfer Object)或VO(Value Object)。 这些数据将根据ViewModel的配置被填充到一个或多个Dataset当中。
- 由于ViewModel本身并不负责界面的布局,因此须利用JSP等技术等对各种Control进行排列和布局。在JSP中我们可以使用Taglib对布局方式进行定义。
- 利用JSP,ViewModel的界面模板将以HTML+XML的形式通过HttpResponse被输出到浏览器当中。 注意:此过程输出的内容只包含各种控件的定义和界面布局,并不包含真正的业务数据。 业务数据将通过步骤5、6输出至Client端。 这种实现方式符合AJAX的4个基本原则中的前两条。
- ViewModel中的数据部分将直接以Dataset的形式交付给Dorado Servlet。 Dorado Servlet是一个专门用于实现Dorado的Server端和BRich客户端进行数据交换的服务。它既可以实现由Server端向Client端输出数据,也可以接受从BRich客户端向Server端提交的数据,还可以独立地响应Client端发出的数据请求,以实现BRich客户端中的局部数据刷新等功能。
- Dorado Servlet将ViewModel交付的数据转化为XML并输出到BRich客户端中。 同时,如果接受到了BRich客户端以XML提交的数据时, Dorado Servlet也会将其解析为Dataset对象并交换给ViewModel。刷新页面时是通过HttpResponse方式把数据已XML方式输出到页面并给BRich客户端解析,而Dataset数据刷新和页面数据提交是通过Ajax把Server端数据转化为XML到BRich客户端解析或者通过Ajax把BRich客户端数据转化为XML到Server端。
- 当4和6的输出转递到浏览器中后,首先将由Dorado提供的BRich Engine对这些信息进行处理。 其中步骤6输出的XML数据将被BRich Engine还原为一个或多个Dataset。如果界面的操作需要对Dataset中的数据进行动态局部刷新(指不刷新整个页面,而只是对其中的数据进行刷新)或提交处理,那么这些数据也将由BRich Engine进行封装以XML的形式通过Ajax发送给Dorado Servlet。
- 步骤4输出的HTML/XML界面模板将被解析并还原为各种真正可见的Control。有一些静态的Control通过HttpResponse的方式输出Html就可以显示出来,有些动态显示数据的Control就需要通过DHtml的方式结合Dataset和Control的属性和数据来动态构建显示的Control。例如: 数据表格,编辑框,树状列表,按钮,菜单等。同时根据这些Control在ViewModel中的定义,还原他们与Dataset之间的绑定关系,以便与最终将数据呈现给用户。
我们通过一个例子来说明上面的原理:
有一个HelloWorld页面通过下面4个代码文件生成的:
图 2.2:HelloWorld 4个代码文件
下面是一个Dorado5 HelloWorld页面:
图 2.3:HelloWorld页面
上面的页面是通过刷新页面得到的。
在chapter2/sample2_1.jsp页面处理<d:View>的时候,Dorado5会根据clazz定义的class或者config定义的view.xml里clazz属性来创建ViewModel对象(如没有定义,默认为Setting.xml定义的view.defaultViewModel),sample2_1例子当中创建了com.bstek.dorado5.docent.chapter2.Sample2_1ViewModel对象。然后Sample2_1ViewModel会根据/com/bstek/dorado5/docent/chapter2/sample2_1.view.xml的配置信息来创建所有的Dataset和Control对象,针对每个对象在view.xml里的配置内容都创建对应的对象存放在起来,也就是说view.xml里所有的内容都会存放在ViewModel对象里,并把它们之间的关联关系绑定好。所以这里能说明view.xml所有的配置内容都可以通过ViewModel对象来创建。
<?xml version="1.0" encoding="UTF-8"?> <view clazz="com.bstek.dorado5.docent.chapter2.Sample2_1ViewModel"> <Datasets> <Dataset type="Form" id="datasetForm"> <MasterLink/> <Fields> <Field name="EMPLOYEE_ID" label="员工编号" dataType=""> <Properties/> </Field> <Field name="DEPT_ID" label="部门" dataType=""> <Properties/> </Field> </Fields> <Parameters/> <Properties/> </Dataset> <Dataset type="Custom" listener="com.bstek.dorado5.docent.chapter2.CustomDatasetListener" id="datasetCustom" maxPropertyLevel="1"> <MasterLink/> <Fields> <Field name="EMPLOYEE_ID" label="员工编号" dataType=""> <Properties/> </Field> <Field name="DEPT_ID" label="部门" dataType=""> <Properties/> </Field> <Field name="EMPLOYEE_NAME" label="员工姓名" dataType=""> <Properties/> </Field> <Field name="SEX" label="性别" dataType="boolean"> <Properties/> </Field> <Field name="BIRTHDAY" label="出生日期" dataType="date"> <Properties/> </Field> </Fields> <Parameters> <Parameter dataType="" name="EMPLOYEE_ID"/> <Parameter dataType="" name="DEPT_ID"/> </Parameters> <Properties/> </Dataset> </Datasets> <Controls> <Control type="AutoForm" dataset="datasetForm" id="formForm"> <FormGroup title="查询条件"> <Element name="EMPLOYEE_ID" type="TextEditor" field="EMPLOYEE_ID"> <FieldLabel/> <TextEditor/> </Element> <Element name="DEPT_ID" type="TextEditor" field="DEPT_ID"> <FieldLabel/> <TextEditor/> </Element> <Element name="element1" controlAlign="center" showLabel="false" colSpan="2" type="Custom" controlId="buttonQuery"> <FieldLabel/> </Element> </FormGroup> </Control> <Control type="DataTable" dataset="datasetCustom" id="tableCustom" width="100%"> <Column field="EMPLOYEE_ID" name="EMPLOYEE_ID"/> <Column field="DEPT_ID" name="DEPT_ID"/> <Column field="EMPLOYEE_NAME" name="EMPLOYEE_NAME"/> <Column field="SEX" name="SEX"/> <Column field="BIRTHDAY" name="BIRTHDAY"/> </Control> <Control id="commandForm" queryDataset="datasetCustom" type="QueryCommand" conditionDataset="datasetForm"> <Parameters/> </Control> <Control id="buttonQuery" width="80" command="commandForm" type="Button" value="查询"/> <Control method="save" id="commandSave" type="UpdateCommand"> <DatasetInfos> <DatasetInfo dataset="datasetCustom"/> </DatasetInfos> <Parameters/> </Control> <Control id="buttonSave" width="80" command="commandSave" type="Button" value="提交"/> <Control type="DataPilot" dataset="datasetCustom" id="datapilotCustom"/> </Controls> <Properties/> </view>
代码 2.1:view.xml
页面中的表格数据是通过datasetCustom来得到的,我们只要对datasetCustom新增一些Record就可以,Records的来源可以是ResultSet或者来自于业务逻辑层的数据(For List)。
package com.bstek.dorado5.docent.chapter2; import com.bstek.dorado.data.AbstractDatasetListener; import com.bstek.dorado.data.Dataset; import com.bstek.dorado.data.ParameterSet; import com.bstek.dorado.data.Record; import com.bstek.dorado.utils.StringHelper; public class CustomDatasetListener extends AbstractDatasetListener { public boolean beforeLoadData(Dataset dataset) throws Exception { ParameterSet parameters = dataset.parameters(); String EMPLOYEE_ID = parameters.getString("EMPLOYEE_ID"); String DEPT_ID = parameters.getString("DEPT_ID"); // 模拟查询数据 if (StringHelper.isNotEmpty(EMPLOYEE_ID) || StringHelper.isNotEmpty(DEPT_ID)) { Record record = dataset.insertRecord(); record.setString("EMPLOYEE_ID", EMPLOYEE_ID); record.setString("DEPT_ID", DEPT_ID); } else { Record record = dataset.insertRecord(); record.setString("EMPLOYEE_ID", "Default"); record.setString("DEPT_ID", "Default"); record = dataset.insertRecord(); record.setString("EMPLOYEE_ID", "Default2"); record.setString("DEPT_ID", "Default2"); } return false; } }
代码 2.2:Dataset数据来源
sample2_1.jsp页面的代码可以看出每个可显示控件都会在页面有自己的标签,然后通过Dorado5封装的布局标签或者Html来进行页面布局。
<%@ page contentType="text/html; charset=UTF-8"%> <%@ taglib uri="http://www.bstek.com/dorado" prefix="d"%> <html> <head> <title></title> </head> <body scroll="no"> <d:View config="com.bstek.dorado5.docent.chapter2.sample2_1"> <table border="0"> <tr valign="top"> <td><d:AutoForm id="formForm" /></td> </tr> <tr valign="top"> <td> <d:Layout type="hflow"> <d:Pane> <d:Button id="buttonSave" /> </d:Pane> <d:Pane align="right"> <d:DataPilot id="datapilotCustom" /> </d:Pane> </d:Layout> </td> </tr> <tr valign="top"> <td><d:DataTable id="tableCustom" /></td> </tr> </table> </d:View> </body> </html>
代码 2.3:JSP页面代码
对应于每个Control标签库都会输出一些Html页面布局和定义元素(通过页面生成的源代码可以看到),也会在页面的最后输出该Control的javascript定义,通过这些javascript的属性定义和DHtml来动态生成Control显示。
<div id="tableCustom" style="width:100%;height:200;"></div> <script language="javascript"> var _t=$$c("DataTable", null, "tableCustom");t.setDataset("datasetCustom");t.setHeaderHeight(20);t.setRowHeight(19);t.setFooterHeight(20);var __t1=t.addColumn("EMPLOYEE_ID");t1.setField("EMPLOYEE_ID");var __t1=t.addColumn("DEPT_ID");t1.setField("DEPT_ID");var __t1=t.addColumn("EMPLOYEE_NAME");t1.setField("EMPLOYEE_NAME");var __t1=t.addColumn("SEX");t1.setField("SEX");var __t1=t.addColumn("BIRTHDAY");_t1.setField("BIRTHDAY"); </script>
代码 2.4:生成的Html和javascript页面源代码
页面刷新时:服务端datasetCustom的一些跟页面相关联的属性和数据内容会组装成XML和javascript输出到页面。
<div style="display: none"><xml id="__datasetCustom" > <rs possibleCount="0" pageCount="1" loadedPages="1"> <r id="1" pageIndex="1" isCurrent="true" state="none" > <new>Default,Default,,,</new> </r> <r id="2" pageIndex="1" state="none" > <new>Default2,Default2,,,</new> </r> </rs> </xml></div> <script language="javascript"> var datasetCustom=$$c("Dataset", null, "datasetCustom", "Custom");var _t=datasetCustom;t.setPageSize(100);var __f=t.addField("EMPLOYEE_ID",0);f.setLabel("\u5458\u5DE5\u7F16\u53F7");var __f=t.addField("DEPT_ID",0);f.setLabel("\u90E8\u95E8");var __f=t.addField("EMPLOYEE_NAME",0);f.setLabel("\u5458\u5DE5\u59D3\u540D");var __f=t.addField("SEX",9);f.setLabel("\u6027\u522B");var __f=t.addField("BIRTHDAY",10);f.setLabel("\u51FA\u751F\u65E5\u671F");var __t=t.parameters();t.addParameter("EMPLOYEE_ID",0).setValue("");_t.addParameter("DEPT_ID",0).setValue(""); </script>
代码 2.5:Dataset生成的页面源代码
数据刷新和数据提交:客户端BRich Engine会把客户端Dataset对象的一些属性或者数据组装成XML通过Ajax提交到服务端,在服务端根据ViewModel来创建服务端Dataset,然后根据服务端Dataset取到数据组装成类似"代码 2.5"中的XML返回到客户端,再通过BRich Engine解析给客户端Dataset。- 页面通过javascript来解析服务端Dataset组装成XML内容来生成记录或者设置一些属性。
- 通过Dataset来与后台进行数据交互。当datasetCustom需要数据查询或者数据提交的时候,通过客户端的BRich Engine来驱动Ajax来进行数据交互。
- 根据4和6的内容分别通过javascript解析来生成各种可视Control,同时根据Control和Dataset的绑定关系进行绑定,并根据Dataset的记录来填充这些Control。由于这些绑定关系形成了客户端的MVC。tableCustom根据javascript设置的属性绑定了datasetCustom,并根据datasetCustom的属性和记录来填充了Table的行显示;tableCustom点击不同的记录,对应的datasetCustom的当前记录也相应改变,反过来datasetCustom的当前记录在发生改变的时候,tableCustom的当前行数据也在改变。
Attachments:
![](images/icons/bullet_blue.gif)
![](images/icons/bullet_blue.gif)
![](images/icons/bullet_blue.gif)