The book viewer is rather limited in scope. It supports only a single book, which is a managed bean that we define in the faces configuration file. The
name
of that bean is
book
.
The
image
property is a string. The application interprets that string as a URL and loads it in the book viewer's header like this:
<h:graphicImage url="#{
book.image
}"/>
The
chapterKeys
property is a read-only list of keys, one for each chapter. The book viewer populates the book viewer's menu with corresponding values from a resource bundle:
<h:dataTable value="
#{book.chapterKeys}
" var="
chapterKey
">
<h:commandLink>
<h:outputText value="
#{msgs[chapterKey]}
"/>
...
</h:commandLink>
</h:dataTable>
The
Book
class uses the
numChapters
property to compute the chapter keys.
The implementation of the
Book
class is rather mundane. You can see it in Listing 8-3 on page 324. Here is how we define an instance of the
Book
class in
faces-config
.xml
:
<faces-config>
<!-- The book -->
<managed-
bean
>
<managed-bean-name>
book
</managed-bean-name>
<managed-bean-class>
com.corejsf.Book
</managed-bean-class>
<managed-bean-scope>
request
</managed-bean-scope>
<managed-
property
>
<property-name>
titleKey
</property-name>
<value>
aliceInWonderland
</value>
</managed-property>
<managed-
property
>
<property-name>
image
</property-name>
<value>
cheshire.jpg
</value>
</managed-property>
<managed-
property
>
<property-name>
numChapters
</property-name>
<property-class>java.lang.Integer</property-class>
<value>
12
</value>
</managed-property>
</managed-bean>
</faces-config>
There are many ways to implement page layout. In this section, we look at three options: a monolithic JSF page, inclusion of common content, and Tiles.
Note
|
We do not set the book's
chapterKeys
property in
faces-config.xml
. This is because the
Book
class creates that list of chapter keys for us. All we have to do is define the
numChapters
property.
|
Monolithic JSF Pages
A monolithic JSF page is perhaps the quickest way to implement the book viewer, shown in Figure 8-2. For example, here is a naive implementation:
<!-- A panel grid, which resides in a form, for the entire page --%> <h:panelGrid
columns
="2" styleClass="book" columnClasses="menuColumn, chapterColumn">
<!-- The header, containing an image, title, and horizontal rule --%>
<f:facet name="header"> <h:panelGrid columns="1" styleClass="bookHeader"> <h:graphicImage value="#{book.image}"/> <h:outputText value="#{msgs[book.titleKey]}" styleClass='bookTitle'/> <hr> </h:panelGrid> </f:facet>
<!-- Column 1 of the panel grid: The menu, which consists of chapter links --%>
<h:dataTable value="#{book.chapterKeys}" var="chapterKey" styleClass="links" columnClasses="linksColumn"> <h:column> <h:commandLink> <h:outputText value="#{msgs[chapterKey]}"/> <f:param name="chapter" value="#{chapterKey}"/> </h:commandLink> </h:column> </h:dataTable> <!--
Column 2 of the panel grid: The chapter content
--%> <c:import url="${param.chapter}.html"/> </h:panelGrid>
The book viewer is implemented with a panel grid with two columns. The header region is
populated
with an image, text, and HTML horizontal rule. Besides the header, the panel grid has only one row—the menu occupies the left column and the current chapter is displayed in the right column.
The menu is
composed
of chapter links. By default,
Book.getChapterKeys()
returns a list of strings that looks like this:
chapter1
chapter2
...
chapterN
ChapterN
represents the last chapter in the book. In the book viewer's resource bundle, we define values for those keys:
chapter1=Chapter 1
chapter2=Chapter 2
...
To create chapter links, we use
h:dataTable
to iterate over the book's chapter keys. For every chapter, we create a link whose text corresponds to the chapter key's value with this expression:
#{msgs[chapterKey]}
. So, for example, we wind up with "Chapter 1" ... "Chapter 12" displayed in the menu when the number of chapters is 12.
The right column is reserved for chapter content. That content is included with JSTL's
c:import
tag.
The directory structure for the book viewer is shown in Figure 8-4. The monolithic JSF version of the book viewer is shown in Listing 8-1 through Listing 8-5.
Note
|
Notice the
f:param
tag inside
h:commandLink
. The JSF framework turns that parameter into a request parameter—named
chapter
—when the link is activated. When the page is reloaded, that request parameter is used to load the chapter's content, like this:
<c:import url="${param.chapter}"/>
|
Listing 8-1.
book-viewer/web/book.jsp
1. <html> 2. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3. <%@ taglib uri="http://java.sun.com/jsf/
core
" prefix="f" %> 4. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 5. 6. <f:view> 7. <f:loadBundle basename="com.corejsf.messages" var="msgs"/> 8. <head> 9. <link href="styles.css" rel="stylesheet" type="text/css"/> 10. <title><h:outputText value="#{msgs.bookWindowTitle}"/></title> 11. </head> 12. 13. <body> 14. <h:form> 15. <h:panelGrid columns="2" styleClass="book" 16. columnClasses="menuColumn, chapterColumn"> 17. <f:facet name="header"> 18. <h:panelGrid columns="1" styleClass="bookHeader"> 19. <h:graphicImage value="#{book.image}"/> 20. <h:outputText value="#{msgs[book.titleKey]}" 21. styleClass='bookTitle'/> 22. <hr/> 23. </h:panelGrid> 24. </f:facet> 25. 26. <h:dataTable value="#{book.chapterKeys}" var="chapterKey" 27. styleClass="links" columnClasses="linksColumn"> 28. <h:column> 29. <h:commandLink> 30. <h:outputText value="#{msgs[chapterKey]}"/> 31. <f:param name="chapter" value="#{chapterKey}"/> 32. </h:commandLink> 33. </h:column> 34. </h:dataTable> 35. 36. <c:import url="${param.chapter}.html"/> 37. </h:panelGrid> 38. </h:form> 39. </body> 40. </f:view> 41. </html>
|
Listing 8-2.
book-viewer/web/WEB-INF/faces-config.xml
1. <?xml version="1.0"?> 2. <faces-config xmlns="http://java.sun.com/xml/ns/javaee" 3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 5. http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd" 6. version="1.2"> 7. <managed-bean> 8. <managed-bean-name>book</managed-bean-name> 9. <managed-bean-class>com.corejsf.Book</managed-bean-class> 10. <managed-bean-scope>request</managed-bean-scope> 11. 12. <managed-property> 13. <property-name>titleKey</property-name> 14. <value>aliceInWonderland</value> 15. </managed-property> 16. 17. <managed-property> 18. <property-name>image</property-name> 19. <value>cheshire.jpg</value> 20. </managed-property> 21. 22. <managed-property> 23. <property-name>numChapters</property-name> 24. <property-class>java.lang.Integer</property-class> 25. <value>12</value> 26. </managed-property> 27. </managed-bean> 28. </faces-config>
|
Listing 8-3.
book-viewer/src/java/com/corejsf/Book.java
1. package com.corejsf; 2. 3. import java.util.LinkedList; 4. import java.util.List; 5. 6. public class Book { 7. private String titleKey; 8. private String image; 9. private int numChapters; 10. private List<String> chapterKeys = null; 11. 12. // PROPERTY: titleKey 13. public void setTitleKey(String titleKey) { this.titleKey = titleKey; } 14. public String getTitleKey() { return titleKey; } 15. 16. // PROPERTY: image 17. public void setImage(String image) { this.image = image; } 18. public String getImage() { return image; } 19. 20. // PROPERTY: numChapters 21. public void setNumChapters(int numChapters) { this.numChapters = numChapters;} 22. public int getNumChapters() { return numChapters; } 23. 24. // PROPERTY: chapterKeys 25. public List<String> getChapterKeys() { 26. if(chapterKeys == null) { 27. chapterKeys = new LinkedList<String>(); 28. for(int i=1; i <= numChapters; ++i) 29. chapterKeys.add("chapter" + i); 30. } 31. return chapterKeys; 32. } 33. }
|
Listing 8-4.
book-viewer/src/java/com/corejsf/messages.properties
1. bookWindowTitle=Welcome to Alice in Wonderland
2. aliceInWonderland=Alice in Wonderland
3.
4. chapter1=Chapter 1
5. chapter2=Chapter 2
6. chapter3=Chapter 3
7. chapter4=Chapter 4
8. chapter5=Chapter 5
9. chapter6=Chapter 6
10. chapter7=Chapter 7
11. chapter8=Chapter 8
12. chapter9=Chapter 9
13. chapter10=Chapter 10
14. chapter11=Chapter 11
15. chapter12=Chapter 12
16. chapter13=Chapter 13
17. chapter14=Chapter 14
18. chapter15=Chapter 15
|
Listing 8-5.
book-viewer/web/styles.css
1. .bookHeader { 2. width: 100%; 3. text-align: center; 4. background-
color
: #eee; 5. padding: 0 px; 6. border: thin solid CornflowerBlue; 7. } 8. .bookTitle { 9. text-align: center; 10. font-style: italic; 11. font-
size
: 1.3em; 12. font-family: Helvetica; 13. } 14. .book { 15. vertical-align: top; 16. width: 100%; 17. height: 100%; 18. } 19. .menuColumn { 20.
vertical-align
: top; 21.
background-color
: #eee; 22. width: 100px; 23. border: thin solid #777; 24. } 25. .chapterColumn { 26. vertical-align: top; 27. text-align: left; 28. width: *; 29. }
|
Common Content Inclusion
A monolithic JSF page is a poor choice for the book viewer because the JSF page is difficult to modify. Also, realize that our monolithic JSF page represents two things: layout and content.
Layout is implemented with an
h:panelGrid
tag, and content is represented by various JSF tags, such as
h:graphicImage
,
h:outputText
,
h:commandLink
, and the book chapters. Realize that
with a monolithic JSF page, we cannot reuse content or layout
.
In the
next
section, we concentrate on including content. In "Looking at Tiles" on page 331, we discuss including layout.
Content Inclusion in JSP-Based Applications
Instead of cramming a bunch of code into a monolithic JSF page, as we did in Listing 8-1 on page 322, it is better to include common content so you can reuse that content in other JSF pages. With JSP, you have three choices for including content:
-
<%@ include file="header.jsp"% >
-
<jsp:include page="header.jsp"/>
-
<c:import url="header.jsp"/>
The first choice listed above—the JSP
include
directive—includes the specified file before the enclosing JSF page is compiled to a servlet. However, the
include
directive suffers from an important limitation: If the included file's content changes after the enclosing page was first
processed
, those changes are not reflected in the enclosing page. That means you must manually update the enclosing pages—whether the including pages changed or not—whenever included content changes.
The last two choices listed above include the content of a page at runtime and merge the included content with the including JSF page. Because the inclusion happens at runtime, changes to included pages are always reflected when the enclosing page is redisplayed. For that reason,
jsp:include
and
c:import
are usually preferred to the
include
directive.
The
c:import
tag works just like
jsp:include
, but it has more features—for example,
c:import
can import resources from another web application, whereas
jsp:include
cannot. Also, prior to JSP 2.0, you cannot use JSP expressions for
jsp:include
attributes, whereas you can with
c:import
. Remember that you must import the JSTL core tag library to use
c:import
.
Throughout this chapter, we use
c:import
for consistency. You can use either
jsp:include
or
c:import
to dynamically include content. If you do not need
c:import
's extra features, then it is ever-so-slightly easier to use
jsp:include
because you do not need to import the JSTL core tag library.
JSF-Specific Considerations
Regardless of whether you include content with the
include
directive,
jsp:include
, or
c:import
, you must take into account two special considerations when you include content in a JavaServer Faces application:
-
You must wrap included JSF tags in an
f:subview
tag.
-
Included JSF tags cannot contain f:
view
tags.
The first rule applies to included content that contains JSF tags. For example, the book viewer should encapsulate header content in its own JSF page so that we can reuse that content:
<%-- this is header.jsp --%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<h:panelGrid columns="1" styleClass="header">
<h:graphicImage value="books/book/cheshire.jpg"/>
<h:outputText value="#{msgs.bookTitle}" styleClass="bookTitle"/>
...
</h:panelGrid>
Now we can include that content from the original JSF page:
<%-- This is from the original JSF page --%>
<f:view>
...
<f:
subview
id="header">
<c:import url="header.jsp"/>
</f:subview>
...
</f:view>
You must assign an ID to each subview. The standard convention for including content is to name the subview after the imported JSF page.
JSF views, which are normally web pages, can contain an unlimited number of subviews. But there can be only one view. Because of that restriction, included JSF tags—which must be wrapped in a subview—cannot contain
f:view
tags.
Caution
|
The
book-viewer-include
application maps the Faces servlet to
*.faces
. That means you can start the application with this URL: http://www.localhost:8080/book-viewer-include/book.faces. The Faces servlet maps
books.faces
to
books.jsp
. However, you cannot use the
faces
suffix when you use
c:import
. If you use
c:import
, you must use the
jsp
suffix.
|