TipTec.essential.jsf.Facelets.and.JBoss.seam
Transcript of TipTec.essential.jsf.Facelets.and.JBoss.seam
Essential JSF, Facelets & JBoss
SeamBy
Kent Ka Iok Tong
Copyright © 2008
TipTec Development
Publisher: TipTec DevelopmentAuthor's email: [email protected] website: http://www.agileskills2.orgNotice: All rights reserved. No part of this publication may be
reproduced, stored in a retrieval system or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the publisher.
ISBN: 978-99937-929-?-?Edition: First edition April 2008
Essential JSF, Facelets & JBoss Seam 3
Foreword
How to learn JSF, Facelets and JBoss Seam easily?If you're required to develop an application in JSF but are just learning it (JSF), this can be a daunting task. If you need to learn Facelets and JBoss Seam at the same time, it will quickly become unmanageable. With this book, you will have much better chances of hopping over the hurdles. How?
• It has a tutorial style that walks you through in a step-by-step manner.
• It is concise. There is no lengthy, abstract description.
• Many diagrams are used to show the flow of processing and high level concepts so that you get a whole picture of what's happening.
• Free sample chapters are available on http://www.agileskills2.org. You can judge it yourself.
Products covered in the bookThis book covers JSF 1.2 (reference implementation), Facelets 1.2, JBoss Seam 2.0, JBoss RichFaces 3.1, JBoss IDE Tools 2.0, Eclipse Europa for Java EE.
Target audience and prerequisitesThis book is suitable for those learning how to develop web-based applications with JSF, along with Facelets and JBoss Seam.
In order to understand what's in the book, you need to know Java and HTML. However, you do NOT need to know servlet, JSP or Tomcat.
AcknowledgmentsI'd like to thank:
• Helena Lei for proofreading this book.
• Eugenia Chan Peng U for doing book cover and layout design.
4 Essential JSF, Facelets & JBoss Seam
Table of ContentsForeword.........................................................................................3
How to learn JSF, Facelets and JBoss Seam easily?...............3Products covered in the book.....................................................3Target audience and prerequisites.............................................3Acknowledgments.......................................................................3
Chapter 1 Getting Started with JSF................................................7What's in this chapter?...............................................................8Developing a Hello World application with JSF.........................8Installing Eclipse.........................................................................8Installing Tomcat........................................................................9Installing the JSF reference implementation............................10Creating a Hello Word application............................................11Generating dynamic content....................................................24Retrieving data from Java code................................................31How the JSP file generates the HTML code............................38Debugging a JSF application...................................................40Summary..................................................................................41
Chapter 2 Using Forms.................................................................43What's in this chapter?.............................................................44Developing a stock quote application.......................................44Getting the stock quote symbol................................................44Displaying the stock value........................................................52Defining the page navigation....................................................54Using a combo box...................................................................62Inputting a date.........................................................................65Handling conversion errors.......................................................70Marking input as required.........................................................76Using the calendar component.................................................77Hooking up the managed beans..............................................80Summary..................................................................................83
Chapter 3 Validating Input............................................................85What's in this chapter?.............................................................86Postage calculator....................................................................86What if the input is invalid?......................................................93Null input and validators...........................................................97Validating the patron code........................................................99
Essential JSF, Facelets & JBoss Seam 5
Displaying the error messages in red.....................................102Displaying the error message along with the field.................103Validating a combination of multiple input values..................108Summary................................................................................111
Chapter 4 Creating an e-Shop....................................................113What's in this chapter?...........................................................114Creating an e-shop.................................................................114Listing the products................................................................115Showing the product details...................................................124Implementing a shopping cart................................................130How Tomcat and the browser maintain the session..............139The checkout function............................................................142Implementing the login function.............................................144Implementing the checkout function......................................151Implementing logout...............................................................158Summary................................................................................160
Chapter 5 Building Interactive Pages with AJAX......................163What's in this chapter?...........................................................164A sample AJAX application....................................................164Refreshing the question only..................................................166Refreshing the answer itself...................................................169Giving rating to a question......................................................169Using a modal panel to get the rating....................................172Setting the look and feel with skins........................................176Summary................................................................................177
Chapter 6 Using Facelets..........................................................179What's in this chapter?...........................................................180Setting up IDE support for Facelets.......................................180Creating a Facelets project....................................................181Creating your own tag............................................................188Hiding JSF tags into HTML tags.............................................191Forbidding direct access to xhtml files...................................193Summary................................................................................195
Chapter 7 Providing a Common Layout with Facelets..............197What's in this chapter?...........................................................198Providing a common layout....................................................198Having two abstract parts.......................................................202Having page specific navigation cases..................................204Summary................................................................................206
6 Essential JSF, Facelets & JBoss Seam
Chapter 8 Using JBoss Seam....................................................207What's in this chapter?...........................................................208Recreating the e-shop with Seam..........................................208Creating the catalog page......................................................218Displaying product details.......................................................223Adding a product to the shopping cart...................................230Confirming the checkout.........................................................237Logging in...............................................................................239Protecting the confirm page...................................................244Implementing logout...............................................................245Redirect vs render..................................................................247Summary................................................................................249
Chapter 9 Supporting Other Languages....................................251What's in this chapter.............................................................252A sample application..............................................................252Supporting Chinese................................................................253Internationalize the date display.............................................259Letting the user change the locale.........................................260Localizing the full stop............................................................263Displaying a logo....................................................................266Making the locale change persistent......................................269Localizing validation messages..............................................271Summary................................................................................272
References..................................................................................273Alphabetical Index......................................................................274
7
Chapter 1 Chapter 1 Getting Started with JSF
8 Chapter 1 Getting Started with JSF
What's in this chapter?In this chapter you'll learn how to set up a development environment and develop a Hello World application with JSF.
Developing a Hello World application with JSFSuppose that you'd like to develop an application like this:
Installing EclipseYou need to make sure you have Eclipse v3.3 (or later) installed and it is the bundle for Java EE (the bundle for Java SE is NOT enough). If not, go to http://www.eclipse.org to download the Eclipse IDE for Java EE Developers (e.g., eclipse-jee-europa-fall-win32.zip). Unzip it into c:\eclipse. Then, create a shortcut to run "c:\eclipse\eclipse -data c:\workspace". This way, it will store your projects under the c:\workspace folder. To see if it's working, run it and make sure you can switch to the Java EE perspective:
BUG ALERT: If you're using Eclipse 3.3.1, there is a serious bug in it: When visually editing JSF pages Eclipse will frequently crash with an OutOfMemoryError. To fix it, modify c:\eclipse\eclipse.ini:
Chapter 1 Getting Started with JSF 9
Installing TomcatNext, you need to install Tomcat. Go to http://tomcat.apache.org to download a binary package of Tomcat 6.x (or later). Download the zip version instead of the Windows exe version. Suppose that it is apache-tomcat-6.0.13.zip. Unzip it into a folder, say c:\tomcat. Note that Tomcat 6.x works with JDK 5 or above.
Before you can run it, make sure the environment variable JAVA_HOME is defined to point to your JDK folder (e.g., C:\Program Files\Java\jdk1.5.0_02):
If you don't have it, define it now. Now, open a command prompt, change to c:\tomcat\bin and then run startup.bat. If it is working, you should see:
-showsplashorg.eclipse.platform--launcher.XXMaxPermSize256m-vmargs-Xms40m-Xmx256m-XX:MaxPermSize=256m
This line must be put after -vmargs
Delete them
10 Chapter 1 Getting Started with JSF
Open a browser and go to http://localhost:8080 and you should see:
Let's shut it down by changing to c:\tomcat\bin and running shutdown.bat.
Installing the JSF reference implementationJSF stands for JavaServer Faces. It is just an API (some Java interfaces). To use it, you need an implementation (Java classes implementing those interfaces). There are mainly two implementations: the reference implementation from SUN and MyFaces from Apache. In this book, you'll use the former.
Chapter 1 Getting Started with JSF 11
So, go to https://javaserverfaces.dev.java.net to download a binary package of the JSF implementation which is called Mojarra. Suppose that it is jsf-1_2_07.zip. Unzip it into a folder, say c:\jsf.
The JSF implementation in turn needs another library called JSTL. So, go to https://maven-repository.dev.java.net/repository/jstl/jars, download the jstl-1.2.jar file and put it into say c:\jstl.
Creating a Hello Word applicationNow, in Eclipse, choose "Windows | Preferences | Web and XML | JavaServer Faces Tools | Libraries":
Click "New" and enter the information as below:
12 Chapter 1 Getting Started with JSF
Then while you're still in the Preferences window, choose "Server | Installed Runtimes":
Click "Add" and choose Apache Tomcat v6.0:
The name doesn't really matter
Click Add and browse to c:\jsf\lib to add these two jar files
Check this
It supports v1.2 of the JSF specification
Chapter 1 Getting Started with JSF 13
Click "Next". Specify c:\tomcat as the Tomcat installation directory:
Click "Finish". Next, right click in the Package Explorer and choose "New |
14 Chapter 1 Getting Started with JSF
Dynamic Web Project":
Enter the information as below:
Chapter 1 Getting Started with JSF 15
Keep clicking "Next" until you see the dialog below. Then choose your implementation:
The name doesn't really matter
Run this application in Tomcat
Eclipse will set some options such as compiling using Java 5
16 Chapter 1 Getting Started with JSF
Then, if you're on the Internet, Eclipse may try to download some XML files and may ask you to accept the licenses. Say yes. Finally, you should see the project structure:
Choose it
Chapter 1 Getting Started with JSF 17
To make the jstl-1.2.jar file available to it, copy it into the WebContent/WEB-INF/lib folder. Right click the project and choose "Refresh" so that Eclipse see the file.
Next, you'll create the web page. To do that, right click the WebContent folder and choose "New | JSP":
Enter "hello" as the file name:
18 Chapter 1 Getting Started with JSF
Click "Finish". This will create a hello.jsp file in the WebContent folder with some initial content:
To edit it visually, right click the hello.jsp file and choose "Open With | Web Page Editor":
Chapter 1 Getting Started with JSF 19
Then you'll see:
Next, in the visual editing area, type "Hello world". Note that the source code will change automatically:
This is the visual editing area
This is the source code
20 Chapter 1 Getting Started with JSF
Alternatively, you could edit the source code and the visual display will change automatically.
To run your application, you need to create a so-called "Tomcat instance". To do that, right click the "Servers" tab at the bottom and choose "New | Server":
Choose the Tomcat v6.0 runtime:
Chapter 1 Getting Started with JSF 21
Click "Next", then you'll see:
22 Chapter 1 Getting Started with JSF
Choose your Hello project and click "Add". This way it will be added to that Tomcat instance:
Chapter 1 Getting Started with JSF 23
Click "Finish". Then you should see this Tomcat instance in the "Servers" window:
Click the icon as shown above to run it (make sure you have indeed stopped the Tomcat you started from the command prompt!). Then you will see some messages in the Console window:
The instance is not running
To run it, just click here.
24 Chapter 1 Getting Started with JSF
To run your hello.jsp page, open a browser and go to this URL:
Then you should see it working:
Generating dynamic contentDisplaying static text is not particularly interesting. Next you'll output some dynamic text. Open hello.jsp. At the right hand side of the visual editing area, there is a collapsed palette:
If you see this line, it means your application was started successfully
This is your project name
It represents the JSF engine in your application
This is called the context path. By default it is determined by the project name.
http://localhost:8080/Hello/faces/hello.jsp
WebContent
hello.jsp
...
/hello.jsp is a relative path from WebContent. The JSF engine will use it to retrieve the hello.jsp file. In JSF such a relative path is called the view id.
Chapter 1 Getting Started with JSF 25
Move the mouse over there and the palette will appear:
Expand the "JSF Core" folder. It contains quite some items. Each item is called a tag and the folder is called a tag library (or tag lib):
A palette is here
26 Chapter 1 Getting Started with JSF
Each tag lib contains some tags in it and has a unique URL as its identifier, just like a Java package contains some classes in it and has a unique package name:
Now, drag the <view> tag and drop it onto the page (either before the "Hello World" text or after it. It doesn't matter):
http://java.sun.com/jsf/core
<view><param><selectItem>
com.foo
class Product {...
}class Order {
...}
Each item is a tag
It is a tag lib
Chapter 1 Getting Started with JSF 27
A <view> element will have been created. Why it is <f:view> instead of <view>? "f" here is used as a short hand (the "prefix") for the URL of the JSF Core tab lib, so <f:view> means the <view> tag in the JSF Core tag lib:
Note that we said a <f:view> element had been created instead of a <f:view> tag. Here is the difference between a tag and an element:
This is like an import statement in Java. It makes the tags in a tag lib available to this JSP file.
In the rest of the JSP file, you can use "f" as a short hand for the long URL.
The URL for the JSF Core tag lib
28 Chapter 1 Getting Started with JSF
This <f:view> element is required whenever you need to use any JSF tag. You must put JSF tags inside it. Delete the existing "Hello world!" text. In the body of the <f:view> element, enter "Hello !":
Then on the palette, expand the "JSF HTML" tag lib. It contains JSF tags for generating HTML code. JSF can generate different markups such as HTML for normal web browsers or special markups for mobile phones. The JSF HTML tag lib contains tags that deal with HTML markup while those JSF tags having nothing to do with any specific markup are put into the JSF Core tag lib.
Anyway, drag the "Output Text" tag and drop it before the exclamation mark:
<f:view>...
</f:view>The whole thing is called an element
This is a tag. Or more specifically, it is a start tag.
This is another tag (the end tag).
Chapter 1 Getting Started with JSF 29
Note the "h" prefix. It is the short hand for the URL for the JSF HTML tag lib:
The <h:outputText> element will output some text. For example, if you like it to output the string "Paul", in the source code editor, add an attribute named "value". To do that, you use the auto-complete function in Eclipse:
The URL for the JSF HTML tag lib
30 Chapter 1 Getting Started with JSF
Choose "value" from the list:
Then input "Paul":
Note that the visual display is also updated automatically. Now, save the file, go to the browser and reload the page. You should see:
Chapter 1 Getting Started with JSF 31
Retrieving data from Java codeNext, you'll let the Output Text tag retrieve the string from Java code. First, create a Java class GreetingService in the hello package:
Input the content as below:package hello;public class GreetingService {
public String getSubject() {return "John";
}}
You'd like the JSF engine to create an instance of your GreetingService and then the Output Text tag retrieve the value of the "subject" property of that instance as the output:
32 Chapter 1 Getting Started with JSF
To do that, the JSF engine maintains two tables (see the diagram below). Let's call them "instance table" and "definition table" respectively. Initially the instance table is empty, meaning that no object has been created yet. The definition table stores the class name for each object. If you have configured the Output Text tag to access the "subject" property of the "foo" object, then when it needs to find the text, it will ask the JSF engine for the "foo" object. The JSF engine will look up the instance table but can't such an entry for "foo". Then it will look up the definition table and find the class name (hello.GreetingService) for "foo". Then it creates a new GreetingService object and add an entry point to the instance table. Finally it tells the Output Text to use this object:
Actually there are many such object tables in a single JSF application: For example, as shown in the diagram below, suppose that there are two browsers (client 1 and client 2) accessing the application. Assume that client 1 has sent totally two requests (request 1 and request 2) to the application and client 2 has sent one (request 3). Then there will have been an object table for each HTTP request. In addition, Tomcat will allocate a memory area for each client. Such an area is called a session. In each session, there is another object table,
1: CreateGreetingServiceJSF Engine
Output Text2: Tell me the value of your "subject" property
3: Output it
JSF Engine
Output Text
1: Give me the object named "foo"
4: Create itGreetingService
2: Look it up in the table. Not found.
5: Add a new entry
6: Here is your "foo" object
Object name Object class
bar ...... ...
foo hello.GreetingService
Object name Object instance... ...... ...... ...
3: Look up the class name
foo
Instance table
Definition table
Chapter 1 Getting Started with JSF 33
meaning that there is an object table for each client. Finally, there is an object table in the whole application. Which tables will be used by Output Text? Suppose that it is serving request 3, it will ask the JSF engine to find the "foo" object. The JSF engine will look up the object table associated with request 3 first. If it's there fine and it will be returned. Otherwise, it will look up the table for client 2 (because request 3 came from client 2). If it is still not found, it will look up the table for the whole application:
As mentioned before, if the object is still not found, the JSF engine will check the definition table to find the class name and then create the object. But which table should the new object be put into? It is specified in the definition:
Object name Object instance... ...... ...... ...
Table for request 1
Object name Object instance... ...... ...... ...
Table for request 2
Object name Object instance... ...... ...... ...
Table for client 1
Object name Object instance... ...... ...... ...
Table for client 2
Object name Object instance... ...... ...... ...
Table for the whole application
Client 1
Request 1
Client 2
Request 3
Request 2
Object name Object instance... ...... ...... ...
Table for request 3
JSF engine
1: Look up "foo" here
2: If not found, try here.
3: If still not found, try here.
Output Text
1: Give me "foo"
34 Chapter 1 Getting Started with JSF
If the object is put into the request, after the request is handled, the table and that object will be discarded. If the object is put into the session, the table and that object will be discarded when there is a say 30 minutes of inactivity.
Such named objects are officially called "managed beans" in JSF. They're called managed because it is JSF that manages (creates and discards) them, not you. They're called beans because they are Java beans (have a no-argument constructor and provide properties):
Now, let's implement this idea. To create the bean definition table, double click the faces-config.xml file:
Choose the "Managed Bean" tab at the bottom of the window:
Object name Object class Scoperequest
bar ... session... ... application
foo hello.GreetingService
Definition table
It should go into the table for the current request (request 3 in this case)
It should go into the table for the whole application
It should go into the table in the session for the client (client 2 in this case)
public class GreetingService {
public String getSubject() {return "Paul";
}}
Getter for property "subject"
No constructor defined. So the Java compiler will give one automatically. Such a constructor will have no argument:
public class GreetingService {public GreetingService() {}public String getSubject() {
return "Paul";}
}
Chapter 1 Getting Started with JSF 35
Choose "request" and click "Add". Browse to choose the GreetingService class:
Click "Next". Enter "foo" as the bean name:
Choose this tab
36 Chapter 1 Getting Started with JSF
Go ahead to finish it. Save the faces-config.xml file. This file is just an XML file. If you'd like to see the XML code, you can choose the "Source" tab at the bottom of the window:
The next step is tell the Output Text tag to access the "foo" bean. To do that, edit hello.jsp. Enter "#{}" as the value as shown below. This #{} syntax tells the Output Text tag that it is not a static string. Instead, there will be a "EL expression" in it (EL stands for Expression Language):
Choose this tabThe bean is defined here
Chapter 1 Getting Started with JSF 37
While the cursor is inside #{}, use auto-completion and choose the "foo" bean:
Then enter a dot and use auto-completion to choose the "subject" property:
38 Chapter 1 Getting Started with JSF
The result will be:
To get the real value, Output Text will evaluate the EL expression "foo.subject". It means it will look up the "foo" bean and call getSubject() on it.
Save the file. Now, go to the browser and reload the page. It should work. Otherwise, run the Tomcat instance again.
How the JSP file generates the HTML codeHow the JSP file generates the HTML code? When the JSF engine needs to display the hello.jsp file, it will ask the JSP (NOT JSF) engine in Tomcat to do that. First, the <%@...> lines are read by the JSP engine and are not output at all (see the diagram below). Then the normal HTML code is output as is. The JSP engine will only handle JSP tags such as <f:view> and <h:outputText> (yes, JSF tags are JSP tags). When it sees the <f:view> tag, it will ask the tag to generate HTML code. However, this tag, like all JSF tags, will not generate any HTML code. Instead, it will create a JSF component. For the case of <f:view>, it will create a so-called "View root" component which represents the root of the component tree. Then through some magical collaboration between the <f:view> tag and the <h:outputText> tag, they create an artificial UI Output component and put the text "Hello " into it so that it will output that text later. Note that if this "Hello " text appeared outside of <f:view>, it would have been output immediately and not turned into a component. The <h:outputText> will
Chapter 1 Getting Started with JSF 39
create a UI Output component. Finally the exclamation mark is put into another artificial UI Output component. At this point the JSF component tree is built but the tree hasn't generated any HTML code. Next, the JSP engine sees normal HTML again and outputs it:
At this point, the HTML output (generated by the JSP engine) is shown below and the JSP engine has finished its job. Next, the JSF engine will tell the view root to generate HTML output (tell it to "encode" itself in JSF terms). The view root will in turn tell its child components to generate HTML output. Their output will go into the marker left by the <f:view>:
These are read by the JSP engine and are not output at all
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%><%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@page language="java"
contentType="text/html; charset=ISO-8859-1"pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Insert title here</title>
</head><body>
<f:view>Hello <h:outputText value="#{foo.subject}"></h:outputText>!</f:view></body></html>
Normal HTML code is output as is
Normal HTML code is output as is
View root
1: Create a "view root" JSF component
UI Output
3: Create a UI outputJSF component
UI Output
UI Output
2: Plain HTML code is put into an artificial UI Output JSF component
4: Plain HTML code again
value: "Hello "
value: "!"
40 Chapter 1 Getting Started with JSF
Debugging a JSF applicationTo debug your application in Eclipse, you can set a breakpoint in your Java code such as:
Then click the Debug icon in the Server window:
JSF engine1: Render
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Insert title here</title>
</head><body>
[JSF COMPONENT TREE WAS HERE]</body></html>
View root
UI Output
UI Output
UI Output
2: Render
Conceptually <f:view> left a marker here
3: Render
4: Render
"Hello "
"Paul"
"!"
Chapter 1 Getting Started with JSF 41
Now go to the browser to load the page again. Eclipse will stop at the breakpoint:
Then you can step through the program, check the variables and whatever. To stop the debug session, just restart Tomcat.
SummaryIn a JSF application, a page is defined by a JSP file and is identified by its view id, which is the relative path to it from the web content folder.
Each JSP tag belongs to a certain tag lib. A tag lib is identified by a URL. To use a tag in a JSP file, you need to introduce a short hand (prefix) for the URL and then use the prefix to qualify the tag name.
To define a JSF component tree in a JSP file, you need to have a <f:view> and put various JSF tags in it. Each JSF tag is also a JSP tag. When they're executed by the JSP engine, they will create their respective JSF components.
This will start Tomcat in debug mode. If it is already running, it will be restarted.
42 Chapter 1 Getting Started with JSF
The root of the component tree is always the view root. To generate HTML code from the component tree, the JSF engine will ask the view root to do that, which in turn will ask its child components to do the same. The process of generating markup in JSF is called encoding.
The JSF Core tag lib contains JSF tags that have nothing to do with any specific markup. The JSF HTML tag lib contains JSF tags that knows about the HTML markup. Usually the former uses a prefix of "f" (standing for faces) while the latter uses a prefix of "h" (standing for HTML).
The <h:outputText> tag will create an outputText component. That component will output the value its "value" attribute. That value can be static string or an EL expression in #{}.
To find the value of a variable appearing in an EL expression, the JSF engine will try to find a managed bean with that name. It will try to find it in the beans associated with the request, in the session of the client and in the whole application, in that order. If it is not found, it will look up the class name and scope in the WebContent/WEB-INF/faces-config.xml file and create it, before putting it into the right location.
For a class to be used as a managed bean class, it needs to be a Java bean, i.e., it has a no-argument constructor and provides getters and/or setters for certain properties.
43
Chapter 2 Chapter 2 Using Forms
44 Chapter 2 Using Forms
What's in this chapter?In this chapter you'll learn how to use forms to get input from the user.
Developing a stock quote applicationSuppose that you'd like to develop an application like this:
That is, the user can enter the stock id and click OK, then the stock value will be displayed.
Getting the stock quote symbolLet's do it. Create a new Dynamic Web project (with JSF enabled) named StockQuote. Copy the jstl jar file into WEB-INF/lib. Then create a getquotesymbol.jsp file in WebContent. Locate the <form> tag in the JSF HTML tag lib. Drag and drop it onto the page:
Chapter 2 Using Forms 45
For the text field, use the <Text Input> tag in the JSF HTML tag lib. Put it inside the form:
For the OK button, use the <commandButton> tag in the JSF HTML tag lib:
The <h:form> tag will create a JSF Form component
When you dropped the <h:form> tag, it noted that you didn't have a <f:view> element, so it created one for you.
46 Chapter 2 Using Forms
It is displaying "Submit Query". To change it to "OK", set its "value" attribute:
Here is the component tree that will be built:
Chapter 2 Using Forms 47
To retrieve the symbol entered by the user, you can link the UI Input component to a property of a Java object:
To do that, create such a QuoteQuery class in the stockquote package:package stockquote;public class QuoteQuery {
private String sym;public String getSym() {
return sym;}public void setSym(String sym) {
this.sym = sym;}
}Then create a managed bean definition for it by modifying faces-config.xml:
UI Form
UI Input
UICommand
UI ViewRoot
UI Form
UI Input
UICommand
class QuoteQuery {String sym;
}
UI ViewRoot
48 Chapter 2 Using Forms
Save the file. Then in getquotesymbol.jsp, set the "value" attribute of the <inputText> tag:
This way, when the form is submitted, the symbol entered by the user will be stored into the "sym" property of this "quoteQuery" managed bean:
Chapter 2 Using Forms 49
In fact, this linkage is bi-directional: When the UI Input component encodes itself, it will get the value of the "sym" property and display it in the HTML input field:
Let's look at the whole render and submit process in details. First, the JSP engine creates the component tree (see the diagram below). The JSF engine gets access to the tree and ask it to encode. It will ask its child components to encode and so on. For the UI Input component, it will try to read the "sym" property of the "quoteQuery" bean. As the bean doesn't yet exist, the JSF engine creates the bean. The UI Input component gets the value ("IBM") and outputs it in the HTML <input> element. In order to be able to handle the form submission, the JSF engine performs some extra action: It generates a unique request id, saves the component tree into the session indexed by the request id and includes this request id into the form as a hidden field. Finally the "quoteQuery" bean is destroyed along with the request. This is called the Render Response phase:
class QuoteQuery {String sym;
}
It will be set to "MSFT"
class QuoteQuery {String sym = "IBM";
}
It will display "IBM"
50 Chapter 2 Using Forms
When the form is submitted (see the diagram below), the JSF engine will get the request id from the hidden field and use to look up the component tree and load it. This is called the Restore View phase:
UI Form
UI Input
...
UI ViewRoot
JSPengine
1: Create the tree
JSFengine
2: Encode
3: I need to access a bean named "quoteQuery"
quoteQuery
4: Create
5: Read its "sym" property
Session for this client
...
...
...
Request id: 1237: Generate a unique request id and save the tree under it
...<form>
<inputtype="input"value="IBM">
<inputtype="hidden"value="123">
</form>
6: Output the value ("IBM")
8: Output the request id into a hidden field
9: Destroy
Chapter 2 Using Forms 51
Then the JSF engine asks the UI View Root to extract the values from the request (strings). It will in turn ask each child component to do that and store the value locally into itself. The purpose is that if later a value is found to be invalid (e.g., "abc" for an int), that invalid value will still be there and can be redisplayed to the user. The act of extracting the value and storing it locally is called decoding. The phase of decoding is called the Apply Request Values phase:
Next, the JSF engine will ask the UI View Root to set the locally stored values into the managed beans. For the UI Input component, it will set the locally stored value into the "sym" property of the "quoteQuery" bean. As the bean doesn't yet exist, the JSF engine creates the bean. Therefore, this bean is NOT the same bean used in rendering. The phase of setting the locally stored values into the managed beans is called the Update Model Values phase:
JSFengine1: Form submission
request arrives
Session for this client
...
...
...
Request id: 123
2: Use 123 to retrieve the tree
MSFT123
UI Form
UI Input
...
UI ViewRoot
JSFengine
1: Extract the values from the requestMSFT
123
UI Form
UI Input
UI ViewRoot
raw value: MSFT...
...
2: Extract the values from the request
3: Store it locally
52 Chapter 2 Using Forms
Displaying the stock valueThe next step is to display the stock value in a result page. Let's call it quoteresult.jsp. Create it in WebContent. By the way, if you'd like to open all JSP files using the Web Page Editor, you can set it as the default. To do that, choose "Window | Preferences", choose "General | Editors | File Associations" on the left hand side, choose *.jsp in the upper half of the window, choose the Web Page Editor in the lower half and set it as the default:
JSFengine
1: Set the values into the beans
UI Form
UI Input
UI ViewRoot
raw value: MSFT...
...
2: I need to access a bean named "quoteQuery"
quoteQuery3: Create it. It is NOT the same bean used in rendering!
4: Set the locally stored value into the bean
Chapter 2 Using Forms 53
Modify quoteresult.jsp like:
Create the getStockValue() method:
You may notice a yellow line here: It is warning that it can't find a "stockValue" property in the bean. This is correct. It doesn't have such a property now. You will create a getStockValue() method in the Java class in the next step.
Warning
3: Choose it
2: Choose it
4: Click here
1: Choose it
54 Chapter 2 Using Forms
Now the result page is ready. The only missing question is how to display it after the form is submitted?
Defining the page navigation To do that, you need to tell the JSF engine something like this:
Usually you'll tell the JSF engine the outcome in a new phase called Invoke Application phase (see the diagram below), which occurs after the Update Domain Values phase. Then it will use the current view id and the outcome to look up the navigation rules to determine the next view id, which will be rendered in the Render Response phase:
public class QuoteQuery {private String sym;
public String getSym() {return sym;
}public void setSym(String sym) {
this.sym = sym;}public int getStockValue() {
return sym.hashCode() % 100;}
}Normally you should find out the stock value for the given symbol. Here, you just get a fake value: the hash code of the symbol modulo 100.
If outcome is "ok"
/getquotesymbol.jsp
/quoteresult.jsp
The whole thing is called a navigation rule
Each branch is called a navigation case
If outcome is "..."anotherview id
The current view id
The next view id
Chapter 2 Using Forms 55
Now, let's define the navigation rules. To do that, open faces-config.xml and choose the "Navigation Rule" tab at the bottom of the window:
On the palette on the right edge, choose the "Page" tool:
Choose this tab
Restoreview
Applyrequest values
Updatedomain values
Invokeapplication
Renderresponse
JSF engine
1: Set the outcome
Navigationrules
2: Use the current view id and outcome to find the next view id
3: Render the next view in the Render Response phase
56 Chapter 2 Using Forms
Then click on the white area in the window. It will pop up a window to let you choose a JSP file (see below). Choose getquotesymbol.jsp:
Then the page will appear in the window:
Chapter 2 Using Forms 57
Now do the same thing for the quoteresult.jsp page:
To link them up, choose the "Link" tool on the palette:
Click on the getquotesymbol page and then on the quoteresult page. A link will
58 Chapter 2 Using Forms
be created:
To specify the outcome for the link, choose the "Select" tool on the palette (or just press Escape):
Then choose the link and choose the "Properties" tab. Enter the outcome there:
Chapter 2 Using Forms 59
If you're curious, you can see its XML source in the "Source" tab:<faces-config ...>
<managed-bean><managed-bean-name>quoteQuery</managed-bean-name><managed-bean-class>stockquote.QuoteQuery</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean><navigation-rule>
<display-name>getquotesymbol</display-name><from-view-id>/getquotesymbol.jsp</from-view-id><navigation-case>
<from-outcome>ok</from-outcome><to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case></navigation-rule>
</faces-config>Save the file. Finally, modify getquotesymbol.jsp to specify the outcome:
If the button is clicked, it will note that in the Apply Request Values phase (see the diagram below) and will register a listener to be invoked in the Invoke Application phase. That listener will set the outcome when it is executed:
Enter the outcome
...<f:view>
<h:form><h:inputText value="#{quoteQuery.sym}"></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>... This is the outcome
60 Chapter 2 Using Forms
Why it doesn't simply execute the listener in the Apply Request Values phase? It is because it would like to update the beans (Update Domain Values) first before performing other any actions.
Now, you're about to run it. To do that, you need to add this project to the Tomcat instance. So, choose the "Servers" tab and double click on the Tomcat instance, you'll see its settings:
Choose the "Modules" tab at the bottom of the window. It will display all the web applications that it is hosting. Currently it should contain only the Hello project:
Double click on it
Restoreview
Applyrequest values
Updatedomain values
Invokeapplication
Renderresponse
Request
UI Command
...
1: Was I clicked?
Listener
2: Schedule a listener to be executed
3: Execute
JSF engine
4: Set the outcome
Chapter 2 Using Forms 61
Click "Add Web Module" to add the StockQuote project to it:
Save the file. Now, start the Tomcat instance and go to http://localhost:8080/StockQuote/faces/getquotesymbol.jsp in a browser. It should work:
http://localhost:8080/StockQuote/faces/getquotesymbol.jsp
The path determines this part of the URL
62 Chapter 2 Using Forms
Using a combo boxSuppose that you'd like to change the application so that the user will choose from a list of stock symbols instead of typing in one:
To do that, delete the <h:inputText> in getquotesymbol.jsp and put a <h:selectOneMenu> there:
Chapter 2 Using Forms 63
This will create a UI Select One component which will allow a single item to be selected only. It should still link to the "sym" property of the bean. So, set its "value" attribute just like before:
To specify the available items in the combo box, choose the <selectItems> tag in the JSF Core tag lib. Note that it is in the JSF Core tag lib instead of the JSF HTML tag lib because selection items in JSF are generic and have nothing to do with HTML markup.
You'd like drop the <selectItems> element into the body of the <selectOneMenu> element, but in the visual editor you can only drop it before the <selectOneMenu> element or after it but not inside it. Fortunately, you can do that in the text editing area. Just make sure that you click the <selectItems> tag and release the mouse right away, then click inside the <selectOneMenu> element:
64 Chapter 2 Using Forms
The <selectItems> element will retrieve the items from a managed bean (again, using its "value" attribute). To do that, create a StockService class in the same package:
Make a managed bean from it. As it is a global thing, put it into the application scope:
Click here to drop
...import java.util.List;import javax.faces.model.SelectItem;public class StockService {
private List<SelectItem> symbols;public StockService() {
symbols = new ArrayList<SelectItem>();symbols.add(new SelectItem("MSFT"));symbols.add(new SelectItem("IBM"));symbols.add(new SelectItem("RHAT"));
}public List<SelectItem> getStockSymbols() {
return symbols;}
}
This class is provided by JSF. It represents an item for the user's selection.
This string will be displayed to the user
It can return a List or an array
Chapter 2 Using Forms 65
Set the "value" attribute of the <selectItems> tag:<f:view>
<h:form><h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/></h:selectOneMenu><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
Now run the application and it should work. However, you may wonder why you need to provide it with a List<SelectItem> instead of just a List<String>? The reason is, for example, instead of displaying short codes like "MSFT" to the user, you'd like to display a longer description such as "Microsoft". Internally all your processing will still use "MSFT" though. To do that, modify the code:
Inputting a dateSuppose that you'd like to allow the user to query the stock value on a particular
public class StockService {private List<SelectItem> symbols;public StockService() {
symbols = new ArrayList<SelectItem>();symbols.add(new SelectItem("MSFT", "Microsoft"));symbols.add(new SelectItem("IBM", "IBM"));symbols.add(new SelectItem("RHAT", "Red Hat"));
}public List<SelectItem> getStockSymbols() {
return symbols;}
}
This string will be displayed to the user
This string will be set into the bean
class QuoteQuery {String sym;
}
66 Chapter 2 Using Forms
date:
To do that, modify getquotesymbol.jsp:<f:view>
<h:form><h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/></h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}"></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
Provide the "quoteDate" property in the bean and use it to calculate the stock value:public class QuoteQuery {
private String sym;private Date quoteDate = new Date();public Date getQuoteDate() {
return quoteDate;}public void setQuoteDate(Date quoteDate) {
this.quoteDate = quoteDate;}public String getSym() {
return sym;}public void setSym(String sym) {
this.sym = sym;}public int getStockValue() {
return (sym + quoteDate.toString()).hashCode() % 100;}
}The UI Input component knows about a few common types such as java.lang.Integer and java.lang.Double and can convert between a value of such types and a string. Unfortunately, it doesn't know java.util.Date. To solve this problem, you need to tell it to use a Date converter (see the diagram below). When it needs to encode itself, it will get the value of its "value" attribute. Here it will get a Date object. Then it will ask the converter to convert this value into a string. Finally it will output the string into the HTML <input> field:
Chapter 2 Using Forms 67
When the user submits the form (see the diagram below), the JSF engine will initiate the Apply Request Values phase. As a result, the UI Input component will store the raw input string stored locally. Before the JSF engine starts the Update Domain Values phase, it will initiate a new phase called Process Validations phase. In this phase, it will ask the UI View Root to convert the raw input string into the desired data type (any Object) and optionally validate the converted object. For the UI Input component, it will ask the converter to convert its locally stored raw string to an object (a Date) and store the result locally. Finally, the JSF engine will initiate the Update Domain Values phase to update the beans:
Therefore, all you need to do is to setup a Date converter. To do that, drop a <convertDateTime> tag from the JSF Core tag lib into the <inputText> element (see the diagram below). When this <convertDateTime> tag is executed by the
UI Input
<inputtype="text"value="6/20/2007" ...>
1: Call getQuoteDate() to get the value (a Date, but it doesn't need to know)
class QuoteQuery {...Date getQuoteDate() {
...}void setQuoteDate(Date d) {
...}
}
Dateconverter
2: Convert the object (a Date) into a string for me
Year: 2007Month: 6Day: 20
3: The string is "6/20/2007"
4: Output the string into the <input> field
Restoreview
Applyrequest values
UI Text
UI ViewRoot
UIForm
Raw: "7/28/2007"Converted:
1: Restore view
quote date: "7/28/2007"...
Request
2: Apply request values
Processvalidations
Dateconverter
Year: 2007Month: 7Day: 28
3: Convert the string into an Object
Updatedomain values
class QuoteQuery {...void setQuoteDate() {
...}
}
4: Update domain values
68 Chapter 2 Using Forms
JSP engine, it will create a Date converter. Then it will ask its parent tag (<inputText>) to find out the JSF component its parent has created (here, it's the UI Input component). Then it will tell the UI Input component to use that Date converter:
Now run it and it should work:
Why it shows "Jan 19, 2008" instead of say 1/19/2008 or 19/1/2008? This is controlled by two factors: the most preferred language set in the browser and the style used by the converter. Here are some examples:
Short style Medium style Long style Full styleUS English 1/19/2008 Jan 19, 2008 January 19, 2008 Saturday,
January 19, 2008
UK English 19/1/2008 ... ... ...
... ... ... ... ...As you can see, by default it uses the medium style. To tell it to use say the
<f:view><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}">
<f:convertDateTime/></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
UI Input
Date converter
1: Create a Date converter
2: What is the JSF component that you created? Oh, it's that UI Input.
3: Tell it to use this converter
Chapter 2 Using Forms 69
short style, do it this way:<f:view>
<h:form><h:selectOneMenu value="#{quoteQuery.sym}">
<f:selectItems value="#{stockService.stockSymbols}"/></h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}">
<f:convertDateTime dateStyle="short" /></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
Now, run it and it should be like:
To change the most preferred language, you can change it in the browser. For example, in Firefox, it is set in "Tools | Options | Advanced":
Click "Choose":
70 Chapter 2 Using Forms
Handling conversion errorsWhat if the user enters some garbage like "abc" as the date? In the Process Validations phase, the Date converter will try convert "abc" to a Date object but it will fail. Then it will log an error message into a list of messages associated with the request and tell the JSF engine to jump to the Render Response phase, skipping any phases in between (i.e., the Update Domain Values phase and the Invoke Application phase):
Restoreview
Applyrequest values
Processvalidations
Updatedomain values
Renderresponse
UI Text
Raw: "abc"
Dateconverter
abc is invalidMessage list2: Log an error
message
1: Try to convert it to a Date but fails
3: Jump to the render response phase directly
abcRequest
Invokeapplication
Chapter 2 Using Forms 71
This is good, because if anything is wrong, you don't want to update the beans (Update Domain Values) and don't want to change the view id (Invoke Application) so that the original page is redisplayed. What else would you like to do? To display an error message in the original page. To do that, modify getquotesymbol.jsp:
The UI Messages component will display all the messages in the message list (if there is no message, it will render nothing). Now, run the application, enter "abc" as the date and click OK, you'll see:
<f:view><h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}">
<f:convertDateTime dateStyle="short" /></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
UI Form
...
UI Input
UIMessages
UI ViewRoot
It will create a UI Messages component
...
72 Chapter 2 Using Forms
The client id is mainly used as the value of the id or name attribute of the HTML element generated. If you view the source of the HTML page, you'll see how various client ids are used:
Form
...
UI Input
UIMessages
UI ViewRoot
...
The id of the UI Form component
The id of the UI Input component
The whole path is called the client id of the UI Input component
Chapter 2 Using Forms 73
Anyway, displaying the client id is quite confusing to users. Instead, you should display a user friend description for the text field. To do that, modify getquotesymbol.jsp:<f:view>
<h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}"/>
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date">
<f:convertDateTime dateStyle="short" /></h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
Run it again and it will display label instead of the client id:
If you don't like this error message, you can provide your own. To do that, create a text file named messages.properties in the stockquote package (the name is not really significant as long as it has a .properties extension):
74 Chapter 2 Using Forms
Then open faces-config.xml, choose the "Others" tab, click "Message Bundle" and then click "Add":
Browse to select the messages.properties file:
javax.faces.converter.DateTimeConverter.DATE={0} is an invalid {2}. \Try something like {1}
This is called the resource key
The label ("quote date")
JSF will fill in the user input ("abc")
An example string that is valid such as "12/20/08"
You may specify TIME here when you use the converter it to convert a time
When the line is too long, you can use a backslash to tell Java to continue to the next line.
1: Click here
2: Click here
3: Click here
Chapter 2 Using Forms 75
BUG ALERT: Due to a bug in Eclipse the screen may not be updated even though the code has been modified. In the source, you should see:<faces-config ...>
<application><message-bundle>stockquote.messages</message-bundle>
</application><managed-bean>
<managed-bean-name>quoteQuery</managed-bean-name><managed-bean-class>stockquote.QuoteQuery</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>stockService</managed-bean-name><managed-bean-class>stockquote.StockService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><navigation-rule>
<display-name>getquotesymbol</display-name><from-view-id>/getquotesymbol.jsp</from-view-id><navigation-case>
<from-outcome>ok</from-outcome><to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case></navigation-rule>
</faces-config>Now the JSF engine will load messages from this file and use them to override the default messages. Run the application and it should work:
76 Chapter 2 Using Forms
If you'd like to specify the error message for that UI Input component only, you can do it this way:
Marking input as requiredNow the UI Input component will not accept garbage. But what if the user enters an empty string? By default the UI Text assumes that you're allowing the input to be optional. If the target data type is a string, it will remain as an empty string. If the target data type is not a string, it will convert be converted to null (in this case as the Date object). Then your code will crash at the code below:public class QuoteQuery {
private String sym;private Date quoteDate = new Date();
public Date getQuoteDate() {return quoteDate;
}public void setQuoteDate(Date quoteDate) {
this.quoteDate = quoteDate;}public String getSym() {
return sym;}public void setSym(String sym) {
this.sym = sym;}public int getStockValue() {
return (sym + quoteDate.toString()).hashCode() % 100;}
}To mark it as required, do it this way:<f:view>
<h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"required="true"><f:convertDateTime dateStyle="short" />
</h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form>
<f:view><h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"><f:convertDateTime dateStyle="short" />
</h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view> This will override the message provided
by the converter. As it is the converter that is providing values for {0}, {1} and {2}, you can't use such placeholders in this string.
Chapter 2 Using Forms 77
</f:view>Now, run it while setting the date to empty, you'll see:
Again, if you don't like the error message, you can override it in the messages.properties file:
If you'd like to set it just for this UI Input component, you can do it this way:<f:view>
<h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"required="true"requiredMessage="Input is missing!"><f:convertDateTime dateStyle="short" />
</h:inputText><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
Using the calendar componentIn fact, you can also allow the user to choose a date:
javax.faces.converter.DateTimeConverter.DATE={0} is an invalid {2}. \Enter something like {1}javax.faces.component.UIInput.REQUIRED=You must input {0}!
The label
78 Chapter 2 Using Forms
Clicking on the calendar icon will display a calendar:
To do that, you can use a JSF component library called RichFaces from JBoss. Go to http://labs.jboss.com/jbossrichfaces to download it. Suppose that it is richfaces-ui-3.1.3.GA-bin.zip. Unzip it into say c:\richfaces-ui. To use it, copy all the jar files in c:\richfaces-ui\lib into your WEB-INF/lib. RichFaces in turn needs a few third party jar files. You can go to http://agileskills2.org/EssentialJSF/richfaces to download them and put them into WEB/lib. Finally refresh the project in Eclipse.
Next, modify WEB-INF/web.xml:<?xml version="1.0" encoding="UTF-8"?><web-app ...>
<display-name>StockQuote</display-name><welcome-file-list>
...</welcome-file-list><servlet>
<servlet-name>Faces Servlet</servlet-name><servlet-class>javax.faces.webapp.FacesServlet</servlet-class><load-on-startup>1</load-on-startup>
Chapter 2 Using Forms 79
</servlet><servlet-mapping>
<servlet-name>Faces Servlet</servlet-name><url-pattern>/faces/*</url-pattern>
</servlet-mapping><context-param>
<param-name>org.richfaces.SKIN</param-name><param-value>blueSky</param-value>
</context-param><filter>
<display-name>RichFaces Filter</display-name><filter-name>richfaces</filter-name><filter-class>org.ajax4jsf.Filter</filter-class>
</filter><filter-mapping>
<filter-name>richfaces</filter-name><servlet-name>Faces Servlet</servlet-name><dispatcher>REQUEST</dispatcher><dispatcher>FORWARD</dispatcher><dispatcher>INCLUDE</dispatcher>
</filter-mapping></web-app>
You don't need to know the exact meaning of this code. Basically it is used to enable the RichFaces engine to intercept requests so that it can deliver Javascript to the browser. Close the getquotesymbol.jsp and open it again. Then you should see some new tag libs such as RichFaces available on the palette:
Next, choose the <calendar> tag from the RichFaces tag lib and drop it into the getquotesymbol.jsp file and modify the file like this:
80 Chapter 2 Using Forms
Now, run it and it should work:
Hooking up the managed beansFor the moment, the stock value calculation is done in the QuoteQuery class:public class QuoteQuery {
private String sym;private Date quoteDate = new Date();
public Date getQuoteDate() {return quoteDate;
}public void setQuoteDate(Date quoteDate) {
this.quoteDate = quoteDate;
<f:view><h:messages></h:messages><h:form>
<h:selectOneMenu value="#{quoteQuery.sym}"><f:selectItems value="#{stockService.stockSymbols}" />
</h:selectOneMenu>on <h:inputText value="#{quoteQuery.quoteDate}" label="quote date"
converterMessage="the quote date is invalid"required="true"requiredMessage="Input is missing!"><f:convertDateTime dateStyle="short" />
</h:inputText><rich:calendar
value="#{quoteQuery.quoteDate}" converterMessage="the quote date is invalid"required="true"requiredMessage="Input is missing!">
</rich:calendar><h:commandButton value="OK" action="ok"></h:commandButton>
</h:form></f:view>
The same attributes are supported except the "label"
Chapter 2 Using Forms 81
}public String getSym() {
return sym;}public void setSym(String sym) {
this.sym = sym;}public int getStockValue() {
return (sym + quoteDate.toString()).hashCode() % 100;}
}In a real implementation, you will need to look up a database or connect to a network service provider to get the stock value. This kind of work is best done in the StockService class. So, to make the code more realistic, let move the calculation logic into the StockService class:public class StockService {
private List<SelectItem> symbols;
public StockService() {symbols = new ArrayList<SelectItem>();symbols.add(new SelectItem("MSFT", "Microsoft"));symbols.add(new SelectItem("IBM", "IBM"));symbols.add(new SelectItem("RHAT", "Red Hat"));
}public List<SelectItem> getStockSymbols() {
return symbols;}public int getStockValue(QuoteQuery q) {
return (q.getSym() + q.getQuoteDate().toString()).hashCode() % 100;}
}Then the code in the QuoteQuery class should call the StockService to get the stock value. But how to get access to it?
To let the "quoteQuery" bean get access to the "stockService" bean, you can say so in the bean definition table. Each row in the definition table can refer to a property initialization table (see the diagram below). When the JSF engine creates the "quoteQuery" bean, it will check its property initialization table. It notes that it needs to initialize the "stkSrv" property of the "quoteQuery" bean.
public class QuoteQuery {private String sym;private Date quoteDate = new Date();
public Date getQuoteDate() {return quoteDate;
}public void setQuoteDate(Date quoteDate) {
this.quoteDate = quoteDate;}public String getSym() {
return sym;}public void setSym(String sym) {
this.sym = sym;}public int getStockValue() {
StockService stkSrv = ???;return stkSrv.getStockValue(this);
}}
How to get access to the "stockService" bean?
82 Chapter 2 Using Forms
The value is specified as an EL expression #{stockService}. So it evaluates it (the result is the "stockService" bean) and stores the result using the setStkSrv() setter:
To specify the property initialization table, select the "quoteQuery" bean in faces-config.xml:
Object name Object class Scope... application... request
... ... ...
stockServicequoteQuery
Definition table
Property Initial value
... ...
... ...
stkSrv #{stockService}
Property initialization table
public class QuoteQuery {private String sym;private Date quoteDate = new Date();private StockService stkSrv;public void setStkSrv(StockService stkSrv) {
this.stkSrv = stkSrv;}...public int getStockValue() {
StockService stkSrv = null;return stkSrv.getStockValue(this);
}}
It will be evaluated and set using this setter
This is the property initialization table
Click here to add an entry
Chapter 2 Using Forms 83
Click "Add" and enter the information:
You also specify the property class so that JSF engine can double check the data type. Save the file. If you're curious, you can check the source:<faces-config ...>
<application><message-bundle>stockquote.messages</message-bundle>
</application><managed-bean>
<managed-bean-name>quoteQuery</managed-bean-name><managed-bean-class>stockquote.QuoteQuery</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>stkSrv</property-name><property-class>stockquote.StockService</property-class><value>#{stockService}</value>
</managed-property></managed-bean><managed-bean>
<managed-bean-name>stockService</managed-bean-name><managed-bean-class>stockquote.StockService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><navigation-rule>
<display-name>getquotesymbol</display-name><from-view-id>/getquotesymbol.jsp</from-view-id><navigation-case>
<from-outcome>ok</from-outcome><to-view-id>/quoteresult.jsp</to-view-id>
</navigation-case></navigation-rule>
</faces-config>You can see that such a property is called a managed property because it is the JSF engine that initializes it. Anyway, run the application and it should continue to work.
Note that you can let the "quoteQuery" bean access the "stockService" bean but not vice versa because the scope of the former (request) is shorter (the same is also fine) than the latter (application). In the life time of the "stockService" bean, there will be multiple "quoteQuery" beans (one for each request). It just doesn't know which one to refer to.
SummaryTo handle a form submission, the JSF engine will go through the following
84 Chapter 2 Using Forms
phases: Restore View, Apply Request Values, Process Validations, Update Domain Values, Invoke Application and Render Response. If there is any error in the Process Validations phase or the Update Domain Values phase, the JSF engine will jump to the Render Response phase directly.
In order to be able to restore the view, it saves the component tree along with a unique request id in the Render Response phase. Although the component tree is restored, if you're accessing request-scoped managed beans, the bean you use on form submission will not be the same as the one you used to render the page.
To let the user edit a string in a text field, use the UI Input component and set its "value" attribute to link it to the property of a managed bean. To let the user choose an entry from a combo box, use the UI Select One component. You need to provide a list of SelectItem to it. Each SelectItem contains an object and its string presentation.
To let the user edit an Object in a text field, provide a converter to the UI Input component. If there is a conversion error, it will log an error message.
To display error messages, use the UI Messages component.
To let the user click a button, use a UI Command component. Specify the outcome in its "action" attribute. The JSF engine will use the current view id to look up the right navigation rule and use the outcome to look up the right navigation case to find the next view id. The UI Command will set the outcome in the Invoke Application phase so that if there is any conversion or validation error, it will not set the outcome and the original page is redisplayed.
You can customize the error messages using a resource bundle. This will affect the whole application. To customize it for a particular component, simply set the right attribute of the component.
You can let one managed bean get access to another using a managed property. Make sure the referring bean has a shorter (or the same) life time (scope) than the referred one.
85
Chapter 3 Chapter 3 Validating Input
86 Chapter 3 Validating Input
What's in this chapter?In the previous chapter you've learned some basic ways of input validation: marking a field as required and specifying a converter. In this chapter you'll learn more advanced ways to validate input.
Postage calculatorSuppose that you'd like to develop an application to calculate the postage for sending a package from some place to another. The user will enter the weight of the package in kg (check the screen shots below). Optionally, he can enter a "patron code" identifying himself as a patron to get a certain discount. After clicking OK, it will display the postage:
To do that, create a new JSF project named Postage as usual. Create a getrequest.jsp file. Then choose the <panelGrid> tag in the JSF HTML tag lib:
Drop it into the getrequest.jsp page:
Chapter 3 Validating Input 87
Why use a <panelGrid> instead of the plain <table>? You could use a <table> just fine:<f:view>
<table><tr>
<td><h:outputText value="item1"></h:outputText></td><td><h:outputText value="item2"></h:outputText></td>
</tr><tr>
<td><h:outputText value="item3"></h:outputText></td><td><h:outputText value="item4"></h:outputText></td>
</tr></table>
</f:view>A major difference is that the UI Panel will work fine with HTML or non-HTML markup. When the <panelGrid> tag creates the UI Panel, it will assign an HTML Panel renderer to it so that it will generate HTML markup (see the diagram below). But if you assign another renderer to it, it will be able to generate some other type of markup:
Next, input the text prompt:
A <panelGrid> will create a UI Panel component
There are 2 columns per row. It means that the 3rd child component will go into the next row.
UI Panel
It will output an HTML <table>
UI Panel
HTML Panelrenderer
Some otherrenderer
Render me
Render me
<table>...
</table>
Output HTML markup
...Output some other type of markup
88 Chapter 3 Validating Input
Select item2 and press the Del key. Then the components following item2 will flow forward to fill its place:
Then put an <inputText> into the original location of item2 (i.e., after the "Weight:" label). Then the following components will flow back to their original locations:
Chapter 3 Validating Input 89
Make the <form> larger:
Delete item4 and put in another <inputText>. You could create the <inputText> as usual, alternatively, you could copy the existing <inputText> by Ctrl-dragging it:
The <form> is too small. It should contain both <inputText> elements.
90 Chapter 3 Validating Input
Put an <outputText> that outputs an empty string and a <commandButton>. Set the "value" attribute of the <commandButton> properly:
Create a Request class in the postage package to act as the bean to be edited:
Ctrl-drag it to copy it
Chapter 3 Validating Input 91
The PostageService class is:
Create the bean definitions for them and hook them up:
package postage;public class PostageService {
private Map<String, Integer> patronCodeToDiscount;public PostageService() {
patronCodeToDiscount = new HashMap<String, Integer>();patronCodeToDiscount.put("p1", 90);patronCodeToDiscount.put("p2", 95);
}public int getPostage(Request r) {
Integer discount = (Integer) patronCodeToDiscount.get(r.getPatronCode());int postagePerKg = 10;int postage = r.getWeight() * postagePerKg;if (discount != null) {
postage = postage * discount.intValue() / 100;}return postage;
}}
Assume that the postage is 10 dollar per kg
Hard code some patrons and their respective discounts. For example p1 has 10% off.
public class Request {private int weight;private String patronCode;private PostageService postageService;public Request() {}public Request(int weight, String patronCode) {
this.weight = weight;this.patronCode = patronCode;
}public int getWeight() {
return weight;}public void setWeight(int weight) {
this.weight = weight;}public String getPatronCode() {
return patronCode;}public void setPatronCode(String patronCode) {
this.patronCode = patronCode;}public void setPostageService(PostageService postageService) {
this.postageService = postageService;}public int getPostage() {
return postageService.getPostage(this);}
}
Need to access a service bean. So, need a setter:
Need getters and setters for it to be edited
Call the service bean to calculate the postage
Need a no-argument constructor
92 Chapter 3 Validating Input
Link the beans and the UI Input components:<f:view>
<h:form><h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText><h:inputText value="#{req.weight}"></h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Next, create the result page. Let's call it showpostage.jsp:<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%><%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Insert title here</title></head><body><f:view>The postage is: <h:outputText value="#{req.postage}"></h:outputText></f:view></body></html>
Set the outcome in the <commandButton>:<f:view>
<h:form><h:panelGrid border="1" columns="2">
<h:outputText value="Weight:"></h:outputText><h:inputText value="#{req.weight}"></h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Define the navigation rule:
<faces-config ...><managed-bean>
<managed-bean-name>postageService</managed-bean-name><managed-bean-class>postage.PostageService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>req</managed-bean-name><managed-bean-class>postage.Request</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>postageService</property-name><property-class>postage.PostageService</property-class><value>#{postageService}</value>
</managed-property></managed-bean>
</faces-config>
Note that you must NOT call it "request" because there is a pre-defined bean named "request" representing the HTTP request.
Chapter 3 Validating Input 93
<faces-config ...><managed-bean>
<managed-bean-name>postageService</managed-bean-name><managed-bean-class>postage.PostageService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>req</managed-bean-name><managed-bean-class>postage.Request</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>postageService</property-name><property-class>postage.PostageService</property-class><value>#{postageService}</value>
</managed-property></managed-bean><navigation-rule>
<display-name>getrequest</display-name><from-view-id>/getrequest.jsp</from-view-id><navigation-case>
<from-outcome>ok</from-outcome><to-view-id>/showpostage.jsp</to-view-id>
</navigation-case></navigation-rule>
</faces-config>Double click the Tomcat instance and add the project as a module. Then, run the application by going to http://localhost:8080/Postage/faces/getrequest.jsp. It should work:
What if the input is invalid?At the moment if the user enters a negative number as the weight (e.g., -5), it will go ahead and return a negative postage:
94 Chapter 3 Validating Input
This is no good. Instead, you'd like the application to tell the user that the weight is invalid:
Similarly, it should also check if the patron code is valid or not. For example, if the user enters "p3", it should tell him that this code is not found:
Note that as the patron code is optional, if he doesn't enter anything, it should NOT be treated as an error. In order to validate the user input, you can add one or more validator objects to a UI Input component (see the diagram below). When the form is submitted, as mentioned before, in the Apply Request Values the UI Input component will store the raw input string ("-5") locally. In the Process Validations phase, it will convert it into an Object (an int -5 here). Then
Chapter 3 Validating Input 95
it will ask each of its validators (if any) in turn to validate the converted value (-5 here). If a validator fails, it will log an error message and will tell the JSF engine to jump to the Render Response phase directly:
In order to create such a validator, choose the <validateLongRange> tag in the JSF Core tag lib:
Drop it into the body of the <inputText> element and the modify the code as shown below:
UI Input
raw: "-5"converted: -5
Apply Request Values Process Validations Update Domain Values
UI Input
raw: "-5"
weight: "-5"
Read it and store it locally
1: Convert
Validator 1
...
2: Validate the converted value (-5)
Invoke ApplicationsRender Response
...Message list
3: Log an error message
4: Go to the Render Response phase directly
Restore View
96 Chapter 3 Validating Input
Now run the application again and it should work:
Again, you can customize the error message using a message bundle. For example, create Postage.properties in the postage package:
Specify the message bundle in faces-config.xml:<faces-config ...>
<application><message-bundle>postage.Postage</message-bundle>
</application>...
</faces-config>
<f:view><h:messages></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:inputText label="weight" value="#{req.weight}">
<f:validateLongRange minimum="0"></f:validateLongRange></h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view> Set the minimum value to 0 so that anything less than 0 is an error. You could set the maximum value too, but here there is no need to.
Display error messages Provide a label to be used in the error message. Otherwise it will display the client id.
javax.faces.validator.LongRangeValidator.MINIMUM={1} must be at least {0}!
It is a validator
The value is less than the minimum value
The label ("weight" here)
The minimum value (0 here)
The name of the validator
Chapter 3 Validating Input 97
Make sure the application is reloaded. Then run it and it should work:
In addition to this validator, there are similar ones for checking doubles and strings:
Their resource keys are:javax.faces.validator.LengthValidator.MINIMUM=...javax.faces.validator.LengthValidator.MAXIMUM=...javax.faces.validator.DoubleRangeValidator.MINIMUM=...javax.faces.validator.DoubleRangeValidator.MAXIMUM=...
Null input and validatorsWhat if the user doesn't input anything as the weight? As mentioned in the previous chapter, the UI Input field will treat it as an empty string (if target data type is string) or null (if the target data is not a string). No matter which case it is, all the validators will be skipped automatically. This design is to allow the case when some input is optional, but if the user does provide some input, then it must be validated. Here in this case, if you enter an empty string as the weight:
<f:validateLength minimum="3" maximum="20"></f:validateLength>
<f:validateDouble minimum="0" maximum="999999"></f:validateDouble>
The length of the string should be between 3 and 20
The double value should be >= 0 and <= 999999
98 Chapter 3 Validating Input
The application will display an error because the UI Input can't store a null into an int property (It could if it was an Integer property):
In this case, you can simply solve the problem by marking the weight as required:<f:view>
<h:messages></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:inputText label="weight" value="#{req.weight}" required="true">
<f:validateLength></f:validateLength><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Then the user will see:
Chapter 3 Validating Input 99
Validating the patron codeNow the weight field is working fine. How to validate the patron code? There is no built-in validator suitable. In that case, you can specify a validator method:
So, create that validatePatron() method:
<f:view><h:messages></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:inputText label="weight" value="#{req.weight}" required="true">
<f:validateLongRange minimum="0"></f:validateLongRange></h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
validator="#{req.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>
public class Request {...public void validatePatron(...) {
...}
}
This EL expression will evaluate to a Method object representing this method. The UI Command will then use this method as a validator.
100 Chapter 3 Validating Input
Create the patronExists() method in the PostageService class:public class PostageService {
private Map<String, Integer> patronCodeToDiscount;
public PostageService() {patronCodeToDiscount = new HashMap<String, Integer>();patronCodeToDiscount.put("p1", 90);patronCodeToDiscount.put("p2", 95);
}public int getPostage(Request r) {
Integer discount = (Integer) patronCodeToDiscount.get(r.getPatronCode());
int postagePerKg = 10;int postage = r.getWeight() * postagePerKg;if (discount != null) {
postage = postage * discount.intValue() / 100;}return postage;
}public boolean patronExists(String patronCode) {
return patronCodeToDiscount.containsKey(patronCode);}
}Now run it and it should work:
However, the Request class is a domain class and thus should not know about JSF stuff such as FacesContext, UIComponent and ValidatorException. To
public class Request {private int weight;private String patronCode;private PostageService postageService;...public void validatePatron(FacesContext context, UIComponent component,
Object convertedValue) {String patronCode = (String) convertedValue;if (!postageService.patronExists(patronCode)) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,"Patron code is invalid","Patron code:" + patronCode + " is invalid"));
}}
}
It represents the JSF engine
The component being validated (the UI Input here)
The converted value (here the patron code). It must be non-empty, otherwise this code would not have been called.
If it doesn't exist, throw a ValidatorException.
A message contains three pieces of information: the severity (INFO, WARN, ERROR, ...), a summary message and a detail message. By default, the UI Messages will display the summary messages.
You'll create this method next
Chapter 3 Validating Input 101
solve this problem, you can create a new bean to do this work. For example, let's create a PatronCodeValidatingBean class in the same package and move the validatePatron() method into there:public class PatronCodeValidatingBean {
private PostageService postageService;public void setPostageService(PostageService postageService) {
this.postageService = postageService;}public void validatePatron(FacesContext context, UIComponent component,
Object convertedValue) {String patronCode = (String) convertedValue;if (!postageService.patronExists(patronCode)) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Patron code is invalid","Patron code:" + patronCode + " is invalid"));
}}
}Define the bean:<faces-config ...>
...<managed-bean>
<managed-bean-name>postageService</managed-bean-name><managed-bean-class>postage.PostageService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>req</managed-bean-name><managed-bean-class>postage.Request</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>postageService</property-name><property-class>postage.PostageService</property-class><value>#{postageService}</value>
</managed-property></managed-bean><managed-bean>
<managed-bean-name>patronCodeValidatingBean</managed-bean-name><managed-bean-class>
postage.PatronCodeValidatingBean</managed-bean-class><managed-bean-scope>application</managed-bean-scope><managed-property>
<property-name>postageService</property-name><property-class>postage.PostageService</property-class><value>#{postageService}</value>
</managed-property></managed-bean>...
</faces-config>Note how it refers to the "postageService" bean. This is OK as they are both application scoped beans and thus have the same life span. Next, modify getrequest.jsp:<f:view>
<h:messages></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:inputText label="weight" value="#{req.weight}" required="true">
<f:validateLongRange minimum="0"></f:validateLongRange></h:inputText><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
102 Chapter 3 Validating Input
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Run it and it should continue to work.
Displaying the error messages in redSuppose that you'd like the error messages to be in red. To do that, modify getrequest.jsp:
Now run it and it will work:
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%><%@ page language="java"
contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Insert title here</title><style type="text/css">
li.err { color: red }</style></head><body><f:view>
<h:messages errorClass="err"></h:messages>...
</f:view></body></html>
Define some styles These styles are called "CSS styles". CSS stands for cascading style sheet.
The browser will look up the styles and find line matching the element (<li>) and the class ("err"). Therefore it will set the color of the <li> element to red.
<ul><li class="err">...</li><li>...</li>
</ul>
Chapter 3 Validating Input 103
Displaying the error message along with the fieldYou may wonder what is the purpose of the detail message in a FacesMessage. It is intended to be displayed along with the field like:
To do that, open getrequest.jsp, choose the <message> tag (NOT the plural <messages> tag!) in the JSF HTML tag lib:
104 Chapter 3 Validating Input
Drop it after the first <inputText> element. However, the page will look like:
This is due to the way the UI Panel lays out its child components. To solve this problem, you can put the UI Input component and the UI Message component into another UI Panel (see the diagram below). It is important that the new UI Panel render its child components one by one without any extra markup. To do that, just let it use a group renderer, while the original UI Panel is using a grid renderer:
Chapter 3 Validating Input 105
To implement this idea, choose the <panelGroup> tag in the JSF HTML tag lib:
Then move the <inputText> element and the <message> element into its body:<f:view>
<h:messages errorClass="err"></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:panelGroup>
<h:inputText label="weight" value="#{req.weight}"required="true"><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:message></h:message>
</h:panelGroup><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
UI Panel
UI Input
UIMessage
...
UI Panel
UI Panel
...
UI Input
UIMessage
Grouprenderer
Gridrenderer
[MARKUP OF CHILD1] [MARKUP OF CHILD2] ...
<table><tr>
<td>[MARKUP OF CHILD1]</td> <td>[MARKUP OF CHILD2]</td>...
106 Chapter 3 Validating Input
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Then the page will look fine again:
Finally, set up the <message> tag:
It is not really required to set the id of the <form>. It is set just to show you what the client id looks it. Run it and it should work fine:
<f:view><h:messages errorClass="err"></h:messages><h:form id="f">
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"required="true"><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:message for="w"></h:message>
</h:panelGroup><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view> 1: "w" is a relative client id. It uses the client id of the form to get the client id (f:w) and use it to look up the message.
Client id Level Sum m ary Detailf :w ERROR ... ...... ... ... ...
2: It will display the detail message
Chapter 3 Validating Input 107
Why is the detail message the same as the summary message? This is because you are not providing the detail message in the message bundle. To provide it, modify Postage.properties:
Run it again and it should work:
To make the message appear in red, just set its "errorClass" attribute:
javax.faces.validator.LongRangeValidator.MINIMUM={1} must be at least {0}!javax.faces.validator.LongRangeValidator.MINIMUM_detail={1} is invalid. It must \be at least {0}!
Just add the string "_detail" to the key
108 Chapter 3 Validating Input
Run it and it should work:
Validating a combination of multiple input valuesSuppose that for a particular patron p1, you will never ship a package that is weighted more than 50kg. As this involves both the weight and the patron code (two components), you can't make a validator and assign it to a single component. In this case, you can register a so-called action listener to perform the validation. The JSF engine will call all action listeners in the Invoke Application phase.
To implement this idea, open getrequest.jsp, choose the <actionListener> tag in
...<style type="text/css">li.err {
color: red}span.err {
color: red}</style>...<f:view>
<h:messages errorClass="err"></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"required="true"><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:message for="w" errorClass="err"></h:message>
</h:panelGroup><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok"></h:commandButton>
</h:panelGrid></h:form>
</f:view>
The UI Message component will output a <span>
Chapter 3 Validating Input 109
the JSF Core tag lib:
Drop it into the body of the <commandButton> element and then modify it:
Create this RequestValidatingListener class in the postage package:
<f:view><h:messages errorClass="err"></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"required="true"><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:message for="w" errorClass="err"></h:message>
</h:panelGroup><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok">
<f:actionListener type="postage.RequestValidatingListener" /></h:commandButton>
</h:panelGrid></h:form>
</f:view>This is the class of the action listener. You'll create this class next.
110 Chapter 3 Validating Input
Make sure the <form> has the id set:<f:view>
<h:messages errorClass="err"></h:messages><h:form id="f">
<h:panelGrid border="1" columns="2"><h:outputText value="Weight:"></h:outputText><h:panelGroup>
<h:inputText id="w" label="weight" value="#{req.weight}"required="true"><f:validateLongRange minimum="0"></f:validateLongRange>
</h:inputText><h:message for="w" errorClass="err"></h:message>
</h:panelGroup><h:outputText value="Patron code:"></h:outputText><h:inputText value="#{req.patronCode}"
validator="#{PatronCodeValidatingBean.validatePatron}"></h:inputText><h:outputText></h:outputText><h:commandButton value="Submit" action="ok">
<f:actionListener type="postage.RequestValidatingListener" /></h:commandButton>
</h:panelGrid></h:form>
</f:view>Create the isValid() method in the Request class:public class Request {
private int weight;private String patronCode;private PostageService postageService;...public boolean isValid() {
if (patronCode.equals("p1") && weight > 50) {return false;
}return true;
}}
public class RequestValidatingListener implements ActionListener {public void processAction(ActionEvent event)
throws AbortProcessingException {FacesContext context = FacesContext.getCurrentInstance();Application app = context.getApplication();Request req = (Request) app.evaluateExpressionGet(context, "#{req}",
Request.class);if (!req.isValid()) {
context.addMessage("f:w", new FacesMessage(FacesMessage.SEVERITY_ERROR,"weight too heavy for the patron",null));
throw new AbortProcessingException();}
}}
Get access to the JSF engine from the view of this request
The application contains various helpers that can be customized. Here, you'll use it to evaluate an EL expression.
Evaluate this EL expression to get access to the "req" bean
Validate it. You'll create this method next
Record the error for this component
If you specify null as the detail message, it will be treated as equal to the summary. Tell the JSF engine to stop
processing this event. As the outcome is set as the last step, it won't be set.
This event means the click on the button
Chapter 3 Validating Input 111
Note that its properties will have been updated in the Update Domain Values phase while the action listener is executed in the Invoke Application phase. Now, run it and it should work:
SummaryA UI Panel component lays out its child components according to its renderer. It can lay them out in a table (<panelGrid>) or just arrange them one by one (<panelGroup>).
To validate the user input in a single UI Input component, you can add one or more validator to it. They will be invoked one by one in the Process Validations phase to check the converted value. If any one fails, it will log an error for that component (or rather, for its client id) and tell the JSF engine to jump to the Render Response phase directly.
There are a few pre-defined validators coming with JSF for checking the range of an int, a double or the length of a string. To customize their error messages, use a message bundle.
To perform custom validation, you can do it in a method of a bean and use the "validate" attribute of the tag to refer to it. It will be treated like a normal validator. If the validation involves two or more components, you can add an action listener to a UI Command. It will be executed in the Invoke Application phase. As such the beans will have been updated so you can check their properties.
A JSF message contains a severity level, a summary and a detail. Usually you will display the summary using a UI Messages component, while display the detail using a UI Message component along with each UI Input component.
To customize the appearance of the HTML output, you can define CSS style classes and let the components to refer to them.
113
Chapter 4 Chapter 4 Creating an e-Shop
114 Chapter 4 Creating an e-Shop
What's in this chapter?In this chapter you'll learn how to create an e-shop. This involves implementing a global product catalog, a shopping cart for each user, user login and logout and requiring authenticated access for the checkout page.
Creating an e-shopSuppose that you'd like to create an e-shop. The front page lists all the products:
As you can see, a product has an id, a name and a price. For example, the first product's id is "p01", its name is "Pencil" and its price is $1.2. If the user clicks on a product say "Eraser", he will see a detailed description of Eraser:
For simplicity, you will be using strings like "a", "b" and "c" as the detailed descriptions for the products.
Chapter 4 Creating an e-Shop 115
Listing the productsOK, let's do it. Create a new Dynamic Web Project named Shop and enable JSF. Then create a showcatalog.jsp page. Enter the heading:
Right click the heading and choose "Style | Paragraph Format | Heading 1":
Then it will become:
116 Chapter 4 Creating an e-Shop
To list the products in a table, you can't use the <panelGrid> as it requires you to add the child components at design time. Instead, now you need to query the database at runtime to determine how many rows to have (one for each product). For this purpose, use the <dataTable> tag in the JSF HTML tag lib:
Drop it into the page after the <h1> element in the source pane:
Chapter 4 Creating an e-Shop 117
Modify the text for the two headers:
It will create a UI Data component which will render as the table
It will create a UI Column which represents the whole column
UI Column
UI Output
1: Create2: Look, a facet name ("header") is set
3: Add the component to the parent as a special kind of child called a facet. How a facet is used is entirely up to the parent to decide.
header
Here, the UI Column will render its header facet if it is the first row only (header row)
You can put other components here. The UI Column will render them if it is a subsequent row (data row).
Another column
118 Chapter 4 Creating an e-Shop
Add components for the data rows:
To add the price column, choose the <column> tag in the JSF HTML tag lib:
<h:dataTable border="1"><h:column id="column1">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Id"></h:outputText></f:facet>
</h:column><h:column id="column2">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column></h:dataTable>
Whether it is put before the <facet> or after it doesn't matter at all
Chapter 4 Creating an e-Shop 119
Drop it into the <dataTable> after the two existing columns:
It has no facet. To add the facet, choose the <facet> tag in the JSF Core tag lib (non-HTML markup may also use facets):
Drop it into the column (works only in the source pane):
120 Chapter 4 Creating an e-Shop
It is in error because it needs a facet name. Modify the code:
To provide the data items, create a Catalog class in the shop package:
<h:dataTable border="1"><h:column id="column1">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Id"></h:outputText></f:facet>
</h:column><h:column id="column2">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column><h:column>
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Price"></h:outputText></f:facet>
</h:column></h:dataTable>
Set the facet name to "header". Must use this name as the UI Column only recognizes this name.
Output the header text. You must use an <outputText>. You can't do it this way:
<h:column><h:outputText></h:outputText><f:facet name="header">
Price</f:facet>
</h:column>
This won't work because you must have one and only one component inside <facet>. It also lacks the intelligence to turn plain HTML code into UI Output components.
For the data rows
Chapter 4 Creating an e-Shop 121
package shop;public class Catalog {
private List<Product> products;public Catalog() {
products = new ArrayList<Product>();products.add(new Product("p01", "Pencil", "a", 1.20));products.add(new Product("p02", "Eraser", "b", 2.00));products.add(new Product("p03", "Ball pen", "c", 3.50));
}public List<Product> getProducts() {
return products;}
}Define the Product class in the same package:package shop;public class Product {
private String id;private String name;private String desc;private double price;public Product(String id, String name, String desc, double price) {
this.id = id;this.name = name;this.desc = desc;this.price = price;
}public String getDesc() {
return desc;}public String getId() {
return id;}public String getName() {
return name;}public double getPrice() {
return price;}
}Define a managed bean for the catalog. As it is a global thing, use the application scope:<faces-config ...>
<managed-bean><managed-bean-name>catalog</managed-bean-name><managed-bean-class>shop.Catalog</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean></faces-config>
Now, tell the UI Data to get the items from the bean (see the diagram below). When the UI Data needs to render itself, it will loop through the list of Product objects. For each Product, it will ask its children to render:
122 Chapter 4 Creating an e-Shop
How can the columns access the current Product? You can do it this way:
<h:dataTable border="1" value="#{catalog.products}"><h:column id="column1">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Id"></h:outputText></f:facet>
</h:column><h:column id="column2">
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column><h:column>
<h:outputText></h:outputText><f:facet name="header">
<h:outputText value="Price"></h:outputText></f:facet>
</h:column></h:dataTable>
UI Data
p01 p02 p03
Column 1
UI Output
Column 2
Column 3
Render them once for p01, once for p02 and once for p03
Chapter 4 Creating an e-Shop 123
Now run it and it should work:
<h:dataTable border="1" value="#{catalog.products}" var="p"><h:column id="column1">
<h:outputText value="#{p.id}"></h:outputText><f:facet name="header">
<h:outputText value="Id"></h:outputText></f:facet>
</h:column><h:column id="column2">
<h:outputText value="#{p.name}"></h:outputText><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText><f:facet name="header">
<h:outputText value="Price"></h:outputText></f:facet>
</h:column></h:dataTable>
UI Data
p01 p02 p03
2: In each turn, add or set an attribute in the table to point to the current item Object name Object instance
p... ...... ...
Attribute table for request 1
Request 1
3: An EL expression can refer to attributes ("p" here). The JSF engine will try to find a bean named "p" in all three scopes first. If not found, it will try to find an attribute.
1: "p" is the name of an "attribute". There is a table for each request like this. It looks like the table for request-scoped managed beans but they aren't the same thing. This table can function without a bean definition table. It only associates an object with a name but won't create objects. Each entry is called an attribute.
124 Chapter 4 Creating an e-Shop
Showing the product detailsNow, let's create the links to show the product details. Choose the <commandLink> tag from JSF HTML tag lib and drop it into the product name column:
Drag the <outputText> for the product name and drop it into the body of the <commandLink>:
Chapter 4 Creating an e-Shop 125
Delete the default <outputText> created when you dropped the <commandLink>.
How to respond to a click on the link? A <commandLink> is very similar to a <commandButton>. They will both create a UI Command component. One is rendered as a link while the other is rendered as a button:
As it is just like a <commandButton>, it will submit a form and thus it must be inside a <form> element. That's why a <form> element was created when you dropped the <commandLink>:<h:column id="column2">
<h:form><h:commandLink>
<h:outputText value="#{p.name}"></h:outputText></h:commandLink>
</h:form><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column>As it is just like a <commandButton>, you can set its "action" attribute to specify
UI Command
Linkrenderer
Buttonrenderer
Render me
Render me
<a href="...">...</a>Output markup
<input type="button" ...>Output markup
126 Chapter 4 Creating an e-Shop
the outcome:<h:column id="column2">
<h:form><h:commandLink action="detail">
<h:outputText value="#{p.name}"></h:outputText></h:commandLink>
</h:form><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column>For it to work, create a productdetails.jsp page:
Create a "product" bean for it:<faces-config ...>
<managed-bean><managed-bean-name>catalog</managed-bean-name><managed-bean-class>shop.Catalog</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>product</managed-bean-name><managed-bean-class>shop.Product</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean></faces-config>
Make sure the Product class has a no-argument constructor:public class Product {
private String id;private String name;private String desc;private double price;
public Product() {
Chapter 4 Creating an e-Shop 127
}public Product(String id, String name, String desc, double price) {
this.id = id;this.name = name;this.desc = desc;this.price = price;
}public String getDesc() {
return desc;}public String getId() {
return id;}public String getName() {
return name;}public double getPrice() {
return price;}
}Create a navigation case for it:
However, there is still a question: Who will set the "product" bean to the selected Product object? You can do it this way (see the diagram below). The <setPropertyActionListener> tag will create a "Set Property" action listener and add it to the component created by its parent tag (here, the UI Command). For some unknown reason, this tag is unavailable on the palette, so you have to type it in manually. When the form is submitted, the UI Data will be asked to apply the request values (decode). It will loop through the list of Product objects. For each Product object, it will ask its child components to decode. For the UI Command, it will check if it was clicked. If no, nothing happens. If yes, it usually will schedule the Set Property action listener to be executed in the Invoke Application phase. However, here the "immediate" attribute of the UI Command is set to true. In that case it will call all its action listeners immediately in the Apply Request Values phase. Here, the Set Property action listener will evaluate #{p.id} and get the current Product id and then pass it to the setId() method of the "product" bean:
128 Chapter 4 Creating an e-Shop
What would happen if "immediate" is not set to true? Then the "p" attribute will change in each turn of the loop. At the end of the loop it will be deleted altogether. These will happen in the Apply Request Values phase. When it reaches the Invoke Application phase, the Set Property action listener is called, but then the "p" attribute no longer exists and #{p} will evaluate to null (Yes, it will return null if it can't find a managed bean or attribute with that name).
Now, define the setId() method to update the other fields:public class Product {
private String id;private String name;private String desc;private double price;private Catalog catalog;public Product() {
}public Product(String id, String name, String desc, double price) {
this.id = id;this.name = name;this.desc = desc;this.price = price;
}public String getDesc() {
<h:column id="column2"><h:form>
<h:commandLink action="detail" immediate="true"><h:outputText value="#{p.name}"></h:outputText>
<f:setPropertyActionListenervalue="#{p.id}" target="#{product.id}" />
</h:commandLink></h:form><f:facet name="header">
<h:outputText value="Name"></h:outputText></f:facet>
</h:column>
UI Command
Set PropertyAction Listener
3: Was I clicked? If yes, call the special action listener. Usually, it is called in the Invoke Application phase. But here, "immediate" is true, so call it in the Apply Request Values phase (now).
Object name Object instancep... ...... ...
p01 p02 p03
UI Data
1: Point to it
...
2: Apply request values
Object name Object instanceproduct... ...... ...
Bean tableAttribute table4: The listener evaluates #{p.id} and stores the value into #{product.id}
Create
Product
void setId(String pid) {...
}
Chapter 4 Creating an e-Shop 129
return desc;}public String getId() {
return id;}public String getName() {
return name;}public double getPrice() {
return price;}public void setId(String pid) {
Product p = catalog.getProduct(pid);id = p.id;name = p.name;desc = p.desc;price = p.price;
}public void setCatalog(Catalog catalog) {
this.catalog = catalog;}
}Create the getProduct() method in the Catalog class:public class Catalog {
private List<Product> products;
public Catalog() {products = new ArrayList<Product>();products.add(new Product("p01", "Pencil", "a", 1.20));products.add(new Product("p02", "Eraser", "b", 2.00));products.add(new Product("p03", "Ball pen", "c", 3.50));
}public List<Product> getProducts() {
return products;}public Product getProduct(String pid) {
for (Product p : products) {if (p.getId().equals(pid)) {
return p;}
}return null;
}}
Inject the "catalog" bean into the "product" bean:<faces-config ...>
<managed-bean><managed-bean-name>catalog</managed-bean-name><managed-bean-class>shop.Catalog</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>product</managed-bean-name><managed-bean-class>shop.Product</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>catalog</property-name><property-class>shop.Catalog</property-class><value>#{catalog}</value>
</managed-property></managed-bean><navigation-rule>
<display-name>showcatalog</display-name><from-view-id>/showcatalog.jsp</from-view-id><navigation-case>
<from-outcome>detail</from-outcome><to-view-id>/productdetails.jsp</to-view-id>
130 Chapter 4 Creating an e-Shop
</navigation-case></navigation-rule>
</faces-config>Note that the UI Command was actually rendered three times and generated three HTML <input> elements. How can it tell which one was clicked? It works like this (see the diagram below): When the UI Data renders each row, the UI Command will try to get a client id for itself. It will ask the UI Data for its client id. Assuming that the id of the UI Data is "d". It will append the current row index (0 here) and return "d:0" as its client id. Assuming that the id of the UI Command is "c", then its client id will be "d:0:c" and will use it to identify the <a> element. For the second row, the client id will be "d:1:c" and etc.:
Suppose that the second product was clicked (so the HTML id is "d:1:c") and the form is submitted. The UI Data will again loop through each Product object. For each Product object, it will ask the UI Command to decode. The UI Command will determine its client id first and then check if it was the link that was clicked:
Now, run the application and it should work.
Implementing a shopping cartNow, let's allow the user to add products to his shopping cart. Your purpose is that the product details page should be like:
UI Command"c"
p01 p02 p03
UI Data"d"
...
2: What's your client id?
1: Current item
3: It is d:0 in which 0 is the row index
4: Use its client id to identify the link
<a id="d:0:c">...</a><a id="d:1:c">...</a><a id="d:2:c">...</a>
clicked=d:1:c...
1: The second Product was clicked. The request arrives.
UI Command"c"
UI Data"d"
...
3: Decode4: Equal to my client id?
p01 p02 p03
2: Current item
Chapter 4 Creating an e-Shop 131
If the user clicks "Continue shopping", the catalog page will be displayed:
If the user clicks "Add to cart", a single piece of the product is added to his shopping cart, then the contents of his shopping cart will be displayed:
From there the user should be able to continue shopping or checkout. Now, let's do it. First, add the two buttons to productdetails.jsp:
132 Chapter 4 Creating an e-Shop
Define this addToCart() method in the Product class:
You need to have the shopping cart as a managed bean. What scope it should be in? For each user to have his own shopping cart, it should be in the session scope. So, let's create the Cart class:
<f:view><h1><h:outputText value="#{product.name}"></h:outputText></h1><h:outputText value="#{product.desc}"></h:outputText><h:form>
<h:commandButton value="Add to cart" action="#{product.addToCart}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></f:view> Usually it is just the outcome, but here
you specify a method. It should take no argument and return a string which is the outcome.
public class Product {...public String addToCart() {}
}
public class Product {private String id;private String name;private String desc;private double price;private Catalog catalog;private Cart cart;...public void setCart(Cart cart) {
this.cart = cart;}public String addToCart() {
cart.add(id);return "added";
}}
You'll inject the shopping cart into this property. So you need a setter.
Add the product id to the shopping cart
Outcome indicating it was added successfully
Chapter 4 Creating an e-Shop 133
Define a bean for the shopping cart and inject it into the "product" bean:
Next, create a showcart.jsp page to display the shopping cart as below. Note that it doesn't specify any "header" facet so the columns will not have any header:
public class Cart implements Serializable {private List<String> productIds;public Cart() {
productIds = new ArrayList<String>();}public void add(String pid) {
productIds.add(pid);}public List<String> getProductIds() {
return productIds;}
}
As it will be stored into the session and Tomcat may save objects in the session to the hard disk or send to other nodes in the cluster, all objects in the session must implement Serializable.
<faces-config ...><managed-bean>
<managed-bean-name>catalog</managed-bean-name><managed-bean-class>shop.Catalog</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>product</managed-bean-name><managed-bean-class>shop.Product</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>catalog</property-name><property-class>shop.Catalog</property-class><value>#{catalog}</value>
</managed-property><managed-property>
<property-name>cart</property-name><property-class>shop.Cart</property-class><value>#{cart}</value>
</managed-property></managed-bean><managed-bean>
<managed-bean-name>cart</managed-bean-name><managed-bean-class>shop.Cart</managed-bean-class><managed-bean-scope>session</managed-bean-scope>
</managed-bean><navigation-rule>
<display-name>showcatalog</display-name><from-view-id>/showcatalog.jsp</from-view-id><navigation-case>
<from-outcome>detail</from-outcome><to-view-id>/productdetails.jsp</to-view-id>
</navigation-case></navigation-rule>
</faces-config>
in the session scope
Inject the "cart" bean into the "product" bean
134 Chapter 4 Creating an e-Shop
To solve this problem, you can't just provide a list of product ids to the UI Data. You need to provide a list of Product objects. To do that, you may try to modify the Cart class:
However, do NOT do that. When the "cart" bean is serialized and saved into the hard disk (see the diagram below), the "catalog" bean is not included due to the effect of the transient keyword. Later, when it is loaded from the hard disk and deserialized, its "catalog" field will be null. The JSF engine is not involved and won't inject the "catalog" bean into it at all:
<f:view><h1>Shopping cart</h1><h:dataTable border="1" value="#{cart.productIds}" var="pid">
<h:column id="column1"><h:outputText value="#{pid}"></h:outputText>
</h:column><h:column id="column2">
<h:outputText value="#{???}"></h:outputText></h:column><h:column id="column3">
<h:outputText value="#{???}"></h:outputText></h:column>
</h:dataTable><h:form>
<h:commandButton value="Checkout" action="checkout"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></f:view>
Loop through the product ids in the shopping cart
Store each product id as an attribute named "pid"
Output the product id
How to output the product name?
public class Cart implements Serializable {private List<String> productIds;private transient Catalog catalog;public Cart() {
productIds = new ArrayList<String>();}public void add(String pid) {
productIds.add(pid);}public List<String> getProductIds() {
return productIds;}public void setCatalog(Catalog catalog) {
this.catalog = catalog;}public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();for (String pid : productIds) {
products.add(catalog.getProduct(pid));}return products;
}}
You'll inject the catalog into here
Return a list of Product objects
When a shopping cart is created, the "catalog" will be injected into it. Therefore there is no need to drag in the catalog during serialization, not to mention that it is also not serializable.
Chapter 4 Creating an e-Shop 135
The take home message is that you should never let a session scoped bean refer to any other managed bean. But how to solve the problem? You can create an extra request scoped bean to retrieve product ids from the "cart" bean and then translate ids into Product objects using the "catalog" bean:
To implement this idea, create a ShowCartHelper class:public class ShowCartHelper {
private Cart cart;private Catalog catalog;public void setCart(Cart cart) {
this.cart = cart;}public void setCatalog(Catalog catalog) {
this.catalog = catalog;}public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();for (String pid : cart.getProductIds()) {
products.add(catalog.getProduct(pid));}return products;
}}
Define the bean and inject the "cart" bean and the "catalog" bean into it:<faces-config ...>
...<managed-bean>
<managed-bean-name>showCartHelper</managed-bean-name><managed-bean-class>shop.ShowCartHelper</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>cart</property-name><property-class>shop.Cart</property-class><value>#{cart}</value>
cart catalogtransient
1: When the "cart" bean is saved into the hard disk, the "catalog" bean is not included. 2: When it is loaded
from the hard disk, its "catalog" field will be null.
cartnull
cart catalog
1: Give me the product ids
showCartHelper 2: Give me the Product
object for p01 and etc.
136 Chapter 4 Creating an e-Shop
</managed-property><managed-property>
<property-name>catalog</property-name><property-class>shop.Catalog</property-class><value>#{catalog}</value>
</managed-property></managed-bean>...
</faces-config>Now, you can easily display the product name and price in showcart.jsp:<f:view>
<h1>Shopping cart</h1><h:dataTable border="1" value="#{showCartHelper.products}" var="p">
<h:column id="column1"><h:outputText value="#{p.id}"></h:outputText>
</h:column><h:column id="column2">
<h:outputText value="#{p.name}"></h:outputText></h:column><h:column id="column3">
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable><h:form>
<h:commandButton value="Checkout" action="checkout"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></f:view>
Next, define the navigation cases:
Now run it. Unfortunately, if you try to add a product to the shopping cart, the product information will still not be displayed:
Chapter 4 Creating an e-Shop 137
This is because when the click "Add to cart", a new "product" bean will be created by the UI Command component:<f:view>
<h1><h:outputText value="#{product.name}"></h:outputText></h1><h:outputText value="#{product.desc}"></h:outputText><h:form>
<h:commandButton value="Add to cart" action="#{product.addToCart}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></f:view>
Obviously the id in that new Product object is not set at all. In order to remember the product id, choose the <inputHidden> tag from the JSF HTML tag lib and drop it into the <form>:
It will create a UI Input component just like <inputText>. The difference is that
138 Chapter 4 Creating an e-Shop
that it will use an HTML Hidden renderer. When it's rendering (see the diagram below), it will get the id of the "product" bean and output it into an HTML hidden field through the HTML Hidden renderer:
When the form is submitted (see the diagram below), the value of the HTML hidden field (p02) is included in the request. The UI Input component will retrieve the value (Apply Request Values) and store it into the id of the "product" bean (Update Domain Values). Then your setId() method will update the other fields:
Alternatively, you can think of an <inputHidden> as an <inputText> except that the value is invisible in the browser and thus the user won't be able to see or change it. Now run it and it should work.
Of course, if you have added more products you will see all of them listed here. This is because the session is accumulating the product ids, and even after the web application is reloaded, the session is not affected at all. In fact, even if you restart Tomcat, the session is still there because Tomcat will save it to disk and load it back later. To get rid of the old session and get a new one, you may wait say 30 minutes, but an easier way is to close the browser and open a new one. But how does it work? To understand it, you need to understand how Tomcat
client-id: p02...
UI Hidden2: Set the id (p02)
"product"
1: Request arrives
<input type="hidden"name="client-id"value="p02">
...
UI Input
1: Get its id (e.g., p02)
3: Generate an HTML hidden field
"product"
Hiddenrenderer
value: p02...
2: Read the value
Chapter 4 Creating an e-Shop 139
and the browser co-operate to maintain the session.
How Tomcat and the browser maintain the sessionBefore further explanation, let's add a product to the shopping cart and then check the "cookies" stored in your browser. For example, for FireFox, choose "Tools | Options":
Click "Privacy" on the top, click "Show Cookies". Locate the site "localhost" and you'll find a cookie whose name is "JSESSIONID". This is how Tomcat and the browser maintain the session:
140 Chapter 4 Creating an e-Shop
When a user first accesses a web application (see the diagram below), Tomcat will generate a random number called "session id" and use it to identify the session. Then it sends this session id back to the browser and tell it to save the session id in a cookie named "JSESSIONID". In addition, Tomcat tells the browser to associate the cookie with the host "localhost" and with the path /Shop. Later when the browser accesses any page of the application (e.g., http://localhost/Shop/faces/foo.jsp), the browser finds that there is a cookie associated with this host ("localhost") and that the path /Shop/faces/foo.jsp being accessed is somewhere under the path associated with the cookie (/Shop), so it will send the content of the cookie (the session id) to the server. When Tomcat receives the session id, it can find out which session to use with the id:
Chapter 4 Creating an e-Shop 141
It means that if you delete this cookie and then access the application again, Tomcat will treat you as a new user. But why restarting the browser also works? The cookies are stored on disk and so they are persistent. So, usually restarting the browser will not delete them. However, each cookie has a maximum age (in seconds). For example, see the "Expires" field of another cookie shown below:
If that age is set to -1, it means the browser should delete the cookie when the browser is closed. As shown in the screen shot below, this is exactly the case with your JSESSIONID cookie ("at end of session"):
Session 111333
Session 123456
TomcatBrowser 1
1: Access http://localhost/Shop2: Create it
3: 111333 is the session id. Please save it into a JSESSIONID.
Host Path Name Value/Shop JSESSIONID 111333localhost
5: Access http://localhost/Shop/faces/foo.jsp
4: Save it along with the host and path
6: Look, there is a cookie for localhost and /Shop
7: Send JSESSIONID=111333 to Tomcat
8: You have this id. Use it.
142 Chapter 4 Creating an e-Shop
The checkout functionYou have implemented the shopping cart. Now, let's add the checkout function. Suppose that for a user to checkout, he must first create an account with you (to enter his credit card # and etc.) and login. For simplicity, let's assume that there are already some existing user accounts:
Email Password Credit card #[email protected] aaa 1111 2222 3333 [email protected] bbb 2222 3333 4444 5555
Suppose that a user can click a login link on the catalog page:
After logging in, the catalog page is displayed again. When a user tries to
Chapter 4 Creating an e-Shop 143
checkout, if he already logged in, he only needs to confirm the checkout:
But if he hasn't logged in when trying to checkout, he will be asked to login first, then he will see confirm page:
144 Chapter 4 Creating an e-Shop
Implementing the login functionNow, let's add the login link in showcatalog.jsp:<f:view>
<h1>Product listing</h1><h:dataTable border="1" value="#{catalog.products}" var="p">
...</h:dataTable><h:form>
<h:commandLink action="login">Login</h:commandLink></h:form>
</f:view>Create a login.jsp file:<f:view>
<h1>Login</h1><h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText><h:outputText value="item2"></h:outputText><h:outputText value="Password:"></h:outputText><h:outputText value="item4"></h:outputText>
</h:panelGrid></f:view>
Delete the item2 and item4. Drop an <inputText> in place of item2. Drop an <inputSecret> in place of item4. Make sure they are in the same <form> element:
Chapter 4 Creating an e-Shop 145
Add a dummy <outputText> and a <commandButton>:<f:view>
<h1>Login</h1><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Email:"></h:outputText><h:inputText></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret></h:inputSecret><h:outputText></h:outputText><h:commandButton value="Login"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Create a LoginRequest class:public class LoginRequest {
private String email;private String password;public String getEmail() {
return email;}public void setEmail(String email) {
this.email = email;}public String getPassword() {
return password;}public void setPassword(String password) {
this.password = password;}
}Create a managed bean from it:<faces-config ...>
...<managed-bean>
<managed-bean-name>loginRequest</managed-bean-name><managed-bean-class>shop.LoginRequest</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean>...
</faces-config>Bind it to the UI Input components:<f:view>
<f:view><h1>Login</h1><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Email:"></h:outputText><h:inputText></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret></h:inputSecret>
</h:panelGrid></h:form>
</f:view> It is just like <inputText> in that it will create a UI Input. The only difference is that the user input will appear as stars. Again, this is done using a different renderer.
146 Chapter 4 Creating an e-Shop
<h1>Login</h1><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Email:"></h:outputText><h:inputText value="#{loginRequest.email}"></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret value="#{loginRequest.password}"></h:inputSecret><h:outputText></h:outputText><h:commandButton value="Login"></h:commandButton>
</h:panelGrid></h:form>
</f:view>To handle the form submission, you need a business action method:<f:view>
<h1>Login</h1><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Email:"></h:outputText><h:inputText value="#{loginRequest.email}"></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret value="#{loginRequest.password}"></h:inputSecret><h:outputText></h:outputText><h:commandButton value="Login" action="#{loginRequest.login}"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Define the login() method:
Chapter 4 Creating an e-Shop 147
To display the error messages, add a <messages> element:<f:view>
<h1>Login</h1><h:messages></h:messages><h:form>
<h:panelGrid border="1" columns="2"><h:outputText value="Email:"></h:outputText><h:inputText value="#{loginRequest.email}"></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret value="#{loginRequest.password}"></h:inputSecret><h:outputText></h:outputText><h:commandButton value="Login" action="#{loginRequest.login}"></h:commandButton>
</h:panelGrid></h:form>
</f:view>Next, create the UserService class:public class UserService {
private List<User> users;public UserService() {
public class LoginRequest {private String email;private String password;private UserService userService;private LoginSession loginSession;public String getEmail() {
return email;}public void setEmail(String email) {
this.email = email;}public String getPassword() {
return password;}public void setPassword(String password) {
this.password = password;}public void setUserService(UserService userService) {
this.userService = userService;}public void setLoginSession(LoginSession loginSession) {
this.loginSession = loginSession;}public String login() {
try {User user = userService.findMatchingUser(this);loginSession.setUser(user);return "loggedIn";
} catch (UserNotFoundException e) {FacesContext context = FacesContext.getCurrentInstance();context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));return null;
}}
}
Try to find a matching user
Return null so that the existing page is redisplayed
Log an error message
You'll create this session scoped bean to remember the logged in user
148 Chapter 4 Creating an e-Shop
users = new ArrayList<User>();users.add(new User("[email protected]", "aaa", "1111 2222 3333 4444"));users.add(new User("[email protected]", "bbb", "2222 3333 4444 5555"));
}public User findMatchingUser(LoginRequest loginRequest) {
for (User user : users) {if (user.matches(loginRequest)) {
return user;}
}throw new UserNotFoundException();
}}
Create the User class:public class User {
private String email;private String password;private String creditCardNo;public User(String email, String password, String creditCardNo) {
this.email = email;this.password = password;this.creditCardNo = creditCardNo;
}public boolean matches(LoginRequest loginRequest) {
return email.equals(loginRequest.getEmail())&& password.equals(loginRequest.getPassword());
}public String getEmail() {
return email;}public String getPassword() {
return password;}public String getCreditCardNo() {
return creditCardNo;}
}Create the UserNotFoundException class:public class UserNotFoundException extends RuntimeException {}
Create the LoginSession class:public class LoginSession {
private User user;public void setUser(User user) {
this.user = user;}public User getUser() {
return user;}
}Create the various beans and hook them up:<faces-config ...>
...<managed-bean>
<managed-bean-name>loginRequest</managed-bean-name><managed-bean-class>shop.LoginRequest</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>userService</property-name><property-class>shop.UserService</property-class><value>#{userService}</value>
</managed-property>
Chapter 4 Creating an e-Shop 149
<managed-property><property-name>loginSession</property-name><property-class>shop.LoginSession</property-class><value>#{loginSession}</value>
</managed-property></managed-bean><managed-bean>
<managed-bean-name>loginSession</managed-bean-name><managed-bean-class>shop.LoginSession</managed-bean-class><managed-bean-scope>session</managed-bean-scope>
</managed-bean><managed-bean>
<managed-bean-name>userService</managed-bean-name><managed-bean-class>shop.UserService</managed-bean-class><managed-bean-scope>application</managed-bean-scope>
</managed-bean>...
</faces-config>As the "loginSession" bean is in the session scope, all its fields must implement Serializable:public class LoginSession {
private User user;public void setUser(User user) {
this.user = user;}public User getUser() {
return user;}
}
So, modify the User class:public class User implements Serializable {
private String email;private String password;private String creditCardNo;
public User(String email, String password, String creditCardNo) {this.email = email;this.password = password;this.creditCardNo = creditCardNo;
}public boolean matches(LoginRequest loginRequest) {
return email.equals(loginRequest.getEmail())&& password.equals(loginRequest.getPassword());
}}
Setup the navigation:
150 Chapter 4 Creating an e-Shop
Now, run the application and try to login using a valid account or an invalid account. It should work.
There is a minor issue though: As shown below, the code to log an error message is using JSF specific classes such as FacesContext and is thus polluting this class:public class LoginRequest {
private String email;private String password;private UserService userService;private LoginSession loginSession;...public String login() {
try {User user = userService.findMatchingUser(this);loginSession.setUser(user);return "loggedIn";
} catch (UserNotFoundException e) {FacesContext context = FacesContext.getCurrentInstance();context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));return null;
}}
}You may put this code into an action listener like this:public class LoginValidatingListener implements ActionListener {
private UserService userService;private LoginRequest loginRequest;...public void processAction(ActionEvent event)
throws AbortProcessingException {try {
userService.findMatchingUser(loginRequest);} catch (UserNotFoundException e) {
FacesContext context = FacesContext.getCurrentInstance();context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));throw new AbortProcessingException();
}}
}<f:view>
<h1>Login</h1>
Chapter 4 Creating an e-Shop 151
<h:form><h:panelGrid border="1" columns="2">
<h:outputText value="Email:"></h:outputText><h:inputText value="#{loginRequest.email}"></h:inputText><h:outputText value="Password:"></h:outputText><h:inputSecret value="#{loginRequest.password}"></h:inputSecret><h:outputText></h:outputText><h:commandButton value="Login" action="#{loginRequest.login}">
<f:actionListener type="shop.LoginValidatingListener"/></h:commandButton>
</h:panelGrid></h:form>
</f:view>However, on one hand this is quite some extra work. On the other hand, sometimes errors will occur only when you try to perform a business action. For example, when withdrawing from an account, checking if the balance is enough beforehand is useless due to concurrent accesses. Anyway, in this case you'll simply leave it as is without using a separate action listener.
Implementing the checkout functionNow, let's work on the checkout function. Create confirm.jsp:
Create CheckoutService.java:public class CheckoutService {
private Cart cart;private Catalog catalog;public void setCart(Cart cart) {
this.cart = cart;}public void setCatalog(Catalog catalog) {
this.catalog = catalog;}public double getTotal() {
double total = 0;for (String pid : cart.getProductIds()) {
total += catalog.getProduct(pid).getPrice();}return total;
}public String checkout() {
// charge the user, schedule the delivery and etc.return "charged";
}
<f:view><h1>Confirm your order</h1><p>You're going to pay <h:outputText
value="#{checkoutService.total}"></h:outputText> with your credit card<h:outputText value="#{loginSession.user.creditCardNo}"></h:outputText>.</p><h:form>
<h:commandButton value="Confirm" action="#{checkoutService.checkout}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></f:view>
You'll create this "checkoutService" bean
Get the user from the login session and then get the credit card # from the user
152 Chapter 4 Creating an e-Shop
}Define the "checkoutService" bean and hook up the beans:<faces-config ...>
...<managed-bean>
<managed-bean-name>checkoutService</managed-bean-name><managed-bean-class>shop.CheckoutService</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>
<property-name>cart</property-name><property-class>shop.Cart</property-class><value>#{cart}</value>
</managed-property><managed-property>
<property-name>catalog</property-name><property-class>shop.Catalog</property-class><value>#{catalog}</value>
</managed-property></managed-bean>...
</faces-config>Create the thankyou.jsp file:<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Insert title here</title></head><body>Thank you for your order!</body></html>
Define the navigation:
Chapter 4 Creating an e-Shop 153
Now run it. Login, add some products, checkout and confirm. It should work fine:
154 Chapter 4 Creating an e-Shop
What if the user hasn't logged in yet? In that case, you should send him to the login page. After logging in, he should be returned to the confirm page automatically. To do that, create a ForceLoginPhaseListener class:
Chapter 4 Creating an e-Shop 155
You need to register this phase listener with the JSF engine. To do that, open faces-config.xml, choose the "Others" tab and click "Phase Listener":
Click here
public class ForceLoginPhaseListener implements PhaseListener {public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;}public void beforePhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();String viewId = context.getViewRoot().getViewId();if (viewId.equals("/confirm.jsp")) {
Application app = context.getApplication();LoginSession loginSession = (LoginSession) app
.evaluateExpressionGet(context, "#{loginSession}",LoginSession.class);
if (loginSession.getUser() == null) {ViewHandler viewHandler = app.getViewHandler();UIViewRoot viewRoot = viewHandler.createView(context, "/login.jsp");context.setViewRoot(viewRoot);
}}
}public void afterPhase(PhaseEvent event) {}
}
It will be called before and after a certain phase. Which phase? It tells the JSF Engine it is only interested in the Render Response phase:
Access the logged in user
The JSP engine is the view handler
Ask the view handler to build the JSF component tree
View idTell the JSF engine to render this view
About to render the confirm page?
156 Chapter 4 Creating an e-Shop
Click "Add" and specify your ForceLoginPhaseListener. If you're curious, you can check the code in the "Source" tab:<faces-config ...>
<managed-bean>...
</managed-bean>...<navigation-rule>
...</navigation-rule><lifecycle>
<phase-listener>shop.ForceLoginPhaseListener</phase-listener></lifecycle>
</faces-config>That's it. Now try to go to the confirm page without logging in. It will send you to the login page. But after logging in, how to return to the original page? In the current case, there is only one protected page (the confirm page) so after logging in, the login page should definitely return the user to the confirm page. But in principle, there could be multiple protected pages such as a page allowing the user to edit his profile (see the diagram below). Then after logging in, the user should be returned to the original page he requested:
If you use the navigation system in JSF, you'll have to add one navigation case for each protected page:
It means whenever you have one more protected page, you'll have to add one more navigation case, meaning that the login page will never become stable. To solve this problem, you can remember the originally requested page in the phase listener:public class ForceLoginPhaseListener implements PhaseListener {
public PhaseId getPhaseId() {return PhaseId.RENDER_RESPONSE;
}public void beforePhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();String viewId = context.getViewRoot().getViewId();
confirm
login
editprofile
1: Try to render a protected page
2: Redirected to the login page
3: Return to the original requested page
...
login
checkout
editProfile
confirm editprofile ...
...
Chapter 4 Creating an e-Shop 157
if (viewId.equals("/confirm.jsp")) {Application app = context.getApplication();LoginSession loginSession = (LoginSession) app
.evaluateExpressionGet(context, "#{loginSession}", LoginSession.class);if (loginSession.getUser() == null) {
loginSession.setOriginalViewId(viewId);ViewHandler viewHandler = app.getViewHandler();UIViewRoot viewRoot = viewHandler.createView(context, "/login.jsp");context.setViewRoot(viewRoot);
}}
}public void afterPhase(PhaseEvent event) {}
}Add this field in the LoginSession class:
Retrieve it after logging in:
public class LoginSession {private User user;private String originalViewId;public void setUser(User user) {
this.user = user;}public User getUser() {
return user;}public String getOriginalViewId() {
String temp = originalViewId;originalViewId = null;return temp;
}public void setOriginalViewId(String originalViewId) {
this.originalViewId = originalViewId;}
}
Once it is read, clear it.
public class LoginRequest {...public String login() {
try {User user = userService.findMatchingUser(this);loginSession.setUser(user);String originalViewId = loginSession.getOriginalViewId();if (originalViewId != null) {
FacesContext context = FacesContext.getCurrentInstance();ViewHandler viewHandler = context.getApplication().getViewHandler();UIViewRoot viewRoot = viewHandler.createView(context, originalViewId);context.setViewRoot(viewRoot);return null;
} else {return "loggedIn";
}} catch (UserNotFoundException e) {
FacesContext context = FacesContext.getCurrentInstance();context.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Login failed", null));return null;
}}
}
Retrieve the originally requested view id
Create the component tree for the original pageTell the JSF
engine to render it
Tell the navigation system to NOT change the view root
It is possible that the user explicitly clicked the login link to go to the login page instead of being redirected to here. In that case there is no original view id (null).
158 Chapter 4 Creating an e-Shop
Now restart the browser to get rid of the session. Then run the application again. Try to checkout without logging in. It should ask you to login and then return you to the confirm page:
Implementing logoutSuppose that you'd like to allow the user to logout:
Chapter 4 Creating an e-Shop 159
The minimum that you need to do is to remove the User object from the "loginSession" bean. However, a better way is to delete the session altogether (including the shopping cart, for example). To do that, modify showcatalog.jsp:
Create the LogoutActionListener class:
<f:view><h1>Product listing</h1><h:dataTable border="1" value="#{catalog.products}" var="p">
...</h:dataTable><h:form>
<h:commandLink action="login">Login</h:commandLink> <h:commandLink>
<h:outputText value="Logout"></h:outputText><f:actionListener type="shop.LogoutActionListener"/>
</h:commandLink></h:form>
</f:view>
It stands for non-breaking space. That is, a space between the two links.
Remove the session in this action listener. Why not specify a method in the "action" attribute? You could do that but removing the session is a UI specific task, not a business task. So an action listener is better.
Do not set the outcome ("action") so that it remains on the catalog page
160 Chapter 4 Creating an e-Shop
Run it. Login and then logout. Then try to checkout and it should ask you to login again.
SummaryA facet is a child component that is subjected to special processing by its parent.
A request has a table of attributes. Each attribute has a name and a value (Object). It allows you to give a name to an object. It is like request-scoped managed bean except that it doesn't create the object; it only associates it with a name.
To loop some items in a table, if the number of items can only be determined at runtime, use the UI Data component. You provide a List of items to it and it will loop through it for each item. Its children must be UI Column components. Each UI Column represents a column in the table. For each item, the UI Data will store the item into an attribute and ask each UI Column to render for it. Each UI Column will render its own child components (excluding the "header" facet). A UI Column can optionally have a facet named "header". In that case, the UI Data will ask the UI Columns to render an extra header row before it starts to process the data rows.
On form submission, the UI Data will loop through the items again, giving each component inside an opportunity to apply request values, process validations, update domain values and invoke application in each respective phase.
As one component inside the UI Data will render multiple times and generate multiple HTML elements, the UI Data will pretend to have a different client id for each item so that the component will have a unique client id for each item.
Because the current item is stored into an attribute, not a bean, if you need to
public class LogoutActionListener implements ActionListener {public void processAction(ActionEvent event)
throws AbortProcessingException {FacesContext context = FacesContext.getCurrentInstance();ExternalContext externalContext = context.getExternalContext();Object session = externalContext.getSession(false);HttpSession httpSession = (HttpSession) session;httpSession.invalidate();
}}
The external context means the platform JSF engine is running on. In this case, it's Tomcat.
Get access to the session. The session is maintained by the platform (Tomcat).
The session is an Object, not an HttpSession. This is because JSF could potentially run on a platform that doesn't use HTTP. Here you're sure it is using HTTP, so you can typecast it.
Remove the session
Chapter 4 Creating an e-Shop 161
pass it onto the next page, most likely you'll want to use the Set Property action listener.
To create a link, use the UI Command component with a link renderer. In terms of behavior, it is just like a UI Command component with a button renderer.
For a UI Command, in addition to setting an outcome in its "action" attribute, you can also specify a method. This is useful when you need to perform a business action. That method should return a string indicating the outcome. If you need to perform a UI specific action, it's better to add an action listener to the UI Command.
A UI Input can be rendered such that user input is echoed as stars. This is good for password input.
If you display the properties of a request-scoped bean using a page, you must be careful when the form is submitted because a new request-scoped bean will be created and its id will not have been set. Usually you will use a UI Input component along with an HTML Hidden renderer to store the id in the browser as a hidden field. You need to load the other fields when the id is set.
The JSF engine uses a view handler to create the JSF component tree from a specified view id. By default the JSP engine is the view handler. You need to use a view handler when you want to by-pass the JSF navigation system: You need to load a view and set it as the one to be render.
A session is identified by the session stored in a cookie in the browser. To start a new session, either restart the browser or delete that cookie. To remove the session on the server, call invalidate() on the session. This is commonly done when logging out. All session scoped managed beans must implement Serializable. Injected fields should be marked as transient.
You should never inject any managed bean into a session scoped bean, as after deseralization the field will become null.
A phase listener will be notified before entering a phase or after exiting from a phase. You can use it to make sure the user has logged in before rendering a certain pages.
The external context means the platform on which the JSF engine is running. In your case it is Tomcat. JSF assumes this platform is responsible for maintaining the session.
163
Chapter 5 Chapter 5 Building Interactive Pages with AJAX
164 Chapter 5 Building Interactive Pages with AJAX
What's in this chapter?In this chapter you'll learn how to build pages that are more interactive than normal HTML forms using a technique called AJAX.
A sample AJAX applicationSuppose that you'd like to develop an application that allows the user to view a list of FAQ like this:
If the user clicks on a question, its answer will be shown instantly, while the rest of page is not refreshed:
If he clicks that question again, the answer will be hidden again. As a first step, you'll show a single question only. To do that, create a Dynamic Web Project named FAQ with JSF enabled. Create a listfaq.jsp page:
Chapter 5 Building Interactive Pages with AJAX 165
Create the FAQService class in the faq package:
Create a bean for it. As it must remember the expanded status, you need to use the session scope:<faces-config ...>
<managed-bean><managed-bean-name>faqService</managed-bean-name><managed-bean-class>faq.FAQService</managed-bean-class><managed-bean-scope>session</managed-bean-scope>
</managed-bean></faces-config>
Therefore, the class must implement Serializable:public class FAQService implements Serializable {
...}
Add the project as a module to the Tomcat instance and then run it. It should work:
public class FAQService {private String questionText = "How to run Eclipse?";private String answerText = "Double click its icon.";private boolean isExpanded = false;public String getQuestionText() {
return questionText;}public String getAnswerText() {
return answerText;}public String trigger() {
isExpanded = !isExpanded;return null;
}public boolean isExpanded() {
return isExpanded;}
}
Return null so that the current page is redisplayed
At the beginning, don't show the answer.
...<f:view>
<div><h:form><h:commandLink action="#{faqService.trigger}">
<h:outputText value="#{faqService.questionText}"></h:outputText>
</h:commandLink></h:form></div><div>
<h:outputTextvalue="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></div>
</f:view>...
You'll trigger the display of the answer in this method
You'll create an isExpanded() method. If it returns true, the component will render itself, otherwise it won't. If the "rendered" attribute is not set, it is assumed to be true.
166 Chapter 5 Building Interactive Pages with AJAX
Refreshing the question onlyNext, you'd like to refresh that question only, not the whole page. To do that (see the diagram below), you need to generate an HTML element enclosing the question and answer. Suppose that it's generated by a component whose id is "qa" so its client id is also "qa" as it is not in a form. Make sure some Javascript is generated for the onclick event of the link (the <a> element). When the link is clicked, that Javascript will send a request to your application, which will reverse the visibility of the answer and then render the "qa" component only (not the whole page). It will generate an HTML element whose HTML id is its client id ("qa"). Your application will then send this element back to the Javascript in the browser. The Javascript will look up an element in the existing HTML page whose HTML id is "qa" and replace it:
Chapter 5 Building Interactive Pages with AJAX 167
To implement this idea, you need to use the JBoss RichFaces components. So, modify web.xml:<?xml version="1.0" encoding="UTF-8"?><web-app ...>
<display-name>FAQ</display-name><welcome-file-list>
<welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file><welcome-file>default.html</welcome-file><welcome-file>default.htm</welcome-file><welcome-file>default.jsp</welcome-file>
</welcome-file-list><servlet>
<servlet-name>Faces Servlet</servlet-name><servlet-class>javax.faces.webapp.FacesServlet</servlet-class><load-on-startup>1</load-on-startup>
</servlet><servlet-mapping>
<servlet-name>Faces Servlet</servlet-name><url-pattern>/faces/*</url-pattern>
</servlet-mapping><context-param>
<param-name>org.richfaces.SKIN</param-name><param-value>blueSky</param-value>
</context-param><filter>
<display-name>RichFaces Filter</display-name><filter-name>richfaces</filter-name><filter-class>org.ajax4jsf.Filter</filter-class>
</filter><filter-mapping>
<filter-name>richfaces</filter-name><servlet-name>Faces Servlet</servlet-name><dispatcher>REQUEST</dispatcher><dispatcher>FORWARD</dispatcher>
<html><span id="qa">
<div><a onclick="some Javascript">How to...</a>
</div></span></html>
Your app
2: The link is clicked. The Javascript sends a request to your application.
<span id="qa"><div>
<a onclick="...">How to...</a></div><div>Double click...</div>
</span>
3: Only render the component whose id is "qa". This time, the answer is displayed. The client id "qa" is used as the HTML id.
1: Make sure there is an HTML element enclosing the whole question and answer
4: Return this HTML element to the Javascript. It will look up the element with "qa" as the ID and replace it.
168 Chapter 5 Building Interactive Pages with AJAX
<dispatcher>INCLUDE</dispatcher></filter-mapping>
</web-app>Copy the required jars files into WEB-INF/lib:
Modify listfaq.jsp:
Run it and it should work.
<%@taglib prefix="a4j" uri="http://richfaces.org/a4j"%>...<f:view>
<h:panelGroup id="qa"><div>
<h:form><a4j:commandLink
action="#{faqService.trigger}"reRender="qa">
<h:outputText value="#{faqService.questionText}"></h:outputText></a4j:commandLink></h:form>
</div><div>
<h:outputTextvalue="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></div>
</h:panelGroup></f:view>
<a onclick="some Javascript">How to...</a>
1: It will set the "onclick" event handler to some Javascript
2: If clicked, it will still call this trigger() method in your application
3: Render this "qa" component only
Use the <commandLink> in the Ajax4Jsf tag lib
Chapter 5 Building Interactive Pages with AJAX 169
Refreshing the answer itselfAt the moment you're refreshing the question and the answer. However, is it possible to refresh just the answer? At present, if the answer component is not rendered, it will not output any HTML element. Then there will be no HTML element to replace! To solve this problem, you can put it inside a <panelGroup> and re-render that <panelGroup> instead:<f:view>
<h:panelGroup id="qa"><div>
<h:form><a4j:commandLink
action="#{faqService.trigger}"reRender="qa answerPanel">
<h:outputText value="#{faqService.questionText}"></h:outputText></a4j:commandLink></h:form>
</div><div>
<h:panelGroup id="answerPanel"><h:outputText
value="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></h:panelGroup>
</div></h:panelGroup>
</f:view>Now run it and it should continue to work.
Giving rating to a questionSuppose that you'd like to allow the user to rate the helpfulness of the question (and its answer). The average rating so far is displayed at the end of the question:
Again, you don't want to refresh the whole page, but just the relevant parts. This is just like the <a4j:commandLink>, but now you need a button. This is done by using an <a4j:commandButton>:
170 Chapter 5 Building Interactive Pages with AJAX
Modify the FAQService class:public class FAQService implements Serializable {
private String questionText = "How to run Eclipse?";private String answerText = "Double click its icon.";private boolean isExpanded = false;private int rating; private int noRatings = 0; private int totalRating = 0;public String getQuestionText() {
return questionText;}public String getAnswerText() {
return answerText;}public String trigger() {
isExpanded = !isExpanded;return null;
}public boolean isExpanded() {
return isExpanded;}public String rate() {
totalRating += rating;noRatings++;return null;
}public int getAverageRating() {
return noRatings == 0 ? 0 : totalRating / noRatings;}public int getRating() {
return rating;}public void setRating(int rating) {
this.rating = rating;}
<f:view><div>
<h:form><a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputTextvalue="#{faqService.questionText} (#{faqService.averageRating})"id="qt">
</h:outputText></a4j:commandLink></h:form><h:form style="float:right">
<h:inputText size="2" value="#{faqService.rating}"></h:inputText><a4j:commandButton
value="Rate"action="#{faqService.rate}"reRender="qt">
</a4j:commandButton></h:form>
</div><div>
<h:panelGroup id="answerPanel"><h:outputText
value="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></h:panelGroup>
</div></f:view>
This part is just a literal string which will be output as is
Literal string again
Put the form to the right
<commandButton> from the Ajax4Jsf tag lib. It will put Javascript into the onclick handler of the HTML submit button.
Refresh this component
Chapter 5 Building Interactive Pages with AJAX 171
}Now run it and it should work:
What if the user enters something invalid such as "abc" as the rating? Then it should display an error. To do that, modify listfaq.jsp:
Now run it and it should work:
<f:view><h:panelGroup id="msgs">
<h:messages></h:messages></h:panelGroup><div>
<h:form><a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputTextvalue="#{faqService.questionText} (#{faqService.averageRating})"id="qt">
</h:outputText></a4j:commandLink></h:form><h:form style="float:right">
<h:inputTextlabel="rating" size="2" value="#{faqService.rating}"></h:inputText>
<a4j:commandButtonvalue="Rate"action="#{faqService.rate}"reRender="qt,msgs">
</a4j:commandButton></h:form>
</div><div>
<h:panelGroup id="answerPanel"><h:outputText
value="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></h:panelGroup>
</div></f:view>
<messages> will render nothing if there is no error. Then later you'll be unable to refresh the HTML element. So, put it inside a <panelGroup>.
Make the error message look better
Refresh both the question text and the error messages
172 Chapter 5 Building Interactive Pages with AJAX
What if you'd like to submit the form when the user moves the mouse over the Rate button (even without clicking it)? You can do it this way:
Now run it and it should work.
Using a modal panel to get the ratingThe form is making the screen too crowded. Therefore, you'd like to change it
<f:view><h:panelGroup id="msgs">
<h:messages></h:messages></h:panelGroup><div>
<h:form><a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputTextvalue="#{faqService.questionText} (#{faqService.averageRating})"id="qt">
</h:outputText></a4j:commandLink></h:form><h:form style="float:right">
<h:inputTextlabel="rating" size="2" value="#{faqService.rating}"></h:inputText>
<h:commandButton value="Rate" action="..." reRender="..."><a4j:support
event="onmouseover"action="#{faqService.rate}"reRender="qt,msgs">
</a4j:support></h:commandButton>
</h:form></div><div>
<h:panelGroup id="answerPanel"><h:outputText
value="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></h:panelGroup>
</div></f:view>
<inputtype="submit"onmouseover="some Javascript">
1: Generate the <input> element
2: Set the onmouseover event handler
3: When the mouse is over the submit button, send a request to your application calling this method.
4: Refresh the components
Use a normal JSF commandButton
No need for any behavior
Chapter 5 Building Interactive Pages with AJAX 173
like this (see the screen shot below): The user can click a "Rate" link. Then it will open a modal panel to show the form to allow him to enter the rating:
After closing the form, the average rating for the question will be refreshed. Now, let's do it. Modify listfaq.jsp:
174 Chapter 5 Building Interactive Pages with AJAX
Now run it and it should work. For the moment, if the user enters some garbage, it will close the modal panel and display the error in the main page. What if you'd like to display the error in the modal panel and not close it as shown below?
<%@taglib prefix="rich" uri="http://richfaces.org/rich"%>...<f:view>
<h:panelGroup id="msgs"><h:messages></h:messages>
</h:panelGroup><div>
<h:form><a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputTextvalue="#{faqService.questionText} (#{faqService.averageRating})"id="qt">
</h:outputText></a4j:commandLink><a href="javascript:Richfaces.showModalPanel('mp')">Rate</a></h:form><rich:modalPanel id="mp">
<f:facet name="header"><h:outputText value="Enter a rating"></h:outputText>
</f:facet><h:form style="float:right">
<h:inputTextlabel="rating" size="2" value="#{faqService.rating}"></h:inputText>
<h:commandButton value="Rate"><a4j:supportevent="onmouseover"action="#{faqService.rate}"reRender="qt,msgs"oncomplete="Richfaces.hideModalPanel('mp')"></a4j:support>
</h:commandButton></h:form>
</rich:modalPanel></div><div>
<h:panelGroup id="answerPanel"><h:outputText
value="#{faqService.answerText}"rendered="#{faqService.expanded}">
</h:outputText></h:panelGroup>
</div></f:view>
This is the modal panel. Initially it is hidden.
When the user clicks on this link, this Javascript will execute and will show the modal panel
Component id
The header of the modal panel. It is optional.
Everything here will appear in the modal panel
This Javascript will be executed after the HTML elements have been refreshed. Here, you will hide the modal panel.
Chapter 5 Building Interactive Pages with AJAX 175
To do that, modify the code:
Run it and it should work.
<f:view><h:panelGroup id="msgs">
<h:messages></h:messages></h:panelGroup><div>
<h:form><a4j:commandLink action="#{faqService.trigger}" reRender="answerPanel">
<h:outputTextvalue="#{faqService.questionText} (#{faqService.averageRating})"id="qt">
</h:outputText></a4j:commandLink><a href="javascript:Richfaces.showModalPanel('mp')">Rate</a></h:form><rich:modalPanel id="mp">
<f:facet name="header"><h:outputText value="Enter a rating"></h:outputText>
</f:facet><h:panelGroup id="msgs">
<h:messages id="m"></h:messages></h:panelGroup><h:form style="float:right">
<h:inputTextlabel="rating" size="2" value="#{faqService.rating}"></h:inputText>
<h:commandButton value="Rate"><a4j:supportevent="onmouseover"action="#{faqService.rate}"reRender="qt,msgs"oncomplete="if (document.getElementById('m')==null)
Richfaces.hideModalPanel('mp')"></a4j:support>
</h:commandButton></h:form>
</rich:modalPanel></div>...
</f:view>
Move the messages to there Check if there is any actual
error message, if no, hide the modal panel. If yes, do nothing so it remains visible.
176 Chapter 5 Building Interactive Pages with AJAX
Finally, the modal panel may be a bit too large. You can set its initial size to say 200 pixels x 100 pixels:<rich:modalPanel id="mp" width="200" height="140">
...</rich:modalPanel>
Run it and the panel should be smaller.
Setting the look and feel with skinsAll RichFaces components supports so-called skins. For example, you can define a skin as:
RichFaces comes with several pre-defined skins. They're named: blueSky, classic, deepMarine and etc. To choose which one to use, do it in web.xml:<web-app ...>
...<context-param>
<param-name>org.richfaces.SKIN</param-name><param-value>blueSky</param-value>
</context-param>...
</web-app>Of course, the normal JSF components won't use the skin. To solve the problem, put everything inside a <rich:panel>:<f:view>
<rich:panel><div>
...THE QUESTION AND THE MODAL PANEL...</div><div>
...THE ANSWER...</div>
</rich:panel></f:view>
The <rich:panel> will generate a <div> like:
text font: Arialtext color: bluetext size: 12ptbackground color: yellow...
Skin name: s1
Chapter 5 Building Interactive Pages with AJAX 177
Now run it again and you should notice that font has changed:
SummaryAJAX means that when a certain event occurs in the browser, a request is sent to the application so that it can perform some action and then only parts of a page are refreshed. You can use an <a4j:commandLink> for a link, an <a4j:commandButton> for a button or an <a4j:support> for any other events. In these tags, you also specify an action method to execute in the application and a list of component ids which are to be refreshed.
A component can be excluded from rendering. In that case, normally it will generate nothing. If you need to show it using AJAX, you can put it inside a panel. Similarly, some components such as the UI Messages may output nothing in normal use. To update them using AJAX, put them inside a panel.
<div class="foo"><span...><form...><input...>
</div>
foo is a CSS class whose definition is generated at runtime:
foo {font-family: Arial;color: ...;
}
The value comes the skin
These elements are generated by the standard JSF components. They don't have CSS class applied so they will inherit the look & feel from the <div>.
text font: Arialtext color: bluetext size: 12ptbackground color: yellow...
Skin name: s1
178 Chapter 5 Building Interactive Pages with AJAX
You can show or hide a modal panel using Javascript.
A skin defines a look and feel including font family, font size, color and etc. All RichFaces components supports skins. It works by creating and using CSS classes at runtime.
179
Chapter 6 Chapter 6 Using Facelets
180 Chapter 6 Using Facelets
What's in this chapter?In this chapter you'll learn how to use a very useful JSF technology called Facelets.
Setting up IDE support for FaceletsEclipse has no built-in support for Facelets. Fortunately, there is a 3rd party plugin that does, which is called the JBoss IDE tools. To install it, in Eclipse, choose "Help | Software Updates | Find and Install", choose "Search for new features to install". Click "New Remote Site" and enter the information as shown below:
Later, choose to install the JBoss IDE:
Continue to finish the installation.
Chapter 6 Using Facelets 181
Creating a Facelets projectNow, let's implement the FAQ page using Facelets. In Eclipse, choose "File | New | Others", then choose "JBoss Tools Web | JSF | JSF Project":
Click Next. Then enter the information as shown below:
182 Chapter 6 Using Facelets
Click Next. It will suggest to add this project as a module to the Tomcat server instance:
Any project name will do
Choose this to enable Facelets support in this project
It means no sample code
It won't put the JSF jars into your WEB-INF/lib. It's OK, you'll copy the files there yourself.
Chapter 6 Using Facelets 183
This is what you want. Click Finish. It will suggest switching to the Web Development perspective (provided by the JBoss IDE Tools). Say Yes. Now the project is created. Copy the JSF RI jar files and those of RichFaces into WEB-INF/lib:
Next, right click the WebContent folder and choose "New | XHTML File". Enter listfaq as the name:
184 Chapter 6 Using Facelets
Click Next. It will then asks you what tag libs you'd like to use in this page. Choose as shown below:
Click Finish. Then you'll see a visual page editor very much like the one you've been using:
Chapter 6 Using Facelets 185
In addition, the file is a strict XML file (a JSP file is not XML) and a tag lib is used as an XML namespace:
The palette
This is the visual editing area
This is the source code
186 Chapter 6 Using Facelets
Why is being strict XML significant? It means you can use the many XML editors to edit the file.
Now modify the file either by entering the code or using drag and drop. Content assist will work as usual:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:a4j="http://richfaces.org/a4j">
<body><div>
<h:form><h:commandLink
value="#{faqService.questionText}" action="#{faqService.trigger}"></h:commandLink>
</h:form></div><div>
<h:outputTextvalue="#{faqService.answerText}" rendered="#{faqService.expanded}">
</h:outputText></div></body></html>
Copy the FAQService class from the previous chapter. Create the bean definition for it. Double click on the faces-config.xml file, the JBoss XML Editor will open:
<!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:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:a4j="http://richfaces.org/a4j"></html>
listfaq.xhtml
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%><%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@ page language="java"
contentType="text/html; charset=ISO-8859-1"pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">...<f:view>
...
listfaq.jsp
Those are not allowed in XML!
"f" is not a prefix for an XML namespace
It is a prefix for an XML namespace
Chapter 6 Using Facelets 187
Choose "Managed Beans" on the left and then click "Add" to create a session scoped bean:
Go ahead to finish it. Start the Tomcat instance and try to access it. Here is the URL:
188 Chapter 6 Using Facelets
Run it and it should work. However, you may ask, so what? It is not that different from using JSP. Read on.
Creating your own tagBefore listing multiple questions, it would be great if you had a tag such as <qa> that will display a question and its answer. Then you would use it like:
To create such a Facelet tag (and the tag lib), create a META-INF folder in your Java source folder and then create a file foo.taglib.xml in it (The file name is unimportant as long as it ends with .taglib.xml). The content is like:
http://localhost:8080/FAQFacelets/listfaq.jsf
WebContent
listfaq.xhtml
...
Context path, as usual.
Tomcat JSF engine
1: Look, jsf extension.
2: You handle it
3: What's the path? Oh, it's /listfaq.jsf.
Faceletengine
4: Replace the extension with xhtml so it gets /listfaq.xhtml as the view id. Ask the view handler to load the view.
5: Use the view id as a relative path to read the file and create a component tree
<htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:a4j="http://richfaces.org/a4j"xmlns:foo="http://foo.com">
<body><h:dataTable value="#{faqService.questions}" var="q">
<h:column><foo:qa question="#{q}"/>
</h:column></h:dataTable></body></html>
This namespace represents your tag lib
Assume that each question object contains the question text, the answer text and the rating information.
Assume that each question object contains the question text, the answer text and the rating information.
Chapter 6 Using Facelets 189
Create the qa.xhtml in the same META-INF folder:
Create the Question class. It is just a copy of the FAQService class:package faq;
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"><facelet-taglib xmlns="http://java.sun.com/JSF/Facelet" >
<namespace>http://foo.com</namespace><tag>
<tag-name>qa</tag-name><source>qa.xhtml</source>
</tag></facelet-taglib>
Define a Facelet tag lib
The XML elements used here to define your own tag (e.g., <facelet-taglib> and <tag>) are all in this facelet namespace
A tag lib is identified by a URL. Here you use this URL for your tag lib.
Define one tag here. You could define many tags in a tag lib.
The tag is <qa>
What does the <qa> tag mean? You'll define its meaning using the file qa.xhtml. This is a relative path to this taglib.xml file.
<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:ui="http://java.sun.com/jsf/facelets"xmlns:a4j="http://richfaces.org/a4j">
<body><ui:component><div>
<h:form><h:commandLink
value="#{q.questionText}" action="#{q.trigger}"></h:commandLink>
</h:form></div><div>
<h:outputTextvalue="#{q.answerText}" rendered="#{q.expanded}">
</h:outputText></div></ui:component></body></html>
This is the Facelet tag lib. It provides tags such as <component> to let you define your own tags.
This is the meaning of the <qa> tag. That is, whenever the Facelet engine sees a <qa> element, it will trace into here and create a JSF subtree:
<f:panelGroup><foo:qa/>
</f:panelGroup>
UI Panel
Dummy
UI Output<div>
UI Form
...
Everything outside will not go into the JSF component tree and thus will have no effect on the output. The code is there so that it looks good in a visual editor.
Assume that there is an attribute "q" containing the Question object
190 Chapter 6 Using Facelets
public class Question implements Serializable {private String questionText = "How to run Eclipse?";private String answerText = "Double click its icon.";private boolean isExpanded = false;private int rating; private int noRatings = 0; private int totalRating = 0;public String getQuestionText() {
return questionText;}public String getAnswerText() {
return answerText;}public String trigger() {
isExpanded = !isExpanded;return null;
}public boolean isExpanded() {
return isExpanded;}public String rate() {
totalRating += rating;noRatings++;return null;
}public int getAverageRating() {
return noRatings == 0 ? 0 : totalRating / noRatings;}public int getRating() {
return rating;}public void setRating(int rating) {
this.rating = rating;}
}Modify the FAQService class:package faq;
public class FAQService implements Serializable {private Question q1 = new Question();public Question getQ1() {
return q1;}
}Modify listfaq.xhtml:
Chapter 6 Using Facelets 191
Now run it and it should continue to work.
How is this better than JSP? First, with JSP you can also create your own tag, but to define the meaning of the tag, you have to create a Java class and can't reuse the existing tags. In contrast, with Facelets, you can use existing tags to define the new tag. This is a huge difference.
Second, a JSP tag can access its attributes such as "q" above at runtime, but they will NOT be available when the JSF component tree is rendered. To create such a variable, it may have to use a request attribute or a bean, but then it will become a global entity that may clash with those from other tags.
Due to these reasons, Facelets is generally considered a better technology than JSP to be used with JSF.
However, it does have some drawbacks. First, Facelets is not in the JSF standard (even though it seems becoming a de-facto industry standard).
Second, for example, even though you're using the familiar tags such as <h:inputText> in your Facelets application, it is not the same <h:inputText> you used in your JSP applications. It was rewritten from scratch while making sure they look the same. It means if you buy/download a component library, you have to check if it supports Facelets. For example, RichFaces does.
Hiding JSF tags into HTML tagsCurrently the qa.xhtml file contains many JSF tags such as <h:form>. It means it is impossible to edit the file using say Dreamweaver. To solve this problem, Facelets allows you to hide a JSF tag into an HTML tag like:
<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:ui="http://java.sun.com/jsf/facelets"xmlns:foo="http://foo.com"xmlns:a4j="http://richfaces.org/a4j">
<body><foo:qa q="#{faqService.q1}" />
</body></html>
Faceletengine
1: Need to trace into its definition to build the tree
Nam e Valueq......
2: Build a table for variables for each attribute in the tag ("q" here). Evaluate the expression to get the value.
q1
...qa.xhtml
3: Trace into the definition to build the tree
Dummy
UI Form
UICommand
value: #{q.questionText}action: ...
4: When the tree is rendered
5: It will look up this variable table first before trying the beans and etc.
192 Chapter 6 Using Facelets
Therefore, you can modify the qa.xhtml file like:<html xmlns="http://www.w3.org/1999/xhtml" ...><body><ui:component><div>
<form jsfc="h:form"><a jsfc="h:commandLink"
value="#{q.questionText}" action="#{q.trigger}"></a>
</form></div><div>
<span jsfc="h:outputText"value="#{q.answerText}" rendered="#{q.expanded}">
</span></div></ui:component></body></html>
You can do the same thing to Facelets tags such as <ui:component> too:<html xmlns="http://www.w3.org/1999/xhtml" ...><body><span jsfc="ui:component"><div>
<form jsfc="h:form"><a jsfc="h:commandLink"
value="#{q.questionText}" action="#{q.trigger}"></a>
</form></div><div>
<span jsfc="h:outputText"value="#{q.answerText}" rendered="#{q.expanded}">
</span></div></span></body></html>
You can do the same thing for your <qa> too. For example, in listfaq.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml" ...><body><ui:component><div>
<h:form><h:commandLink
value="#{q.questionText}" action="#{q.trigger}"></h:commandLink>
</h:form></div><div>
<h:outputTextvalue="#{q.answerText}" rendered="#{q.expanded}">
</h:outputText></div></ui:component></body></html>
<a jsfc="h:commandLink" value="..." action="...">
jsfc stands for JSF component
The real tag name is here
Now it is a HTML <a> tag
</a>
The other attributes are unchanged
Chapter 6 Using Facelets 193
<htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:foo="http://foo.com">
<body><span jsfc="foo:qa" q="#{faqService.q1}" />
</body></html>
Run it and it will continue to work.
Forbidding direct access to xhtml filesLet's do an experiment: Try to access http://localhost:8080/FAQFacelets/listfaq.xhtml (NOT listfaq.jsf). You'll get a blank page like:
If you view the HTML source code in the browser, you'll see:
It means the source code of your web pages is being exposed! This is a serious security problem. To fix it, modify web.xml:
194 Chapter 6 Using Facelets
Restart the application and try to access the file again. This time you will get an error:
This is good. Of course, you can still access it as listfaq.jsf.
<web-app ...>...<servlet>
<servlet-name>Faces Servlet</servlet-name><servlet-class>javax.faces.webapp.FacesServlet</servlet-class><load-on-startup>1</load-on-startup>
</servlet><servlet-mapping>
<servlet-name>Faces Servlet</servlet-name><url-pattern>*.jsf</url-pattern>
</servlet-mapping><security-constraint>
<display-name>Block browser access to xhtml</display-name><web-resource-collection>
<web-resource-name>xhtml files</web-resource-name><url-pattern>*.xhtml</url-pattern>
</web-resource-collection><auth-constraint></auth-constraint>
</security-constraint>...
</web-app>
Here, all *.xhtml are protected.
It represents some "protected" files. That is, those files can be accessed by a certain users only.
Usually here you would list the user groups, but in this you don't want anyone to have access, so leave it empty.
Chapter 6 Using Facelets 195
SummaryFacelets is a view handler that can replace the JSP engine in a JSF application. It allows you to easily create new tags using existing tags. In addition, it allows you to hide JSF and Facelets tags into normal HTML tags so that you can work on the pages using web authoring tools such as Dreamweaver.
By default users can retrieve your xhtml files in the browser. This is a security problem. You should block such direct accesses using web.xml.
Facelets is a recommended technology by many JSF developers.
As Facelets is not in the JSF standard, JSF component libraries may not support it.
197
Chapter 7 Chapter 7 Providing a Common Layout with Facelets
198 Chapter 7 Providing a Common Layout with Facelets
What's in this chapter?In this chapter you'll learn how to create pages that share a common layout using Facelets.
Providing a common layoutSuppose that you'd like to develop an application shown below. It is unimportant what it does. What is important is that on each page there is a menu on the left:
To do that, create a new Facelets project named "Layout". Copy the two JSF jar files into WEB-INF/lib. Create three pages: home.xhtml, products.xhtml and contact.xhtml. home.xhtml and products.xhtml may be like:
Chapter 7 Providing a Common Layout with Facelets 199
Or graphically, the situation is like what is shown below, the HTML page structures are the same, the only difference is the cell content:
In that case, you can create a page to contain the common structure (see the diagram below). The varying parts are left as abstract. Then let each page extend this page and provide its unique content, just like Java class inheritance:
To do that, create base.xhtml (also in the WebContent folder):
Base
Abstract
Home Products
Unique contentUnique content
<html ...><span jsfc="h:form"><table>
<tr><td width="40%">
<a jsfc="h:commandLink"action="home">Home</a>
<br/><a jsfc="h:commandLink"
action="products">Products</a><br/><a jsfc="h:commandLink"
action="contact">Contact</a></td><td>This is the Home page.</td>
</tr></table></span></html>
home.xhtml<html ...><span jsfc="h:form"><table>
<tr><td width="40%">
<a jsfc="h:commandLink"action="home">Home</a>
<br/><a jsfc="h:commandLink"
action="products">Products</a><br/><a jsfc="h:commandLink"
action="contact">Contact</a></td><td>This is the Products page.</td>
</tr></table></span></html>
products.xhtml
Unique content
Must not use <br> as XML requires a closing tag
Table
Row
Cell Cell
MenuContent unique to the Home page
Html
FormTable
Row
Cell Cell
MenuContent unique to the Products page
Html
Form
200 Chapter 7 Providing a Common Layout with Facelets
Modify home.xhtml to "inherit" base.xhtml:
products.xhtml is:<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:ui="http://java.sun.com/jsf/facelets">
<span jsfc="ui:composition" template="/base.xhtml">This is the Products page.</span></html>
Run it and it should work (except for the links). To create the links, you may define the navigation rules like:
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:ui="http://java.sun.com/jsf/facelets">
<span jsfc="h:form"><table>
<tr><td width="40%">
<a jsfc="h:commandLink" action="home">Home</a> <br/><a jsfc="h:commandLink" action="products">Products</a> <br/><a jsfc="h:commandLink" action="contact">Contact</a>
</td><td>
<span jsfc="ui:insert">unique content</span></td>
</tr></table></span></html>
It indicates that this is an abstract part and the concrete part will be inserted.
The <insert> tag is a Facelets tag
<html xmlns="http://www.w3.org/1999/xhtml"xmlns:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html"xmlns:ui="http://java.sun.com/jsf/facelets">
<span jsfc="ui:composition" template="/base.xhtml">This is the home page.</span></html>
Include a certain page (here /base.xhtml)
This path starts from the context root folder (WebContent)
The tag body is the concrete part
Everything outside the <composition> element will NOT be output
Everything outside the <composition> element will NOT be output
Chapter 7 Providing a Common Layout with Facelets 201
However, it is a lot of duplication. A better way is:
To do that, modify faces-config.xml:
home
/home.xhtml
/home.xhtml
/products.xhtmlproducts
/contact.xhtmlcontact
home
/products.xhtml
/home.xhtml
/products.xhtmlproducts
/contact.xhtmlcontact
home
*
/home.xhtml
/products.xhtmlproducts
/contact.xhtmlcontact
A star will match any view id
202 Chapter 7 Providing a Common Layout with Facelets
The source is:<?xml version="1.0" encoding="UTF-8"?><faces-config ...>
<navigation-rule><from-view-id>*</from-view-id><navigation-case>
<from-outcome>products</from-outcome><to-view-id>/products.xhtml</to-view-id>
</navigation-case><navigation-case>
<from-outcome>home</from-outcome><to-view-id>/home.xhtml</to-view-id>
</navigation-case></navigation-rule>...
</faces-config>Restart the application and the links should work fine.
Having two abstract partsSuppose that each page may need to have a particular header that may contain any HTML elements or even JSF components:
Chapter 7 Providing a Common Layout with Facelets 203
Now, the situation is like:
This is like a base class having two abstract methods. For this to work, you need to give a name to each abstract part:
Base
Abstract1
Home Products
Unique content 2
home.html products.xhtml
base.xhtml
Abstract2
Unique content 1
Unique content 2
Unique content 1
This header may contain markup such as <h1> or even components
204 Chapter 7 Providing a Common Layout with Facelets
Modify home.xhtml to provide the two concrete parts:
Similarly, modify products.xhtml:<html ...><span jsfc="ui:composition" template="/base.xhtml">
<span jsfc="ui:define" name="p1"><h1><i>Products</i></h1></span><span jsfc="ui:define" name="p2">This is the Products page.</span>
</span></html>
Run it and it should work.
Having page specific navigation casesSuppose that you'd like to have a link on the Products page to display hot deals:
<html ...><span jsfc="ui:insert" name="p1">foo</span><span jsfc="h:form"><table>
<tr><td width="40%">
<a jsfc="h:commandLink" action="home">Home</a> <br/><a jsfc="h:commandLink" action="products">Products</a> <br/><a jsfc="h:commandLink" action="contact">Contact</a>
</td><td>
<span jsfc="ui:insert" name="p2">unique content</span></td>
</tr></table></span></html>
Let's name it "p1". You can use any name you'd like.
Let's name it "p2"
<html ...><span jsfc="ui:composition" template="/base.xhtml">
<span jsfc="ui:define" name="p1"><h1>Home</h1></span><span jsfc="ui:define" name="p2">This is the home page.</span>
</span></html>
p1
p2
The <define> tag is used to provide a named concrete part
Chapter 7 Providing a Common Layout with Facelets 205
To do that, modify products.xhtml:<html ...><span jsfc="ui:composition" template="/base.xhtml">
<span jsfc="ui:define" name="p1"><h1><i>Products</i></h1></span><span jsfc="ui:define" name="p2">This is the Products page. Here are some <a jsfc="h:commandLink" action="hotDeals">hot deals</a>.</span>
</span></html>
There is nothing special. Create a simple hotdeals.xhtml page:<!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">Hot deals here!</html>
Then, create a normal navigation rule for the Products page:
The source is:
206 Chapter 7 Providing a Common Layout with Facelets
Run it and it will work.
SummaryIf you have pages with a common layout, you can extract the common stuff into a base page and mark the abstract parts using <ui:define>. Then in each child page, suck in the base page using <ui:composition> and provide each concrete part using <ui:define>. Each part should have a unique name. If there is only one abstract part, you can omit the name and provide the concrete part as the body of the <ui:composition> element.
You can use a star as the view id in a navigation rule. In that case it will match any view id. This is useful if multiple pages share the same navigation cases. If a page needs some additional navigation cases, it can have its own normal navigation rule which will take be checked first.
<faces-config ...><navigation-rule>
<from-view-id>*</from-view-id><navigation-case>
<from-outcome>products</from-outcome><to-view-id>/products.xhtml</to-view-id>
</navigation-case><navigation-case>
<from-outcome>home</from-outcome><to-view-id>/home.xhtml</to-view-id>
</navigation-case></navigation-rule><navigation-rule>
<from-view-id>/products.xhtml</from-view-id><navigation-case>
<from-outcome>hotDeals</from-outcome><to-view-id>/hotdeals.xhtml</to-view-id>
</navigation-case></navigation-rule>...
</faces-config>
1: It will be considered first as it specifies a specific view id
2: It will be considered if no matching navigation case was found in the specific rule
207
Chapter 8 Chapter 8 Using JBoss Seam
208 Chapter 8 Using JBoss Seam
What's in this chapter?In this chapter you'll learn how to use the JBoss Seam framework. It is like a much enhanced version of the JSF bean management facility.
Recreating the e-shop with SeamLet's recreate the e-shop with Seam. First, go to http://labs.jboss.com/projects/download to download a binary package of Seam. Suppose that it is jboss-seam-2.0.1.GA.zip. Unzip it into say c:\jboss-seam.
Next, in Eclipse, choose "File | New | Others". Then choose "Seam | Seam Web Project":
If you don't see this option, it means you didn't install the JBoss IDE Tools as told in the previous chapter. Click Next. Then enter a project name and choose to use Seam 2.0:
Chapter 8 Using JBoss Seam 209
Keep clicking Next until you see the dialog below:
210 Chapter 8 Using JBoss Seam
Click "Add" to define a Seam runtime:
Then click "New" to define a so-called connection profile. Usually a Seam application needs to connect to a database. In a connection profile you specify
Chapter 8 Using JBoss Seam 211
things like database name, user name and password. Here, choose the HSQLDB type:
Enter a name for the profile:
Click Next and you'll see:
212 Chapter 8 Using JBoss Seam
Click "..." to choose a database driver. You'll see:
Chapter 8 Using JBoss Seam 213
Click "Add":
Choose the hsqldb.jar file and click "Edit Jar/Zip" to specify its correct location:
Choose it
214 Chapter 8 Using JBoss Seam
If you don't have Hypersonic DB, you can download it from http://www.hsqldb.org. Unzip it into say c:\hsqldb. You can find the hsqldb.jar file in c:\hsqldb\lib. Then go back to choose the newly created driver:
Chapter 8 Using JBoss Seam 215
Click OK. Finally, enter the information as shown below:
Finish the part about the DB. Then specify the Java packages to be used:
The name of the DB as used in Eclipse
The URL to be used in the JDBC URL
The default user in Hypersonic DB. It has an empty password.
216 Chapter 8 Using JBoss Seam
Finish the creation of the project. It will asks if you'd like to switch to the Seam perspective. Say Yes. Then copy commons-collections.jar, commons-logging.jar, dom4j.jar, hibernate-validator.jar, javassist.jar, jta.jar and persistence-api.jar in c:\jboss-seam\lib into WEB-INF/lib. Refresh the project.
By default, the Seam plugin assumes that your application will connect to the database at startup. As you don't have a database, modify WEB-INF/components.xml (see the diagram below). This components.xml file is used to configured the Seam components, which are like the JSF managed beans or Spring beans:
The packages for things like "session beans" or "entity beans". As you won't be using these EJB3 stuff, the packages don't really matter.
Chapter 8 Using JBoss Seam 217
In addition, by default Seam will start and commit a transaction for you automatically at suitable times (for example, when updating domain values and invoking application):
This in turn requires a database connection (or an EJB3 server). As you have neither, you need to tell Seam not to manage the transactions for you. This is again done by configuring components in components.xml:
<?xml version="1.0" encoding="UTF-8"?><components ...>
<core:init debug="true" jndi-pattern="@jndiPattern@" /><core:manager concurrent-request-timeout="500"
conversation-timeout="120000" conversation-id-parameter="cid" />
<!-- <persistence:managed-persistence-context name="entityManager"
auto-create="true"entity-manager-factory="#{ShopSeamEntityManagerFactory}" />
<persistence:entity-manager-factoryname="ShopSeamEntityManagerFactory" persistence-unit-name="ShopSeam" />
-->...
</components>
Comment out these two elements. They'll try to connect to a database.
Update Domain Values Invoke Application
1: Start a transaction 2: Commit the transaction
218 Chapter 8 Using JBoss Seam
Now start the Tomcat instance and observe the output in the console. Seam should start and print out a lot of information saying various components are initialized:INFO: Initializing Sun's JavaServer Faces implementation (1.2_07-b03-FCS) for context '/ShopSeam'INFO: Welcome to Seam 2.0.1.GA...INFO: Installing components...Feb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>INFO: Component: authenticator, scope: EVENT, type: JAVA_BEAN, class: shop.session.AuthenticatorFeb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>INFO: Component: org.jboss.seam.async.dispatcher, scope: APPLICATION, type: JAVA_BEAN, class: org.jboss.seam.async.ThreadPoolDispatcherFeb 17, 2008 1:16:03 PM org.jboss.seam.Component <init>INFO: Component: org.jboss.seam.captcha.captcha, scope: SESSION, type: JAVA_BEAN, class: org.jboss.seam.captcha.Captcha...
Most importantly, there shouldn't be any exception.
Creating the catalog pageTo create the catalog page, you can use JSP or Facelets. Seam supports both and recommends Facelets. You will use Facelets here. Create showcatalog.xhtml as usual:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<components xmlns="http://jboss.com/products/seam/components"xmlns:core="http://jboss.com/products/seam/core"xmlns:persistence="http://jboss.com/products/seam/persistence"xmlns:drools="http://jboss.com/products/seam/drools"xmlns:bpm="http://jboss.com/products/seam/bpm"xmlns:security="http://jboss.com/products/seam/security"xmlns:mail="http://jboss.com/products/seam/mail"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:transaction="http://jboss.com/products/seam/transaction"xsi:schemaLocation="
http://jboss.com/products/seam/corehttp://jboss.com/products/seam/core-2.0.xsd ...http://jboss.com/products/seam/transactionhttp://jboss.com/products/seam/transaction-2.0.xsd">
<core:init debug="true" jndi-pattern="@jndiPattern@" transaction-management-enabled="false"/>
<transaction:no-transaction />...
</components>It tells the component named "init" not to start and commit transactions automaticallySeam assumes that there is always
a transaction component that can start or commit a transaction. Here you install a no-op transaction component that does nothing for those operations:
transaction1: start a transaction 2: Do nothing
What elements are included in this namespace? This xsd file includes all the definitions. Having this information Eclipse can perform content assist.
Chapter 8 Using JBoss Seam 219
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body><h:form>
<h:dataTable border="1" value="#{showCatalog.products}" var="p"><h:column>
<h:outputText value="#{p.id}"></h:outputText></h:column><h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}"></h:commandLink>
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable></h:form></body></html>
There is nothing special here. The interesting thing is the "showCatalog" bean. Create a class ShowCatalog in the shop.helper package. Note that there are two existing source folders in the project:
Which one to use doesn't really affect the functionality. However, as the helper belongs to the UI, not the domain, so the src/action folder seems a better fit. So create the ShowCatalog class there:
220 Chapter 8 Using JBoss Seam
The injection performed by Seam is a bit different from JSF: For example, in this case, if someone calls any method on "showCatalog" component (see the diagram below), Seam will intercept the call and set the "catalogService" field to point to the "catalogServie" component before the body of the method is executed:
The advantage of this is that even if the "showCatalog" component were in the session scope, after deserialization it would still gain access to the injected components.
Next, create the "catalogService" component and the CatalogService class. As it is a service, let's put it into the shop.service package. Strictly speaking, a
package shop.helper;import org.jboss.seam.ScopeType;import org.jboss.seam.annotations.In;import org.jboss.seam.annotations.Name;import org.jboss.seam.annotations.Scope;@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
return "details";}
}
Define a bean named "showCatalog" from this class. It is actually not a JSF bean, but a Seam component that can be used like a JSF bean.
The scope for the bean (i.e., component). Event scope the same thing as the request scope.
Inject a Seam component into this field. The name of the component is the field name ("catalogService" here).
Note that you don't need a setter. Seam can inject it directly into the field.
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;
public List<Product> getProducts() {return catalogService.getProducts();
}public String showDetails() {
return "details";}
}
1: Someone calls any method (e.g., the getProducts method)
Name Value
... ...
... ...
catalogService
Seam components for the app
...
2: Seam will set the field to the component
3: The body of the method starts to execute
Chapter 8 Using JBoss Seam 221
service doesn't belong to the UI or the business domain, but for simplicity, let's put it into the src/model folder:
Create the Product class in the shop.domain package. Put it into the src/model folder:package shop.domain;public class Product {
private String id;private String name;private String desc;private double price;public Product() {}public Product(String id, String name, String desc, double price) {
this.id = id;this.name = name;this.desc = desc;this.price = price;
}public String getDesc() {
return desc;}public String getId() {
return id;}public String getName() {
return name;}public double getPrice() {
return price;}
}It is not a Seam component. It is just a normal Java class. Now, run it by going
package shop.service;@Name("catalogService")@Scope(ScopeType.APPLICATION)public class CatalogService {
private List<Product> products;public CatalogService() {
products = new ArrayList<Product>();products.add(new Product("p01", "Pencil", "a", 1.20));products.add(new Product("p02", "Eraser", "b", 2.00));products.add(new Product("p03", "Ball pen", "c", 3.50));
}public List<Product> getProducts() {
return products;}public Product getProduct(String pid) {
for (Product p : products) {if (p.getId().equals(pid)) {
return p;}
}return null;
}}
Application scope
Component name
222 Chapter 8 Using JBoss Seam
to the URL:
Unfortunately, it will fail with an exception:
This is because unlike JSF managed beans, by default Seam will NOT create Seam components automatically, unless it is accessed directly by an EL expression in your page. In your case the "showCatalog" component will be created automatically but not the "catalogService" component:<html ...><body><h:form>
<h:dataTable border="1" value="#{showCatalog.products}" var="p"><h:column>
<h:outputText value="#{p.id}"></h:outputText></h:column><h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}"></h:commandLink>
Context path, as usual.
http://localhost:8080/ShopSeam/showcatalog.seam
By default it uses .seam as the extension instead of .jsf
public class ShowCatalog {@Inprivate CatalogService catalogService;...
}
When it tried to inject the "catalogService" component, the component didn't exist so it got a null.
Chapter 8 Using JBoss Seam 223
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable></h:form></body></html>
To tell Seam to automatically create the "showCatalog" component, do it this way:@Name("catalogService")@Scope(ScopeType.APPLICATION)@AutoCreatepublic class CatalogService {
...}
Run it again and it should work (except for the links).
Displaying product detailsTo make the links work, create the productdetails.xhtml:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body><h1><h:outputText value="#{product.name}"></h:outputText></h1><p><h:outputText value="#{product.desc}"></h:outputText></p><h:form>
<h:commandButton value="Add to cart" action="#{productDetails.addToCart}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></body></html>
For it to work, you need a component named "product". To do that, modify the Product class:@Name("product")@Scope(ScopeType.EVENT)public class Product {
private String id;private String name;private String desc;private double price;...
}Next, implement the link in the ShowCatalog class (see the diagram below). Note the @Out annotation. What does it mean? It means for example, if someone calls any method of the "showCatalog" component (e.g., the showDetails method), the method body will execute as usual. But before it returns to the caller, Seam will intercept it and set the "product" component point to the "product" field:
224 Chapter 8 Using JBoss Seam
As a test, simply set the product field to a hard coded Product:@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Outprivate Product product;
public List<Product> getProducts() {return catalogService.getProducts();
}public String showDetails() {
product = new Product("p04", "a", "b", 3.6);return "details";
}}
For it to work, you need to define the navigation case. In a Seam application, you don't specify it in faces-config.xml. You do it in WEB-INF/pages.xml:
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required=false)private Product product;public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
product = ???;return "details";
}} Need to find out the
selected product and store it into the field
showCatalog
product:
???
3: Point to some Product object
Name Valueproduct... ...... ...
Seam components for request 1
4: Just before the method returns to the caller, Seam intercepts it and sets the "product" component to the "product" field.
Outject this field
By default, if the product field is null, Seam will treat it as an error. Setting required to false will suppress the error.
1: Someone calls any method on the component
2: The method body starts to execute
Chapter 8 Using JBoss Seam 225
Run it and it should work. Now, the next question is how to find out the selected product. In the past you use the <setPropertyActionListener> to do that but it is quite a low level thing and it requires setting "immediate" attribute of the command link to true and thus is not really a good solution. The idea is, instead of providing a List to the UI Data, you provide a DataModel (see the diagram below). The UI Data will ask it to see how many rows there are, loop through each row and get the object on the each row:
The key is, when a link is clicked, in the Invoke Application phase, the UI Data will set the current row to the selected row:
As long as you can get access to the DataModel, you can find out the selected row index and get the right Product object. To implement this idea, modify ShowCatalog:
<?xml version="1.0" encoding="UTF-8"?><pages ...
no-conversation-view-id="/home.xhtml" login-view-id="/login.xhtml">
<page view-id="/showcatalog.xhtml"><navigation>
<rule if-outcome="details"><render view-id="/productdetails.xhtml" />
</rule></navigation>
</page><page view-id="*">
<navigation><rule if-outcome="home">
<redirect view-id="/home.xhtml" /></rule>
</navigation></page>...
</pages>
Information about the /showcatalog.xhtml page
If outcome is "details"
Then render /productdetails.xhtml
UI Data
DataModelHow many rows?
Set the current row to 0
Tell me the object on the current row
UI Data
DataModel
2: Set the current row to the selected row
1: A link is clicked
226 Chapter 8 Using JBoss Seam
Modify showcatalog.xhtml to use the DataModel instead of the List:<html ...><body><h:form>
<h:dataTable border="1" value="#{showCatalog.dataModel}" var="p"><h:column>
<h:outputText value="#{p.id}"></h:outputText></h:column><h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}"></h:commandLink>
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable></h:form></body></html>
As this usage of DataModel is so common, Seam provides some support for it. To see how to make use of it, first, let's create the DataModel as a component:
...import javax.faces.model.DataModel;import javax.faces.model.ListDataModel;@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required=false)private Product product;private DataModel dataModel;public DataModel getDataModel() {
if (dataModel == null) {dataModel = new ListDataModel(getProducts());
}return dataModel;
}public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
product = new Product("p04", "a", "b", 3.6);product = (Product) dataModel.getRowData();return "details";
}}
Create a DataModel wrapping around this List of Product objects
ListDataModel
1: Get the object on the current row
List2: Call get(0) on the list, assuming that the current row is 0.
Current row: 0
Chapter 8 Using JBoss Seam 227
Modify showcatalog.xhtml to use the "productList" component:<html ...><body><h:form>
<h:dataTable border="1" value="#{productList}" var="p">...
</h:dataTable></h:form></body></html>
Run it and it should continue to work. Next, Seam can create a DataModel for you automatically as long as you give it a List:
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)private Product product;@Outprivate DataModel productList;@Factory("productList")public void loadProducts() {
productList = new ListDataModel(getProducts());}public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
product = (Product) productList.getRowData();return "details";
}}
Name Value... ...... ...... ...
5: Outjects the field value (the DataModel) as a component
4: The field is set
Seam
1: Give me the component named "productList"
2: Check if there is such a component there. At the beginning there is not.
3: Look, there is a factory method for the component. Call it.
228 Chapter 8 Using JBoss Seam
Run it and it will continue to work. In addition, Seam can help you inject the row data of a DataModel component you outjected:
import org.jboss.seam.annotations.datamodel.DataModel;@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)private Product product;@Out@DataModelprivate DataModel List<Product> productList;@Factory("productList")public void loadProducts() {
productList = new ListDataModel(getProducts());}public List<Product> getProducts() {
return catalogService.getProducts();}@In(value = "productList", required = false)private javax.faces.model.DataModel dataModel;public String showDetails() {
product = (Product) dataModel.getRowData();return "details";
}}
5: @DataModel is like @Out in that it will outject a component. However, it doesn't outject the field value directly. Instead, it creates a DataModel wrapping the field value and then outjects that DataModel.
As the DataModel is no longer a field, to access it, you need to inject it.
You only need to provide a List
@DataModel will create the DataModel. You only need to provide a List.
This is the name of the component to be injected. As it is different from the field name ("dataModel"), you have to specify it explicitly.
Chapter 8 Using JBoss Seam 229
Run it and it will continue to work. Finally, the code can be further improved a little bit. Currently the flow is:
Then why not combine the two fields into one?
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)private Product product;@DataModelprivate List<Product> productList;
@Factory("productList")public void loadProducts() {
productList = getProducts();}public List<Product> getProducts() {
return catalogService.getProducts();}@DataModelSelectionprivate Product selected;@In(value = "productList", required = false)private javax.faces.model.DataModel dataModel;
public String showDetails() {product = (Product) dataModel.getRowData();product = selected;return "details";
}}
DataModel1: Find a @DataModel and get the component name ("productList")
Name Value... ...... ...... ...
2: Look up the "productList" DataModel
3: Call getRowData() on it and inject the result into the field
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)private Product product;@DataModelprivate List<Product> productList;
@Factory("productList")public void loadProducts() {
productList = getProducts();}public List<Product> getProducts() {
return catalogService.getProducts();}@DataModelSelectionprivate Product selected;
public String showDetails() {product = selected;return "details";
}}
1: Inject
2: Assign
3: Outject
230 Chapter 8 Using JBoss Seam
Run it and it will continue to work.
Adding a product to the shopping cartNow, let's implement the "Add to cart" button. Note the action method in productdetails.xhtml:<html ...><body><h1><h:outputText value="#{product.name}"></h:outputText></h1><p><h:outputText value="#{product.desc}"></h:outputText></p><h:form>
<h:commandButton value="Add to cart" action="#{productDetails.addToCart}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></body></html>
Therefore, you need to create a ProductDetails class. Put it into the shop.helper package:package shop.helper;@Name("productDetails")@Scope(ScopeType.EVENT)public class ProductDetails {
@Inprivate Product product;public String addToCart() {
//add the product to the cartreturn "addedToCart";
}}
However, as the "product" component is in the event scope, when the button is
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)@DataModelSelectionprivate Product product;@DataModelprivate List<Product> productList;
@Factory("productList")public void loadProducts() {
productList = getProducts();}@DataModelSelectionprivate Product selected; public String showDetails() {
product = selected;return "details";
}}
Now it is both injected and outjected. It is said to be bijected.
Chapter 8 Using JBoss Seam 231
clicked, a new one will be created which is not the one displayed. In the past you solved this problem by storing the product id in a hidden field and loading all its properties when the id is set. This is a lot of work. Seam provides a very powerful solution. It provides a new scope: conversation scope. For example, suppose at a certain time you start a new conversation (see the diagram below). Seam will assign a unique id (say 123) to it. Then a request comes in and a response is rendered. Assume in the process a Seam component (say "foo") is created and it is marked to be in the conversation scope, then it will be stored into a component table for conversation 123. That table is stored in the session. Suppose that another request comes in. The "foo" component will still be there for you to access. If later you decide to end the conversation, Seam will then destroy the component table for conversation 123:
How is it better than using the session scope? For example, if the user views product 1 in a tab in the browser (see the diagram below), if you're using the session scope, product 1 will be stored in the session scope. Suppose that the user then opens a new tab and views product 2. Then product 2 will replace product 1 in the session. Finally, suppose that the user returns to the original tab and clicks the "Add" button. Even though he is looking at product 1 in the tab, your application will add product 2 to the shopping cart as product 2 is in the session! This is extremely confusing to the user:
2: Handle a submission and render the response. In the process, a conversation-scoped component may be created.
Time
1: Start a conversation. Seam gives it an id (say 123)
Name Value
... ...
... ...
foo
components for conversation 123
3: The component is put there
4: Handle a submission and render the response. The component is still there!
5: End the conversation. The components in conversation 123 will be destroyed.
Session for the client
232 Chapter 8 Using JBoss Seam
What if you use the conversation scope instead? Then, you'll have a separate conversation for each tab and the two tabs will work independently:
Actually, whenever a request arrives, even if you don't start a conversation, Seam will always create a temporary conversation for you. Such a conversation will be ended automatically after the response is rendered:
Product 1
1: View product 1 in a tab in a browser
Product 2
Add Add
Product 1
Add
3: Open a new tab and view product 2 in that tab
5: Return to the original tab and click "Add". Ouch! Product 2 is added to the cart!
Session components for the clientName Valueproduct... ...... ...
2: Put product 1 into there
4: Put product 2 into there, replacing product 1.
Product 1
1: View product 1 in a tab in a browser
Product 2
Add Add
Product 1
Add
3: Open a new tab and view product 2 in that tab
5: Return to the original tab and click "Add". This will correctly add product 1 to the cart.
Conversation 1Name Valueproduct... ...... ...
2: Put product 1 into there
Conversation 2Name Valueproduct... ...... ...
4: Put product 2 into there
Chapter 8 Using JBoss Seam 233
Why is it important? When handling the request, you may create a conversation scope component first (see the diagram below) and then decide to start a conversation. In that case, Seam will NOT create a new conversation. Instead, it will use that temporary conversation and mark it as a normal (non-temporary) conversation. The result is, the conversation scoped component will remain there even though when it was added, the conversation was still a temporary one:
To make use of this feature, change the scope of the "product" component and let it implement Serializable:@Name("product")@Scope(ScopeType.EVENT)@Scope(ScopeType.CONVERSATION)public class Product implements Serializable {
private String id;private String name;private String desc;private double price;...
}
2: Handle a submission and render the response
Time
1: Seam starts a temporary conversation
3: Seam ends the conversation automatically
2: Handle a submission and render the response
Time
1: Seam starts a temporary conversation
Name Valuefoo... ...... ...
components for conversation 123
3: A component is created
4: You decide to start a conversation. The temporary conversation is used and marked as non-temporary.
234 Chapter 8 Using JBoss Seam
In addition, you need start and end the conversation:
To do that, modify pages.xml:
However, depending on the caller to begin the conversation is no good. The productdetails page should start the conversation itself:
showcatalog productdetails
showcart
start conversation
end conversation
end conversation
<pages ...><page view-id="/showcatalog.xhtml">
<navigation><rule if-outcome="details">
<begin-conversation/><render view-id="/productdetails.xhtml" />
</rule></navigation>
</page><page view-id="/productdetails.xhtml">
<navigation><rule if-outcome="catalog">
<end-conversation/><render view-id="/showcatalog.xhtml" />
</rule><rule if-outcome="addedToCart">
<end-conversation/><render view-id="/showcart.xhtml" />
</rule></navigation>
</page>...
</pages>
Start a conversation before displaying the productdetails page
End the conversation before leaving the productdetails page
Chapter 8 Using JBoss Seam 235
To test it, modify the ProductDetails class:@Name("productDetails")@Scope(ScopeType.EVENT)public class ProductDetails {
@Inprivate Product product;
public String addToCart() {System.out.println(product.getName());return "addedToCart";
}}
Then create a dummy showcart page:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body>cart</body></html>
Run it and try to add a product to the cart. You should see the product name displayed in the console:
<pages ...><page view-id="/showcatalog.xhtml">
<navigation><rule if-outcome="details">
<begin-conversation/><render view-id="/productdetails.xhtml" />
</rule></navigation>
</page><page view-id="/productdetails.xhtml">
<begin-conversation/><navigation>
<rule if-outcome="catalog"><end-conversation/><render view-id="/showcatalog.xhtml" />
</rule><rule if-outcome="addedToCart">
<end-conversation/><render view-id="/showcart.xhtml" />
</rule></navigation>
</page>...
</pages>
Start a conversation before rendering this page
236 Chapter 8 Using JBoss Seam
To really add the product to the shopping cart, you need to create a session scoped component named "cart". To do that, create a Cart class in the shop.domain package:
Modify the productdetails page:@Name("productDetails")@Scope(ScopeType.EVENT)public class ProductDetails {
@Inprivate Product product;@Inprivate Cart cart;public String addToCart() {
System.out.println(product.getName());cart.add(product.getId());return "addedToCart";
}}
Next, modify the showcart page to display the cart content:<html ...><body><h:dataTable border="1" value="#{showCart.products}" var="p">
<h:column><h:outputText value="#{p.id}"></h:outputText>
</h:column><h:column>
<h:outputText value="#{p.name}"></h:outputText>
package shop.domain;@Name("cart")@Scope(ScopeType.SESSION)@AutoCreatepublic class Cart implements Serializable {
private List<String> productIds;public Cart() {
productIds = new ArrayList<String>();}public void add(String pid) {
productIds.add(pid);}public List<String> getProductIds() {
return productIds;}
}
No page is going to refer to it directly, so need to auto-create it.
Everything in the session must implement Serializable
Chapter 8 Using JBoss Seam 237
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable><h:form>
<h:commandButton value="Checkout" action="checkout"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></body></html>
Create the ShowCart class in the shop.helper package:
Define the navigation cases in pages.xml:<pages ...>
...<page view-id="/showcart.xhtml">
<navigation ><rule if-outcome="checkout">
<render view-id="/confirm.xhtml" /></rule><rule if-outcome="catalog">
<render view-id="/showcatalog.xhtml" /></rule>
</navigation></page>
</pages>Now run it and it should work.
Confirming the checkoutNext, create the confirm.xhtml file:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body><p>You're going to pay <h:outputText value="#{confirm.total}"></h:outputText> withcredit card <h:outputText value="#{confirm.creditCardNo}"></h:outputText>.</p>
package shop.helper;@Name("showCart")@Scope(ScopeType.EVENT)public class ShowCart {
@Inprivate Cart cart;@Inprivate CatalogService catalogService;public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();for (String pid : cart.getProductIds()) {
products.add(catalogService.getProduct(pid));}return products;
}}
Inject it to get the product ids
Inject it to get the product names and prices from the ids
238 Chapter 8 Using JBoss Seam
<h:form><h:commandButton value="Confirm" action="#{confirm.confirm}"></h:commandButton><h:commandButton value="Continue shopping" action="catalog"></h:commandButton>
</h:form></body></html>
Create the Confirm class in the shop.helper package:
Define the navigation cases:<pages ...>
...<page view-id="/confirm.xhtml">
<navigation ><rule if-outcome="charged">
<render view-id="/thankyou.xhtml" /></rule><rule if-outcome="catalog">
<render view-id="/showcatalog.xhtml" /></rule>
</navigation></page>
</pages>Create a simple thankyou.xhtml page:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body>Thank you!</body></html>
Run it and it should work.
package shop.helper;@Name("confirm")@Scope(ScopeType.EVENT)public class Confirm {
@Inprivate Cart cart;@Inprivate CatalogService catalogService;public double getTotal() {
double total = 0;for (String pid : cart.getProductIds()) {
total += catalogService.getProduct(pid).getPrice();}return total;
}public String getCreditCardNo() {
return "123";}public String confirm() {
//charge his card, schedule the delivery and etc.return "charged";
}}
You need to get the card # from the account of the logged in user. For now, just return a hard code value.
Chapter 8 Using JBoss Seam 239
Logging inIn order to retrieve the credit card number from the user account, you need to allow the user to login first. To do that, Seam already provides a session scoped component that can hold the user name and password:
The plugin even creates a login.xhtml file for you. But here, let's modify it as:
How can its login() method know if the user name and password are valid or not? It doesn't know. It will assume that you will provide an "authenticator" component which has an authenticate() method. Furthermore, the plugin actually created one for you in the shop.session package:package shop.session;
import org.jboss.seam.annotations.In;import org.jboss.seam.annotations.Logger;import org.jboss.seam.annotations.Name;import org.jboss.seam.log.Log;import org.jboss.seam.security.Identity;
@Name("authenticator")public class Authenticator{ @Logger Log log; @In Identity identity; public boolean authenticate() { log.info("authenticating #0", identity.getUsername()); //write your authentication logic here, //return true if the authentication was //successful, false otherwise identity.addRole("admin");
Identity
username:password:
<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<body><h:messages/><h:form id="f">
<h:panelGrid columns="2"><h:outputLabel for="email">Email</h:outputLabel><h:inputText id="email" value="#{identity.username}" /><h:outputLabel for="password">Password</h:outputLabel><h:inputSecret id="password" value="#{identity.password}" />
</h:panelGrid><h:commandButton value="Login" action="#{identity.login}" />
</h:form></body></html>
It will output an HTML <label> element:
<label for="f:email">Email</label><input id="f:email">....</input>
The "identity" component also has a login() method to authenticate the user
240 Chapter 8 Using JBoss Seam
return true; }}
To authenticate the user, create the UserService class in the shop.service package:package shop.service;@Name("userService")@Scope(ScopeType.APPLICATION)@AutoCreatepublic class UserService {
private List<User> users;public UserService() {
users = new ArrayList<User>();users.add(new User("[email protected]", "aaa", "1111 2222 3333 4444"));users.add(new User("[email protected]", "bbb", "2222 3333 4444 5555"));
}public User findMatchingUser(String email, String password) {
for (User user : users) {if (user.matches(email, password)) {
return user;}
}throw new UserNotFoundException();
}public User getUser(String email) {
for (User user : users) {if (user.getEmail().equals(email)) {
return user;}
}throw new UserNotFoundException();
}}
Create the User class in the shop.domain package:package shop.domain;public class User {
private String email;private String password;private String creditCardNo;public User(String email, String password, String creditCardNo) {
this.email = email;this.password = password;this.creditCardNo = creditCardNo;
}public boolean matches(String email, String password) {
return this.email.equals(email) && this.password.equals(password);}public String getEmail() {
return email;}public String getPassword() {
return password;}public String getCreditCardNo() {
return creditCardNo;}
}Create the UserNotFoundException in the same package:package shop.domain;public class UserNotFoundException extends RuntimeException {
Chapter 8 Using JBoss Seam 241
}Now, fill in the code in the Authenticator class:
Create the login and logout links on the showcatalog page:<html ...><body><h:form>
<h:dataTable border="1" value="#{productList}" var="p"><h:column>
<h:outputText value="#{p.id}"></h:outputText></h:column><h:column>
<h:commandLink value="#{p.name}" action="#{showCatalog.showDetails}"></h:commandLink>
</h:column><h:column>
<h:outputText value="#{p.price}"></h:outputText></h:column>
</h:dataTable><h:commandLink value="Login" action="login"></h:commandLink><h:commandLink value="Logout" action="#{showCatalog.logout}"></h:commandLink>
</h:form></body></html>
Define the navigation case in pages.xml:<pages ...>
...<page view-id="/showcatalog.xhtml">
<navigation><rule if-outcome="details">
<render view-id="/productdetails.xhtml" /></rule><rule if-outcome="login">
<render view-id="/login.xhtml" /></rule>
</navigation>
package shop.session;@Name("authenticator")public class Authenticator{ @Logger Log log; @In Identity identity; @In private UserService userService; public boolean authenticate() { log.info("authenticating #0", identity.getUsername()); //write your authentication logic here, //return true if the authentication was //successful, false otherwise try { userService.findMatchingUser(
identity.getUsername(), identity.getPassword()); return true; } catch (UserNotFoundException e) { return false; } identity.addRole("admin"); return true; }}
No scope is specified. Default is EVENT.
This is the email
242 Chapter 8 Using JBoss Seam
</page>...
</pages>Where it will go after logging in? The plugin already created a login.page.xml file to define the navigation case:
The home.xhtml page is a dummy page created by the plugin. Modify it to tell the browser to go to your showcatalog page:
Modify the confirm page to get the credit card number from the logged in user:
<?xml version="1.0" encoding="UTF-8"?><page ...> <navigation from-action="#{identity.login}"> <rule if="#{identity.loggedIn}"> <redirect view-id="/home.xhtml"/> </rule> </navigation></page>
<h:form id="login"><h:panelGrid columns="2">
<h:outputLabel for="email">Email</h:outputLabel><h:inputText id="email" value="#{identity.username}" /><h:outputLabel for="password">Password</h:outputLabel><h:inputSecret id="password" value="#{identity.password}" />
</h:panelGrid><h:commandButton value="Login" action="#{identity.login}" />
</h:form>
This navigation case will apply only if the action expression is #{identity.login}, which is indeed the case here.
Instead of checking the outcome, it evaluates an EL expression to check if the user has logged in.
Using <redirect> instead of <render> will generate a response to tell the browser to go to http://localhost:8080/ShopSeam/home.seam.
<!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"><head> <meta http-equiv="Refresh" content="0; URL=showcatalog.seam"/></head></html>
Tell the browser to refresh Wait 0 second before refreshing. It means do not wait.
Relative path to the desired page
Chapter 8 Using JBoss Seam 243
Finally, by default the "identity" component is configured (in components.xml) to use "security rules":<components ...>
...<drools:rule-base name="securityRules">
<drools:rule-files><value>/security.drl</value>
</drools:rule-files></drools:rule-base><security:identity
authenticate-method="#{authenticator.authenticate}"security-rules="#{securityRules}" remember-me="true" />
</components>As you don't have any security rule, get rid of it:<components ...>
...<!--<drools:rule-base name="securityRules">
<drools:rule-files><value>/security.drl</value>
</drools:rule-files></drools:rule-base>--><security:identity
authenticate-method="#{authenticator.authenticate}"security-rules="#{securityRules}" remember-me="true" />
</components>Restart the application. Login and then go to the confirm page. It should display the correct credit card number.
import org.jboss.seam.security.Identity;@Name("confirm")@Scope(ScopeType.EVENT)public class Confirm {
@Inprivate Cart cart;@Inprivate CatalogService catalogService;@Inprivate UserService userService;public double getTotal() {
double total = 0;for (String pid : cart.getProductIds()) {
total += catalogService.getProduct(pid).getPrice();}return total;
}public String getCreditCardNo() {
Identity identity = Identity.instance();String email = identity.getUsername();User user = userService.getUser(email);return user.getCreditCardNo();return "123";
}public String confirm() {
//charge his card, schedule the delivery and etc.return "charged";
}}
You could inject it. This is an alternative way to access it.
All you get is the email. Need to find the User object to get his credit card number.
244 Chapter 8 Using JBoss Seam
Protecting the confirm pageTo make sure a user has logged in before rendering the confirm page, all you need to do is modify pages.xml:
Try to access it without logging. It will tell you to login:
After logging in, it will return you to the originally requested page (the confirm page). How does it do that? If you check the components.xml file, you'll see:
<pages ...no-conversation-view-id="/home.xhtml" login-view-id="/login.xhtml">
...<page view-id="/confirm.xhtml" login-required="true">
<navigation ><rule if-outcome="charged">
<render view-id="/thankyou.xhtml" /></rule><rule if-outcome="catalog">
<render view-id="/showcatalog.xhtml" /></rule>
</navigation></page>
</pages>
Check if a user has logged in (by asking the "identity" component). If not display the login page. Which page is the login page? It is specified here:
Chapter 8 Using JBoss Seam 245
Implementing logoutThe logout link is already defined:<html ...><body><h:form>
<h:dataTable border="1" value="#{productList}" var="p">...
</h:dataTable><h:commandLink value="Login" action="login"></h:commandLink><h:commandLink value="Logout" action="#{showCatalog.logout}"></h:commandLink>
</h:form></body></html>
For it to work, define the logout method in the ShowCatalog class:
<components ...>...<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}" /></event><event type="org.jboss.seam.security.loginSuccessful">
<action execute="#{redirect.returnToCapturedView}" /></event>
</components>
Securitycheck
1: Try to access the confirm page without logging in
Listener 1 Listener 2
Event2: Create an event of the type org.jboss.seam.security.notLoggedIn. The type is is a string. 3: Want to
handle me?
4: Yes, execute this action method.
Redirect
5: Capture the view id for later restoreThis will return
to the original view id
246 Chapter 8 Using JBoss Seam
Run it. Try to logout and then click a product link. It will trigger an error:
To fix this problem, you can force a browser redirect. To do that modify the ShowCatalog class to return an outcome:@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)@DataModelSelectionprivate Product product;
@Name("showCatalog")@Scope(ScopeType.EVENT)public class ShowCatalog {
@Inprivate CatalogService catalogService;@Out(required = false)@DataModelSelectionprivate Product product;@DataModelprivate List<Product> productList;@Factory("productList")public void loadProducts() {
productList = getProducts();}public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
return "details";}public String logout() {
Identity.instance().logout();return null;
}}
This will invalidate the session
Do not change the view id
Chapter 8 Using JBoss Seam 247
@DataModelprivate List<Product> productList;
@Factory("productList")public void loadProducts() {
productList = getProducts();}public List<Product> getProducts() {
return catalogService.getProducts();}public String showDetails() {
return "details";}public String logout() {
Identity.instance().logout();return "loggedOut";
}}
Define the navigation case:<pages ...>
...<page view-id="/showcatalog.xhtml">
<navigation><rule if-outcome="details">
<render view-id="/productdetails.xhtml" /></rule><rule if-outcome="login">
<render view-id="/login.xhtml" /></rule><rule if-outcome="loggedOut">
<redirect view-id="/showcatalog.xhtml" /></rule>
</navigation></page>
</pages>Run it again and it will work.
Redirect vs renderLet's take an experiment: Click a product link from the catalog page. Note that the URL is not changed:
This is because the navigation case is using <render> instead of <redirect>:<page view-id="/showcatalog.xhtml">
<navigation><rule if-outcome="details">
The URL is still showcatalog.seam
248 Chapter 8 Using JBoss Seam
<render view-id="/productdetails.xhtml" /></rule>...
</navigation></page>
Having the old URL is not only confusing to the user, in addition, if you click the Refresh or Reload button in the browser in an attempt to refresh the product details, it will ask if you'd like to submit the form again:
This is no good. To solve these problems, you're advised to always use <redirect> instead of <render>. However, for this to work, if one page creates a component to be used by the next page (see the diagram below). When the browser tries to access the next page, the component may not still exist if it is in the event scope:
Therefore, to use <redirect>, make sure the component is in the conversation scope.
In your application, you can safely change all navigation cases to <redirect>:<pages ...>
<page view-id="/showcatalog.xhtml"><navigation>
<rule if-outcome="details"><redirect view-id="/productdetails.xhtml" />
</rule><rule if-outcome="login">
<redirect view-id="/login.xhtml" /></rule><rule if-outcome="loggedOut">
<redirect view-id="/showcatalog.xhtml" /></rule>
</navigation></page><page view-id="/productdetails.xhtml">
<begin-conversation join="true"/><navigation >
<rule if-outcome="catalog"><end-conversation/><redirect view-id="/showcatalog.xhtml" />
Page 1
...
Page 2
Name Valuefoo... ...... ...
1: Put a component thereBrowser
2: Redirect to page 2
3: Render page 2 4: Is foo still there?
Chapter 8 Using JBoss Seam 249
</rule><rule if-outcome="addedToCart">
<end-conversation/><redirect view-id="/showcart.xhtml" />
</rule></navigation>
</page><page view-id="/showcart.xhtml">
<navigation ><rule if-outcome="checkout">
<redirect view-id="/confirm.xhtml" /></rule><rule if-outcome="catalog">
<redirect view-id="/showcatalog.xhtml" /></rule>
</navigation></page><page view-id="/confirm.xhtml" login-required="true">
<navigation ><rule if-outcome="charged">
<redirect view-id="/thankyou.xhtml" /></rule><rule if-outcome="catalog">
<redirect view-id="/showcatalog.xhtml" /></rule>
</navigation></page>...
</pages>Run it and it should continue to work. Besides, the URL will change with each click.
SummaryA Seam component is like a JSF managed bean except that it is more powerful. The injection does it work before each method call and thus prevents the problem of deserialization. It also supports outjection so that you can store components. If a property is both injected and outjected, it is said to be bijected.
Seam components are defined using annotations so that you don't have to edit configuration files. If you need to change their settings, you can do it in the components.xml file.
Seam provides a very powerful scope: conversation scope. You define when to start a conversation and when to end it. During this time period, all conversation scoped components will remain available, across different requests.
You can use the @DataModel annotation to create a DataModel wrapping a List and outject that DataModel as a component. Most likely you'll use a @Factory annotation to mark a method as a factory method for the DataModel component and load the List in the method. You can inject the selected object in the DataModel using @DataModelSelection.
You define the navigation cases in the pages.xml file. In that file you can define additional properties of the pages: for example, if a page requires a logged in user or requires to start a new conversation.
Seam provides an "identity" component to keep track of the currently logged in
250 Chapter 8 Using JBoss Seam
user (if any). All you need is to provide a page to store the user input into that "identity" component and a method in the "authenticator" component. By default, event listeners have been set up to capture the original view id and to return to it on a successful login.
To render the next page, you can choose between a plain render or a redirect. Redirect will show the new URL in the browser and will work fine with the Reload/Refresh button. To allow redirect, if one page needs to pass a component to the next page, it should be in the conversation scope.
251
Chapter 9 Chapter 9 Supporting Other Languages
252 Chapter 9 Supporting Other Languages
What's in this chapterIn this chapter you'll learn how to develop an application that can appear in two or more different languages to suit users in different countries.
A sample applicationSuppose that you have an application that displays the current date:
This is easy. Create a JBoss JSF project named MultiLang and enable Facelets. Copy the JSF RI jar files into WEB-INF/lib. Create a showdate.xhtml file in the WebContent folder:<!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:f="http://java.sun.com/jsf/core"xmlns:h="http://java.sun.com/jsf/html">
<head><title>Current date</title></head><body>Today is: <h:outputText value="#{showDate.today}"/>.</body></html>
Create a ShowDate class in the multilang package:package multilang;import java.util.Date;public class ShowDate {
public Date getToday() {return new Date();
}}
Define the "showdate" bean:<faces-config ...>
<managed-bean><managed-bean-name>showDate</managed-bean-name><managed-bean-class>multilang.ShowDate</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean>
Chapter 9 Supporting Other Languages 253
<application><view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application></faces-config>
Start the Tomcat instance and try to access http://localhost:8080/MultiLang/showdate.jsf in the browser. It should work.
Supporting ChineseSuppose that some of your users are Chinese. They would like to see the application in Chinese when they run the application. To do that, create a file msgs.properties (the filename is not really important) in the multilang package:currentDate=Current datetodayIs=Today is:
To support Chinese, create another file msgs_zh.properties. "zh" represents Chinese. Usually, people use the Big5 encoding to encode Chinese. However, Java requires this file be in a special encoding called "escaped Unicode encoding". For example, the Chinese for "Current date" consists of four Unicode characters (see the diagram below). Their Unicode values (hexadecimal) are also shown. The properties file should be written as:
Obviously, this is very difficult to do. Fortunately, the JBoss IDE tools includes a properties file editor that allows you to enter any Unicode characters directly including Chinese:
當前日期
currentDate=\u7576\u524d\u65e5\u671ftodayIs=...
0x7576 0x65E5
0x524D 0x671F
254 Chapter 9 Supporting Other Languages
As you probably don't know how to input Chinese, you can simply type some random text pretending to be Chinese. To make use of the properties files, modify showdate.xhtml:
To read messages from the resource bundle, do it this way:
You can enter Chinese or any Unicode character directly
It works only in the "Properties" tab. If you work in the "Source" tab, you'll have to enter the Unicode code values directly!
<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title>Current date</title></head><body>Today is: <h:outputText value="#{showDate.today}"/>.</body></html> 1: Create this UI Load
Bundle component
UI LoadBundle
UI View Root
WEB-INF
classes
multilang
ShowDate.classmsgs.propertiesmsgs_zh.properties
2: Render
3: Load multilang.msgs from the class path and assume an extension of .properties. Create a table from the content. Such a table is called a "resource bundle":
Object name Object instanceb... ...... ...
Attribute table for the request
Key StringCurrent dateToday is:
currentDatetodayIs
4: Put the resource bundle into an attribute in the request scope. The attribute name is "b" as specified.
Chapter 9 Supporting Other Languages 255
Run it and it should continue to work. How to make the page use the Chinese version of the resource bundle (msgs_zh.properties)? For example, in FireFox, choose "Tools | Options | Advanced":
Click "Choose" and make sure that Chinese is listed as the first entry (most preferred):
<html ...><head><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}"/>.</body></html>
b (resource bundle)
Key StringCurrent dateToday is:
currentDatetodayIs
1: Evaluate "b" and get access to the resource bundle
2: JSF will call getCurrentDate() on "b". It will fail. Then it will check if it is a resource bundle. If so, it will look up the key "currentDate" and return the string ("Current date").
Look up the "todayIs" key
256 Chapter 9 Supporting Other Languages
When the browser sends a request, it will include this list in the request (see the diagram below). After Tomcat receives it, it will let the view handler handle it (here it's the Facelets engine but could have been the JSP engine). The view handler will create the component tree as usual. Then it will get the most preferred language ("zh" here) from the list and store it into the view root. Later, the UI Load Bundle component will find that the language of the view root is "zh", so it will load msgs_zh.properties instead:
There is still a minor twist: Actually, the view handler will not blindly store the most preferred language into the view root. It will check if that language is supported by your application (see the diagram below). For example, if the most preferred language is en or zh, it is supported and will be stored into the view root. However, if it is, say, de (German), then it is not supported and the default language will be used (en here):
Browser Tomcat
...languages: zh, en, ...
1: Send a request
View handler
2: You handle it
Viewroot
Loadbundle
3: Create the component tree
4: Look, zh is the most preferred language.
5: Store zh into it as its language
6: What's your language? Oh it's zh. Load msgs_zh.properties.
Chapter 9 Supporting Other Languages 257
To specify the default language and supported languages, modify faces-config.xml:
The corresponding XML code is:
default: ensupported: zhsupported: fr
View handler
1: Do you support en? Yes. Use it.
2: Do you support zh? Yes. Use it.
3: Do you support de? No. Use the default (en here).
1: Choose this tab
2: Choose it
3: Enter the default language
4: Click "Add" to enter "zh"
A language is also called a locale
258 Chapter 9 Supporting Other Languages
Save the file, restart the Tomcat instance and then reload the page. You should see the Chinese version:
If you can't see Chinese on your computer, make sure it has a font that supports Chinese. For example, login as the Administrator, open the Control Panel and choose "Regional Settings" and ensure that traditional Chinese support is enabled.
You may be wondering why the UI Load Bundle component doesn't load the msgs_en.properties file when the most preferred locale is en. To understand how it works, first consider the case when the most preferred locale is zh. In that case, it will load msgs_zh.properties and use msgs.properties as the parent (see the diagram below). If a key is not found in the child, the child will look for it in the parent:
<faces-config ...><managed-bean>
<managed-bean-name>showDate</managed-bean-name><managed-bean-class>multilang.ShowDate</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean><application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler><locale-config>
<default-locale>en</default-locale><supported-locale>zh</supported-locale>
</locale-config></application>
</faces-config> If you also supported, say, fr, you would add one more <supported-locale> element there:
Chapter 9 Supporting Other Languages 259
That's why it is called a bundle. Now, consider the case when the most preferred locale is en. In that case, it try to load msgs_en.properties but it is not found (see the diagram below). Then you can consider it will use a non-existing or empty msgs_en.properties as the child. Effectively only the parent will be used:
Anyway, now you are done. It is said that you have "internationalized" this page (let it use a resource bundle) and "localized" it to Chinese (provide msgs_zh.properties). If in the future you need to add support for say French, you will not need to internationalize it again but just need to localize it to French (provide msgs_fr.properties). As the word "internationalization" is very long, sometimes people use "i18n" as its short form because there are 18 characters between the starting "i" and the ending "n". Similarly, people use "l10n" as a short form for "localization".
Internationalize the date displayFor the moment, the date is still displayed in English. To solve the problem, modify showdate.xhtml:
Key StringCurrent dateToday is:
currentDatetodayIs
Key String當前日期
今日是:currentDatetodayIs
msgs.properties
msgs_zh.properties
If a key is not found (e.g., "foo"), look for it in the parent.
Key StringCurrent dateToday is:
currentDatetodayIs
msgs.properties
msgs_en.properties
parent
NON-EXISTING
260 Chapter 9 Supporting Other Languages
Run the application and it should work:
English Chinese
Letting the user change the localeSuppose that a user is using a browser that prefers Chinese the most, but he would like to show the application to his friend who doesn't understand Chinese but understands English. To support this, you should enhance the application to allow the user to explicitly choose a locale:
The date time converter will use the locale stored in the view root to format the Date
<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/></h:outputText>.</body></html> This setting is not really
required. It is set to long so that you can see Chinese characters in the date display.
Chapter 9 Supporting Other Languages 261
After choosing a locale, he can click "Change":
Let's do it. Modify showdate.xhtml:<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/></h:outputText>.<h:form>
<h:selectOneMenu value="#{showDate.locale}"><f:selectItems value="#{showDate.locales}"/>
</h:selectOneMenu><h:commandButton action="#{showDate.changeLocale}" value="Change"/>
</h:form></body></html>
Define the properties required in ShowDate.java:
262 Chapter 9 Supporting Other Languages
Define the action method:
Run it and it will work. Finally, the Change button should also be internationalized and localized:
public class ShowDate {private String locale;public String getLocale() {
return locale;}public void setLocale(String locale) {
this.locale = locale;}public List<SelectItem> getLocales() {
Application app = FacesContext.getCurrentInstance().getApplication();List<Locale> locales = new ArrayList<Locale>();locales.add(app.getDefaultLocale());Iterator<Locale> iter = app.getSupportedLocales();while (iter.hasNext()) {
locales.add(iter.next());}List<SelectItem> items = new ArrayList<SelectItem>();for (Locale locale : locales) {
items.add(new SelectItem(locale.toString(),locale.getDisplayName(locale)));
}return items;
}public Date getToday() {
return new Date();}
}
Add the supported locales (including the default locale) into a List
It returns an iterator for the supported locales (the default locale is NOT included)
Convert each locale into a SelectItem
For example, this may be "en" or "zh".
This is the name for the locale displayed in that locale (e.g., "English" or "中文 ").
public class ShowDate {private String locale;...public String changeLocale() {
UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();viewRoot.setLocale(new Locale(locale));return null;
}} Store the new Locale
object into the view root
It needs a Locale object, not a string like "en".
Chapter 9 Supporting Other Languages 263
It's easy. Modify showdate.xhtml:<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/></h:outputText>.<h:form>
<h:selectOneMenu value="#{showDate.locale}"><f:selectItems value="#{showDate.locales}"/>
</h:selectOneMenu><h:commandButton action="#{showDate.changeLocale}" value="#{b.change}"/>
</h:form></body></html>
Define the entry in the resource bundles:
msgs.properties msgs_zh.propertiescurrentDate=Current datetodayIs=Today is:change=Change
currentDate=當前日期todayIs=今日是:change=變更
Run it and it should work:
Localizing the full stopThere is still a minor issue here. The full stop used at the end of the sentence above is the English one, not the Chinese one (yes, there is a Chinese full
Should be in Chinese
264 Chapter 9 Supporting Other Languages
stop). To solve this problem, you could add a new entry to your properties files for the full stop:
msgs.properties msgs_zh.propertiescurrentDate=Current datetodayIs=Today is:change=ChangefullStop=.
currentDate=當前日期todayIs=今日是:change=變更fullStop=。
Then change showdate.xhtml to:<h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/></h:outputText><h:outputText value="#{b.fullStop}"/>
This would work. But you are now breaking the sentence up into three parts:
This is getting too complicated. As an alternative, you could put the whole sentence into a single entry:
msgs.properties msgs_zh.propertiescurrentDate=Current datetodayIs=Today is: {0}.change=Change
currentDate=當前日期todayIs=今日是:{0}。change=變更
You will put the date display into {0} at runtime. To do that, modify showdate.xhtml as below. The <h:outputFormat> tag will create a UI Output component (just like the <h:outputText> tag does) but associate it with a Format renderer. The <f:param> will create a UI Parameter component and add it as a child of the UI Output. The UI Parameter component by itself has no meaning at all. It is entirely up to the parent component (the UI Output here) how to make use of it. Here, the UI Output will let the Format renderer do the work, which will use the value of the UI Output as a format pattern. Then it will find out the value of the 0th UI Parameter and substitute it for the {0} in the pattern:
Today is: March 30, 2008 .
Chapter 9 Supporting Other Languages 265
Now, run the application and it should be like:
The full stop is working. However, the date display is no longer in the long style. To fix it, modify the format pattern:
msgs.properties msgs_zh.propertiescurrentDate=Current datetodayIs=Today is: {0, date, long}.change=Change
currentDate=當前日期todayIs=今日是:{0, date, long}。change=變更
Now run it and it should work:
<h:outputText value="#{b.todayIs}"/><h:outputText value="#{showDate.today}">
<f:convertDateTime dateStyle="long"/></h:outputText>.
<h:outputFormat value="#{b.todayIs}"><f:param value="#{showDate.today}"/>
</h:outputFormat>
UI Output
UIParameter
Formatrenderer
value: "Today is: {0}."
value: Date
1: Read the value of the 0th parameter
2: Put it into the format pattern
266 Chapter 9 Supporting Other Languages
BUG ALERT: Due to a bug in the JSF reference implementation, the Format renderer is not using the locale of the view root and therefore it is displaying the date in English.
Displaying a logoSuppose that you'd like to display a logo on the showdate page. You have created a logo (a GIF image) as shown below:
In addition, note the "4" in the logo. In Chinese, "4" doesn't mean "for" at all. In fact, it is pronounced just like the word "death" in Chinese so people tend to avoid it in names. So, you'd like to have a Chinese version of the logo:
To display the right version of the logo, save them into the WebContent folder as logo_en.gif and logo_zh.gif respectively:
Chapter 9 Supporting Other Languages 267
Then modify showdate.xhtml:
268 Chapter 9 Supporting Other Languages
Run it and it should display the English logo (regardless of the most preferred locale):
To make it depend on the locale, modify it:
http://localhost:8080/MultiLang/logo_en.gif
<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:graphicImage value="logo_en.gif"></h:graphicImage><p/><h:outputFormat value="#{b.todayIs}">
<f:param value="#{showDate.today}"/></h:outputFormat><h:form>
<h:selectOneMenu value="#{showDate.locale}"><f:selectItems value="#{showDate.locales}"/>
</h:selectOneMenu><h:commandButton action="#{showDate.changeLocale}"
value="#{b.change}"/></h:form></body></html>
http://localhost:8080/MultiLang/showdate.jsf
UI Graphic1: Create a UI Graphic component ...
<img src="logo_en.gif">2: Render an <img> element
4: To get the image data, the browser will use the "src" attribute as a relative path from the current URL.
3: Copy
Chapter 9 Supporting Other Languages 269
Now run it and it should work:
Making the locale change persistentSuppose that you're most preferred locale is Chinese in the browser. Let's do an experiment: Change the locale to English using the Change button, then press Enter in the location bar in the browser. The page will be displayed in Chinese again. It means the locale change is for temporary (for the current view root only).
To make the change persistent say for the current session, you can store the chosen locale into the session:
<html ...><head><f:loadBundle basename="multilang.msgs" var="b" /><title><h:outputText value="#{b.currentDate}"/></title></head><body><h:graphicImage value="logo_#{view.locale}.gif"></h:graphicImage><p/><h:outputFormat value="#{b.todayIs}">
<f:param value="#{showDate.today}"/></h:outputFormat><h:form>
<h:selectOneMenu value="#{showDate.locale}"><f:selectItems value="#{showDate.locales}"/>
</h:selectOneMenu><h:commandButton action="#{showDate.changeLocale}"
value="#{b.change}"/></h:form></body></html>
"view" means the view root. You can consider that there is such a pre-defined application scoped bean.
Get the Locale object in the view root. Then convert it to a String so that you'll get something like "en" or "zh".
270 Chapter 9 Supporting Other Languages
The next step is to ask the view handler to use it if it exists. To do that, you need to create your own view handler. Let's call create a MyViewHandler class in the multilang package:
To tell the JSF engine use your own view handler, modify faces-config.xml:
public class ShowDate {private String locale;...public String changeLocale() {
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
if (session != null) {session.setAttribute("multilang.locale", locale);
}UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();viewRoot.setLocale(new Locale(locale));return null;
}}
Try to access the session
If there is indeed a session, store the chosen locale as a session attribute.
The name of the attribute
public class MyViewHandler extends FaceletViewHandler {public MyViewHandler(ViewHandler parent) {
super(parent);}public Locale calculateLocale(FacesContext context) {
HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
if (session != null) {String locale = (String) session.getAttribute("multilang.locale");if (locale != null) {
return new Locale(locale);}
}return super.calculateLocale(context);
}}
Extend the view handler provided by Facelets
The Facelets view handler has this method to get the most preferred locale from the request
Check if a locale was stored in the session. If yes, use it instead of the one in the request.
Get the locale from the request as usual
Chapter 9 Supporting Other Languages 271
The corresponding XML code is:<faces-config ...>
<managed-bean><managed-bean-name>showDate</managed-bean-name><managed-bean-class>multilang.ShowDate</managed-bean-class><managed-bean-scope>request</managed-bean-scope>
</managed-bean><application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler><view-handler>multilang.MyViewHandler</view-handler><locale-config>
<default-locale>en</default-locale><supported-locale>zh</supported-locale>
</locale-config></application>
</faces-config>Save the file, restart the Tomcat instance and run it again. This time the locale change should persist until you end the session (e.g., by restarting the browser).
Localizing validation messagesRemember that you can customize the validation messages using a resource bundle. For example, you may have a file such as MyApp.properties in the multilang package:javax.faces.converter.DateTimeConverter.DATE={0} is an invalid {2}!javax.faces.component.UIInput.REQUIRED=You must input {0}!javax.faces.validator.LongRangeValidator.MINIMUM={1} must be at least {0}!javax.faces.validator.LongRangeValidator.MINIMUM_detail={1} is invalid!
To use it, you need to say so in faces-config.xml:<faces-config ...>
<application>
1: Choose it
2: Click "Browse" to choose your MyViewHandler class
272 Chapter 9 Supporting Other Languages
<message-bundle>multilang.MyApp</message-bundle></application>...
</faces-config>It is simply a global resource bundle used by the built-in components. To localize it for, say, Chinese, all you need is to create MyApp_zh.properties, just like you would any other resource bundle.
SummaryTo internationalize a page, you can extract the strings into resource bundles, one for each supported language and a default resource bundle to act as the parent. To look up a string for a certain key in a resource bundle, use <loadBundle> to load bundle as a request attribute and access it like using an EL expression like #{attribute-name.key}.
To determine the locale to use, the view handler will check the most preferred locale as specified in the HTTP request and check if it is supported by your application. If yes, it will store it into the view root. If no, it will use the default locale specified in your application.
If you'd like to let the user specify a particular locale to use, overriding the most preferred locale set in the browser, you may want to store it into the session and provide a view handler subclass to retrieve it later.
If you'd like to fill in various slots in a pattern before outputting it, you can use <outputFormat> and specify the value for each slot using a UI Parameter component (created by a <param>). The meaning of UI Parameter is entirely determined by its parent component.
To display an image, use a <graphicImage> tag. Its "value" attribute is a relative path from the current URL. To internationalize an image, just internationalize its "value" attribute.
Essential JSF, Facelets & JBoss Seam 273
References• Apache Software Foundation. Tomcat 6 Documentation.
http://tomcat.apache.org/tomcat-6.0-doc/index.html.
• Facelets developers. Facelets - JavaServer Faces View Definition Framework. https://facelets.dev.java.net/nonav/docs/dev/docbook.html.
• RedHat JBoss. JBoss Seam Reference Documentation. http://docs.jboss.com/seam/2.0.1.GA/reference/en/html/
• RedHat JBoss. RichFaces Developer Guide. http://www.jboss.org/file-access/default/members/jbossrichfaces/freezone/docs/devguide/en/html/index.html.
• Sun Microsystems, Inc. Expression Language Specification v2.1. http://jcp.org/en/jsr/detail?id=245.
• Sun Microsystems, Inc. Java™ Servlet Specification v2.4. http://jcp.org/en/jsr/detail?id=154.
• Sun Microsystems, Inc. JavaServer™ Faces Specification v1.2 Rev A. http://www.jcp.org/en/jsr/detail?id=252.
• Sun Microsystems, Inc. JavaServer Pages™ Specification v2.1. http://jcp.org/en/jsr/detail?id=245.
274 Essential JSF, Facelets & JBoss Seam
Alphabetical IndexAction listener...............................................................................................108
Execution..................................................................................................59Immediate execution...............................................................................127
Action method..............................................................................................131AJAX............................................................................................................164
How it works............................................................................................166Refreshing multiple components.............................................................171Using a panel to refresh optional elements.............................................169
Apache MyFaces............................................................................................10Application....................................................................................................109Attribute..............................................................................................................
In request scope......................................................................................122In session scope.....................................................................................269
Big5..............................................................................................................253Calendar component......................................................................................77Cascading style sheet..................................................................................102Client id..........................................................................................................71Component........................................................................................................
Visibility...................................................................................................164Component tree.............................................................................................38Component tree.................................................................................................
Created by JSP engine..............................................................................38Components.xml..........................................................................................216Connection profile........................................................................................210Context path...................................................................................................24Conversation......................................................................................................
Beginning and ending..............................................................................234Temporary...............................................................................................232
Conversation scope......................................................................................231Vs session scope....................................................................................231
Conversion.........................................................................................................Customizing error message......................................................................73Process.....................................................................................................67
Conversion errors...........................................................................................70Cookies........................................................................................................139CSS..............................................................................................................102Database connection....................................................................................217DataModel....................................................................................................225Date converter................................................................................................66Debugging a JSF application..........................................................................40Decode...........................................................................................................51Eclipse..............................................................................................................8EL expression................................................................................................36
Accessing a resource bundle..................................................................254
Essential JSF, Facelets & JBoss Seam 275
Concatenating literals and child EL expressions.....................................169Representing a method.............................................................................99
Encode...........................................................................................................39Escaped Unicode encoding..........................................................................253ExternalContext............................................................................................159Facelets........................................................................................................180
Creating custom tags..............................................................................188Forbidding direct access to xhtml files.....................................................193Hiding JSF tags into HTML tags..............................................................191IDE support.............................................................................................180Variables.................................................................................................190Vs JSP....................................................................................................191
Faces-config.xml............................................................................................34FacesContext.................................................................................................99FacesMessage...............................................................................................99FacesMessages.................................................................................................
Detail message.......................................................................................103Facet............................................................................................................116HSQLDB......................................................................................................211Hypersonic DB.............................................................................................214I18n..............................................................................................................259Image.................................................................................................................
Displaying a localized version..................................................................268Internationalization.......................................................................................259Javascript.....................................................................................................166JavaServer Faces..........................................................................................10JBoss IDE tools............................................................................................180JBoss IDE Tools.................................................................................................
Editing Unicode properties file.................................................................253JBoss Seam.................................................................................................208JSF.................................................................................................................10
Installing....................................................................................................10Markup independence...............................................................................87Reference implementation........................................................................10
JSF Core tag lib..............................................................................................25JSF HTML tag lib............................................................................................28JSTL...............................................................................................................11L10n.............................................................................................................259Label..............................................................................................................73ListDataModel..............................................................................................225Locale...........................................................................................................257
Default.....................................................................................................257Set in browser...........................................................................................69Setting.....................................................................................................260Setting for the whole session...................................................................269Supported................................................................................................257
Localization..................................................................................................259Logout..........................................................................................................158
276 Essential JSF, Facelets & JBoss Seam
Managed beans..............................................................................................34Accessing in Java code...........................................................................109Hooking up................................................................................................80Managed property.....................................................................................83Scopes......................................................................................................32
Message Bundle.............................................................................................74Modal panel..................................................................................................172Mojarra...........................................................................................................11Navigation..........................................................................................................
Remaining in the current view.................................................................146Setting the next view id in Java...............................................................154
Navigation case..............................................................................................54Redirect...................................................................................................247
Navigation rule...............................................................................................54In Seam...................................................................................................224More specific one is considered first.......................................................205Using wildcard.........................................................................................201
Outcome........................................................................................................54Setting.......................................................................................................59Using null as the outcome.......................................................................146
Pages.xml....................................................................................................224Phase listener...............................................................................................154Phases...............................................................................................................
Affected by conversion errors....................................................................70Affected by validation errors......................................................................95Apply Request Values...............................................................................51Invoke Application.....................................................................................54Process Validations.............................................................................67, 94Render Response.....................................................................................49Restore View.............................................................................................50Update Model Values................................................................................51
Properties file.................................................................................................73Protected pages.................................................................................................
Enforcing login........................................................................................154Returning to after logging in....................................................................156
Redirect..............................................................................................................Vs render.................................................................................................247
Renderer........................................................................................................87Request..........................................................................................................32Resource bundle..........................................................................................254
Access in an EL expression....................................................................254Parent.....................................................................................................258
Resource key.................................................................................................73RichFaces......................................................................................................78
Skins.......................................................................................................176Seam............................................................................................................208
Authenticator component........................................................................239Event.......................................................................................................244
Essential JSF, Facelets & JBoss Seam 277
Identity component..................................................................................239Protected pages......................................................................................244Redirect...................................................................................................247Removing the session.............................................................................245Runtime...................................................................................................210
Seam component...............................................................................................Bijecting...................................................................................................229Conversation scope.................................................................................231Defining...................................................................................................219Injecting...................................................................................................219Outjecting................................................................................................223
Seam components.......................................................................................216SelectItem......................................................................................................64Serializable...................................................................................................132Session..........................................................................................................32
Attribute...................................................................................................269Get rid of.................................................................................................138How to maintain......................................................................................139Id.............................................................................................................140JSESSIONID...........................................................................................139Removing................................................................................................159
Session scoped managed beans.......................................................................Must implement Serializable....................................................................132Must not refer to other managed beans..................................................135
Tag lib............................................................................................................25Tag library......................................................................................................25Tomcat.............................................................................................................9Tomcat instance.................................................................................................
Creating.....................................................................................................20Modules.....................................................................................................60
Transaction..................................................................................................217Transient......................................................................................................134UI Column component..................................................................................116UI Command component...............................................................................46
Rendered as a button..............................................................................125Rendered as a link..................................................................................125
UI Data component............................................................................................Client id...................................................................................................130
UI Form component.......................................................................................46UI Graphic component.................................................................................267UI Input component........................................................................................46
Rendered as a hidden field......................................................................137Rendered as a password field.................................................................144
UI Load Bundle component..........................................................................254UI Messages component................................................................................71UI Output component.....................................................................................38
Using a format renderer..........................................................................264UI Panel.............................................................................................................
278 Essential JSF, Facelets & JBoss Seam
Grid renderer...........................................................................................104Group renderer........................................................................................104
UI Panel component.......................................................................................86UI Parameter component.............................................................................264UI Select One component..............................................................................63Validating a combination of multiple input values.........................................108Validation...........................................................................................................
Customizing the "required" message.......................................................77Customizing error message......................................................................96Localizing error messages......................................................................271Marking input as required..........................................................................76Process.....................................................................................................94Skipping null input.....................................................................................97Validator method.......................................................................................99
Validator.........................................................................................................94Verbatim HTML code.....................................................................................38View handler.................................................................................................154
Creating your own...................................................................................270Facelets..................................................................................................187Setting the locale.....................................................................................256
View root component......................................................................................38Web.xml.............................................................................................................
Security constraint...................................................................................193XHTML File..................................................................................................183XHTML vs JSP.............................................................................................185XML namespace..........................................................................................185@AutoCreate...............................................................................................223@DataModel................................................................................................227@DataModelSelection..................................................................................228@Factory......................................................................................................226@In..............................................................................................................219
Specifying the component name.............................................................227@Name........................................................................................................219@Out............................................................................................................223@Scope.......................................................................................................219<a4j:commandButton> tag...........................................................................169<a4j:commandLink> tag...............................................................................168<a4j:support> tag.........................................................................................172<actionListener> tag.....................................................................................108<calendar> tag...............................................................................................79<column> tag...............................................................................................116<commandButton> tag...................................................................................45<commandLink> tag.....................................................................................124<convertDateTime> tag..................................................................................67<dataTable> tag...........................................................................................116<facet> tag...................................................................................................116<form> tag......................................................................................................44<graphicImage> tag.....................................................................................267
Essential JSF, Facelets & JBoss Seam 279
<inputHidden> tag........................................................................................137<inputSecret> tag.........................................................................................144<inputText> tag..............................................................................................45<loadBundle> tag.........................................................................................254<message> tag............................................................................................103
Setting the CSS class..............................................................................107<messages> tag.............................................................................................71
Setting the CSS class..............................................................................102<outputFormat> tag......................................................................................264<outputText> tag............................................................................................28<panelGrid> tag..............................................................................................86<panelGroup> tag........................................................................................105<param> tag.................................................................................................264<rich:panel> tag............................................................................................176<selectItems> tag...........................................................................................63<selectOneMenu> tag....................................................................................62<setPropertyActionListener> tag..................................................................127<ui:component> tag......................................................................................189<ui:composition> tag....................................................................................200
Providing multiple concrete parts............................................................204<ui:define> tag..............................................................................................204<ui:insert> tag...............................................................................................199
Specifying a name...................................................................................203<validateDouble> tag......................................................................................97<validateLength> tag......................................................................................97<validateLongRange> tag..............................................................................95<view> tag......................................................................................................27