Rendering Views Introduction This chapter shows you how to use the view-state element to render views within a flow. Defining view states Use the view-state element to define a step of the flow that renders a view and waits for a user event to resume: <view-state id="enterBookingDetails" > <transition on="submit" to="reviewBooking" /> </view-state> 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 /WEB-INF/hotels/booking/enterBookingDetails.xhtml if the flow itself was located in the /WEB-INF/hotels/booking directory. Below is a sample directory structure showing views and other resources like message bundles co-located with their flow definition: Flow Packaging Specifying view identifiers Use the view attribute to explictly specify the id of the view to render. Flow relative view ids The view id may be a relative path to view resource in the flow's working directory: <view-state id="enterBookingDetails" view="bookingDetails.xhtml"> Absolute view ids The view id may be a absolute path to a view resource in the webapp root directory: <view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml"> Logical view ids With some view frameworks, such as Spring MVC's view framework, the view id may also be a logical identifier resolved by the framework: <view-state id="enterBookingDetails" view="bookingDetails"> See the Spring MVC integration section for more information on how to integrate with the MVC ViewResolver infrastructure. View scope A view-state allocates a new viewScope 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. Allocating view variables Use the var tag to declare a view variable. Like a flow variable, any @Autowired references are automatically restored when the view state resumes. <var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" /> Assigning a viewScope variable Use the on-render tag to assign a variable from an action result before the view renders: <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> Manipulating objects in view scope 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. <view-state id="searchResults"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="searchResultsTFragment" /> </transition> <transition on="previous"> <evaluate expression="searchCriteria.previousPage()" /> <render fragments="searchResultsFragment" /> </transition> </view-state> Executing render actions Use the on-render 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. <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> Binding to a model Use the model attribute to declare a model object the view binds to. This attribute is typically used with views that render data controls, such as forms. The following example declares the enterBookingDetails state manipulates the booking model: <view-state id="enterBookingDetails" model="booking"> The model may be in any accessible scope, such as flowScope or viewScope. Specifying a model triggers the following behavior when a view event occurs: View-to-model binding. On view postback, form values are bound to model object properties for you. Model validation. After binding, if the model object requires validation, that validation logic will be invoked. 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. The exact model binding and validation semantics are a function of the view technology in use. See the Spring MVC and Faces section for more information on MVC and JSF semantics, respectively. Regardless of the view technology used, your flow should not change. Suppressing binding Use the bind attribute to suppress model binding and validation for particular view events. <view-state id="enterBookingDetails"> <transition on="proceed" to="reviewBooking"> <transition on="cancel" to="bookingCancelled" bind="false" /> </view-state> Validating a model Model validation is driven by constraints specified against the model object. These constraints may be specified declaratively, or enforced using a programmatic validation routine or external Validator. Programmatic validation There are two ways to perform model validation programatically. Implementing a model validate method The first way is to define a validate method on the model object class. To do this, create a public method with the name validate<state>, where state is the id of the view-state. The method must declare a MessageContext parameter for recording validation error messages. For example: public void validateEnterBookingDetails(MessageContext context) { Calendar calendar = Calendar.getInstance(); if (checkinDate.before(today())) { context.addMessage(new MessageBuilder().error().source("checkinDate").defaultText( "Check in date must be a future date").build()); } else if (!checkinDate.before(checkoutDate)) { context.addMessage(new MessageBuilder().error().source("checkoutDate").defaultText( "Check out date must be later than check in date").build()); } } Implementing a Validator The second way is to define a separate object, called a Validator, which validates your model object. To do this, create a class that defines a public method with the name validate<state>, where state is the id of the view-state. The method must declare a Object parameter to accept your model object, and a MessageContext parameter for recording validation error messages. For example: public class BookingValidator { public void validateEnterBookingDetails(Object object, MessageContext context) { Booking booking = (Booking) object; if (booking.getCheckinDate().before(today())) { context.addMessage(new MessageBuilder().error().source("checkinDate").defaultText( "Check in date must be a future date").build()); } else if (!booking.getCheckinDate().before(checkoutDate)) { context.addMessage(new MessageBuilder().error().source("checkoutDate").defaultText( "Check out date must be later than check in date").build()); } } } A Validator can also accept a Spring MVC Errors object, which is required for invoking existing Spring Validators. Handling events From a view-state, transitions without targets can also be defined. Such transitions are called "event handlers": <transition on="event"> <-- Handle event --> </transition> 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. Rendering partials Use the render element to request partial re-rendering of a view after handling an event: <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="searchResultsFragment" /> </transition> 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. Such partial rendering is often used with events signaled by Ajax to update a specific zone of the view. Working with messages Spring Web Flow's MessageContext 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 MessageSource. Messages are renderable by views and automatically survive flow execution redirects. Three distinct message severities are provided: info, warning, and error. In addition, a convenient MessageBuilder exists for fluently constructing messages. Adding plain text messages 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()); Adding internationalized messages 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()); Using message bundles Internationalized messages are defined in message bundles accessed by a Spring MessageSource. To create a flow-specific message bundle, simply define messages.properties file(s) in your flow's directory. Create a default messages.properties file and a .properties file for each additional Locale you need to support. #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 Displaying popups Use the popup attribute to render a view in a modal popup dialog: <view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true"> 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. View backtracking 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 view-state basis by using the history attribute. Discarding History Set the history attribute to discard to prevent backtracking to a view: <view-state id="changeSearchCriteria" history="discard"> Invalidating History Set the history attribute to invalidate to prevent backtracking to a view as well all previously displayed views: <view-state id="changeSearchCriteria" history="invalidate">