ZK Web Flow
This article is out of date, please refer to http://books.zkoss.org/wiki/ZK_Spring_Essentials/Working_with_ZK_Spring/Working_with_ZK_Spring_Webflow for more up to date information.
Henri Chen, Principal Engineer, Potix Corporation
June 16, 2009
ZK Web Flow 1.0 RC
Introduction
This is the second article regarding ZK and web flow system. In the previous article, I have explained how to use ZK and Spring Web Flow together. We received a lot of feedback from that article and most complained about the complex XML configurations. I cannot agree more regarding these opinions on such XML configuration issues. These complexities are actually inherited from the Spring Web Flow; or I shall say mostly from Spring-MVC.
Thinking about the way to simplify the system, we figured that we can actually implement a pure Ajax based web flow system without these XML things. As I have mentioned in that article, Spring-MVC is designed for page-oriented or form-based navigation which is invented before Ajax is popular. While ZK can do event-oriented navigation the Spring Web Flow actually limits what ZK can do and what ZK Spring Web Flow can do.
Therefore, we think we shall leave the implementation of Spring Web Flow framework away and pick those good concepts of and design our own ZK Web Flow system instead. And this article shows you what we have achieved.
The Example
This is the same hotel searching and booking example provided in Spring Web Flow 2.0.3. Only that I rewrite the view pages and flow definition files per the ZK Web Flow system. Following is the demo.
Demo
No XML Configuration Files
In designing this system, we setup three rules:
- There shall be no extra XML configuration needed except the application dependent flow definition files.
- The XML schema of flow definition file shall be similar to that of the zul page. That is, if you are familiar with designing ZK's zul page, you shall be familiar with designing ZK Web Flow definition file.
- Reuse the naming of Spring Web Flow tags as possible so for users familiar with Spring Web Flow will quickly get familiar with ZK Web Flow system.
As Simple As a ZUL Page
So where do we start the flow? Following is the list of the entry zul page(index.zul) and also the layout page. Remember that this is an Ajax Based web flow system. We don't use the legacy concept of common layout pages(switch pages with one common layout design); instead, we use the concept of single-layout page(switch partial content of a single main layout page).
index.zul
<?page title="ZK Web Flow Sample Application" contentType="text/html;charset=UTF-8"?>
<?init class="org.zkoss.zwf.FlowHandler" arg0="/WEB-INF/flow/main/main.xml"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver" ?>
<zk xmlns="http://www.zkoss.org/2005/zul">
<zscript>
<![CDATA[
//@IMPORT
import org.zkoss.zwfdemo.samples.booking.*;
]]>
<![CDATA[
//TODO, Take out following code when security is done
User keith = new User("Keith", null, "keith");
User currentUser = keith;
]]>
</zscript>
<window width="800px" height="100%" style="margin: 0 auto;">
<borderlayout>
<north size="60px">
<borderlayout>
<center border="none" style="background-color:#B1CBD5"><html><![CDATA[<h2 style="color:#0C7A9A;margin-left:10px">ZK Hotel Booking Sample Application</h2>]]></html></center>
<east border="none" style="background-color:#B1CBD5"><toolbarbutton src="/img/zkpowered_s.png" href="http://www.zkoss.org"/></east>
</borderlayout>
</north>
<south style="background-color:#B1CBD5">
<span style="font-size:small">Copyright © 2009 Potix Corporation. All rights reserved.</span>
</south>
<west>
<toolbarbutton src="/img/hotel.jpg" href="http://www.zkoss.org" />
</west>
<center>
<div id="working" self="@{view(content)}"/> <!-- working view -->
</center>
</borderlayout>
</window>
</zk>
As you can see in the index.zul example code, simply specify the FlowHandler in the main layout page, tell where the flow definition file (main.xml) is, and the proper working view as annotated by (@{view(content)} and you are done.
When you visit the main layout page, the FlowHandler will read the flow definition file from the specified location and start update the working view of the main layout page per the view state changes as defined in the flow definition file main.xml.
As Simple As Defining a ZUL Page
So how do we define a flow definition file? Following is the list of the flow definition file main.xml:
main.xml
<?xml version="1.0" encoding="UTF-8"?>
<flow id="main" onEntry='flowScope.put("searchCriteria", new org.zkoss.zwfdemo.samples.booking.SearchCriteria())'>
<view-state id="enterSearchCriteria" onEntry='stateScope.put("bookings", bookingService.findBookings(currentUser.name))'
uri="enterSearchCriteria.zul, bookingSection.zul">
<transition id="search" to="reviewHotels" >
<attribute name="onTransit">
searchCriteria.searchString = searchString.value;
searchCriteria.pageSize = pageSize.getSelectedItem().getValue();
searchCriteria.resetPage();
</attribute>
</transition>
<transition id="cancelBooking" onTransit='bookingService.cancelBooking(componentScope.get("booking"));'/>
</view-state>
<view-state id="reviewHotels">
<attribute name="onEntry">
stateScope.put("hotels", bookingService.findHotels(searchCriteria));
stateScope.put("hotelsCount", bookingService.findHotelsCount(searchCriteria));
</attribute>
<transition id="sort" onTransit='searchCriteria.sortBy = componentScope.get("sortBy"))' />
<transition id="paging" onTransit='searchCriteria.page = paging.getActivePage()' />
<transition id="select" to="reviewHotel" onTransit='flowScope.put("hotel", componentScope.get("hotel"))' />
<transition id="changeSearch" to="changeSearchCriteria" />
</view-state>
<view-state id="reviewHotel">
<transition id="book" to="bookHotel" />
<transition id="cancel" to="enterSearchCriteria" />
</view-state>
<subflow-state id="bookHotel" subflow="../booking/booking.xml" onEntry='flowScope.put("hotelId", hotel.id)'>
<transition id="bookingConfirmed" to="finish" />
<transition id="bookingCancelled" to="enterSearchCriteria" />
</subflow-state>
<view-state id="changeSearchCriteria" uri="enterSearchCriteria.zul" popup="yes" >
<transition id="search" to="reviewHotels" bookmark="no">
<attribute name="onTransit">
searchCriteria.searchString = searchString.value;
searchCriteria.pageSize = pageSize.getSelectedItem().getValue();
searchCriteria.resetPage();
</attribute>
</transition>
</view-state>
<end-state id="finish" />
</flow>
A web flow basically is composed of states. Inside each state, you can define transitions to tell the flow engine(FlowHandler) which state to go on some predefined flow action(i.e. the id of the transition). Also, you can do some evaluation in the life cycle of the web flow. You can use onXxx flow event handler and <zscript/> tags as used in ZK's zul pages to specify what to do in a specific moment during flow execution. We reuse the ZK's zscript mechanism here so you can choose to use your favorite scripting language in the flow definition file; i.e. Java(BeanShell), Groovy, JavaScript, Ruby, or Python as already supported by ZK Framework.
Following I will brief those tags and attributes that related to the flow definitions.
Flow Components
These are flow components used in defining a flow.
- <flow>: define a flow or subflow. The root tag used in a flow definition file.
- <view-state>: define the view state. Each view state is mapped to one or more zul content files. You can either specify them in the optional uri attribute or if omitted the engine will map it to the same name zul file as of the view state's id. (e.g. <view-state id="reviewHotels"/> is mapped to reviewHotels.zul file in the same folder of the main.xml flow definition file)
- <subflow-state>: define the state to go to a subflow. You can specify the subflow definition file in its subflow attribute; either in absolute path (starts with a slash /) or relative to the main flow path. When a state transit to a subflow state, the subflow is loaded by the flow engine and executed. Note that you can recursively define subflow in a subflow.
- <end-state>: define the end state of a flow. When transit to an end state, the flow engine will leave the current flow or subflow.
- <action-state>: define the state to dispatch the transition based on the result of the test attribute. It is usually used for condition transition based on some value of calculated result.
- <transition>: define the transition action. It is always under states. The id tells which flow action triggers which flow transition, and the optional attribute to="target-state" tells which target state to go when the specified transition is executed. If attribute to is omitted, it is like a transition to the same state. That is, the onExit of the state and the onEntry of the state would still be fired, in that order. As for how to fire a transition action in the zul page, I will explain later with an example zul page.
Flow Events
These are flow events that you can write associated flow event listener and do things to customize the flow.
- onEntry: onEntry flow event is fired to a flow when entering a flow or fired to a xxx-state when entering a state. Flow designers can thus intervene and customize the behavior of the flow execution. For example, you might do some preparation of the data models or pass information across states in the onEntry event handler.
- onExit: onExit flow event is fired to a flow when exiting a flow or fired to a xxx-state when exiting a state. Flow designers can thus intervene and customize the behavior of the flow execution. For example, you might want to persist some data or do some notifications to other systems in the onExit event handler.
- onTransit: onTransit flow event is fired to a transition when the transition is being executed. Flow designers can thus intervene and customize the behavior of the flow execution. For example, you can do some transition specific operations in the onTransit event handler.
Implicit Flow Objects
These are implicit flow object that you can access to customize the flow. Implicit flow objects are accessible in .zul and/or in flow definition file. Also, implicit zul objects (desktop, page, self, sessionScope, desktopScope, ...,etc..) are also accessible in flow definition file. Therefore, you can use these implicit objects to pass information across view pages and across flow states.
- flow: the current executing flow.
- topFlow: the entry flow(NOT a sub flow) executed by the flow engine.
- parentFlow: the parent flow of the current executing flow. It could be null if top flow is the current executing flow.
- state: the current executing state.
- flowScope: the attribute map associated with the current executing flow. It is created when enter a flow; destroyed when exit a flow.
- flashScope: another attribute map associated with the current executing flow. It is created when enter a flow; destroyed when exit a flow; cleared after rendering a view state.
- topFlowScope: the attribute map associated with the top flow. It is created when enter the top flow; destroyed when exit the top flow. If top flow is the current executing flow, topFlowScope is the same to flowScope.
- parentFlowScope: the attribute map associated with the parent flow of the current executing flow. It can be null if top flow is the current executing flow.
- stateScope: the attribute map associated with current executing state.
Load the First View State
When the main layout page is first loaded, the flow engine will render the content of the first <view-state/> onto the working view area. In our example, it is the enterSearchCriteria view state which includes two UI .zul definition files enterSearchCriteria.zul and bookingSection.zul(see uri attribute of <view-state/>). The flow engine will render .zul files specified in sequence.
enterSearchCriteria.zul
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?>
<zk>
<div id="hotel search" onOK="" self="@{action(search,when=onOK)}">
<html><![CDATA[<h2>Search Hotels</h2>]]></html>
<grid>
<columns>
<column width="200px"/>
<column/>
</columns>
<rows>
<row>
Search String:
<textbox id="searchString" value="${searchCriteria.searchString}" tooltiptext="Search hotels by name, address, city, or zip." />
</row>
<row>
Maximum Results:
<listbox id="pageSize" rows="1" mold="select">
<listitem forEach="${referenceData.pageSizeOptions}" value="${c:int(each)}" label="${each}" selected="${c:int(each) == searchCriteria.pageSize}"/>
</listbox>
</row>
<row spans="2" align="right">
<button id="findHotels" label="Find Hotels" onClick="" self="@{action(search)}"/>
</row>
</rows>
</grid>
</div>
</zk>
Update the Content of the Working View
When an end user press a button or change text of a textbox, the flow engine will be triggered to do state transition per the predefined flow action. We use @{action({action}[, when={ZK-event}]} annotation on the ZK UI component to specify what flow action would be fired when the associated ZK-event is triggered by the end user. The when argument is optional and is default to onClick so you do not have to specify it in components such as button and toolbutton, etc.. As defined in the enterSearchCriteria.zul, we annotate the Find Hotels button with self="@{action(search)}". When an end user press the Find Hotels button, a search flow action would be fired to the flow engine. Per the flow definition in main.xml file, the onTransit flow event handler is called then the state is transited into next state -- reviewHotels.
Auto History Management
While this is a pure Ajax based web flow system, we still do the history bookmark automatically for you. That is, the end user can press the browser's back and forward button and restore to the previous state. Of course, in some cases you might not want such automatic history bookmark. The system allows you to control the history bookmark on a per-transition granularity.
For bookmark what the system does is to snapshot the current flow context(the data). And then restore the view per that snapshot flow context when the end user press the browser back button or forward button. Therefore, to guarantee the restoring of the same view results, the .zul page has to be designed on using the data from the flow context; i.e., the flowScope, stateScope, etc..
Restore of a View
Summary
In some sense, designing a flow defintion file is somehow like writing in XML way a controller of MVC programming . Then why not just write own flow controller but bother to use a web flow system? I think the most reasonable answer is that a good framework shall decrease significantly the developers' effort to implement from scratch and maintain an application. A framework is used to cover most common parts of an application and provide some enough easy to add flexibility for application customization. The term enough and easy is the catch of the whole framework idea. I hope you enjoy this ZK Web Flow system and feedback us to make it better. We like to hear from you.
Download
- ZK Web Flow 1.0 RC zwf-1.0.0.RC.jar
- ZK Web Flow 1.0 RC2 (ZK 5) zwf-1.0.0.RC2.jar
- ZK Web Flow demo zwfdemo.war
- ZK Web Flow demo source zwfdemo-src.zip
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |