Creating Composition Components in JSF 2.0

JSF 2.0 Cookbook


June 2010

$26.99

Over 100 simple but incredibly effective recipes for taking control of your JSF applications

(For more resources on JSF, see here.)

A great feature of Facelets consists in composition components (available starting with JSF 2.0). For a better understanding, we will start with a traditional application, and we will compare it with an application that uses composition components. At the end, you will love composition components.

Getting ready

We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.

How to do it...

Let's suppose that we have a list of books, characterized by title, author, and price, and we need to display this list in a table and provide the sorting (ascending/descending) action for each category (see next screenshot):

Creating Composition Components in JSF 2.0

Focusing on this view (not on functionality or backing beans), we will probably write a JSF page like the following (this is the traditional approach):

<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

<h:head>
<title> Composition components in JSF 2.0</title>
<style type="text/css">
.header { text-align: left;
letter-spacing:5px;
color:#000099
}
.odd { background-color: yellow }
.even { background-color: orange }
</style>
</h:head>

<f:view>
<h:form>
<h:dataTable id="booksId" value="#{booksStore.books}" var="bk"
rowClasses="odd, even" headerClass="header">

<!-- book title -->
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Book Title" />
<f:verbatim>[</f:verbatim>
<!-- Ascending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="booktitleascid" value="ascending" />
<f:param name="by" value="title"/>
<f:param name="order" value="ascending"/>
</h:commandLink>

<h:outputText value="," />
<!-- Descending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="booktitledescid" value="descending" />
<f:param name="by" value="title"/>
<f:param name="order" value="descending"/>
</h:commandLink>
<f:verbatim>]</f:verbatim>

</h:panelGroup>
</f:facet>

<h:outputText value="#{bk.title}" />
</h:column>

<!-- book author -->
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Book Author" />
<f:verbatim>[</f:verbatim>
<!-- Ascending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="bookauthorascid" value="ascending" />
<f:param name="by" value="author"/>
<f:param name="order" value="ascending"/>
</h:commandLink>

<h:outputText value="," />
<!-- Descending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="bookauthordescid" value="descending" />
<f:param name="by" value="author"/>
<f:param name="order" value="descending"/>
</h:commandLink>

<f:verbatim>]</f:verbatim>
</h:panelGroup>
</f:facet>
<h:outputText value="#{bk.author}" />

</h:column>

<!-- book price -->
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Book Price" />

<f:verbatim>[</f:verbatim>
<!-- Ascending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="bookpriceascid" value="ascending" />
<f:param name="by" value="price"/>
<f:param name="order" value="ascending"/>
</h:commandLink>

<h:outputText value="," />
<!-- Descending link -->
<h:commandLink action="#{booksStore.sortBooks}">
<h:outputText id="bookpricedescid" value="descending" />
<f:param name="by" value="price"/>
<f:param name="order" value="descending"/>
</h:commandLink>
<f:verbatim>]</f:verbatim>
</h:panelGroup>
</f:facet>
<h:outputText value="#{bk.price}" />

</h:column>
</h:dataTable>

</h:form>
</f:view>
</html>

In addition, we have two backing beans as follows:

A Book.java backing bean that maps the book characteristics:

package bean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class Book {

private String title;
private String author;
private String price;

public Book(String title, String author, String price) {
this.title = title;
this.author = author;
this.price = price;
}

public Book() {
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public String getPrice() {
return price;
}

public void setPrice(String price) {
this.price = price;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}

A BooksStore.java backing bean that defines a list of Book instances and defines a method for sorting the books by title, author, or price is shown next:

package bean;

import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

@ManagedBean
@SessionScoped
public class BooksStore {

private List books = new ArrayList();

public BooksStore() {
books.add(new Book("Learning Website Development with Django",
"Ayman Hourieh", "€26.34"));
books.add(new Book("Building Websites with Joomla! 1.5",
"Hagen Graf", "€29.74"));
books.add(new Book("ASP.NET 3.5 Application Architecture and
Design", "Vivek Thakur", "€30.99"));
books.add(new Book("Drupal 6 Themes", "Ric Shreves", "€26.34"));
books.add(new Book("WordPress Theme Design",
"Tessa Blakeley Silver", "€26.34"));
}

public List getBooks() {
return books;
}

public void setBooks(List books) {
this.books = books;
}

public void sortBooks() {
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletRequest httpServletRequest = (HttpServletRequest)
facesContext.getExternalContext().getRequest();

String by = httpServletRequest.getParameter("by");
String order = httpServletRequest.getParameter("order");

System.out.println("The books should be order " + order +
" by " + by + "!");

//ordering books
}
}

Obviously, the redundancy of the JSF view is annoying and very primitive. We can fix this by defining a composition component that can be invoked instead of repeating code (the backing beans remain unchanged). The composition component can be created by following a few steps. To start with, we define the composition component page and place it under the /WEB-INF 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:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

<ui:composition>
<h:column>
<f:facet name="header">
<h:panelGroup>
<f:verbatim>Book-</f:verbatim>
<h:outputText value="${attr}" />
<f:verbatim>[</f:verbatim>

<!-- Ascending link -->
<h:commandLink action="#{compbean.sortBooks}">
<h:outputText value="ascending" />
<f:param name="by" value="${attr}"/>
<f:param name="order" value="ascending"/>
</h:commandLink>

<h:outputText value="," />

<!-- Descending link -->
<h:commandLink action="#{compbean.sortBooks}">
<h:outputText value="descending" />
<f:param name="by" value="${attr}"/>
<f:param name="order" value="descending"/>
</h:commandLink>

<f:verbatim>]</f:verbatim>
</h:panelGroup>
</f:facet>
<h:outputText value="${book[attr]}" />

</h:column>
</ui:composition>
</html>

Next, we define a tag library file to map the tag name to the tag source or, in other words, to map the name of the composition component with the composition component source page. In addition, it defines the namespace used to access the tag. This is an XML file that looks like this:

<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.my.facelets.component.com/jsf</namespace>

<tag>
<tag-name>tableColumn</tag-name>
<source>mycomp.xhtml</source>
</tag>

</facelet-taglib>

(For more resources on JSF, see here.)

Further, we declare the tag library in the web.xml descriptor—you need to indicate the path to the tag library as follows:

<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/facelets/tags/taglibmycomp.xml</param-value>
</context-param>

Finally, we import the corresponding namespace and invoke the composition component. The client page for our composition component is listed as follows:

<!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:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:x="http://www.my.facelets.component.com/jsf">

<h:head>
<title>Composition components in JSF 2.0</title>
<style type="text/css">
.header { text-align: left;
letter-spacing:5px;
color:#000099
}
.odd { background-color: yellow }
.even { background-color: orange }
</style>
</h:head>

<f:view>
<h:form>
<h:dataTable id="booksId" value="#{booksStore.books}" var="bk"
rowClasses="odd, even" headerClass="header">

<x:tableColumn book="${bk}" attr="title"
compbean="${booksStore}" />
<x:tableColumn book="${bk}" attr="author"
compbean="${booksStore}" />
<x:tableColumn book="${bk}" attr="price"
compbean="${booksStore}" />
</h:dataTable>

</h:form>
</f:view>
</html>

As you can see, now the code is simpler, cleaner, and more optimal.

Regarding the values passed by the client page to the composition component, we need to notice the following (is very important to keep this in mind and to adapt it to your applications):

  • When the composition component is invoked we pass three attributes, representing a Book instance (bk), a constant string, and a BooksStore instance (booksStore)
  • The Book is passed using the book="${bk}" construction, and it is used as the ${book[attr]} construction, where attr can be title, author, or price depending on attr attribute value
  • The constant is passed as attr="title", "author", and "price" and it is used as ${attr}
  • The BooksStore instance is passed as compbean="${booksStore}", and it is used as #{compbean.sortBooks}

How it works...

The composition component acts as a reusable component. The client page calls the composition component as many times as it wants and each time it customizes it by passing it different values. As you just saw, Facelets provides support for passing different kinds of values, and, as you will see in the next two recipes, we can go even deeper on this line. As we can customize the composition component aspect, behavior, and so on, we can create a application without multiplying "islands" of code all over the application. Put this in correlation with templating and you will obtain great code!

Passing sub-elements to composition components

First of all, you need to keep in mind that this recipe uses the knowledge and code from the previous recipe, therefore it is recommended to read the previous recipe first!

Now, focusing on this recipe, you should know that the key to its design lies in the fact that a Facelets composition component is actually a type of template. Based on this important observation, we can pass a template argument using the ui:define (associated to a corresponding ui:insert). Also, we can pass the body as a default ui:insert.

Getting ready

We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.

How to do it...

First, we place an anonymous ui:insert in the composition component (you should place it exactly in the place where you need it to be replaced by Facelets) For example, we place it in each table column as shown next:

...
<h:outputText value="${book[attr]}" />

<ui:insert />
</h:column>

</ui:composition>
</html>

Now, when we invoke the composition component, the anonymous insert introduces the passed body. If the body is present, then nothing is introduced. In the following example, we are using a body for author and price columns (our body is just an f:verbatim component, but it can be anything else).

...
<x:tableColumn book="${bk}" attr="title" compbean="${booksStore}" />
<x:tableColumn book="${bk}" attr="author" compbean="${booksStore}">
<f:verbatim> [*****]</f:verbatim>
</x:tableColumn>
<x:tableColumn book="${bk}" attr="price" compbean="${booksStore}">
<f:verbatim> - Promotion!</f:verbatim>
</x:tableColumn>
...

Now the rendered table looks similar to the following screenshot:

Creating Composition Components in JSF 2.0

How it works...

Well, as we said in the description of the recipe, the secret lies in the fact that a Facelets composition component is acting like a template. I think that this observation says it all!

Passing actions to composition components

In the recipe Creating composition components in JSF 2.0, we passed different types of values to our composition component. In this recipe, we take a step forward and pass an action to it, instead of explicitly mapping the action in the composition component.

Getting ready

We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.

How to do it...

The solution is based on two steps:

  1. We pass the action name as shown next (focus on the action attribute):

    ...
    <x:tableColumn book="${bk}" attr="title" action="sortBooks"
    compbean="${booksStore}" />
    ...

  2. We use the passed action in the composition component:

    ...
    <h:commandLink action="#{compbean[action]}">
    <h:outputText value="ascending" />
    <f:param name="by" value="${attr}"/>
    <f:param name="order" value="ascending"/>
    </h:commandLink>
    ...

That's all! Now you should be able to pass an action binding to create different elements such as toolbars.

How it works...

As the standard EL can't help us here, we have used a little trick supported by Facelets—we have referenced the value binding in a generic way!

Summary

In this article, we saw how to work with composition components, and how to pass actions, and sub-elements to composition components.


Further resources on this subject:

Books to Consider

comments powered by Disqus
X

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free