The flow-builder-services attribute for specifying a ValidationHintResolver was originally added to use (incorrectly) camelback notation, i.e. validationHintResolver. This change fgiixes that to the XML convention i.e. validation-hint-resolver. SWF-1629
906 lines
44 KiB
XML
906 lines
44 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<chapter xml:id="views"
|
|
xmlns="http://docbook.org/ns/docbook" version="5.0"
|
|
xmlns:xl="http://www.w3.org/1999/xlink"
|
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="
|
|
http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd
|
|
http://www.w3.org/1999/xlink http://www.docbook.org/xml/5.0/xsd/xlink.xsd">
|
|
<title>Rendering views</title>
|
|
<sect1 xml:id="views-introduction">
|
|
<title>Introduction</title>
|
|
<para>
|
|
This chapter shows you how to use the <code>view-state</code> element to render views within a flow.
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="view-convention">
|
|
<title>Defining view states</title>
|
|
<para>
|
|
Use the <code>view-state</code> element to define a step of the flow that renders a view and waits for a user event to resume:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails">
|
|
<transition on="submit" to="reviewBooking" />
|
|
</view-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
By convention, a view-state maps its id to a view template in the directory where the flow is located.
|
|
For example, the state above might render <filename>/WEB-INF/hotels/booking/enterBookingDetails.xhtml</filename>
|
|
if the flow itself was located in the <filename>/WEB-INF/hotels/booking</filename> directory.
|
|
</para>
|
|
<para>
|
|
Below is a sample directory structure showing views and other resources like message bundles co-located with their flow definition:
|
|
</para>
|
|
<mediaobject>
|
|
<imageobject role="fo">
|
|
<imagedata fileref="images/flow-view-packaging.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<imageobject role="html">
|
|
<imagedata fileref="images/flow-view-packaging.png" format="PNG" align="center"/>
|
|
</imageobject>
|
|
<caption>
|
|
<para>Flow Packaging</para>
|
|
</caption>
|
|
</mediaobject>
|
|
</sect1>
|
|
<sect1 xml:id="view-explicit">
|
|
<title>Specifying view identifiers</title>
|
|
<para>
|
|
Use the <code>view</code> attribute to specify the id of the view to render explicitly.
|
|
</para>
|
|
<sect2 xml:id="view-explicit-flowrelative">
|
|
<title>Flow relative view ids</title>
|
|
<para>
|
|
The view id may be a relative path to view resource in the flow's working directory:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" view="bookingDetails.xhtml">]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="view-explicit-absolute">
|
|
<title>Absolute view ids</title>
|
|
<para>
|
|
The view id may be a absolute path to a view resource in the webapp root directory:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml">]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="view-explicit-logical">
|
|
<title>Logical view ids</title>
|
|
<para>
|
|
With some view frameworks, such as Spring MVC's view framework, the view id may also be a logical identifier resolved by the framework:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" view="bookingDetails">]]>
|
|
</programlisting>
|
|
<para>
|
|
See the Spring MVC integration section for more information on how to integrate with the MVC <code>ViewResolver</code> infrastructure.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-scope">
|
|
<title>View scope</title>
|
|
<para>
|
|
A view-state allocates a new <code>viewScope</code> when it enters.
|
|
This scope may be referenced within the view-state to assign variables that should live for the duration of the state.
|
|
This scope is useful for manipulating objects over a series of requests from the same view, often Ajax requests.
|
|
A view-state destroys its viewScope when it exits.
|
|
</para>
|
|
<sect2 xml:id="view-scope-var">
|
|
<title>Allocating view variables</title>
|
|
<para>
|
|
Use the <code>var</code> tag to declare a view variable.
|
|
Like a flow variable, any <code>@Autowired</code> references are automatically restored when the view state resumes.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" />]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="view-scope-actions">
|
|
<title>Assigning a viewScope variable</title>
|
|
<para>
|
|
Use the <code>on-render</code> tag to assign a variable from an action result before the view renders:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<on-render>
|
|
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
|
|
</on-render>]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="view-scope-ajax">
|
|
<title>Manipulating objects in view scope</title>
|
|
<para>
|
|
Objects in view scope are often manipulated over a series of requests from the same view.
|
|
The following example pages through a search results list.
|
|
The list is updated in view scope before each render.
|
|
Asynchronous event handlers modify the current data page, then request re-rendering of the search results fragment.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="searchResults">
|
|
<on-render>
|
|
<evaluate expression="bookingService.findHotels(searchCriteria)"
|
|
result="viewScope.hotels" />
|
|
</on-render>
|
|
<transition on="next">
|
|
<evaluate expression="searchCriteria.nextPage()" />
|
|
<render fragments="searchResultsFragment" />
|
|
</transition>
|
|
<transition on="previous">
|
|
<evaluate expression="searchCriteria.previousPage()" />
|
|
<render fragments="searchResultsFragment" />
|
|
</transition>
|
|
</view-state>]]>
|
|
</programlisting>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-on-render">
|
|
<title>Executing render actions</title>
|
|
<para>
|
|
Use the <code>on-render</code> element to execute one or more actions before view rendering.
|
|
Render actions are executed on the initial render as well as any subsequent refreshes, including any partial re-renderings of the view.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<on-render>
|
|
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
|
|
</on-render>]]>
|
|
</programlisting>
|
|
</sect1>
|
|
<sect1 xml:id="view-model">
|
|
<title>Binding to a model</title>
|
|
<para>
|
|
Use the <code>model</code> attribute to declare a model object the view binds to.
|
|
This attribute is typically used in conjunction with views that render data controls, such as forms.
|
|
It enables form data binding and validation behaviors to be driven from metadata on your model object.
|
|
</para>
|
|
<para>
|
|
The following example declares an <code>enterBookingDetails</code> state manipulates the <code>booking</code> model:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">]]>
|
|
</programlisting>
|
|
<para>
|
|
The model may be an object in any accessible scope, such as <code>flowScope</code> or <code>viewScope</code>.
|
|
Specifying a <code>model</code> triggers the following behavior when a view event occurs:
|
|
</para>
|
|
<orderedlist>
|
|
<listitem><para>View-to-model binding. On view postback, user input values are bound to model object properties for you.</para></listitem>
|
|
<listitem><para>Model validation. After binding, if the model object requires validation that validation logic will be invoked.</para></listitem>
|
|
</orderedlist>
|
|
<para>
|
|
For a flow event to be generated that can drive a view state transition, model binding must complete successfully.
|
|
If model binding fails, the view is re-rendered to allow the user to revise their edits.
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="view-type-conversion">
|
|
<title>Performing type conversion</title>
|
|
<para>
|
|
When request parameters are used to populate the model (commonly referred to as data binding), type conversion is required to parse String-based request parameter values before setting target model properties.
|
|
Default type conversion is available for many common Java types such as numbers, primitives, enums, and Dates.
|
|
Users also have the ability to register their own type conversion logic for user-defined types, and to override the default Converters.
|
|
</para>
|
|
<sect2 xml:id="converter-options">
|
|
<title>Type Conversion Options</title>
|
|
<para>
|
|
Starting with version 2.1 Spring Web Flow uses the <link xl:href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#core-convert">type conversion</link> and <link xl:href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#format">formatting</link> system introduced in Spring 3 for nearly all type conversion needs.
|
|
Previously Web Flow applications used a type conversion mechanism that was different from the one in Spring MVC, which relied on the <code>java.beans.PropertyEditor</code> abstraction.
|
|
Spring 3 offers a modern type conversion alternative to PropertyEditors that was actually influenced by Web Flow's own type conversion system.
|
|
Hence Web Flow users should find it natural to work with the new Spring 3 type conversion.
|
|
Another obvious and very important benefit of this change is that a single type conversion mechanism can now be used across Spring MVC And Spring Web Flow.
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="converter-upgrade-to-spring-3">
|
|
<title>Upgrading to Spring 3 Type Conversion And Formatting</title>
|
|
<para>
|
|
What does this practically mean for existing applications?
|
|
Existing applications are likely registering their own converters of type <code>org.springframework.binding.convert.converters.Converter</code> through a sub-class of <code>DefaultConversionService</code> available in Spring Binding.
|
|
Those converters can continue to be registered as before.
|
|
They will be adapted as Spring 3 <code>GenericConverter</code> types and registered with a Spring 3 <code>org.springframework.core.convert.ConversionService</code> instance.
|
|
In other words existing converters will be invoked through Spring's type conversion service.
|
|
</para>
|
|
<para>
|
|
The only exception to this rule are named converters, which can be referenced from a <code>binding</code> element in a <code>view-state</code>:
|
|
<programlisting language="java"><![CDATA[
|
|
public class ApplicationConversionService extends DefaultConversionService {
|
|
public ApplicationConversionService() {
|
|
addDefaultConverters();
|
|
addDefaultAliases();
|
|
addConverter("customConverter", new CustomConverter());
|
|
}
|
|
}]]>
|
|
</programlisting>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<binder>
|
|
<binding property="checkinDate" required="true" converter="customConverter" />
|
|
</binder>
|
|
</view-state>]]>
|
|
</programlisting>
|
|
Named converters are not supported and cannot be used with the type conversion service available in Spring 3.
|
|
Therefore such converters will not be adapted and will continue to work as before, i.e. will not involve the Spring 3 type conversion.
|
|
However, this mechanism is deprecated and applications are encouraged to favor Spring 3 type conversion and formatting features.
|
|
</para>
|
|
<para>
|
|
Also note that the existing Spring Binding <code>DefaultConversionService</code> no longer registers any default converters.
|
|
Instead Web Flow now relies on the default type converters and formatters in Spring 3.
|
|
</para>
|
|
<para>
|
|
In summary the Spring 3 type conversion and formatting is now used almost exclusively in Web Flow.
|
|
Although existing applications will work without any changes, we encourage moving towards unifying the type conversion needs of Spring MVC and Spring Web Flow parts of applications.
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="converter-configuration">
|
|
<title>Configuring Type Conversion and Formatting</title>
|
|
<para>
|
|
In Spring MVC an instance of a <code>FormattingConversionService</code> is created automatically through the custom MVC namespace:
|
|
<programlisting language="xml"><![CDATA[
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/mvc
|
|
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
|
|
http://www.springframework.org/schema/beans
|
|
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
|
|
|
|
<mvc:annotation-driven/>
|
|
]]>
|
|
</programlisting>
|
|
Internally that is done with the help of <code>FormattingConversionServiceFactoryBean</code>, which registers a default set of converters and formatters.
|
|
You can customize the conversion service instance used in Spring MVC through the <code>conversion-service</code> attribute:
|
|
<programlisting language="xml"><![CDATA[
|
|
<mvc:annotation-driven conversion-service="applicationConversionService" />]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
In Web Flow an instance of a Spring Binding <code>DefaultConversionService</code> is created automatically, which does not register any converters.
|
|
Instead it delegates to a <code>FormattingConversionService</code> instance for all type conversion needs.
|
|
By default this is not the same <code>FormattingConversionService</code> instance as the one used in Spring 3.
|
|
However that won't make a practical difference until you start registering your own formatters.
|
|
</para>
|
|
<para>
|
|
The <code>DefaultConversionService</code> used in Web Flow can be customized through the flow-builder-services element:
|
|
<programlisting language="xml"><![CDATA[
|
|
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" />]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Connecting the dots in order to register your own formatters for use in both Spring MVC and in Spring Web Flow you can do the following.
|
|
Create a class to register your custom formatters:
|
|
<programlisting language="java"><![CDATA[
|
|
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
|
|
|
|
@Override
|
|
protected void installFormatters(FormatterRegistry registry) {
|
|
// ...
|
|
}
|
|
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
Configure it for use in Spring MVC:
|
|
<programlisting language="xml"><![CDATA[
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/mvc
|
|
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
|
|
http://www.springframework.org/schema/beans
|
|
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
|
|
|
|
<mvc:annotation-driven conversion-service="applicationConversionService" />
|
|
|
|
<!--
|
|
Alternatively if you prefer annotations for DI:
|
|
1. Add @Component to the factory bean.
|
|
2. Add a component-scan element (from the context custom namespace) here.
|
|
3. Remove XML bean declaration below.
|
|
-->
|
|
|
|
<bean id="applicationConversionService" class="somepackage.ApplicationConversionServiceFactoryBean">
|
|
|
|
]]>
|
|
</programlisting>
|
|
Connection the Web Flow <code>DefaultConversionService</code> to the same "applicationConversionService" bean used in Spring MVC:
|
|
<programlisting language="xml"><![CDATA[
|
|
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" ... />
|
|
|
|
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" ... />
|
|
|
|
<bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService">
|
|
<constructor-arg ref="applicationConversionSevice"/>
|
|
</bean>]]>
|
|
</programlisting>
|
|
Of course it is also possible to mix and match.
|
|
Register new Spring 3 <code>Formatter</code> types through the "applicationConversionService".
|
|
Register existing Spring Binding <code>Converter</code> types through the "defaultConversionService".
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="converter-working-with">
|
|
<title>Working With Spring 3 Type Conversion And Formatting</title>
|
|
<para>
|
|
An important concept to understand is the difference between type converters and formatters.
|
|
</para>
|
|
<para>
|
|
Type converters in Spring 3, provided in <code>org.springframework.core</code>, are for general-purpose type conversion between any two object types.
|
|
In addition to the most simple <code>Converter</code> type, two other interfaces are <code>ConverterFactory</code> and <code>GenericConverter</code>.
|
|
</para>
|
|
<para>
|
|
Formatters in Spring 3, provided in <code>org.springframework.context</code>, have the more specialized purpose of representing Objects as Strings.
|
|
The <code>Formatter</code> interface extends the <code>Printer</code> and <code>Parser</code> interfaces for converting an Object to a String and turning a String into an Object.
|
|
</para>
|
|
<para>
|
|
Web developers will find the <code>Formatter</code> interface most relevant because it fits the needs of web applications for type conversion.
|
|
<note>
|
|
<para>
|
|
An important point to be made is that Object-to-Object conversion is a generalization of the more specific Object-to-String conversion.
|
|
In fact in the end <code>Formatters</code> are reigstered as <code>GenericConverter</code> types with Spring's <code>GenericConversionService</code> making them equal to any other converter.
|
|
</para>
|
|
</note>
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="converter-formatting-annotations">
|
|
<title>Formatting Annotations</title>
|
|
<para>
|
|
One of the best features of the new type conversion is the ability to use annotations for a better control over formatting in a concise manner.
|
|
Annotations can be placed on model attributes and on arguments of @Controller methods that are mapped to requests.
|
|
Out of the box Spring provides two annotations <code>NumberFormat</code> and <code>DateTimeFormat</code> but you can create your own and have them registered along with the associated formatting logic.
|
|
You can see examples of the <code>DateTimeFormat</code> annotation in the <link xl:href="https://src.springframework.org/svn/spring-samples/travel">Spring Travel</link> and in the <link xl:href="https://src.springframework.org/svn/spring-samples/petcare">Petcare</link> along with other samples in the <link xl:href="https://src.springframework.org/svn/spring-samples">Spring Samples</link> repository.
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="converter-dates">
|
|
<title>Working With Dates</title>
|
|
<para>
|
|
The <code>DateTimeFormat</code> annotation implies use of <link xl:href="http://joda-time.sourceforge.net/">Joda Time</link>.
|
|
If that is present on the classpath the use of this annotation is enabled automatically.
|
|
By default neither Spring MVC nor Web Flow register any other date formatters or converters.
|
|
Therefore it is important for applications to register a custom formatter to specify the default way for printing and parsing dates.
|
|
The <code>DateTimeFormat</code> annotation on the other hand provides more fine-grained control where it is necessary to deviate from the default.
|
|
</para>
|
|
<para>
|
|
For more information on working with Spring 3 type conversion and formatting please refer to the relevant sections of the <link xl:href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/index.html">Spring documentation</link>.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-bind">
|
|
<title>Suppressing binding</title>
|
|
<para>
|
|
Use the <code>bind</code> attribute to suppress model binding and validation for particular view events.
|
|
The following example suppresses binding when the <code>cancel</code> event occurs:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<transition on="proceed" to="reviewBooking">
|
|
<transition on="cancel" to="bookingCancelled" bind="false" />
|
|
</view-state>]]>
|
|
</programlisting>
|
|
</sect1>
|
|
<sect1 xml:id="view-binder">
|
|
<title>Specifying bindings explicitly</title>
|
|
<para>
|
|
Use the <code>binder</code> element to configure the exact set of model bindings usable by the view.
|
|
This is particularly useful in a Spring MVC environment for restricting the set of "allowed fields" per view.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<binder>
|
|
<binding property="creditCard" />
|
|
<binding property="creditCardName" />
|
|
<binding property="creditCardExpiryMonth" />
|
|
<binding property="creditCardExpiryYear" />
|
|
</binder>
|
|
<transition on="proceed" to="reviewBooking" />
|
|
<transition on="cancel" to="cancel" bind="false" />
|
|
</view-state>
|
|
]]>
|
|
</programlisting>
|
|
<para>
|
|
If the binder element is not specified, all public properties of the model are eligible for binding by the view.
|
|
With the binder element specified, only the explicitly configured bindings are allowed.
|
|
</para>
|
|
<para>
|
|
Each binding may also apply a converter to format the model property value for display in a custom manner.
|
|
If no converter is specified, the default converter for the model property's type will be used.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<binder>
|
|
<binding property="checkinDate" converter="shortDate" />
|
|
<binding property="checkoutDate" converter="shortDate" />
|
|
<binding property="creditCard" />
|
|
<binding property="creditCardName" />
|
|
<binding property="creditCardExpiryMonth" />
|
|
<binding property="creditCardExpiryYear" />
|
|
</binder>
|
|
<transition on="proceed" to="reviewBooking" />
|
|
<transition on="cancel" to="cancel" bind="false" />
|
|
</view-state>
|
|
]]>
|
|
</programlisting>
|
|
<para>
|
|
In the example above, the <code>shortDate</code> converter is bound to the
|
|
<code>checkinDate</code> and <code>checkoutDate</code> properties.
|
|
Custom converters may be registered with the application's ConversionService.
|
|
</para>
|
|
<para>
|
|
Each binding may also apply a required check that will generate a validation error
|
|
if the user provided value is null on form postback:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<binder>
|
|
<binding property="checkinDate" converter="shortDate" required="true" />
|
|
<binding property="checkoutDate" converter="shortDate" required="true" />
|
|
<binding property="creditCard" required="true" />
|
|
<binding property="creditCardName" required="true" />
|
|
<binding property="creditCardExpiryMonth" required="true" />
|
|
<binding property="creditCardExpiryYear" required="true" />
|
|
</binder>
|
|
<transition on="proceed" to="reviewBooking">
|
|
<transition on="cancel" to="bookingCancelled" bind="false" />
|
|
</view-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
In the example above, all of the bindings are required.
|
|
If one or more blank input values are bound, validation errors will be generated and the view will re-render with those errors.
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="view-validate">
|
|
<title>Validating a model</title>
|
|
<para>
|
|
Model validation is driven by constraints specified against a model object.
|
|
Web Flow supports enforcing such constraints programatically as well as
|
|
declaratively with JSR-303 Bean Validation annotations.
|
|
</para>
|
|
<sect2 xml:id="view-validation-jsr303">
|
|
<title>JSR-303 Bean Validation</title>
|
|
<para>
|
|
Web Flow provides built-in support for the JSR-303 Bean Validation API
|
|
building on equivalent support available in Spring MVC.
|
|
To enable JSR-303 validation configure the flow-builder-services with
|
|
Spring MVC's <code>LocalValidatorFactoryBean</code>:
|
|
</para>
|
|
<programlisting language="xml">
|
|
<webflow:flow-registry flow-builder-services="flowBuilderServices" />
|
|
|
|
<webflow:flow-builder-services id="flowBuilderServices" validator="validator" />
|
|
|
|
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
|
|
</programlisting>
|
|
|
|
<para>
|
|
With the above in place, the configured validator will be applied to
|
|
all model attributes after data binding.
|
|
</para>
|
|
|
|
<para>
|
|
Note that JSR-303 bean validation and validation by convention
|
|
(explained in the next section) are not mutually exclusive.
|
|
In other words Web Flow will apply all available validation
|
|
mechanisms.
|
|
</para>
|
|
|
|
<sect3 xml:id="view-validation-jsr303-partial">
|
|
<title>Partial Validation</title>
|
|
<para>
|
|
JSR-303 Bean Validation supports partial validation through validation groups. For example:
|
|
|
|
<programlisting language="java">
|
|
@NotNull
|
|
@Size(min = 2, max = 30, groups = State1.class)
|
|
private String name;
|
|
</programlisting>
|
|
In a flow definition you can specify validation hints on a view state
|
|
or on a transition and those will be resolved to validation groups.
|
|
For example:
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="state1" model="myModel" validation-hints="'group1,group2'">
|
|
]]>
|
|
</programlisting>
|
|
The <emphasis>validation-hints</emphasis> attribute is an expression
|
|
that in the above example resolves to a comma-delimited String consisting
|
|
of the hints "group1" and "group2". A <classname>ValidationHintResolver</classname>
|
|
is used to resolve these hints. The <classname>BeanValidationHintResolver</classname>
|
|
used by default tries to resolve these strings to Class-based bean validation
|
|
groups. To do that it looks for matching inner types in the model or its parent.
|
|
</para>
|
|
<para>
|
|
For example given <classname>org.example.MyModel</classname> with inner types
|
|
<classname>Group1</classname> and <classname>Group2</classname> it is
|
|
sufficient to supply the simple type names, i.e. "group1" and "group2".
|
|
You can also provide fully qualified type names.
|
|
</para>
|
|
<para>
|
|
A hint with the value "default" has a special meaning and is translated
|
|
to the default validation group in Bean Validation
|
|
<classname>javax.validation.groups.Default</classname>.
|
|
</para>
|
|
<para>
|
|
A custom <classname>ValidationHintResolver</classname>
|
|
can be configured if necessary through the validationHintResolver property
|
|
of the flow-builder-services element:
|
|
<programlisting language="xml">
|
|
<webflow:flow-registry flow-builder-services="flowBuilderServices" />
|
|
|
|
<webflow:flow-builder-services id="flowBuilderServices" validator=".." validation-hint-resolver=".." />
|
|
</programlisting>
|
|
</para>
|
|
</sect3>
|
|
|
|
</sect2>
|
|
<sect2 xml:id="view-validation-programmatic">
|
|
<title>Programmatic validation</title>
|
|
<para>
|
|
There are two ways to perform model validation programatically.
|
|
The first is to implement validation logic in your model object.
|
|
The second is to implement an external <code>Validator</code>.
|
|
Both ways provide you with a <code>ValidationContext</code> to record error messages and access information about the current user.
|
|
</para>
|
|
<sect3 xml:id="view-validation-programmatic-validate-method">
|
|
<title>Implementing a model validate method</title>
|
|
<para>
|
|
Defining validation logic in your model object is the simplest way to validate its state.
|
|
Once such logic is structured according to Web Flow conventions, Web Flow will automatically invoke that logic during the view-state postback lifecycle.
|
|
Web Flow conventions have you structure model validation logic by view-state, allowing you to easily validate the subset of model properties that are editable on that view.
|
|
To do this, simply create a public method with the name <code>validate${state}</code>, where <code>${state}</code> is the id of your view-state where you want validation to run.
|
|
For example:
|
|
<programlisting language="java"><![CDATA[
|
|
public class Booking {
|
|
private Date checkinDate;
|
|
private Date checkoutDate;
|
|
...
|
|
|
|
public void validateEnterBookingDetails(ValidationContext context) {
|
|
MessageContext messages = context.getMessageContext();
|
|
if (checkinDate.before(today())) {
|
|
messages.addMessage(new MessageBuilder().error().source("checkinDate").
|
|
defaultText("Check in date must be a future date").build());
|
|
} else if (!checkinDate.before(checkoutDate)) {
|
|
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
|
|
defaultText("Check out date must be later than check in date").build());
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
In the example above, when a transition is triggered in a <code>enterBookingDetails</code> view-state that is editing a <code>Booking</code> model,
|
|
Web Flow will invoke the <code>validateEnterBookingDetails(ValidationContext)</code> method automatically unless validation has been suppressed for that transition.
|
|
An example of such a view-state is shown below:
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="enterBookingDetails" model="booking">
|
|
<transition on="proceed" to="reviewBooking">
|
|
</view-state>]]>
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Any number of validation methods are defined. Generally, a flow edits a model over a series of views. In that case, a validate method would be defined
|
|
for each view-state where validation needs to run.
|
|
</para>
|
|
</sect3>
|
|
<sect3 xml:id="view-validation-programmatic-validator">
|
|
<title>Implementing a Validator</title>
|
|
<para>
|
|
The second way is to define a separate object, called a <emphasis>Validator</emphasis>, which validates your model object.
|
|
To do this, first create a class whose name has the pattern ${model}Validator, where <code>${model}</code> is the capitialized form of the model expression, such as <code>booking</code>.
|
|
Then define a public method with the name <code>validate${state}</code>, where <code>${state}</code> is the id of your view-state, such as <code>enterBookingDetails</code>.
|
|
The class should then be deployed as a Spring bean. Any number of validation methods can be defined.
|
|
For example:
|
|
</para>
|
|
<programlisting language="java"><![CDATA[
|
|
@Component
|
|
public class BookingValidator {
|
|
public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
|
|
MessageContext messages = context.getMessageContext();
|
|
if (booking.getCheckinDate().before(today())) {
|
|
messages.addMessage(new MessageBuilder().error().source("checkinDate").
|
|
defaultText("Check in date must be a future date").build());
|
|
} else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
|
|
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
|
|
defaultText("Check out date must be later than check in date").build());
|
|
}
|
|
}
|
|
}]]>
|
|
</programlisting>
|
|
<para>
|
|
In the example above, when a transition is triggered in a <code>enterBookingDetails</code> view-state that is editing a <code>Booking</code> model,
|
|
Web Flow will invoke the <code>validateEnterBookingDetails(Booking, ValidationContext)</code> method automatically unless validation has been suppressed for that transition.
|
|
</para>
|
|
<para>
|
|
A Validator can also accept a Spring MVC <code>Errors</code> object, which is required for invoking existing Spring Validators.
|
|
</para>
|
|
<para>
|
|
Validators must be registered as Spring beans employing the naming convention <code>${model}Validator</code> to be detected and invoked automatically.
|
|
In the example above, Spring 2.5 classpath-scanning would detect the <code>@Component</code> and automatically register it as a bean with the name <code>bookingValidator</code>.
|
|
Then, anytime the <code>booking</code> model needs to be validated, this <code>bookingValidator</code> instance would be invoked for you.
|
|
</para>
|
|
</sect3>
|
|
<sect3 xml:id="default-validate-method">
|
|
<title>Default validate method</title>
|
|
<para>
|
|
A <emphasis>Validator</emphasis> class can also define a method called <code>validate</code> not associated (by convention) with any specific view-state.
|
|
</para>
|
|
<programlisting language="java"><![CDATA[
|
|
@Component
|
|
public class BookingValidator {
|
|
public void validate(Booking booking, ValidationContext context) {
|
|
//...
|
|
}
|
|
}]]>
|
|
</programlisting>
|
|
<para>
|
|
In the above code sample the method <code>validate</code> will be called every time a Model of type <code>Booking</code> is validated (unless validation has been suppressed for that transition).
|
|
If needed the default method can also be called in addition to an existing state-specific method. Consider the following example:
|
|
</para>
|
|
<programlisting language="java"><![CDATA[
|
|
@Component
|
|
public class BookingValidator {
|
|
public void validate(Booking booking, ValidationContext context) {
|
|
//...
|
|
}
|
|
public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
|
|
//...
|
|
}
|
|
}]]>
|
|
</programlisting>
|
|
<para>
|
|
In above code sample the method <code>validateEnterBookingDetails</code> will be called first.
|
|
The default <code>validate</code> method will be called next.
|
|
</para>
|
|
</sect3>
|
|
</sect2>
|
|
<sect2 xml:id="view-validation-context">
|
|
<title>ValidationContext</title>
|
|
<para>
|
|
A ValidationContext allows you to obtain a <code>MessageContext</code> to record messages during validation.
|
|
It also exposes information about the current user, such as the signaled <code>userEvent</code> and the current user's <code>Principal</code> identity.
|
|
This information can be used to customize validation logic based on what button or link was activated in the UI, or who is authenticated.
|
|
See the API Javadocs for <code>ValidationContext</code> for more information.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-validation-suppression">
|
|
<title>Suppressing validation</title>
|
|
<para>
|
|
Use the <code>validate</code> attribute to suppress model validation for particular view events:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="chooseAmenities" model="booking">
|
|
<transition on="proceed" to="reviewBooking">
|
|
<transition on="back" to="enterBookingDetails" validate="false" />
|
|
</view-state>]]>
|
|
</programlisting>
|
|
<para>
|
|
In this example, data binding will still occur on <code>back</code> but validation will be suppressed.
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="view-transitions">
|
|
<title>Executing view transitions</title>
|
|
<para>
|
|
Define one or more <code>transition</code> elements to handle user events that may occur on the view.
|
|
A transition may take the user to another view, or it may simply execute an action and re-render the current view.
|
|
A transition may also request the rendering of parts of a view called "fragments" when handling an Ajax event.
|
|
Finally, "global" transitions that are shared across all views may also be defined.
|
|
</para>
|
|
<para>
|
|
Implementing view transitions is illustrated in the following sections.
|
|
</para>
|
|
<sect2 xml:id="transition-actions">
|
|
<title>Transition actions</title>
|
|
<para>
|
|
A view-state transition can execute one or more actions before executing.
|
|
These actions may return an error result to prevent the transition from exiting the current view-state.
|
|
If an error result occurs, the view will re-render and should display an appropriate message to the user.
|
|
</para>
|
|
<para>
|
|
If the transition action invokes a plain Java method, the invoked method may return false to prevent the transition from executing.
|
|
This technique can be used to handle exceptions thrown by service-layer methods.
|
|
The example below invokes an action that calls a service and handles an exceptional situation:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="submit" to="bookingConfirmed">
|
|
<evaluate expression="bookingAction.makeBooking(booking, messageContext)" />
|
|
</transition>]]>
|
|
</programlisting>
|
|
<programlisting language="java"><![CDATA[
|
|
public class BookingAction {
|
|
public boolean makeBooking(Booking booking, MessageContext context) {
|
|
try {
|
|
bookingService.make(booking);
|
|
return true;
|
|
} catch (RoomNotAvailableException e) {
|
|
context.addMessage(new MessageBuilder().error().
|
|
.defaultText("No room is available at this hotel").build());
|
|
return false;
|
|
}
|
|
}
|
|
}]]>
|
|
</programlisting>
|
|
<note>
|
|
<para>
|
|
When there is more than one action defined on a transition, if one returns an error result the remaining actions in the set will <emphasis>not</emphasis> be executed.
|
|
If you need to ensure one transition action's result cannot impact the execution of another, define a single transition action that invokes a method that encapsulates all the action logic.
|
|
</para>
|
|
</note>
|
|
</sect2>
|
|
<sect2 xml:id="event-handlers-global">
|
|
<title>Global transitions</title>
|
|
<para>
|
|
Use the flow's <code>global-transitions</code> element to create transitions that apply across all views.
|
|
Global-transitions are often used to handle global menu links that are part of the layout.
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<global-transitions>
|
|
<transition on="login" to="login" />
|
|
<transition on="logout" to="logout" />
|
|
</global-transitions>]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="simple-event-handlers">
|
|
<title>Event handlers</title>
|
|
<para>
|
|
From a view-state, transitions without targets can also be defined. Such transitions are called "event handlers":
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="event">
|
|
<!-- Handle event -->
|
|
</transition>]]>
|
|
</programlisting>
|
|
<para>
|
|
These event handlers do not change the state of the flow.
|
|
They simply execute their actions and re-render the current view or one or more fragments of the current view.
|
|
</para>
|
|
</sect2>
|
|
<sect2 xml:id="event-handlers-render">
|
|
<title>Rendering fragments</title>
|
|
<para>
|
|
Use the <code>render</code> element within a transition to request partial re-rendering of the current view after handling the event:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="next">
|
|
<evaluate expression="searchCriteria.nextPage()" />
|
|
<render fragments="searchResultsFragment" />
|
|
</transition>]]>
|
|
</programlisting>
|
|
<para>
|
|
The fragments attribute should reference the id(s) of the view element(s) you wish to re-render.
|
|
Specify multiple elements to re-render by separating them with a comma delimiter.
|
|
</para>
|
|
<para>
|
|
Such partial rendering is often used with events signaled by Ajax to update a specific zone of the view.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-messages">
|
|
<title>Working with messages</title>
|
|
<para>
|
|
Spring Web Flow's <code>MessageContext</code> is an API for recording messages during the course of flow executions.
|
|
Plain text messages can be added to the context, as well as internationalized messages resolved by a Spring <code>MessageSource</code>.
|
|
Messages are renderable by views and automatically survive flow execution redirects.
|
|
Three distinct message severities are provided: <code>info</code>, <code>warning</code>, and <code>error</code>.
|
|
In addition, a convenient <code>MessageBuilder</code> exists for fluently constructing messages.
|
|
</para>
|
|
<sect2 xml:id="plain-text-message">
|
|
<title>Adding plain text messages</title>
|
|
<programlisting language="java"><![CDATA[
|
|
MessageContext context = ...
|
|
MessageBuilder builder = new MessageBuilder();
|
|
context.addMessage(builder.error().source("checkinDate")
|
|
.defaultText("Check in date must be a future date").build());
|
|
context.addMessage(builder.warn().source("smoking")
|
|
.defaultText("Smoking is bad for your health").build());
|
|
context.addMessage(builder.info()
|
|
.defaultText("We have processed your reservation - thank you and enjoy your stay").build());]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="plain-text-message-intl">
|
|
<title>Adding internationalized messages</title>
|
|
<programlisting language="java"><![CDATA[
|
|
MessageContext context = ...
|
|
MessageBuilder builder = new MessageBuilder();
|
|
context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build());
|
|
context.addMessage(builder.warn().source("smoking").code("notHealthy")
|
|
.resolvableArg("smoking").build());
|
|
context.addMessage(builder.info().code("reservationConfirmation").build());]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="message-bundles">
|
|
<title>Using message bundles</title>
|
|
<para>
|
|
Internationalized messages are defined in message bundles accessed by a Spring <code>MessageSource</code>.
|
|
To create a flow-specific message bundle, simply define <code>messages.properties</code> file(s) in your flow's directory.
|
|
Create a default <code>messages.properties</code> file and a .properties file for each additional <code>Locale</code> you need to support.
|
|
</para>
|
|
<programlisting><![CDATA[
|
|
#messages.properties
|
|
checkinDate=Check in date must be a future date
|
|
notHealthy={0} is bad for your health
|
|
reservationConfirmation=We have processed your reservation - thank you and enjoy your stay]]>
|
|
</programlisting>
|
|
<para>
|
|
From within a view or a flow, you may also access message resources using the <code>resourceBundle</code> EL variable:
|
|
</para>
|
|
<programlisting><![CDATA[
|
|
<h:outputText value="#{resourceBundle.reservationConfirmation}" />]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="message-generation">
|
|
<title>Understanding system generated messages</title>
|
|
<para>
|
|
There are several places where Web Flow itself will generate messages to display to the user.
|
|
One important place this occurs is during view-to-model data binding.
|
|
When a binding error occurs, such as a type conversion error, Web Flow will map that error to a message retrieved from your resource bundle automatically.
|
|
To lookup the message to display, Web Flow tries resource keys that contain the binding error code and target property name.
|
|
</para>
|
|
<para>
|
|
As an example, consider a binding to a <code>checkinDate</code> property of a <code>Booking</code> object.
|
|
Suppose the user typed in a alphabetic string.
|
|
In this case, a type conversion error will be raised.
|
|
Web Flow will map the 'typeMismatch' error code to a message by first querying your resource bundle for a message with the following key:
|
|
</para>
|
|
<programlisting>
|
|
booking.checkinDate.typeMismatch
|
|
</programlisting>
|
|
<para>
|
|
The first part of the key is the model class's short name.
|
|
The second part of the key is the property name. The third part is the error code.
|
|
This allows for the lookup of a unique message to display to the user when a binding fails on a model property.
|
|
Such a message might say:
|
|
</para>
|
|
<programlisting>
|
|
booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.
|
|
</programlisting>
|
|
<para>
|
|
If no such resource key can be found of that form, a more generic key will be tried.
|
|
This key is simply the error code. The field name of the property is provided as a message argument.
|
|
</para>
|
|
<programlisting>
|
|
typeMismatch=The {0} field is of the wrong type.
|
|
</programlisting>
|
|
</sect2>
|
|
</sect1>
|
|
<sect1 xml:id="view-popup">
|
|
<title>Displaying popups</title>
|
|
<para>
|
|
Use the <code>popup</code> attribute to render a view in a modal popup dialog:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">]]>
|
|
</programlisting>
|
|
<para>
|
|
When using Web Flow with the Spring Javascript, no client side code is necessary for the popup to display.
|
|
Web Flow will send a response to the client requesting a redirect to the view from a popup, and the client will honor the request.
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="view-backtracking">
|
|
<title>View backtracking</title>
|
|
<para>
|
|
By default, when you exit a view state and transition to a new view state, you can go back to the previous state using the browser back button.
|
|
These view state history policies are configurable on a per-transition basis by using the <code>history</code> attribute.
|
|
</para>
|
|
<sect2 xml:id="history-discard">
|
|
<title>Discarding history</title>
|
|
<para>
|
|
Set the history attribute to <code>discard</code> to prevent backtracking to a view:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="cancel" to="bookingCancelled" history="discard">]]>
|
|
</programlisting>
|
|
</sect2>
|
|
<sect2 xml:id="history-invalidate">
|
|
<title>Invalidating history</title>
|
|
<para>
|
|
Set the history attribute to <code>invalidate</code> to prevent backtracking to a view as well all previously displayed views:
|
|
</para>
|
|
<programlisting language="xml"><![CDATA[
|
|
<transition on="confirm" to="bookingConfirmed" history="invalidate">]]>
|
|
</programlisting>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter>
|