网路冷眼@BlogJava

熙熙攘攘一闲人 以冷静的眼光观察技术
posts - 88, comments - 193, trackbacks - 0, articles - 28
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Chapter 08. Forms(表单)

Table of Contents

Build-in form rendering(内置表单渲染)
External form rendering(外部表单渲染)
Form properties(表单属性)

Activiti provides a convenient and flexible way to add forms for the manual steps of your business processes. We support two strategies to work with forms: Build-in form rendering and external form rendering

Activiti为你的业务流程的手动步骤提供了一个方便和灵活的增加表单的方法。我们为表单的工作支持两种策略:内置渲染和外部渲染。

Build-in form rendering(内置表单渲染)

Build-in form rendering is the simplest to get started with. We'll explain it with an example.

最简单的开始的方式是内置表单的渲染。我们将以一个示例进行解释。

The demo setup script installs the vacationRequest business process as an example of using task forms through Activiti Explorer. Please check the example for the complete source code. The business process diagram looks like this:

作为通过Activiti Explorer使用任务表单的一个示例,demo setup脚本安装了业务流程。请检查示例的完整的源代码。业务流程图如下所示:

taskform.vacation.request.model

To use the build-in rendering, the form files have to be included in the deployment. That can be done programmatically like this:

为了使用内置的渲染,在部署中必须包括表单文件。通过如下的编程方式来完成:

Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("org/activiti/examples/taskforms/VacationRequest.bpmn20.xml")
.addClasspathResource("org/activiti/examples/taskforms/approve.form")
.addClasspathResource("org/activiti/examples/taskforms/request.form")
.addClasspathResource("org/activiti/examples/taskforms/adjustRequest.form")
.deploy();

Or that can be done with an ant task by first building a business archive zip file that contains the process and forms like above and then deploy it like this:

或者,首先通过构建一个包含如上所示的流程和表单的业务档案zip文件,然后,如下部署这个文件。这些通过一个ant任务来完成。

<taskdef name="deploy-bar" classname="org.activiti.engine.impl.ant.DeployBarTask">
<classpath>
<pathelement path="files/demo"/>
<fileset dir="build">
<include name="activiti-cfg.jar"/>
</fileset>
<fileset dir="${activiti.home}/examples/activiti-engine-examples/libs-runtime" />
<fileset dir="${activiti.home}/examples/activiti-engine-examples/libs-test" />
</classpath>
</taskdef>
<deploy-bar file="${activiti.home}/setup/build/activiti-examples.bar" />

The BPMN 2.0 specification does not specify how tasks or task forms should be rendered, as such forms are defined using Activiti specific constructs. There is the attributeuserTasksthat can be specified on startEvents and userTasks.

BPMN 2.0 规范并没有指明任务或者任务表单应当如何渲染,使用Activiti特定的构造型来定义如此的表单。在和 startEvents和 userTask之上能够制定这个属性 userTask

<startEvent id="request"
activiti:formKey="org/activiti/examples/taskforms/request.form" />
<sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest" />
<userTask id="handleRequest" name="Handle vacation request"
activiti:formKey="org/activiti/examples/taskforms/approve.form" >
<documentation>
Vacation request by ${employeeName}
</documentation>
...
</userTask>

The activiti:formKey attribute can contain any text value which you use to identify your form in case you do your own form rendering. But the build-in form rendering expects the activiti:formKey to be a reference to a resource in the same business archive (= deployment).

当你采用自己的渲染时,属性能够包含你所用来表明你的表单的任何文本值。但是内置的表单渲染器猜想activiti:formKey 引用到同一业务档案(=部署)的一个资源。

Activiti Explorer uses the build-in form rendering engines. Currently, there is only one form rendering engine configured, which is Juel. So it resolves resource files as a Juel expression and the resulting HTML String is sent to the client. In the future, we hope to add a FreeMarker form engine, but that will require more library dependencies so we opt for Juel as the default form engine.

Activiti Explorer使用内置的表单渲染引擎。当前,只有一个以配置的表单渲染器。这个渲染器叫Juel。所以它把资源文件解析为a Juel expression 。解析结果 HTML String 被发送到客户。未来,我们希望加入表单引擎,但是它需要更多的库依赖,所以我们优选把Juel作为缺省的表单引擎。

Here is the rendered form defined in resource org/activiti/examples/taskforms/request.form. Its a form to capture the data necessary to start a new process instance.

这里是在资源org/activiti/examples/taskforms/request.form里定义的渲染过的表单。它是一个捕获启动一个新的流程实例所必须的数据的表单。

taskform.example

And here is the contents of that form file:

这里是那个表单文件的内容:

<h1>Vacation Request</h1>
Employee Name:<br/>
<input type="text" name="employeeName" value="" />
<input type="hidden" name="employeeName_required" value="true" />
Number of days:<br/>
<input type="number" name="numberOfDays" value="1" />
<input type="hidden" name="numberOfDays_type" value="Integer" />
First day of vacation:<br/>
<input type="date" name="startDate" />
<input type="hidden" name="startDate_type" value="Date" />
Date of return to work:<br/>
<input type="date" name="returnDate" />
<input type="hidden" name="returnDate_type" value="Date" />
Vacation pay requested
<input type="checkbox" name="vacationPay"/> Vacation pay requested
<input type="hidden" name="vacationPay_boolean" value="true"/>
Motivation:<br/>
<textarea name="vacationMotivation" value=""></textarea>

<EXPERIMENTAL> The hidden fields provide extra information to the Activiti Explorer client application. So the Javascript in the browser will use the hidden fields and enhance the corresponding input fields. For example, it's possible to specify that a certain text field is a date and Activiti Explorer will add a date picker.

<EXPERIMENTAL> 隐藏字段提供Activiti Explorer 客户应用程序的额外信息。这样浏览器将使用这些隐藏字段并加强相应输入字段。例如,制定某一文本字段是date,Activiti Explorer 将加入一个日期选取器是可能的。

  • Variable names must be camel-cased.

    变量名必须是骆驼方式的。

  • The default type of the process variable that is stored is string. Use a hidden field with the input variable name followed by '_type' to define the type (and hence also the conversion from the HTML input to the variable):

    保存的流程变量的缺省值是 string。使用一个输入变量后面跟有'_type' 的隐藏字段来定义类型(这样将HTML的输入约定为这个变量):

    <input type="hidden" name="numberOfDays_type" value="Integer" />

    Currently supported are String, Integer, Boolean, Date.

    当前支持的类型为:String, Integer, Boolean, Date

  • Input variables can be made required by adding a hidden field with the input variable name followed by '_required':

    通过使用一个输入变量后面跟有'_type' 的隐藏字段来产生必须的输入变量。

    <input type="hidden" name="employeeName_required" value="true" />
  • In Activiti-Explorer, the Date type validates to ISO 8601 (YYYY-MM-DD). This field will also use any native date picker tools in the browser (using the HTML5 input type="date") and fall back on a pop-up date picker using the YUI calendar widget. It is, of course still possible to manually enter your own date, which is validated as you type.

    在Activiti-Explorer里,Date类型用ISO 8601 (YYYY-MM-DD)来验证。这个字段也将使用浏览器里的任何本地日期拾取器(使用HTML5的input type="date")。通过YUI calendar widget返回一个弹出日期拾取器。当然,也可能手动输入你自己的日期,由你输入的验证方法。

  • The Integer form field in the demo has also been enhanced to make use of HTML5 input type=number, which provides native validation and custom input fields in the supported browser(s), although Activiti-Explorer provides client side validation as well.

    在demo里面的Integer表单字段也已经被加强产生HTML 5 input type=number。尽管Activiti-Explorer 提供客户端验证,但是在可支持HTML5的浏览器也提供内置验证和定制输入字段。

It is expected that Activiti Explorer in the future will use FormService.getStartFormData instead of these hidden field values to enhance the form input fields. That's why the hidden fields part is marked as experimental. </EXPERIMENTAL>

可以预料, Activiti Explorer 在未来将会使用 FormService.getStartFormData来代替这些隐藏字段值来加强表单输入字段。那是为什么隐藏字段被标记为实验性的原因。. </EXPERIMENTAL>

Use FormService.getRenderedStartForm to get the rendered form string with the API:

通过API,使用 FormService.getRenderedStartForm 来获取渲染表单字符串。

String FormService.getRenderedStartForm(String processDefinitionId)

Use FormService.submitStartFormData to start a new process instance with the properties that the user provided as input for the form:

以一个用户为表单输入的属性,采用 FormService.submitStartFormData来启动一个新的流程实例。

ProcessDefinition FormService.submitStartFormData(String processDefinitionId, Map<String, String> properties)

To learn about the difference between starting a new process instance with this FormService method in comparison with the ProcessInstance RuntimeService.startProcessInstanceById(String processDefinitionId), read the section called “Form properties(表单属性)”

为了了解和 ProcessInstance RuntimeService.startProcessInstanceById(String processDefinitionId)比较,以这个 FormService 方法启动一个新的流程实例之间的差异,请参见 read the section called “Form properties(表单属性)”

After submitting the form, a process instance is started and now someone of the management team needs to handle the request.

在提交这个表单之后,一个流程实例被启动。管理小组的有些人需要处理这个请求。

taskform.vacation.request.management.group

The corresponding user task has a task form attached to it, which uses the variables which were given as input by the employee in the start form. These variables are referenced as expressions and are resolved at runtime to their text representation.

相关的用户任务具有和它绑定的任务表单。。这些作为表达式引用,在运行期解析为它们的文本表示。

<h1>Vacation Approval</h1>
<p>
${employeeName} would like to take ${numberOfDays} day(s) of vacation.
</p>
<p>
Motivation: ${vacationMotivation}
</p>
<p>
Do you approve this?
<select name="vacationApproved">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
<input type="hidden" name="vacationApproved_type" value="Boolean" />
</p>
<p>
<label>
Motivation:<br/>
<textarea name="managerMotivation" value=""></textarea>
</label>
</p>

The manager will now indicate in the form whether the vacation request is approved or not, by selecting the appropriate input in the form.

通过在表单里面选择合适的输入,经理将在表单里面指示休假请求是否批注。

taskform.vacation.request.approve.form

The result is stored in as a process variable, which is then used in the exclusive gateway after the form is submitted.

结果作为一个流程变量保存,在表单提交之后在总网关里使用。

<sequenceFlow id="flow5" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
<conditionExpression xsi:type="tFormalExpression">${!vacationApproved}</conditionExpression>
</sequenceFlow>

Depending on the input of the manager in the user task, the employee will now get a new task in his personal task list, stating that the vacation request was disapproved and it needs can be refilled if wanted.

依赖在用户任务里面经理的输入,雇员现在将在他的个人任务列表里面得到一个新的任务。说明休假请求被拒绝。如果再想休假,需要重填。

taskform.vacation.request.adjust.form

The employee can now choose to resend the vacation request, which brings process execution again in the user task for the manager. Or the employee can throw away the request, ending the process.

雇员现在能够选择重发休假请求。这样让经理的用户流程重新执行。或者雇员可以抛弃这个请求,终止这个流程。

<h1>Adjust vacation Request</h1>
<p>
Your manager has disapproved your vacation request for ${numberOfDays} days. <br/>
Reason: ${managerMotivation}
</p>
<p>
Number of days:<br/>
<input type="text" name="numberOfDays" value="${numberOfDays}" />
<input type="hidden" name="numberOfDays_type" value="Integer" />
</p
...

External form rendering(外部表单渲染)

Above we showed the build-in task form rendering. But the API also allows for you to perform your own task form rendering outside of the Activiti Engine. These steps explain the hooks that you can use to render your task forms yourself.

上面我们展示了内置的任务表单渲染。但是API也允许你从 Activiti Engine之外执行你自己的任务表单渲染器。这些步骤解释了你能使用的渲染你的任务表单的钩子。

Essentially, all the data that's needed to render a form is assembled in one of these two service methods: StartFormData FormService.getStartFormData(String processDefinitionId) andTaskFormdata FormService.getTaskFormData(String taskId).

必要的,渲染一个表单所需的所有数据被组装在这连个服务方法的其中之一:StartFormData FormService.getStartFormData(String processDefinitionId)TaskFormdata FormService.getTaskFormData(String taskId)

Submitting form properties can be done with ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties) and void FormService.submitStartFormData(String taskId, Map<String,String> properties)

通过ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)void FormService.submitStartFormData(String taskId, Map<String,String> properties)方法能够完成提交表单的属性。

To learn about how form properties map to process variables, see the section called “Form properties(表单属性)”

为了学习表单属性如何映射到流程变量,参见the section called “Form properties(表单属性)”

You can just stick any form template resource inside the business archives that you deploy (in case you want to store them versioned with the process). It will be available as a resource in the deployment. You can use the String ProcessDefinition.getDeploymentId() and InputStream RepositoryService.getResourceAsStream(String deploymentId, String resourceName); to obtain the file that you included in the deployments. That could be your form template definition file.

Btw, you can use this capability of accessing the deployment resources beyond task forms for any other purposes as well.

随便,为了其它任何目的,你能使用出除了任务表单之外访问部署资源的能力。

The attribute <userTask activiti:formKey="..." is exposed by the API through String FormService.getStartFormData(String processDefinitionId).getFormKey() and String FormService.getTaskFormData(String taskId).getFormKey(). You could for instance store a generic key in the form attribute and apply an algorithm or transformation to get to the actual template that needs to be used. This might be handy when you want to render different forms for different UI technologies like e.g. one form for usage in a web app of normal screen size, one form for mobile phone's small screens and maybe even a template for a IM form or an email form.

属性 <userTask activiti:formKey="..." 通过 String FormService.getStartFormData(String processDefinitionId).getFormKey()String FormService.getTaskFormData(String taskId).getFormKey()被API 暴露。你可能为实例在表单里面保存一个通用键,应用一个算法或者转换来得到实际所需的模板。当你想从不同的UI技术渲染不同的表单,这可能方便。例如,一个表单用来在正常屏幕大小的Web应用使用;一个表单在手机小屏幕使用,甚至是IM表单或者email表单的一个模板。

Form properties(表单属性)

All information relevant to a business process is either included in the process variables themselves or referenced through the process variables. Activiti supports complex Java objects to be stored as process variables like Serializable objects, JPA entities or whole XML documents as Strings.

与业务流程相关的所有信息既可包含流程变量自己,也可通过流程变量包括它们的引用。Activiti支持将复杂的Java对象保存为像 Serializable 对象,JPA实体或者作为String 的整个XML文档的流程变量。

Starting a process and completing tasks is where people are involved into a process. Communicating with people requires forms to be rendered in some UI technology. In order to facilitate multiple UI technologies easy, the process definition can includes the logic of transforming of the complex Java typed objects in the process variables to aMap<String,String> of properties.

启动一个流程和完成任务是人们介入到流程的地方。和人通信需要采用一些UI技术来渲染表单。为了让多个UI技术和平共处,流程定义能够包含转化逻辑:将流程变量里面的复杂对象传化为一个属性的Map<String,String>

Any UI technology can then build a form on top of those properties. The properties can provide a dedicated (and more limited) view on the process variables. The properties needed to display a form are available in the FormData returnvalues of StartFormData FormService.getStartFormData(String processDefinitionId) and TaskFormdata FormService.getTaskFormData(String taskId). Those properties are obtained from the process variables.

然后任何UI技术能在这些属性之上构建一个表单。这些属性能在这些流程变量之上提供一个专有(有更多限制)视图。显示一个表单所需的属性在返回StartFormData FormService.getStartFormData(String processDefinitionId)TaskFormdata FormService.getTaskFormData(String taskId). 的FormData 值里。这些属性从流程变量取得。

By default, the build-in form engines, will 'see' the properties as well as the process variables. So there is no need to declare task form properties if they match 1-1 with the process variables. For example, with the following declaration:

缺省地,内置的表单引擎,将‘看见‘属性,正如看见流程变量一样。所以,如果任务表单属性和流程变量是1-1的匹配关系,没有必要对它声明。

<startEvent id="start" />

A form will see all the process variables but the formService.getStartFormData(String processDefinitionId).getFormProperties() will be empty.

In the above case, all the submitted properties will be stored as process variables. This means that by simply adding a new inputfield in the form, a new variable can be stored.

表单将看见所有的流程变量,而 formService.getStartFormData(String processDefinitionId).getFormProperties() 将为空。

Properties are derived from process variables, but they don't have to be stored as process variables. For example, a process variable could be a JPA entity of class Address. And a form property StreetName used by the UI technology could be linked with an expression #{address.street}

尽管属性从流程变量继承而来,但是它们不必存储为流程变量。例如,一个流程变量可能是一个类Address的JPA实体。并且,由UI技术使用的表单属性 StreetName 能够和一个表达式#{address.street} 连接。

Analogue, the properties that a user is supposed to submit in a form can be stored as a process variable or as a nested property in one of the process variables with a UEL value expression like e.g. #{address.street} .

Analogue the default behavior of properties that are submitted is that they will be stored as process variables unless a formProperty declaration specifies otherwise.

Also type conversions can be applied as part of the processing between form properties and process variables.

类型约定能够应用作为表单属性和流程变量之间处理环节的部分。

For example:

例如:

<userTask id="task"
<extensionElements>
<activiti:formProperty id="room" />
<activiti:formProperty id="duration" type="long"/>
<activiti:formProperty id="speaker" variable="SpeakerName" writable="false" />
<activiti:formProperty id="street" expression="#{address.street}" required="true" />
</extensionElements>
</userTask>
  • Form property room will be mapped to process variable room as a String.

    表单属性 room :将作为一个String映射为流程变量 room

  • Form property duration will be mapped to process variable duration as a java.lang.Long

    表单属性 duration :将作为一个java.lang.Long映射为流程变量 duration

  • Form property speaker will be mapped to process variable SpeakerName. It will only be available in the TaskFormData. If property speaker is submitted, an ActivitiException will be thrown. Analogue, with attribute readable="false", a property can be excluded from the FormData, but still be processed in the submit.

    表单属性会将 speaker 映射为流程变量 SpeakerName。他只能在TaskFormData获得。如果提交属性speaker,那么将会抛出ActivitiException。模拟地,带有属性 readable="false",尽管一个属性将会从 FormData排除在外,但是在提交里面仍将处理。

  • Form property street will be mapped to Java bean property street in process variable address as a String. And required="true" will throw an exception during the submit if the property is not provided.

    表单属性 street在流程变量里面将会作为String映射为Java bean 属性 street 。如果没有提供这个属性,在提交期间, required="true" 将抛出一个异常。

It's also possible to provide type meta data as part of the FormData that is returned from methods StartFormData FormService.getStartFormData(String processDefinitionId) andTaskFormdata FormService.getTaskFormData(String taskId)

提供类型元数据作为从StartFormData FormService.getStartFormData(String processDefinitionId)TaskFormdata FormService.getTaskFormData(String taskId)返回的FormData 的部分也是有可能的。

We support following form property types:

我们支持下列表单属性类型:

  • string (org.activiti.engine.impl.form.StringFormType)

  • long (org.activiti.engine.impl.form.LongFormType)

  • enum (org.activiti.engine.impl.form.EnumFormType)

  • date (org.activiti.engine.impl.form.DateFormType)

For each form property declared, the following FormProperty information will be made available through List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties() and List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()

对于每个声明的表单属性,通过 List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties() 和and List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties() 产生如下的 FormProperty信息。

public interface FormProperty {
/** the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}
* or {@link FormService#submitTaskFormData(String, java.util.Map)} */
String getId();
/** the display label */
String getName();
/** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
FormType getType();
/** optional value that should be used to display in this property */
String getValue();
/** is this property read to be displayed in the form and made accessible with the methods
* {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
boolean isReadable();
/** is this property expected when a user submits the form? */
boolean isWritable();
/** is this property a required input field */
boolean isRequired();
}

For example:

例如:

<startEvent id="start">
<extensionElements>
<activiti:formProperty id="speaker"
name="Speaker"
variable="SpeakerName"
type="string" />
<activiti:formProperty id="start"
type="date"
datePattern="dd-MMM-yyyy" />
<activiti:formProperty id="direction" type="enum">
<activiti:value id="left" name="Go Left" />
<activiti:value id="right" name="Go Right" />
<activiti:value id="up" name="Go Up" />
<activiti:value id="down" name="Go Down" />
</activiti:formProperty>
</extensionElements>
</startEvent>

All that information is accessible through the API. The type names can be obtained with formProperty.getType().getName(). And even the date pattern is available withformProperty.getType().getInformation("datePattern") and the enumeration values are accessible with formProperty.getType().getInformation("values")

通过API来访问那个信息。通过formProperty.getType().getName() 获取类型名;通过formProperty.getType().getInformation("datePattern")获取时间模式;通过formProperty.getType().getInformation("values") 获取枚举值。


评论

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2011-02-13 22:42 by 若水
你好啊
最近开始学习activiti 过了一遍文档 目前对这个form还是不太明白 是可以自动生成表单以方便开发的意思吗? 可是我在例子里看到有.form的文件 如果这个.form的文件需要手动去写的话 那貌似也没什么方便的吧 还是我理解的概念本身就错了 恳请能指教一下 感谢!

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2011-02-24 22:14 by 网路冷眼@BlogJava
@若水
是需要自己写的。只要具有HTML的经验加上变量替换,很容易写的。这个功能主要是针对manual Task的;针对Service Task的项目,情况要复杂许多。

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2011-04-26 16:28 by 志成
activiti5能实现自定义表单(最终用户定义,而不是开发人员定义)吗?

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2011-09-02 11:09 by 風過無痕
請問Activiti中如何應用其它的UI Teachnology,例如extjs、jquery?

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2012-11-05 14:00 by hackerthinker
formProperty.getType().getValue()取得的值为null,是怎么回事啊?难道不可以这么取值吗?

# re: Activiti User Guide(Activiti用户指南)-Chapter 08. Forms(表单)  回复  更多评论   

2012-11-14 10:15 by man1900
Activiti的表单是基于模析引擎的一种解析方式,所以是需要开发人员来参与设计及开发展示的。

参考一下这里的设计思路:
http://man1900.iteye.com/blog/1724324

只有注册用户登录后才能发表评论。


网站导航: