Professional Documents
Culture Documents
TipTec Development http://www.agileskills2.org 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. 978-99937-929-?-? First edition April 2008
ISBN: Edition:
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.
Acknowledgments
I'd like to thank:
Helena Lei for proofreading this book. Eugenia Chan Peng U for doing book cover and layout design.
Table of Contents
Foreword.........................................................................................3 How to learn JSF, Facelets and JBoss Seam easily?...............3 Products covered in the book.....................................................3 Target audience and prerequisites.............................................3 Acknowledgments.......................................................................3 Chapter 1 Getting Started with JSF................................................7 What's in this chapter?...............................................................8 Developing a Hello World application with JSF.........................8 Installing Eclipse.........................................................................8 Installing Tomcat........................................................................9 Installing the JSF reference implementation............................10 Creating a Hello Word application............................................11 Generating dynamic content....................................................24 Retrieving data from Java code................................................31 How the JSP file generates the HTML code............................38 Debugging a JSF application...................................................40 Summary..................................................................................41 Chapter 2 Using Forms.................................................................43 What's in this chapter?.............................................................44 Developing a stock quote application.......................................44 Getting the stock quote symbol................................................44 Displaying the stock value........................................................52 Defining the page navigation....................................................54 Using a combo box...................................................................62 Inputting a date.........................................................................65 Handling conversion errors.......................................................70 Marking input as required.........................................................76 Using the calendar component.................................................77 Hooking up the managed beans..............................................80 Summary..................................................................................83 Chapter 3 Validating Input............................................................85 What's in this chapter?.............................................................86 Postage calculator....................................................................86 What if the input is invalid?......................................................93 Null input and validators...........................................................97 Validating the patron code........................................................99
Displaying the error messages in red.....................................102 Displaying the error message along with the field.................103 Validating a combination of multiple input values..................108 Summary................................................................................111 Chapter 4 Creating an e-Shop....................................................113 What's in this chapter?...........................................................114 Creating an e-shop.................................................................114 Listing the products................................................................115 Showing the product details...................................................124 Implementing a shopping cart................................................130 How Tomcat and the browser maintain the session..............139 The checkout function............................................................142 Implementing the login function.............................................144 Implementing the checkout function......................................151 Implementing logout...............................................................158 Summary................................................................................160 Chapter 5 Building Interactive Pages with AJAX......................163 What's in this chapter?...........................................................164 A sample AJAX application....................................................164 Refreshing the question only..................................................166 Refreshing the answer itself...................................................169 Giving rating to a question......................................................169 Using a modal panel to get the rating....................................172 Setting the look and feel with skins........................................176 Summary................................................................................177 Chapter 6 Using Facelets..........................................................179 What's in this chapter?...........................................................180 Setting up IDE support for Facelets.......................................180 Creating a Facelets project....................................................181 Creating your own tag............................................................188 Hiding JSF tags into HTML tags.............................................191 Forbidding direct access to xhtml files...................................193 Summary................................................................................195 Chapter 7 Providing a Common Layout with Facelets..............197 What's in this chapter?...........................................................198 Providing a common layout....................................................198 Having two abstract parts.......................................................202 Having page specific navigation cases..................................204 Summary................................................................................206
Chapter 8 Using JBoss Seam....................................................207 What's in this chapter?...........................................................208 Recreating the e-shop with Seam..........................................208 Creating the catalog page......................................................218 Displaying product details.......................................................223 Adding a product to the shopping cart...................................230 Confirming the checkout.........................................................237 Logging in...............................................................................239 Protecting the confirm page...................................................244 Implementing logout...............................................................245 Redirect vs render..................................................................247 Summary................................................................................249 Chapter 9 Supporting Other Languages....................................251 What's in this chapter.............................................................252 A sample application..............................................................252 Supporting Chinese................................................................253 Internationalize the date display.............................................259 Letting the user change the locale.........................................260 Localizing the full stop............................................................263 Displaying a logo....................................................................266 Making the locale change persistent......................................269 Localizing validation messages..............................................271 Summary................................................................................272 References..................................................................................273 Alphabetical Index......................................................................274
Chapter 1
Chapter 1
Installing Eclipse
You 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:
Installing Tomcat
Next, 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:
10
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.
12
It supports v1.2 of the JSF specification Click Add and browse to c:\jsf\lib to add these two jar files
Check this
Then while you're still in the Preferences window, choose "Server | Installed Runtimes":
13
Click "Finish". Next, right click in the Package Explorer and choose "New |
14
15
Keep clicking "Next" until you see the dialog below. Then choose your implementation:
16
Choose it
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:
17
To make the jstl-1.2.jar file available to it, copy it into the WebContent/WEBINF/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":
18
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":
19
Next, in the visual editing area, type "Hello world". Note that the source code will change automatically:
20
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":
21
22
Choose your Hello project and click "Add". This way it will be added to that Tomcat instance:
23
Click "Finish". Then you should see this Tomcat instance in the "Servers" window:
To run it, just click here.
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:
24
If you see this line, it means your application was started successfully This is your project name
/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. WebContent hello.jsp ...
25
A palette is here
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):
26
It is a tag lib
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:
http://java.sun.com/jsf/core
<view> <param> <selectItem>
com.foo
class Product { ... } class Order { ... }
Now, drag the <view> tag and drop it onto the page (either before the "Hello World" text or after it. It doesn't matter):
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:
This is like an import statement in Java. It makes the tags in a tag lib available to this JSP file. The URL for the JSF Core tag lib
In the rest of the JSP file, you can use "f" as a short hand for the long URL.
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:
28
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:
29
Note the "h" prefix. It is the short hand for the URL for the JSF HTML tag lib:
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:
30
Note that the visual display is also updated automatically. Now, save the file, go to the browser and reload the page. You should see:
31
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
GreetingService
Output Text
3: Output it
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:
Instance table
JSF Engine
GreetingService
Output Text
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,
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:
Output Text 1: Give me "foo" 1: Look up "foo" here Table for request 1 3: If still not found, try here.
JSF engine
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:
34
It should go into the table for the current request (request 3 in this case) Definition table
It should go into the table in the session for the client (client 2 in this case)
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 noargument constructor and provide properties):
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"; } }
Now, let's implement this idea. To create the bean definition table, double click the faces-config.xml file:
35
Choose "request" and click "Add". Browse to choose the GreetingService class:
36
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):
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
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.
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:
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 1: Create a "view root" JSF component 2: Plain HTML code is put into an artificial UI Output JSF component 3: Create a UI output JSF component 4: Plain HTML code again
View root
UI Output
UI Output
value: "!"
UI Output
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>:
40
<!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>
Conceptually <f:view> left a marker here
JSF engine
1: Render
View root
"Hello "
2: Render
UI Output "Paul"
3: Render
UI Output
4: Render
UI Output
"!"
41
This will start Tomcat in debug mode. If it is already running, it will be restarted.
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.
Summary
In 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.
42
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
That is, the user can enter the stock id and click OK, then the stock value will be displayed.
45
When you dropped the <h:form> tag, it noted that you didn't have a <f:view> element, so it created one for you.
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:
46
47
UI Input
UI Command
To retrieve the symbol entered by the user, you can link the UI Input component to a property of a Java object:
UI View Root UI Form
UI Command
48
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:
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:
class QuoteQuery { String sym = "IBM"; }
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:
50
JSP engine 1: Create the tree 6: Output the value ("IBM") UI View Root UI Form 5: Read its "sym" property UI Input 3: I need to access a bean named "quoteQuery" quoteQuery
9: Destroy
4: Create 8: Output the request id into a hidden field Session for this client Request id: 123 ... ... ...
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:
51
UI Input
MSFT 123
1: Form submission request arrives JSF engine ... 2: Use 123 to retrieve the tree
Session for this client Request id: 123 ... ... ...
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:
UI View Root UI Form
MSFT 123
UI Input
...
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:
52
UI Input
JSF engine
...
quoteQuery
53
1: Choose it
2: Choose it
3: Choose it
4: Click here
Warning
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.
54
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.
Now the result page is ready. The only missing question is how to display it after the form is submitted?
If outcome is "ok"
If outcome is "..."
another view id
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:
55
2: Use the current view id and outcome to find the next view id Navigation rules
JSF engine
1: Set the outcome Restore view Apply request values Update domain values Invoke application
Render response
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:
56
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:
57
Click on the getquotesymbol page and then on the quoteresult page. A link will
58
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:
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>
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:
60
Request ... JSF engine 1: Was I clicked? 2: Schedule a listener to be executed 4: Set the outcome Listener 3: Execute
UI Command
Restore view
Invoke application
Render response
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:
Double click on it
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:
61
Save the file. Now, start the Tomcat instance and go to http://localhost: 8080/StockQuote/faces/getquotesymbol.jsp in a browser. It should work:
62
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
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:
... import java.util.List; import javax.faces.model.SelectItem; public class StockService { an item for the user's selection. 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; } }
It can return a List or an array This class is provided by JSF. It represents
Make a managed bean from it. As it is a global thing, put it into the application scope:
65
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:
This string will be set into the bean
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; }
Inputting a date
Suppose that you'd like to allow the user to query the stock value on a particular
66
date:
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:
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:
Request
Date converter
1: Restore view
Restore view
Process validations
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
68
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:
<f:view> that you created? Oh, it's that <h:form> UI Input. <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>
1: Create a Date converter 2: What is the JSF component
UI Input
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 US English 1/19/2008 Jan 19, 2008 Long style Full style
UK English
19/1/2008
...
... ... ... ... ... As you can see, by default it uses the medium style. To tell it to use say the
69
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
abc
abc is invalid
UI Text
Date converter
Raw: "abc"
Restore view
Process validations
Render response
Invoke application
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:
<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 View Root It will create a UI Messages component
UI Messages
UI Form
...
UI Input
...
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:
72
Form
UI Input
...
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:
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
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.
Then open faces-config.xml, choose the "Others" tab, click "Message Bundle" and then click "Add":
1: Click here
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
If you'd like to specify the error message for that UI Input component only, 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"> <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.
77
</f:view>
Again, if you don't like the error message, you can override it in the messages.properties file:
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
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>
78
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>
79
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:
</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>
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
<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}" The same attributes converterMessage="the quote date is invalid" are supported except the "label" required="true" requiredMessage="Input is missing!"> </rich:calendar> <h:commandButton value="OK" action="ok"></h:commandButton> </h:form> </f:view>
81
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; }
} public String getSym() { return sym; } public void setSym(String sym) { this.sym = sym; } public int getStockValue() { return (sym + quoteDate.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?
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() { How to get access to StockService stkSrv = ???; the "stockService" return stkSrv.getStockValue(this); bean? }
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.
82
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:
Definition table
public void setStkSrv(StockService stkSrv) { this.stkSrv = stkSrv; } ... public int getStockValue() { StockService stkSrv = null; return stkSrv.getStockValue(this); }
To specify the property initialization table, select the "quoteQuery" bean in faces-config.xml:
83
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.
Summary
To handle a form submission, the JSF engine will go through the following
84
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.