Spring Webflow 2 - basic conditional flow without beans - spring-mvc

I'm new to SWF (2.3.1), and am playing around to see if I can get something basic working. When I say 'basic', I really do mean basic. At this stage if I can possibly avoid it, I want to avoid writing any java. I just want to get a quick and dirty flow working, ideally all in the flow config.
I have managed to get a very basic linear flow working, going from view-state (screen?) A to B to C (AboutYou to AboutYourCar to YourQuote)
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<view-state id="AboutYou" view="/WEB-INF/views/jsp/motor/AboutYou.jsp">
<transition on="Next" to="AboutYourCar" />
</view-state>
<view-state id="AboutYourCar" view="/WEB-INF/views/jsp/motor/AboutYourCar.jsp">
<transition on="Back" to="AboutYou" />
<transition on="Next" to="YourQuote" />
</view-state>
<view-state id="Modifications" view="/WEB-INF/views/js/motor/Modifications.jsp">
<transition on="Back" to="AboutYourCar" />
</view-state>
<view-state id="YourQuote" view="/WEB-INF/views/jsp/motor/YourQuote.jsp">
<transition on="Back" to="AboutYou" />
</view-state>
</flow>
In the AboutYourCar view, I have an input field asking if the car has been modified:
Is your car modified?
<input type="radio" name="modified" value="No">
<input type="radio" name="modified" value="Yes">
What I would like to do is go to the Modifications view-state if the request parameter 'modified' is Yes; but I cant work out how to do this.
I think I would be able to get it working with an action-state, but (as I understand it) you need to provide an action bean which would perform some logic.
I also think I might be able to use a decision-state, but again, not really sure how I would write it.
Or maybe my whole approach is wrong, and that Modifications is not a view-state, but actually should be a sub-view??
Any help anyone could offer would be very much appreciated,
Thanks, Nathan

Something like:
<decision-state id="checkSelection">
<if test="requestParameters.modified == 'Yes'" then="Modifications" else="YourQuote"/>
</decision-state>
See Spring EL for what you can do with the Expression Language available within the flow configuration file (with or without your own Java objects) and EL Variables for the implicit variables available to Spring WebFlow.

Related

How does Spring WebFlow work in terms of code execution?

I am new to Spring Webflow and I am going through the codes and explanation on internet. I have a basic doubt regarding the flow of execution of codes in spring webflow applications.
As I have understood, A flow request is mapped to flow.xml file(I am now aware of FlowHandlerAdapter, FlowHandlerMapping, FlowRegistry). The starting state in the flow xml file, if it is a view-state, it renders a view.
For example -
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<secured attributes="ROLE_USER" />
<input name="hotelId" required="true" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)" result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails" model="booking">
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed">
<evaluate expression="bookingService.persistBooking(booking)" />
</transition>
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" />
<end-state id="bookingCancelled" />
</flow>
Here, when the flow file is being executed, the first view-state with id - "enterBookingDetails" renders the view enterBookingDetails.xhtml.
Does the control now go to the view page enterBookingDetails.xhtml and wait for the user event? And after the user clicks on button "proceed", the control comes back to flow XML file and executes the <transition> element to transit to reviewBooking view-state?
Am I understanding correctly that the flow execution is paused until a user event occurs, and upon occurrence of user event, control goes from web page to flow xml file and executes the transition and transits to corresponding state.
Yes, your understanding is essentially correct. Spring web flow framework renders the view, and then once the next action "proceed" is received, it will transit to the "reviewBooking" state, where again it would essentially pause until the next user action. Execution is based on transfer of state from one to another to complete the whole flow step-by-step.
NOTE : You may also re-use flows defined in other web-flow xml files in your own flow. This makes the framework very powerful.

Terminate actual spring web flow execution

is it possible to determine the actual running flow in a spring mvc context and terminate it ?
The reason why I'm asking is, that in my shop web app the whole checkout process is a web flow but the header menu is still visible and now I experience the problem that if a menu link is klicked the flow is exited but I don't recognize it.
I hope it is understandable what I want and I appreciate any help to get through this issue :)
Thanks.
If I understood your problem correctly - you wanted to terminate the current flow but still have the menu click work - is that right?
If it is, have a global common flow that is inherited by all your subflows. Then, add this in the flow tag of each of your flows, where commonFlow is the name or ID of your global flow definition file.
parent="commonFlow"
In this global flow definition, define the transition for the menu option:
<global-transitions>
<!-- If Menus are triggered on flows, we end them first. -->
<transition on="menuClick" to="endCurrentFlowThenMenu"/>
</global-transitions>
And then define a redirect for the menu:
<end-state id="endCurrentFlowThenMenu" view="flowRedirect:menuView"></end-state>
However, this only works when current flow is a parent/top-level flow. If it is a subflow, it gets a little bit dirty - you need a work-around so that all subflows are ended first:
<global-transitions>
<!-- If Menus are triggered on flows and subflows, we end them first. -->
<transition on="menuClick" to="endCurrentFlowThenMenuLevel1"/>
<transition on="endCurrentFlowThenMenuLevel1" to="endCurrentFlowThenMenuLevel2"/>
</global-transitions>
In which case you define the matching end states:
<end-state id="endCurrentFlowThenMenuLevel1" view="flowRedirect:newFlow">
</end-state>
<end-state id="endCurrentFlowThenMenuLevel2" view="flowRedirect:newFlow">
</end-state>
The reason why I repeat the view attribute on each end state is so that it could still work even if the current flow is a top-level flow. Depending on the maximum deepest subflow, you need have multiple transitions and end states to match them (i.e. if you max deepest functionality can have 2 subflows, repeat the above 3 times).
The trick here is, if the current flow is already a top level flow, SWF would not bubble up the parent and will just execute the flowRedirect.
If, however, the current flow is a subflow, SWF will not execute the redirect on the subflow, but instead will bubble up to the parent first, looking for a matching transition for the current subflow that just ended. It would continue doing this until it finds the top-level flow in which case it would execute the redirect, effectively ending all subflows in the process as well.
It's possible share global transition between flows and subflows, the way to do it's similar to above code.
Basically, you must define an abstract flow,commons-headers, setting abstract=true inside flow tag, then to define global-transitions which will be share among flow/subflow, these global-transitions can redirect to some kind of event, in this example to end-state.
Calling flow define, main flow, define subflow-state tag, here you must write returned transition from called flow,subflow, which must be the same id shared between transition and subflow's end-state tag, this is the way to finish subflow and return to the main flow. If you wish use some global transitions then you must define these transitions inside subflow-state tag setting same value for main flow's on attribute and global-flow's to attribute (in this example will be loginEnd and signEnd). Last step, main flow's to attribute must coincide with some global flow state id attribute (in this example loginEnd and signEnd again).
Commons-headers
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
abstract="true">
<end-state id="loginEnd" view="flowRedirect:login" />
<end-state id="signupEnd" view="flowRedirect:signup" />
<global-transitions>
<transition on="login" to="loginEnd" />
<transition on="signup" to="signupEnd"/>
</global-transitions>
Main flow
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
parent="commons-header">
<view-state id="inicio" view="main.xhtml">
<transition on="manageSpace" to="adminSpaces" />
<transition on="goSpace" to="visitSpace" />
</view-state>
<subflow-state id="visitSpace" subflow="space">
<on-entry>
<evaluate expression="space.showSpace(requestParameters.idSpace,flowRequestContext)" result="conversationScope.visitedSpace" />
</on-entry>
<transition on="finishSubFlow" to="inicio" />
<transition on="loginEnd" to="loginEnd" />
<transition on="registerEnd" to="registerEnd" />
</subflow-state>
<end-state id="error" view="flowRedirect:error" />
Subflow
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
parent="commons-header">
<view-state id="showPicture" view="space_pictures.xhtml">
<on-entry>
<evaluate expression="multimediaProvider.showPages('picture')" result="viewScope.pagesList"/>
</on-entry>
<transition on="goMain" to="finishSubFlow" />
<transition on="visit" to="visitPicturePage" />
</view-state>
<view-state id="visitPicturePage" view="show_picturePage.xthml">
<on-entry>
<set name="flowScope.code" value="requestParameters.code" />
</on-entry>
<on-render>
<evaluate expression="multimediaProvider.loadPage(flowScope.code)" />
</on-render>
<transition on="goMain" to="finishSubFlow" />
</view-state>
<end-state id="finishSubFlow" />

Webflow: Manipulating a conversation scoped object in an action

I'm new to Webflow and am having an issue: I have some form objects which delegate to an underlying conversation scoped object (and thus modifying it when the form is bound on post). However, subsequent actions don't see the modifications - it appears when binding, the form is manipulating a different instance of the object than the one in the conversation scope.
Here is one of the view states that has this issue:
<view-state id="groupAccount" model="groupAccountForm" >
<on-entry>
<evaluate expression="enrollmentAction.createApplication()"
result="conversationScope.application" />
<evaluate expression="enrollmentAction.createGroupAccountForm(fapplication)"
result="viewScope.groupAccountForm" />
</on-entry>
<transition on="cancel" to="finish"></transition>
<transition on="continue" to="employee">
<!-- <evaluate
expression="groupAccountForm.getApplication()" result="conversationScope.application" /> -->
<evaluate
expression="enrollmentAction.save(application)" />
</transition>
</view-state>
On entry, I create my conversation scoped object and then create the form bean passing it. When the page is submitted, the binding works just fine however, the application passed to enrollmentAction.save(..) doesn't have the values from the page. The commented out line above it solves the issue but there's got to be a better way of handling this.
This is running on Jetty 6.1, it's a prototype so no authenticated user.

How to implement conditional transitions in Spring Webflow

Just getting the hang of Spring Webflow. I have some simple forms working and binding back and forth - very cool. One thing not obvious to me at the moment is how to dynamically launch a flow based on user input.
i.e. imagine a flow where the user chooses an option in the first screen, and based on the choice taken, different subflows can be initiated. In pseudo-terms something like the following pseudo-flow:
<view-state id="selectService" model="serviceType">
<transition on="proceed">
<if "serviceType.selectedValue==1" to="subFlow1" />
<if "serviceType.selectedValue==2" to="subFlow2" />
<if "serviceType.selectedValue==3">
<if "serviceType.isValid==3" to="subFlow3" />
</if>
<default to="cancel" />
</transition>
<transition on="cancel" to="cancel" />
</view-state>
I've trawled the examples, docs, stackoverflow and the spring forums but haven't seen this anywhere..
It is covered in Spring in Action 3, which is a great book for Spring development in my eyes.
To answer your question here though, I think you are looking for the decision-state transition element. To get to user input, you should be able to use Spring Expression Language (SpEL) in the test attribute.
I tried using the decider recently, but frankly, for any non-trivial logic, it's best to move it to a Java method where you can easily unit test it. Then call said method and use the output from there. It's best to keep the flow XML files as simple as you can.

Spring webflow - mvc controller as subflow

Is there a way of using spring MVC contollers as subflows in spring webflow?
I have some data browsers written as plain MVC controllers and I would like to use them in my flows to browse/select data.
I managed to solve my problem. I am using a view state pointing to an external url (my controller). Here is the example:
<view-state id="itemBrowser" view="externalRedirect:contextRelative:/itemBrowser?callbackUrl=#{flowExecutionUrl}&itemSelectionMode=true">
<transition on="itemSelected" to="wizardStepBasic">
<evaluate expression="wizardActions.onItemSelected"/>
</transition>
</view-state>
The 'callbackUrl' parameter is used at the controller side to return to the flow. For example:
<a href=${callbackUrl}&selectedItemId=${item.id}&_eventId_itemSelected>
<img src="static/images/accept.png"/>
</a>
Hope, it'll be of some help to somebody :-)

Resources