Learning JSF 2 series continues with page parameters and page actions. Read other Learning JSF 2 articles.
If you worked with JSF 1.2, you know that calling a page action wasn’t simple. A page action is useful when you request a page for the first time (GET request) and would like to prepare some data to be rendered on a view (page). Of course there are some workarounds, one of them is to use @PostConstruct method inside the bean or even create a phase listener. Both could work but developers desired a more robust, and out of the box solution similar to what Seam 2 provides.
A feature that closely relates to page action is page params (page parameters). Usually when you issue a get request like this:
http://host/app/page.jsf?productId=101
you want the productId request parameter to be assigned to a property inside a bean so that you can use the id inside action (page action) to load data to display on the page. In JSF 1.2, you would end up doing something like this:
Managed bean:
@ManagedBean(name="bean") public class Bean { private Integer productId; // getter and setter }
JSF configuration file:
<managed-bean> <managed-bean-name>bean</managed-bean-name> <managed-bean-class>test.Bean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>productId</property-name> <value>#{param['productId']}</value> </managed-property> </managed-bean>
#{param} is an implicit object in JSF that holds all request parameters. In the code snippet above, we are taking the value of productId and assigning it to productId property inside the bean using JSF managed bean facility. It’s just not an elegant solution and it doesn’t offer any flexibility, ability to validate the value, and also defined separately from the actual page.
The good news is that both page actions and page params are supported by JSF 2. Let’s start with page params.
Page params
We all know that when we have a JSF input field bound to a bean property and we submit the page ( POST), the value entered in the page automatically get pushed into the bean (assuming no conversion/validation errors or anything else stopping the life cycle).
JSF page:
<h:inputText value="#{bean.productId}" />
Bean:
public class Bean { private Integer productId; // getter and setter }
The easiest way to think of page params is that the same thing is happening as above but only without input fields on the page. Instead, the values are submitted via the GET URL and pushed in the same way into the bean:
http://host/app/page.jsf?productId=101
In both cases the result is the same, the value either from the page or URL is set into the property. We know how to do it with input field bound to a property during a POST. Let’s see what needs to be done when a GET request is issued. We have to somehow map the request param to bean properties.
JSF 2 introduced a new metadata tag called
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head></h:head> <f:metadata> <f:viewParam name="productId" value="#{bean.productId}"/> <f:viewParam name="text" value="#{bean.text}"/> </f:metadata> <body> ... </body> </html>
In the code example above
http://localhost:port/app/page.jsf?productId=111&text=nice
A quick and easy way to test that values were set into the proprieties is just to render the values on the page:
<h:outputText value="ProductId: #{bean.productId}" /> <h:outputText value="Text: #{bean.text}" />
Default values
If bean properties are set to some initial values page params will overwrite them (same as with a POST). If page params are not provided then during rendering the default values will be displayed.
Good to know
The
<f:view> <f:facet name="javax_faces_metadata" ></f:facet> </f:view>
As this metadata tag going to be used extensively, a shortcut tag (alias) was created:
<f:view> <f:metadata></f:metadata> </f:view>
The
Conversation and validation
When a value is submitted via a POST it can bean converted and validated before being pushed into the bean property. For a GET request, conversation and validation can be invoked in a similar fashion. When either fails, error message can be displayed just like with a POST.
When components are bound to a bean property, a converted or validator is registered with the component on the page. As we don’t have a component, conversation and validation is added to the
Requiring a value
To make a value required, we use the familiar required attribute and overwrite the default error message:
<f:metadata> <f:viewParam name="productId" value="#{bean.productId}" required="true" requiredMessage="Product id is required"/> <f:viewParam name="text" value="#{bean.text}" required="true" requiredMessage="Text is required"/> </f:metadata>
To display the error message,
<h:messages /> <h:outputText value="ProductId: #{bean.productId}" /> <h:outputText value="Text: #{bean.text}" />
Note: we have to use
For example, the following request:
http://localhost:port/app/page.jsf?productId=111&text=
will produce the following:
As we didn’t provide a value for text, validation fails. productId is also not set as Update Model phase was not executed (as expected after validation fails).
Good to know
When a page (view) is requested via a GET and no page params are present, Restore View and Render Respose phases will be executed (same behavior you get in JSF 1.2). Once page params are added to a GET request, JSF will run through the full life cycle in order to convert and validate the values and then set the values into the bean.
More validation
In addition to requiring a value, we can attach other standard validators to page params such as range validator for numbers and length validator for strings. For example:
<f:metadata> <f:viewParam name="productId" value="#{bean.productId}" required="true" requiredMessage="Product id is required" validatorMessage="Product id must be between 0 and 1000"> <f:validateLongRange minimum="0" maximum="1000"/> </f:viewParam> <f:viewParam name="text" value="#{bean.text}" required="true" requiredMessage="Text is required" validatorMessage="Text must be at least 3 chars long"> <f:validateLength minimum="3"/> </f:viewParam> </f:metadata>
In this example,
For example the request with the parameters which not meets the requirements specified will produce the following:
Lastly, a custom validator can be attached as well via validator attribute (in which case the method is validation method is placed inside the bean):
<f:viewParam name="text" value="#{bean.text}" validator="#{bean.validateProduct}"/>
or with
<f:viewParam name="text" value="#{bean.text}"> <f:validator validator="produdIdValidator"/> </f:viewParam>
Conversation
As with validation, conversation is added in similar fashion. When we do a POST, all values are first converted and then validated. All values entered on a page are sent as strings and need to be converted before pushed into bean properties. Page param values are also sent as plain strings. Taking the productId param, a converter could be added like this:
<f:viewParam name="productId" value="#{bean.productId}" required="true" requiredMessage="Product id is required" validatorMessage="Product id must be between 0 and 1000" converter="javax.faces.Integer" converterMessage="Product id must be an integer"> <f:validateLongRange minimum="0" maximum="1000"/> </f:viewParam>
This is just to demonstrate how it’s done but we don’t actually have to register the integer converter. Integer converter is one of the default converters in JSF which means that JSF will try to convert the value automatically. The converter attribute could be used to attached a custom converter as well.
Page actions
Page actions can be used to initialize data to be rendered in the view. Page actions are added with new the
<f:metadata> <f:viewParam../> <f:viewParam.../> <f:event type="preRenderView" listener="#{bean.loadAction}" /> </f:metadata>
Two things are specified above, the event type (preRenderView) and the action listener to be invoked. The preRenderView event type indicates that this listener will be invoked during Render Respone phase.
Just for demonstration purposes, let’s use the productId value as a counter for the number of times to insert text value into a list and then display it.
Bean:
public ArrayList<String> list; // getter public void loadAction() { list = new ArrayList<String>(); for (int i = 0; i < productId ; i++) { list.add(text); } }
For this to work, page params need to be set first and that’s exactly what happens. Page params are set into the model during Update Model phase and page action is invoked during Render Response phase (just before the rendering). We use a page param (productId) to decide how many times to insert the text value into the list. To render the list, we use this:
<ol> <ui:repeat value="#{bean.list}" var="txt"> <li> #{txt} </li> </ui:repeat> </ol>
Entering the following URL:
http://host:port/app/page.jsf?productId=5&text=JSF2
will produce the following:
Note that if conversation or validation fails, the listener (page action) is still invoked. However, an exception will be thrown as productId is null (unless it’s initialized to some value). One option is to check if any error messages were queued and skip the listener logic. For example:
public void loadAction() { List<FacesMessage> messages = FacesContext.getCurrentInstance() .getMessageList(); if (messages.size() == 0) { list = new ArrayList<String>(); for (int i = 0; i < productId ; i++) { list.add(text); } } }
Note: if only page action is present without page params, then only Restore View and Render Response phase are executed.
Summary
Page params and page actions are two great features in JSF 2. Being able to issue a GET with request params also allows to create bookmarkable URLs. Bookmarkable URLs and how page params are tied to navigation as well as the new