TipTec.essential.jsf.Facelets.and.JBoss.seam

279
Essential JSF, Facelets & JBoss Seam By Kent Ka Iok Tong Copyright © 2008 TipTec Development Publisher: TipTec Development Author's email: [email protected] Book website: http://www.agileskills2.org Notice: 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

Transcript of TipTec.essential.jsf.Facelets.and.JBoss.seam

Page 1: 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

Page 2: TipTec.essential.jsf.Facelets.and.JBoss.seam
Page 3: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 4: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 5: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 6: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 7: TipTec.essential.jsf.Facelets.and.JBoss.seam

7

Chapter 1 Chapter 1 Getting Started with JSF

Page 8: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 9: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 10: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 11: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 12: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 13: TipTec.essential.jsf.Facelets.and.JBoss.seam

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 |

Page 14: TipTec.essential.jsf.Facelets.and.JBoss.seam

14 Chapter 1 Getting Started with JSF

Dynamic Web Project":

Enter the information as below:

Page 15: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 16: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 17: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 18: TipTec.essential.jsf.Facelets.and.JBoss.seam

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":

Page 19: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 20: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 21: TipTec.essential.jsf.Facelets.and.JBoss.seam

Chapter 1 Getting Started with JSF 21

Click "Next", then you'll see:

Page 22: TipTec.essential.jsf.Facelets.and.JBoss.seam

22 Chapter 1 Getting Started with JSF

Choose your Hello project and click "Add". This way it will be added to that Tomcat instance:

Page 23: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 24: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 25: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 26: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 27: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 28: TipTec.essential.jsf.Facelets.and.JBoss.seam

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).

Page 29: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 30: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 31: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 32: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 33: TipTec.essential.jsf.Facelets.and.JBoss.seam

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"

Page 34: TipTec.essential.jsf.Facelets.and.JBoss.seam

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";}

}

Page 35: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 36: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 37: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 38: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 39: TipTec.essential.jsf.Facelets.and.JBoss.seam

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: "!"

Page 40: TipTec.essential.jsf.Facelets.and.JBoss.seam

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"

"!"

Page 41: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 42: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 43: TipTec.essential.jsf.Facelets.and.JBoss.seam

43

Chapter 2 Chapter 2 Using Forms

Page 44: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 45: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 46: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 47: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 48: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 49: TipTec.essential.jsf.Facelets.and.JBoss.seam

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"

Page 50: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 51: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 52: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 53: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 54: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 55: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 56: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 57: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 58: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 59: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 60: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 61: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 62: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 63: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 64: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 65: TipTec.essential.jsf.Facelets.and.JBoss.seam

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;

}

Page 66: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 67: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 68: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 69: TipTec.essential.jsf.Facelets.and.JBoss.seam

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":

Page 70: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 71: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

...

Page 72: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 73: TipTec.essential.jsf.Facelets.and.JBoss.seam

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):

Page 74: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 75: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 76: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 77: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Boykma
Text Box
Download at WoweBook.com
Page 78: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 79: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 80: TipTec.essential.jsf.Facelets.and.JBoss.seam

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"

Page 81: TipTec.essential.jsf.Facelets.and.JBoss.seam

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?

Page 82: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 83: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 84: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 85: TipTec.essential.jsf.Facelets.and.JBoss.seam

85

Chapter 3 Chapter 3 Validating Input

Page 86: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 87: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 88: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 89: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 90: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 91: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 92: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 93: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 94: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 95: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 96: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 97: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 98: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 99: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 100: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 101: TipTec.essential.jsf.Facelets.and.JBoss.seam

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}"

Page 102: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 103: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 104: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 105: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>...

Page 106: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 107: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 108: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 109: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 110: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 111: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 112: TipTec.essential.jsf.Facelets.and.JBoss.seam
Page 113: TipTec.essential.jsf.Facelets.and.JBoss.seam

113

Chapter 4 Chapter 4 Creating an e-Shop

Page 114: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 115: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 116: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 117: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 118: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 119: TipTec.essential.jsf.Facelets.and.JBoss.seam

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):

Page 120: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 121: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 122: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 123: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 124: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>:

Page 125: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 126: TipTec.essential.jsf.Facelets.and.JBoss.seam

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() {

Page 127: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 128: TipTec.essential.jsf.Facelets.and.JBoss.seam

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) {...

}

Page 129: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 130: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 131: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 132: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 133: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 134: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 135: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 136: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 137: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 138: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 139: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 140: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 141: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 142: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 143: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 144: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 145: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 146: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 147: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 148: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 149: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 150: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 151: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 152: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 153: TipTec.essential.jsf.Facelets.and.JBoss.seam

Chapter 4 Creating an e-Shop 153

Now run it. Login, add some products, checkout and confirm. It should work fine:

Page 154: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 155: TipTec.essential.jsf.Facelets.and.JBoss.seam

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?

Page 156: TipTec.essential.jsf.Facelets.and.JBoss.seam

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 ...

...

Page 157: TipTec.essential.jsf.Facelets.and.JBoss.seam

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).

Page 158: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Boykma
Text Box
Download at WoweBook.com
Page 159: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>&nbsp;<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

Page 160: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 161: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 162: TipTec.essential.jsf.Facelets.and.JBoss.seam
Page 163: TipTec.essential.jsf.Facelets.and.JBoss.seam

163

Chapter 5 Chapter 5 Building Interactive Pages with AJAX

Page 164: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 165: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 166: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 167: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 168: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 169: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>:

Page 170: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 171: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 172: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 173: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 174: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 175: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 176: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 177: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 178: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 179: TipTec.essential.jsf.Facelets.and.JBoss.seam

179

Chapter 6 Chapter 6 Using Facelets

Page 180: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 181: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 182: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 183: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 184: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 185: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 186: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 187: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 188: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 189: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 190: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 191: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 192: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 193: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 194: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 195: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 196: TipTec.essential.jsf.Facelets.and.JBoss.seam
Page 197: TipTec.essential.jsf.Facelets.and.JBoss.seam

197

Chapter 7 Chapter 7 Providing a Common Layout with Facelets

Page 198: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 199: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 200: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 201: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 202: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 203: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 204: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 205: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 206: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 207: TipTec.essential.jsf.Facelets.and.JBoss.seam

207

Chapter 8 Chapter 8 Using JBoss Seam

Page 208: TipTec.essential.jsf.Facelets.and.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:

Page 209: TipTec.essential.jsf.Facelets.and.JBoss.seam

Chapter 8 Using JBoss Seam 209

Keep clicking Next until you see the dialog below:

Page 210: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 211: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 212: TipTec.essential.jsf.Facelets.and.JBoss.seam

212 Chapter 8 Using JBoss Seam

Click "..." to choose a database driver. You'll see:

Page 213: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 214: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 215: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 216: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 217: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 218: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 219: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 220: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 221: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 222: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 223: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 224: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 225: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 226: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 227: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 228: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 229: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 230: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 231: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 232: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 233: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 234: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 235: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 236: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 237: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 238: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 239: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 240: TipTec.essential.jsf.Facelets.and.JBoss.seam

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 {

Page 241: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 242: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 243: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 244: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 245: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 246: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 247: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 248: TipTec.essential.jsf.Facelets.and.JBoss.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?

Page 249: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 250: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Boykma
Text Box
Download at WoweBook.com
Page 251: TipTec.essential.jsf.Facelets.and.JBoss.seam

251

Chapter 9 Chapter 9 Supporting Other Languages

Page 252: TipTec.essential.jsf.Facelets.and.JBoss.seam

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>

Page 253: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 254: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 255: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 256: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 257: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 258: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 259: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 260: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 261: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 262: TipTec.essential.jsf.Facelets.and.JBoss.seam

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".

Page 263: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 264: TipTec.essential.jsf.Facelets.and.JBoss.seam

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 .

Page 265: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 266: TipTec.essential.jsf.Facelets.and.JBoss.seam

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:

Page 267: TipTec.essential.jsf.Facelets.and.JBoss.seam

Chapter 9 Supporting Other Languages 267

Then modify showdate.xhtml:

Page 268: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 269: TipTec.essential.jsf.Facelets.and.JBoss.seam

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".

Page 270: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 271: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 272: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 273: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.

Page 274: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 275: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 276: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 277: TipTec.essential.jsf.Facelets.and.JBoss.seam

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.............................................................................................................

Page 278: TipTec.essential.jsf.Facelets.and.JBoss.seam

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

Page 279: TipTec.essential.jsf.Facelets.and.JBoss.seam

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