Spring Web Flow allows you to handle JSF action events in a decoupled way, requiring no direct dependencies in your Java code on JSF API's. In fact, these events can often be handled completely in the flow definiton language without requiring any custom Java action code at all. This allows for a more agile development process since the artifacts being manipulated in wiring up events (JSF view templates and SWF flow definitions) are instantly refreshable without requiring a build and re-deploy of the whole application.
A simple but common case in JSF is the need to signal an event
that causes manipulation of the model in some way and then redisplays
the same view to reflect the changed state of the model. The flow
definition language has special support for this in the
transition element.
A good example of this is a table of paged list results. Suppose
you want to be able to load and display only a portion of a large result
list, and allow the user to page through the results. The initial
view-state definition to load and display the list would
be:
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
</view-state>
You construct a JSF DataTable that displays the current
hotels list, and then place a "More Results" link below the
table:
<h:commandLink id="nextPageLink" value="More Results" action="next"/>
This commandLink signals a "next" event from its action attribute.
You can then handle the event by adding to the view-state
definition:
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
</view-state>
Here you handle the "next" event by incrementing the page count on
the searchCriteria instance. The on-render action is then
called again with the updated criteria, which causes the next page of
results to be loaded into the DataModel. The same view is re-rendered
since there was no to attribute on the
transition element, and the changes in the model are
reflected in the view.
The next logical level beyond in-page events are events that require navigation to another view, with some manipulation of the model along the way. Achieving this with pure JSF would require adding a navigation rule to faces-config.xml and likely some intermediary Java code in a JSF managed bean (both tasks requiring a re-deploy). With the flow defintion language, you can handle such a case concisely in one place in a quite similar way to how in-page events are handled.
Continuing on with our use case of manipulating a paged list of
results, suppose we want each row in the displayed DataTable to contain
a link to a detail page for that row instance. You can add a column to
the table containing the following commandLink
component:
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>
This raises the "select" event which you can then handle by adding
another transition element to the existing
view-state :
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
<transition on="select" to="reviewHotel">
<set name="flowScope.hotel" value="hotels.selectedRow" />
</transition>
</view-state>
Here the "select" event is handled by pushing the currently
selected hotel instance from the DataTable into flow scope, so that it
may be referenced by the "reviewHotel" view-state .
JSF provides useful facilities for validating input at field-level before changes are applied to the model, but when you need to then perform more complex validation at the model-level after the updates have been applied, you are generally left with having to add more custom code to your JSF action methods in the managed bean. Validation of this sort is something that is generally a responsibility of the domain model itself, but it is difficult to get any error messages propagated back to the view without introducing an undesirable dependency on the JSF API in your domain layer.
With Web Flow, you can utilize the generic and low-level
MessageContext in your business code and any messages added
there will then be available to the FacesContext at render
time.
For example, suppose you have a view where the user enters the
necessary details to complete a hotel booking, and you need to ensure
the Check In and Check Out dates adhere to a given set of business
rules. You can invoke such model-level validation from a
transition element:
<view-state id="enterBookingDetails">
<transition on="proceed" to="reviewBooking">
<evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
</transition>
</view-state>
Here the "proceed" event is handled by invoking a model-level
validation method on the booking instance, passing the generic
MessageContext instance so that messages may be recorded.
The messages can then be displayed along with any other JSF messages
with the h:messages component,
JSF 2 provides built-in support for sending Ajax requests and performing partial processing and rendering on the server-side. You can specify a list of id's for partial rendering through the <f:ajax> facelets tag.
In Spring Web Flow you also have the option to specify the ids to use for partial rendering on the server side with the render action:
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="hotels:searchResultsFragment" />
</transition>
</view-state>
Most JSF component providers include some form of 'file upload' component. Generally when working
with these components JSF must take complete control of parsing multi-part requests and Spring MVC's
MultipartResolver cannot be used.
Spring Web Flow has been tested with file upload components from PrimeFaces and RichFaces. Check the documentation of your JSF component library for other providers to see how to configure file upload.
PrimeFaces provides a <p:fileUpload> component for uploading files. In order
to use the component you need to configure the org.primefaces.webapp.filter.FileUploadFilter
servlet filter. The filter needs to be configured against Spring MVC's
DispatcherServlet in your web.xml:
<filter> <filter-name>PrimeFaces FileUpload Filter</filter-name> <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class> </filter> <filter-mapping> <filter-name>PrimeFaces FileUpload Filter</filter-name> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> </filter-mapping>
For more details refer to the PrimeFaces documentation.
RichFaces provides a <rich:fileUpload> component for uploading files. No
special configuration is required to use the component, however, you will need to perform
some additional steps in your fileUploadListener.
Here is some typical XHTML markup. In this example the fileUploadBean refers
to Spring singleton bean.
<rich:fileUpload id="upload"
fileUploadListener="#{fileUploadBean.listener}"
acceptedTypes="jpg, gif, png, bmp">
</rich:fileUpload>Within your fileUploadBean you need to tell Web Flow that the response has been
handled and that it should not attempt any redirects. The org.springframework.webflow.context.ExternalContext
interface provides a recordResponseComplete() for just such purposes.
In addition, it is imperative that some partial response data is returned to the client. If your
<rich:fileUpload> component does not specify a render attribute you
may need to call processPartial(PhaseId.RENDER_RESPONSE) on the JSF
PartialViewContext.
public class FileUploadBean {
public void listener(FileUploadEvent event) throws Exception{
FacesContext.getCurrentInstance().getPartialViewContext().processPartial(PhaseId.RENDER_RESPONSE);
ExternalContextHolder.getExternalContext().recordResponseComplete();
UploadedFile file = event.getUploadedFile();
// Do something with the file
}
}For more details refer to the RichFaces documentation.
For JSF 1.2 the Spring Faces UICommand components
have the ability to do Ajax-based partial view updates. These components
degrade gracefully so that the flow will still be fully functional by
falling back to full page refreshes if a user with a less capable
browser views the page.
Revisiting the earlier example with the paged table, you can
change the "More Results" link to use an Ajax request by replacing the
standard commandButton with the Spring Faces component
version (note that the Spring Faces command components use Ajax by
default, but they can alternately be forced to use a normal form submit
by setting ajaxEnabled="false" on the component):
<sf:commandLink id="nextPageLink" value="More Results" action="next" />
This event is handled just as in the non-Ajax case with the
transition element, but now you will add a special
render action that specifies which portions of the
component tree need to be re-rendered:
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="hotels:searchResultsFragment" />
</transition>
</view-state>
The fragments="hotels:searchResultsFragment" is an
instruction that will be interpreted at render time, such that only the
component with the JSF clientId "hotels:searchResultsFragment" will be
rendered and returned to the client. This fragment will then be
automatically replaced in the page. The fragments attribute
can be a comma-delimited list of ids, with each id representing the root
node of a subtree (meaning the root node and all of its children) to be
rendered. If the "next" event is fired in a non-Ajax request (i.e., if
JavaScript is disabled on the client), the render action
will be ignored and the full page will be rendered as normal.
In addition to the Spring Faces commandLink
component, there is a corresponding commandButton component
with the same functionality. There is also a special
ajaxEvent component that will raise a JSF action even in
response to any client-side DOM event. See the Spring Faces tag library
docs for full details.
An additional built-in feature when using the Spring Faces
Ajax-enabled components is the ability to have the response rendered
inside a rich modal popup widget by setting popup="true" on
a view-state .
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchFragment" />
</on-entry>
<transition on="search" to="reviewHotels">
<evaluate expression="searchCriteria.resetPage()"/>
</transition>
</view-state>
If the "changeSearchCriteria" view-state is reached
as the result of an Ajax-request, the result will be rendered into a
rich popup. If JavaScript is unavailable, the request will be processed
with a full browser refresh, and the "changeSearchCriteria" view will be
rendered as normal.