Upload
luigi-bear-arauz
View
30
Download
0
Embed Size (px)
Citation preview
JBoss RichFaces with SpringThis article is going to show you how to build a RichFaces application with Spring. We are going to replace the standard JSF managed beans with Spring beans. We will first use an XML-based configuration approach and then switch to an annotations approach in Spring.
(Note - The Project Template for this article can be downloaded here)If you want first to try RichFaces with standard JSF managed beans, no problem. Start with this article.
To make it possible to create the application in just under an hour, we are not going to persist any data into a database. To keep things simple, we will keep the data in memory. Of course it's not very difficult to add a persistence layer to this application. Spring provides many tools to do that.Finally an article on how to use RichFaces with Spring Web Flow, is something that I'm saving for a future article.
What Are We Going to Build?A pretty common request that I hear is how do you build a wizard in RichFaces. So, that's exactly what we are going to build. You come into a bar and on each table there is a screen via which you place an order. You click to start an order, a wizard is launched where you enter all the required information and place the order. We will also have the ability to view all placed orders. Additionally, we will be able to change the look and feel of the ordering screen using RichFaces’ skinnability feature.
Note: All screen shots show a custom skin which you are going to create later.
JBoss RichFacesJBoss RichFaces is a framework that consists of three main parts: AJAX (rich) enabled JSF components, skinnability, and CDK (Component Development Kit). RichFaces UI components are divided into two tag libraries a4j: and rich:. Both tag libraries offer out-of-the-box AJAX enabled JSF components. Skinnability enables you to skin a JSF application with default or custom skins (themes). Finally, the CDK is a facility for creating, generating and testing rich JSF components.
RichFaces is not a JSF implementation. RichFaces UI components are just extra AJAX JSF components that work with any JSF implementation (1.1, 1.2, 2.0), any view technology (Facelets, JSP), and are integrated with 3rd party components (MyFaces, Tomahawk, Trinidad, etc).
Installing RichFaces
Installing RichFaces is very easy.
Download
Download the latest RichFaces version from http://www.jboss.org/jbossrichfaces/ .
Add three RichFaces JAR files to your application WEB-INF/lib directory:
richfaces-api-X.X.X.jar,
richfaces-impl-X.X.X.jar,
richfaces-ui-X.X.X.jar
RichFaces also depends on the following libraries: commons-beanutils.jar, commons-
collections.jar, commons-digester.jar, commons-logging.jar
As of the writing of this article, the latest RichFaces version is 3.3.0 .
Note: to use Hibernate Validation, additional JAR files are needed. The JAR files are
included in the project.
RichFaces Filter
Register the RichFaces filter in a web.xml file:view sourceprint?
01.<filter>02. <display-name>Ajax4jsf Filter</display-name>03. <filter-name>richfaces</filter-name>04. <filter-class>org.ajax4jsf.Filter</filter-class>05.</filter>06.<filter-mapping>07. <filter-name>richfaces</filter-name>08. <servlet-name>Faces Servlet</servlet-name>09. <dispatcher>REQUEST</dispatcher>10. <dispatcher>FORWARD</dispatcher>11. <dispatcher>INCLUDE</dispatcher>12.</filter-mapping>
Skinning
Optionally, to use one of the existing skins, add the following:view sourceprint?
1.<context-param>2. <param-name>org.richfaces.SKIN</param-name>3. <param-value>laguna</param-value>4.</context-param>
Note: new skins such as laguna, glassX and darkX are available in their own separate
JAR files.
Page Setup
If you are using Facelets, then add this to your page:
view sourceprint?
1.xmlns:a4j="http://richfaces.org/a4j"2.xmlns:rich="http://richfaces.org/rich"
Just in case you are still stuck with JSPs, you would then add this to your pages:
view sourceprint?
1.<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>2.<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
A little bit further in this article, I have created a ready-to-use Eclipse project with
RichFaces and Spring already configured. We are going to import the project into
Eclipse and then start development. In the “real” world, you would probably use Maven
2, but, to keep things simple and concentrate on RichFaces and Spring, we are going
to use a ready-made project.
SpringNot to repeat what has been said about Spring in numerous great articles and other
resources, I will not list every Spring feature. You can find numerous resources on the
Internet. Simply stated, Spring is a framework that greatly simplifies enterprise Java
development.
The part that's of interest to us here is Spring beans and injection of control (there's a great article on it here ). Spring is often a great candidate for a middle tier in a Web
application. While JSF offers us managed beans and basic dependency injection,
Spring's dependency injection is much more powerful. Why use two containers (JSF
and Spring) to manage beans when you can use just one?
As I mentioned at the beginning, I will show you two ways to configure Spring beans.
One is XML-based and the other is annotation-based. Even the XML-based
configuration significantly reduces the amount of XML you have to write compared to
the XML configuration of JSF managed beans
Installing Spring
For the purpose of this example, Spring is installed and configured as follows.
The following JAR files are added to the application:
spring-beans.jar
spring-context.jar
spring-core.jar
spring-web.jar
slf4-api-1.5.5.jar
slf4-simple-1.5.5.jar
Registering Spring in web.xml
Add the following to web.xml file:view sourceprint?
01.<context-param>02. <param-name>contextConfigLocation</param-name>03. <param-value>/WEB-INF/spring-beans.xml</param-value>04.</context-param>05.<listener>06. <listener class> org.springframework.web.context.ContextLoaderListener07. </listener-class>08.</listener>09.<listener>10. <listener-class>11. org.springframework.web.context.request.RequestContextListener12. </listener-class>13.</listener>
The contextConfigLocation parameter specifies a Spring beans configuration file
where Spring beans are going to be defined and managed. It looks (when almost
empty) like this:
view sourceprint?
01.<?xml version="1.0" encoding="UTF-8"?>02.<beans xmlns="http://www.springframework.org/schema/beans"03.xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"04.xsi:schemaLocation="http://www.springframework.org/schema/beans05.http://www.springframework.org/schema/beans/spring-beans.xsd06.http://www.springframework.org/schema/context07.http://www.springframework.org/schema/context/spring-context-2.5.xsd">08. 09.<context:annotation-config />10.</beans>
We will be adding more stuff as we go. For now we only have <context:annotation-
config>. This tag enables support for Java EE 5 annotations such as @PostConstruct,
@PreDestory, and @Resource.
JSF Configuration File
As we are going to use Spring beans, we want Spring to resolve the beans and thus
need to register Spring's EL resolver in a JSF configuration file:view sourceprint?
1.<application>2. <el-resolver> org.springframework.web.jsf.el.SpringBeanFacesELResolver </el-res olver>3.</application>
That's all the configuration you need to use RichFaces and Spring.
Getting the ProjectI have used JBoss Tools to build the project. You can of course use any tool of your
choice. Below I show steps for how to import the project as a JSF project into JBoss
Tools. You can also import the project as an existing Eclipse project. It's up to you. If
you need to install JBoss Tools, here is a [link]
1. Download this project
2. Import the project into Eclipse
1. File/Import/Other
2. Select JSF Project
3. Point to web.xml file and follow the wizard steps.
Before we continue, let's make sure that the project was deployed correctly and that
we can run it. Start the server by right-clicking Tomcat in the Servers view and
selecting Run.
Right-click the project and select Run As\Run On Server. You should get this welcome
page.
Assembling the ApplicationCreating the Model
Let's start by creating the model for an order. In a real application, this object would
usually be an entity – an object that is saved and retrieved from a database.
The class is called Order and looks like this (note the package):view sourceprint ?
01.package bar.model;02. 03.public class Order {04. 05.private String name;06.private String email;07.private String drink;08.private String comments;09. 10.public Order(String name, String email, String drink, String comments) {11. super();12. this.name = name;13. this.email = email;14. this.drink = drink;15. this.comments = comments;16. 17. // getter and setters for each property18.}
Don't forget to generate getters and setters for each property. There is nothing
interesting about this object.
Creating the Order Service
Next we are going to create a service class that knows how to add a new order. You
can place other methods into this service, such as finding and deleting orders.
The OrderService class looks like this:view sourceprint ?
01.package bar.service;02. 03.import java.util.ArrayList;04.import java.util.List;05.import javax.annotation.PostConstruct;06.import bar.model.Order;07. 08.public class OrderService {09. 10. private List <Order>orders;11. 12. public List<Order> getOrders() {
13. return orders;14. }15. 16. public void addOrder (String name, String email, String drink,String comments){17. Order order = new Order(name, email, drink, comments);18. orders.add(order);19. }20. 21. @PostConstruct22. public void create (){23. orders = new ArrayList <Order> ();24. }25.}
A few things to note:
This service class will keep the list of all orders.
The addOrder method adds a new order to the list. The @PostConstruct annotation marks the create() method to be
invoked right after the OrderService class has been created. To be more technically correct, this method is called after all injections have been satisfied. While the class doesn't have dependencies, this method is still invoked and is a good place to initialize any properties. In our case, we initialize the list object.
Registering as a Spring Bean
We also need to register the Service bean with Spring. We want Spring to manage this
bean. Remember that we will first use XML to configure that. Registration looks like
this:view sourceprint ?
1.<bean id="service" class="bar.service.OrderService" scope="session" />
id is just the id or the name of this bean.
We are placing this bean in session scope because we are keeping the list of all
orders in it.
We used the @PostConstruct annotation to initialized the list property. This is a
standard Java 5 annotation which is supported by Spring.
Note: for @PostConstruct, @PreDestory and @Resource annotations to
work, the following has to be added to Spring configuration file:view sourceprint ?
1.<context:annotation-config/>
Alternatively , Spring also provides init-method attribute when registering the bean
as shown here:view sourceprint ?
1.<bean id="orderService" class="bar.service.Service" scope="session" init-method="create"/>
This attribute points to a custom initialization method to invoke after setting bean
properties.
A third option is to implement InitializingBean and DisposableBean interfaces:
[http://static.springframework.org/spring/docs/2.5.x/reference/beans.html].
Next we are going to create two view beans, one for the wizard and one for displaying
current orders.
View BeansLet's start with BarBean class:view sourceprint ?
01.package bar.view;02. 03.import java.util.List;04.import bar.service.Service;05. 06.public class BarBean {07. 08. private OrderService orderService;09. 10. @SuppressWarnings("unchecked")11. public List getOrders () {12. return orderService.getOrders();13. }14. public int getRowCount () {15. return orderService.getOrders().size();16. }17. public Service getOrderService() {18. return orderService;19. }20. public void setOrderService(Service orderService) {21. this.orderService = orderService;22. }23.}
getOrders method calls the service class and returns all the orders.
getRowCount will return the number of rows. It's used not to
render rich:dataTable component if the order is empty.
The other question is how do we get an instance of orderService into this bean.
First, let's register this bean in Spring configuration file:view sourceprint ?
1.<bean id="barBean" class="bar.view.BarBean" scope="request"/>
We still have to deal with userService and how it gets initialized inside the bean.
This is were Spring DI comes into play. We want Spring to inject userService into
the barBean, when barBean is being created. To accomplish that, we modify the
configuration to look like this:
view sourceprint ?
1.<bean id="barBean" class="bar.view.BarBean" scope="request">2. <property name="orderService" >3. <ref bean="service" />4. </property>5.</bean>
service is the name under which we registered the Service class.
We are ready to look at WizardBean class. This class is the back end for the
wizard.view sourceprint ?
01.package bar.view;02. 03.import java.util.List;04.import javax.annotation.PostConstruct;05.import javax.faces.context.FacesContext;06.import javax.faces.event.ValueChangeEvent;07.import javax.faces.model.SelectItem;08.import org.hibernate.validator.Email;09.import org.hibernate.validator.NotEmpty;10.import org.hibernate.validator.Pattern;11.import bar.service.Service;12.import bar.utils.JSFUtils;13. 14.public class WizardBean implements java.io.Serializable {15. 16.private static final long serialVersionUID = 1L;17. 18.@NotEmpty(message = "Name must not be empty")19.@Pattern(regex = ".*[^\\s].*", message = "This string contain only spaces")20.private String name;21. 22.@NotEmpty23.@Email(message = "Invalid email format")24.private String email;25. 26.private String drink;27.private String comments;28.private String drinkCategorySelected;29. 30.private Service orderService;31. 32.private String startPage;33. 34.private List<SelectItem> drinkCategory;
35.private List<SelectItem> drinkList;36. 37.// init all drinks38.private static final String [] CATEGORY = {"Brandy", "Rum", "Tequila","Whiskey", "Wine", "Beer"};39.private static final String [] BRANDY = {"XO", "VSOP", "VS"};40.private static final String [] RUM = {"Medium", "Full-bodied","Aromatic"};41.private static final String [] TEQUILA = {"Reposado", "Añejo","Blanco"};42.private static final String [] WHISKEY = {"Malt", "Grain", "Single Malt", };43.private static final String [] WINE = {"Red", "White", "Pink"};44.private static final String [] BEER = {"Ales", "Lager", "Specialty Beer", };45. 46.public void changeDrink(ValueChangeEvent event) {47.String newValue = (String) event.getNewValue();48. 49.if (newValue.equals("Brandy")) {drinkList = JSFUtils.createList(BRANDY); drink=BRANDY[0];}50.else if (newValue.equals("Rum")) {drinkList = JSFUtils.createList(RUM); drink=RUM[0];}51.else if (newValue.equals("Tequila")) {drinkList = JSFUtils.createList(TEQUILA);drink=TEQUILA[0];}52.else if (newValue.equals("Whiskey")) {drinkList = JSFUtils.createList(WHISKEY);drink=WHISKEY[0];}53.else if (newValue.equals("Wine")) {drinkList = JSFUtils.createList(WINE);drink=WINE[0];}54.else if (newValue.equals("Beer")) {drinkList = JSFUtils.createList(BEER);drink=BEER[0];}55.}56. [email protected] void create() {59.drinkCategorySelected = CATEGORY[0];60.drinkCategory = JSFUtils.createList(CATEGORY);61.drinkList = JSFUtils.createList(BRANDY);62.drink = BRANDY[0];63.}64.public void save() {65.orderService.addOrder(name, email, drink, comments);66.this.startPage = "/page1.xhtml";67.
68.FacesContext.getCurrentInstance().getExternalContext().getRequestMap()69..remove("wizardBean");70.}71. 72.}
Don't forget to generate setters and getters for each property. I have omitted them in
order to save space.
It looks like a lot more happening in this class, but in reality not that much. First, there
are propeties such as email, name, drink and comments which correspond to different
wizard screens. If you notice, they also correspond to Order model class.
email and name properties are annotated with Hibernate Validations annotations.
We will come back to them when we build the pages.
The large section preceded by // init all drinks comment is how drinks list is
created. It's not the most pretty way, but it will do for this example.
There is also JSF changeDrink value change listener. It will be invoked each time a
new drink category is selected and will load the drinks associated with that category.
The save method saves the new order. The last line in save method removes the
bean from request scope. We will come back to it shortly.
In this class as in BarBean, we have a reference to orderService. No where in
the class we instantiate the property. You probably guessed, but we are going to use
Spring's DI to inject the service into this bean. Let's register this bean in Spring
configuratin file.
view sourceprint ?
1.<bean id="wizardBean" class="bar.view.WizardBean" scope="request">2. <property name="startPage" value="/page1.xhtml"/>3. <property name="orderService" >4. <ref bean="service" />5. </property>6.</bean>
startPage property is initialized to the first page in the wizard.
orderService is injected with service bean.
One last class is the JSFUtils:view sourceprint ?
01.package bar.utils;02. 03.import java.util.ArrayList;04.import java.util.List;05. 06.import javax.faces.model.SelectItem;07. 08.public class JSFUtils {09. 10. public static List <SelectItem> createList (String [] data){11. ArrayList <SelectItem>list = new ArrayList <SelectItem>();12. for (String item : data){13. list.add (new SelectItem (item, item));14. }15. return list;16. }17.}
This only method in this class creates a list of SelectItem's. We are ready to create
the views.
Creating Views
Let's start with the main view which has a button to launch the wizard as well as
display all current orders.
start.xhtml:
view sourceprint?
01.<h:form>02. <!-- launch wizard button -->03. <rich:panel header="Welcome to RichBar">04. <a4j:commandButton value="Click to start your order"05. oncomplete="#{rich:component('wizard')}.show()" />06. </rich:panel>07. <rich:editor rendered="false" />08.</h:form>09. 10.<!-- modal panel where the wizard is running -->11.<rich:modalPanel id="wizard" width="550" height="300">12. <f:facet name="header">Drink Order</f:facet>13. <f:facet name="controls">14. <a href="#" onclick="#{rich:component('wizard')}.hide()">X</a>15. </f:facet>16. 17. <h:panelGroup id="wizardInclude">18. <a4j:include viewId="#{wizardBean.startPage}" />19. </h:panelGroup>20. 21.</rich:modalPanel>22. 23.<!-- order table -->24.<a4j:outputPanel id="orders">25. <rich:dataTable value="#{barBean.orders}" var="order"26. rendered="#{barBean.rowCount>0}" rowKeyVar="row">27. <rich:column>
28. <f:facet name="header">Order #</f:facet>29. <h:outputText value="#{row+1}" />30. </rich:column>31. <rich:column>32. <f:facet name="header">Name</f:facet>33. <h:outputText value="#{order.name}" />34. </rich:column>35. <rich:column>36. <f:facet name="header">Email</f:facet>37. <h:outputText value="#{order.email}" />38. </rich:column>39. <rich:column>40. <f:facet name="header">Drink</f:facet>41. <h:outputText value="#{order.drink}" />42. </rich:column>43. <rich:column>44. <f:facet name="header">Comments</f:facet>45. <h:outputText value="#{order.comments}" escape="false" />46. </rich:column>47. </rich:dataTable>48.</a4j:outputPanel>49.</body>
There are three main things here.
At the very top of the page, we place a button to launch the wizard.
a4j:commandButton opens the modal panel by using a built-in RichFaces JavaScript
function rich:component('id') and invoking the show() method on the modal panel.
Next we have the actual modal panel. Two facets define modal panel header and a
control to close the modal panel. To close the modal panel we use the hide()
JavaScript function on the modal panel. Next is a4j:include tag. The tag works similar
to Facelets ui:include but also allows to navigate within the included content. That's
what's going to give us wizard functionality. Remember that #{wizardBean.startPage}
is initialized to /page1.xhtml in Spring configuration file.
The last portion is a rich:dataTable control that displays all current orders. The table is
not rendered if the list is empty. We have also defined rowKeyVar attribute which holds
the current row number. We use it as order number in the display.
The next four pages are part of the wizard.
page1.xhtml
view sourceprint?
01.<h:form xmlns="http://www.w3.org/1999/xhtml"02.xmlns:ui="http://java.sun.com/jsf/facelets"03.xmlns:h="http://java.sun.com/jsf/html"04.xmlns:f="http://java.sun.com/jsf/core"05.xmlns:rich="http://richfaces.org/rich"06.xmlns:a4j="http://richfaces.org/a4j">07. 08.<a4j:keepAlive beanName="wizardBean" />09. <rich:panel>10. <h:panelGrid>11. <h:panelGroup>12. <h:outputLabel value="Email:" for="email" />13. <h:outputText value=" (We will email you your receipt!)"14.style="FONT-SIZE: small;" />15. </h:panelGroup>16. <h:panelGroup>17. <h:inputText id="email" value="#{wizardBean.email}">18. <rich:ajaxValidator event="onkeyup" />19. </h:inputText>20. <rich:message for="email" />21. </h:panelGroup>22. <a4j:commandButton value="Next" action="next" />23. </h:panelGrid>24. </rich:panel>25.</h:form>
In the first page of the wizard, we are only asking for an email so we can send the
customer the receipt via email (just like in Apple stores). Notice that the page uses
rich:ajaxValidator to validate the email field. rich:ajaxValidator works against standard
Hibernate Validation set in the bean:
view sourceprint?
1.@NotEmpty2.@Email(message = "Invalid email format")3.private String email;
This eliminates having to register a validator with the component on the page. Another
benefit is thatrich:ajaxValidator skips the Process Validations and Update Model
phases. As we are only validating the field, we can slightly increase performance by
not executing those phases. Skipping those two phases is identical to
setting bypassUpdates="true" (see RichFaces developers guide for further
information).
One last thing we need to cover is a4:keepAlive tag placed at the top of the page. If
you look atwizardBean registration in Spring configuration file, you will notice that
the scope of this bean is request. As you know, this bean is what backing up the
wizard screens. Now, as we move from wizard screen to wizard screen, the values
entered on the previous screen are somehow being remembered. They are being
remembered because on the last screen (summary.xhtml), we are shown all the
values entered. But the bean is in request scope which means that for every request a
new bean is created and if that's the case then the old value will be lost. So, how are
the values saved?
That's exactly what a4j:keepAlive tag does. It stores the bean (wizardBean) with
the UI component tree. On postback, the UI component tree is restored and also
restores the bean placing it back intorequest scope. This is basically a page scope,
as long as we are staying on this page, the bean and its properties will be available to
us on postback.
The way a4j:keepAlive works is that it adds the bean to current UI view root. The
following is important, even though we placed a4j:keepAlive in the first wizard
screen (page1.xhtml), technically, the bean will be added to main.xhtml UI view
root. That's also the reason we only had to place a4j:keepAlive on the first page
without having it on other wizard screens. Again, by placinga4j:keepAlive in the
first included page (page1.xhtml), we really added the tag to main.xhtml view.
Now, because we are always staying on the same page, the wizardBean will always
be there. This is the correct behavior as we are basically getting page scope.
However, we want to clear the wizardBean in order to launch it again and not have old
values populated. Thus, we have to programmatically end the page scope and restart
it on next wizard launch. That's exactly what the last line in save method does:view sourceprint?
1.public void save() {2. orderService.addOrder(name, email, drink, comments);3. 4. FacesContext.getCurrentInstance().getExternalContext(). getRequestMap()5.. remove("wizardBean");6.}
page2.xhtml
view sourceprint?
01.<h:form xmlns="http://www.w3.org/1999/xhtml"02. xmlns:ui="http://java.sun.com/jsf/facelets"03. xmlns:h="http://java.sun.com/jsf/html"04 xmlns:f="http://java.sun.com/jsf/core"05. xmlns:rich="http://richfaces.org/rich"06 .xmlns:a4j="http://richfaces.org/a4j">07.<rich:panel>08. <h:panelGrid>09. <h:outputLabel value="Name:" for="name" />10. <h:panelGroup>11. <h:inputText id="name" value="#{wizardBean.name}" >
12. <rich:ajaxValidator event="onkeyup"/>13. </h:inputText>14. <rich:message for="name"/>15. </h:panelGroup>16. <h:panelGroup>17. <a4j:commandButton value="Previous" action="prev" />18. <a4j:commandButton value="Next" action="next" />19. </h:panelGroup>20. </h:panelGrid>21.</rich:panel>22.</h:form>
The second page in the wizard prompts to enter the name. We again use
rich:ajaxValidator to validate the name field with Hibernate Validation. Validation is
defined as follows:
view sourceprint?
1.@NotEmpty(message = "Name must not be empty")2.@Pattern(regex = ".*[^\\s].*", message = "This string contain only spaces")3.private String name;
page3.xhtml
view sourceprint ?
01.<h:form xmlns="http://www.w3.org/1999/xhtml"02.xmlns:ui="http://java.sun.com/jsf/facelets"03.xmlns:h="http://java.sun.com/jsf/html"04.xmlns:f="http://java.sun.com/jsf/core"05.xmlns:rich="http://richfaces.org/rich"06.xmlns:a4j="http://richfaces.org/a4j"> 07.<rich:panel>08. <h:panelGrid>09. <h:panelGrid columns="2">10 .<h:outputLabel value="Category:" for="want" />11. <rich:inplaceSelect id="want" value="#{wizardBean.drinkCategorySelected}"12. valueChangeListener="#{wizardBean.changeDrink}">13. <f:selectItems value="#{wizardBean.drinkCategory}"/>14. <a4j:support event="onviewactivated" ajaxSingle="true"reRender="drink"/>15. </rich:inplaceSelect>16. 17. <h:outputLabel value="Drink:" for="drink" />
18. <rich:inplaceSelect id="drink" value="#{wizardBean.drink}" >19. <f:selectItems value="#{wizardBean.drinkList}"/>20. </rich:inplaceSelect>21. 22. </h:panelGrid>23. <h:panelGroup>24. <a4j:commandButton value="Previous" action="prev" />25. <a4j:commandButton value="Next" action="next" />26. </h:panelGroup>27. </h:panelGrid>28.</rich:panel>29.</h:form>
The third screen in the wizard uses two rich:inplaceSelect components. The first one
allows you to select a drink category. Once a category is selected, drinks that belong
to that category are loaded in the second select via AJAX. Value change
listener wizardBean.changeDrink is called and loads the drinks associated with
selected category. Using a4j:support tag we attached an onchange event to
rich:inplaceSelect UI component to be fired when value changes. When the event is
fired, an AJAX request is sent. ajaxSingle="true" means we only want to process
that UI component on the server. Finally, reRender points to the second list we are
rendering back now with a list of drinks.
page4.xhtml
view source
print ?
01.<h:form xmlns="http://www.w3.org/1999/xhtml"02.xmlns:ui="http://java.sun.com/jsf/facelets"03.xmlns:h="http://java.sun.com/jsf/html"04.xmlns:f="http://java.sun.com/jsf/core"05.xmlns:rich="http://richfaces.org/rich"06.xmlns:a4j="http://richfaces.org/a4j">07.<rich:panel>08. <h:panelGrid>09. <h:outputLabel value="Anything else?" for="comments" />10. <rich:editor id="comments" autoResize="true"11. value="#{wizardBean.comments}">12. </rich:editor>13. 14. <h:panelGroup>15. <a4j:commandButton value="Previous" action="prev" />16. <a4j:commandButton value="Next" action="next" />17. </h:panelGroup>18. </h:panelGrid>19.</rich:panel>20.</h:form>
This page uses one of the new UI components introduced in RichFaces 3.3.0 – a rich
editor. rich:editor is based on tinyMCE widget. Rich:editor can be configured in many
different ways, here we are using a very simple configuration. You can check out
another example here.
summary.xhtml
view sourceprint ?
01.<h:form xmlns="http://www.w3.org/1999/xhtml"02.xmlns:ui="http://java.sun.com/jsf/facelets"03.xmlns:h="http://java.sun.com/jsf/html"04.xmlns:f="http://java.sun.com/jsf/core"05.xmlns:rich="http://richfaces.org/rich"06.xmlns:a4j="http://richfaces.org/a4j">07. 08.<rich:panel>09. <h:panelGrid columns="2">10. <f:facet name="header">Summary</f:facet>11. 12. <h:outputText value="Name" />13. <h:outputText value="#{wizardBean.name}" />14. 15. <h:outputText value="Email:" />16. <h:outputText value="#{wizardBean.email}" />17. 18. <h:outputText value="Drink ordered:" />19. <h:outputText value="#{wizardBean.drink}" />20. 21. 22. <h:outputText value="Comments:" />23. <h:outputText value="#{wizardBean.comments}" escape="false"/>24. 25. </h:panelGrid>26. <h:panelGrid columns="2">27. <a4j:commandButton value="Wait.. Go back" action="prev" />28. <a4j:commandButton value="Place Order"
29. action="#{wizardBean.save}"30. oncomplete="#{rich:component('wizard')}.hide()"31. reRender="orders, wizard"/>32. </h:panelGrid>33.</rich:panel>34.</h:form>
The last page is just a summary page. If any corrections are needed, it's possible to
click the back button. The Place Order button is bound to a save method and once the
action is completed we close the modal panel and re render the table – to update the
orders.
Creating Navigation
We won't be able to navigation inside the wizard without defining navigation rules.
Navigation rules are defined in JSF configuration file and look like this:
view sourceprint ?
01.<navigation-rule>02. <from-view-id>/page1.xhtml</from-view-id>03. <navigation-case>04. <from-outcome>next</from-outcome>05. <to-view-id>/page2.xhtml</to-view-id>06. </navigation-case>07.</navigation-rule>
08.<navigation-rule>09. <from-view-id>/page2.xhtml</from-view-id>10. <navigation-case>11. <from-outcome>next</from-outcome>12. <to-view-id>/page3.xhtml</to-view-id>13. </navigation-case>14. <navigation-case>15. <from-outcome>prev</from-outcome>16. <to-view-id>/page1.xhtml</to-view-id>17. </navigation-case>18.</navigation-rule>
19.<navigation-rule>20. <from-view-id>/page3.xhtml</from-view-id>
21. <navigation-case>22. <from-outcome>next</from-outcome>23. <to-view-id>/page4.xhtml</to-view-id>24. </navigation-case>25. <navigation-case>26. <from-outcome>prev</from-outcome>27. <to-view-id>/page2.xhtml</to-view-id>28. </navigation-case>29.</navigation-rule>
30.<navigation-rule>31. <from-view-id>/summary.xhtml</from-view-id>32. <navigation-case>33. <from-outcome>prev</from-outcome>34. <to-view-id>/page3.xhtml</to-view-id>35. </navigation-case>36.</navigation-rule>
37.<navigation-rule>38. <from-view-id>/page4.xhtml</from-view-id>39. <navigation-case>40. <from-outcome>next</from-outcome>41. <to-view-id>/summary.xhtml</to-view-id>42. </navigation-case>43. <navigation-case>44. <from-outcome>prev</from-outcome>45. <to-view-id>/page3.xhtml</to-view-id>46. </navigation-case>47.</navigation-rule>
Creating Custom Skins
If you open web.xml file, you will see the following entry that defines the current skin
used in the application:view sourceprint ?
1.<context-param>2. <param-name>org.richfaces.SKIN</param-name>
3. <param-value>laguna</param-value>4.</context-param>
It's very easy to create a custom skin by changing just a few skin properties.
Skinnability is just an extension to CSS that allows to change the look and feel of
entire application by modifying a simple property file.
Create a new property file in JavaSource root, call it laguna-largefont.skin.properties (all skins have to follow this naming convention):view sourceprint ?
1.baseSkin=laguna2.generalSizeFont=20px3.headerSizeFont=20px
All we are doing is using the existing laguna skin and overwriting just two properties.
Now just update skin in web.xml file:
view sourceprint ?
1.<context-param>2. <param-name>org.richfaces.SKIN</param-name>3. <param-value>laguna-largefont</param-value>4.</context-param>
Save, restart and run the application.
At this point save everything and run the application.
Learn how to change skins in runtime.
Switching to Spring Annotations
Using annotations instead off XML-base configurations is the fashionable thing in Java
enterprise development today. On a more serious note, both approaches are needed.
I will leave it up to you to decide which to use and when. There are lots of resources
on the Internet the deal with that question.
Using annotations does make it a little easier to develop the application you don't need
to worry about XML files which can grow rather large. In this last section, we are going
to use annotations instead of XML-based configuration.
First, open WEB-INF/spring-beans.xml and add the following tag to support
annotations-based configuration:view sourceprint ?
1.<context:component-scan base-package="bar" />
base-package is the package from which Spring container will start looking for
annotated classes.
Next we should comment (or delete) the XML-based bean configuration:
view sourceprint ?
01.<!--02.<bean id="barBean" class="bar.view.BarBean" scope="request">03. <property name="orderService" >04. <ref bean="service" />05. </property>06.</bean>
07.<bean id="wizardBean" class="bar.view.WizardBean" scope="request">08. <property name="startPage" value="/page1.xhtml"/>09. <property name="orderService" >10. <ref bean="service" />11. </property>12.</bean>
13.<bean id="service" class="bar.service.Service" scope="session" />-->
Let's start with bar.service.Service bean:
We are going to annotate it as a Spring component:view sourceprint ?
1.@Service("orderService")2.@Scope ("session")3.public class OrderService {4....5.}
@Service is basically a specialization of @Component annotation and is better
suited to annotate components that perform service-layer
functionality. @Scope annotation specified the scope into which this object will be
placed. If we didn't specify a scope, the default scope would be Singleton which means
there is just a once instance of this object in application which would also work in our
case.
Next we need to annotate barBean and wizardBean classes. Both have identical
annotations. Let's first start with BarBean. view sourceprint ?
1.@Component("barBean")2.@Scope("request")3.public class BarBean {4....5.}6. [email protected] OrderService orderService;
@Component registers the bean as a component with name specified in
parenthesis. One last thing is that we have to inject the service component into this
bean. This is done via @Autowiredannotation. The annotation will inject a
dependency based on type (@Qualified annotation can be used to to gain more
control in case Spring context contains more than one object of the expected type).
Finally, WizardBean looks similar:view sourceprint ?
1.@Component("wizardBean")2.@Scope("request"3.public class WizardBean {4....5.}6. [email protected] OrderService orderService;
Well, we are done. Save and restart the server.
SummaryI'm hoping this example gave you a good idea of how to use RichFaces with Spring.
While it can still be considered “introductory”, we covered some core concepts and
features. If you are just starting with RichFaces, I suggest first to try this example which uses standard JSF managed beans and then move to this one. For
more RichFaces examples and tips, check out my blog
athttp://mkblog.exadel.com/