Dorado 5 : 6.4.实现类 (RF1)

一个业务界面通常会包含很多的业务逻辑操作,如下图:

图表 74
上图页面中的项目合同管理中状态控制,支付检查等操作。这些都需要与Server交互,在传统的页面流技术中,我们通常执行一个业务操作就需要跳转一次,刷新本页面或跳转到另一个页面,而采用了AJAX处理技术之后,我们就没有必要这么麻烦了。当用户单击页面上的各个按钮时,都由系统内部自动的向Server发出AJAX请求,并将反馈信息显示到当前页面上,在这整个业务操作中,页面是不需要转向的。
因此开发上来说我们只要在Server端提供各种Services接受当前页面向Server发出的AJAX请求即可。
而dorado中的视图模型实现类就是这样的一个Service。ViewModel的实现类提供了组件和dataset的初始化事件,负责接收客户端UpdateCommand的数据提交请求和RPCCommand发出RPC请求。以及客户端获取数据的请求。
因此在多数情况下我们建议使用ViewModel的实现类作为后台业务逻辑的调用接口。

状态说明

ViewModel不会在服务端进行缓存。我们也可以把ViewModel看成是服务端同客户端进行信息交换的接口。当用户执行页面请求、局部数据刷新、远程方法调用等操作时服务端都会创建相应的ViewModel 实例。我们可以通过其state属性来判断当前ViewModel在何种情况下被创建。state有下列几种取值:

状态

说明

ViewModel.STATE_VIEW

打开视图状态。即用户请求一个新的视图时的状态。在此状态下ViewModel在创建时会自动创建所有已声明的Dataset,并自动根据每个Dataset的配置来装载数据。所有的控件(Control)将以懒装载的方式被创建。

ViewModel.STATE_SERVICE

服务状态。当一个已打开的视图在执行局部数据刷新、远程方法调用等操作时的状态。在此状态下ViewModel中所有的Dataset和控件(Control)将以懒装载的方式被创建。

ViewModel.STATE_DESIGN

设计时状态。该状态只有在为dorado studio提供服务时有效。

常用方法说明

通常,我们会在ViewModel的实现类中使用到下列的一些方法。这些方法都来自与父类,在大多数情况下,使用时都需要利用super保留字调用父类中的逻辑。

方法

说明

void init(int state)

ViewModel的初始化方法。参数为ViewModel当前的状态。

void initDatasets()

ViewModel的初始化其中的各个Dataset的方法。如前面关于ViewModel状态的文字所述,究竟有那些Dataset会在该方法中被初始化是取决于ViewModel的当前状态的。

void initControls()

ViewModel的初始化其中所有的不可视控件的方法。不可视控件包括下拉框、菜单、命令。其他的可视控件默认是不会在此处初始化的,它们甚至还没有被创建。可视控件默认都是"懒创建"的,只有当将来JSP的Taglib真正引用到它或者用户利用ViewModel的getControl(id)方法获取它时才会被创建起来。

void initControl(Control control)

此方法会在initControls()的执行过程中以及JSP的执行过程中,针对每一个创建出来的控件被激发。

void doLoadData()

当ViewModel开始为其中的Dataset装载数据时调用的方法。只有在initDatasets()中已被初始化的Dataset才会在此处装载数据,因此究竟有那些Dataset会被处理是取决于ViewModel的当前状态的。

void doLoadData(ViewDataset dataset)

此方法会在doLoadData()的执行过程中针对每一个要处理的Dataset被激发。如果doLoadData()中需处理3个Dataset,那么doLoadData(ViewDataset dataset)也将被调用3次。

void doUpdateData(ParameterSet parameters, ParameterSet outParameters)

默认的数据提交的处理方法。

以上方法不是在所有情况都会被执行的。
在页面打开时的处理过程是这样的。此时ViewModel的state为STATE_VIEW。
void init(int state)
void initDatasets() 初始化ViewModel中所有的Dataset。
void doLoadData()
void doLoadData(ViewDataset dataset)
void initControls()
void initControl(Control control)
执行Dataset动态数据刷新时的过程是这样。此时ViewModel的state为STATE_SERVICE。
void init(int state)
void initDatasets() 仅初始化当前执行数据加载动作的这一个Dataset。
void doLoadData()
void doLoadData(ViewDataset dataset)
执行数据提交时的过程是这样的。此时ViewModel的state为STATE_SERVICE。
void init(int state)
void initDatasets() 仅初始化被提交上来的Dataset。
void doUpdateData(ParameterSet parameters, ParameterSet outParameters)
由此,在使用ViewModel实现类时应注意下面的一些事项。
最好不要将自定义的初始化Dataset或Control的代码写在init()中,因为init()会在ViewModel的每一种状态下被执行。例如你在init()中写了一段自动为DataTable创建表格列的代码,那么当界面上某个Dataset执行flushData()时,这段代码同样会被执行,这毫无意义的。正确的做法是写在initControls()或者initControl()中。由Dorado来帮你判断这段代码应在什么时候执行。
事实上,在实际应用中init()应该是一个很少被用到的方法。有时我们可能会在这里做一些权限的校验,判断当前登录用户是否有权访问这个ViewModel。再比如可以在这里指定ViewModel的role属性。
同样是这面提到的这端代码,写在initControl()中往往优于写在initControls()中。因为在initControls()中你必须通过getControl()方法才能得到你所需要的DataTable对象,而getControl()会强制Dorado创建这个DataTable对象,这会打破该对象的"懒创建"机制。对于一个只在ViewModel的XML中定义过但并没有在JSP中引用的对象,在默认情况下Dorado是根本不会创建它的,而getControl()会强制Dorado去创建它。
对于Dataset同样适用上面的规则。手工处理Dataset的数据加载过程时,相关的代码写在doLoadData(ViewDataset dataset)要优于doLoadData()中。在initDatasets()或doLoadData()必须利用getDataset()方法来获取某个Dataset,这会导致一些本不需要创建的Dataset被强制的创建。例如:在执行Dataset的flushData()的过程中,ViewModel中应该只有一个Dataset被创建。多创建几个Dataset有时并不是什么大的系统开销,但是多余的Dataset会引起多余的数据装载过程(如执行SQL),这是相当昂贵的操作。
其实对于针对某个Dataset的特别操作,像利用Java代码动态的为Dataset创建Field或者设定其他参数等。我们更加推荐把代码写在Dataset的Listener中,而不是前面提到的doLoadData(ViewDataset dataset)中。因为在doLoadData(ViewDataset dataset)必须判断ID来确定当前传入的是否我们所关心的那个Dataset,这有时会让代码显得有些混乱。

UpdateCommand,RPCCommand与ViewModel实现类的交互

Command调用方法

Dorado中的UpdateCommand与RPCCommand都被设计为与实现类交互的管理对象。通常情况下在Command中拥有一个method属性,用于指定调用实现类的方法名称:

<Control id="commandDeleteSelection" type="UpdateCommand"
method="deleteSelection">
<DatasetInfos>
<DatasetInfo dataset="datasetEmployee" submitScope="selected" />
</DatasetInfos>
<Parameters />
</Control>

在实现类就可以针对该Command的设定定一方法deleteSelection:

public void deleteSelection(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
super.doUpdateData(parameters, outParameters);
}

该处方法的作用域注意采用public类型的,确保Command执行时可以通过反射机制调用该方法。方法调用时注意保留super的处理逻辑,保证调用父类中的默认处理逻辑。
方法中包含两个参数:
parameters:Command提交时上传的参数集合对象
outParameters:调用结束后由该对象负责将信息反馈到客户端调用该方法的Command
范例说明
Command定义如下:

<Control id="commandDeleteSelection" type="UpdateCommand"
method="deleteSelection">
<DatasetInfos>
<DatasetInfo dataset="datasetEmployee" submitScope="selected" />
</DatasetInfos>
<Parameters>
<Parameter name="operationId" value="ANLIN" dataType="string" />
</Parameters>
</Control>

关于Command对象的参数定义也可以在客户端调用的时候动态指定,详细使用技巧参考组件使用详解中的Command的参数定义技巧。
这样在视图模型实现类的deleteSelection方法中就可以通过parameters获得参数operationId的值:

public void deleteSelection(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
String operationId = parameters.getString("operationId");
super.doUpdateData(parameters, outParameters);
}


方法向客户端返回信息

如果在Command对应的方法调用结束后还想返回一些信息到客户端,则可以通过方法的outParameters参数实现(Java代码):

public void deleteSelection(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
String operationId = parameters.getString("operationId");
outParameters.setString("msg", "信息保存成功!");
super.doUpdateData(parameters, outParameters);
}

将一段提示信息保存到outParameters对象中,客户端command调用结束后就可以获得该参数信息(JS代码):

if (commandDeleteSelection.execute()){
var msg = commandDeleteSelection.outParameters().getValue("msg");
}

其中的execute()方法为command的执行方法,该方法返回值为boolean类型,表示调用成功或者失败。

方法的异常处理机制

Command与实现类交互的时候注意方法中Exception的处理,dorado根据方法中是否产生Exception判断业务逻辑是否操作完成,进而通知客户端的下一步处理机制,例如记录状态的处理和Command的事件处理等,这就要求不要轻易的屏蔽方法中的异常抛出。如下例代码的处理对于dorado而言是意义完全不一样的:

public void deleteSelection(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
try{
super.doUpdateData(parameters, outParameters);
}
catch(Exception e){
Log.error(e);
}
}

以上代码中deleteSelection方法调用对于dorado的Command调用意味着调用成功。客户端的各种逻辑就按照成功调用的方式加以处理。所以千万要记得抛出异常或者不要处理异常:

public void deleteSelection(ParameterSet parameters,
ParameterSet outParameters)
throws Exception {
try{
super.doUpdateData(parameters, outParameters);
}
catch(Exception e){
Log.error(e);
throw e;
}
}