X

True Abstraction: Composite UI Components in JSF 2.0 -- Part 2

Guest Author

by Ed Burns

Part 1 of this Tech Tip
introduced a powerful new feature in
JavaServer Faces (JSF) 2.0 technology: composite user
interface (UI) components. This feature allows you to turn any collection of page markup into a JSF UI
component -- with attached validators, converters, action listeners, and value change listeners -- for use by page authors.

In Part 1, you learned you how create a composite UI component and use it in a web application. In Part 2,
you will learn how to add functionality to the composite component that you created. Note that
there are many more things you can accomplish with composite UI components than are covered in this two-part tip.
Also understand that the tip is based on a pre-release version of the JSF 2.0 specification, and an immature implementation
of the composite component feature. So don't use the code you develop in this tip in a production JSF 2.0 application without
first modifying it to conform to the final version of the JSF 2.0 specification.

The tip assumes that you're already familiar with
Java EE 5 web tier technologies, especially with
JSF 1.2. A good place to learn about JSF 1.2, is the book
JavaServer Faces 1.2: The Complete Reference.

Looking Back at Part 1

This part of the tip assumes that you have created the composite UI as described in Part 1. Recall that in Part 1
you created a using page named index.xhtml. You then added a composite component tag,
<ez:loginPanel>, to the using page. Then you created a composite component by creating a file
named loginPanel.xhtml in the /resources/ezcomp directory of your project and including
<composite:interface> and <composite:implementation> tags in the
loginPanel.xhtml file. The <composite:interface> and
<composite:implementation> tags are new tags in JSF 2.0 and are used to declare the usage contract
and implementation of the composite component.

To view the application, you pointed your browser to http://localhost:8080/jsf-example01/. Figure 1
displays the composite component rendered in the page.













The Composite Component Rendered on the Page

Figure 1. The Composite Component Rendered on the Page



Add Functionality: Make the Composite Component Emit an Event

Now that you have created a composite UI, you'll want to add some true component behavior to it. So let's do that by adding
some text fields to the login panel component and allow the page author to declare that the component emits a
loginEvent event. The page author can listen for this event and take appropriate action.

Step 1. Add the implementation

Let’s create a very simple, nontemplated, unlocalized, nonaccessible, login panel UI. Naturally, if you wanted to, you
could create a fully localized and accessible UI, using the features users have come to expect with JSF. You could also
create a templated UI, using the features users have come to expect with Facelets. All of this is a part of the JSF 2.0
specification.

Delete the following line in the loginPanel.xhtml file:

   <p>This is the composite component.</p>

and replace it with the following:



   <table>
<tr>
<td>Username: <h:inputText id="username" /> </td>
</tr>
<tr>
<td>Password: <h:inputSecret id="password" /></td>
</tr>
<tr>
<td><h:commandButton value="Login" id="loginEvent" /></td>
</tr>
</table>




The new code represents a simple table that contains JSF components for the username and password fields, and for
the login button.

Step 2. Expose the parts of the implementation that you want to be the composite component contract

In general, the process of creating a composite component follows this pattern: You do the implementation, make it
look the way you want, then create an abstraction that allows the page author to easily work with the component.
Correctly using the abstraction can greatly reduce the conceptual burden on the page author.

In this case, the minimum you could expose to the page author is the fact that the login button was pressed.
To expose that event, insert the following line into the <composite:interface> section of
loginPanel.xhtml:

   <composite:actionSource name="loginEvent" />

This declares that the composite component contains a component that implements the
javax.faces.component.ActionSource2 interface, and therefore any features exposed by that interface
are accessible to the user of the composite component. In this case,
<composite:actionSource name="loginEvent"> refers to
<h:commandButton value="Login" id="loginEvent" /> in the loginPanel.xhtml
file.

The loginPanel.xhtml file should now look like this:



   <!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:ui="http://java.sun.com/jsf/facelets"
xmlns:ez="http://java.sun.com/jsf/composite">
<h:head>
<title>This will not be present in rendered output</title>
</h:head>
<h:body>
<composite:interface>
<composite:actionSource name="loginEvent"/>
</composite:interface>
<composite:implementation>
<table>
<tr>
<td>Username: <h:inputText id="username" /> </td>
</tr>
<tr>
<td>Password: <h:inputSecret id="password" /></td>
</tr>
<tr>
<td><h:commandButton value="Login" id="loginEvent" /></td>
</tr>
</table>
</composite:implementation>
</h:body>
</html>




Step 3. Make the using page take advantage of the exposed features

Now that you've added some function to the composite component, you need to go back to the using page and make it use
the new function.


  1. Insert the following line inside the <ez:loginPanel> element in the index.xhtml file.
       <f:actionListener for=loginEvent type="example01.LoginActionListener" />

    The for attribute says that this listener is for the action event on the composite component
    (<composite:actionSource>) named "loginEvent".

    Note that the use of an actionListener attribute on <ez:loginPanel> is not yet supported,
    it will probably be supported in the final release of JSF 2.0.


  2. Add the following line of code to index.xhtml below the line
    <p><h:commandButton value="reload"/></p>:
       <p><h:outputText value="#{loginActionMessage}" /></p>

    When the login event happens, a value is stored in request scope and then printed. A login component in a production
    application would likely do more.

    Here is what the index.xhtml file should now look 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:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:ez="http://java.sun.com/jsf/composite/ezcomp">
    <h:head>
    <title>Example 01</title>
    <style type="text/css">
    .grayBox { padding: 8px; margin: 10px 0; border: 1px solid #CCC; background-color: #f9f9f9; }
    </style>
    </h:head>
    <h:body>
    <p>Usage of Login Panel Component</p>
    <ui:debug hotkey="p" rendered="true"/>
    <h:form>
    <div id="compositeComponent" class="grayBox" style="border: 1px solid #090;">
    <ez:loginPanel>
    <f:actionListener for="loginEvent" type="example01.LoginActionListener" />
    </ez:loginPanel>
    </div>
    <p><h:commandButton value="reload" /></p>
    <p><h:outputText value="#{loginActionMessage}" /></p>
    </h:form>
    </h:body>
    </html>




     


  3. Add some Java code to the ActionListener to process the login. Do this by expanding the
    Source Packages node in the jsf-example01 project. Then open the
    LoginActionListener.java file and insert the following code into the body of the
    processAction method.
       FacesContext context = FacesContext.getCurrentInstance();
    context.getExternalContext().getRequestMap().put("loginActionMessage"
    "Login event happened");

    The updated content of the LoginActionListener.java file is as follows:



       package example01;
    import javax.faces.component.UIComponent;
    import javax.faces.component.ValueHolder;
    import javax.faces.context.FacesContext;
    import javax.faces.event.AbortProcessingException;
    import javax.faces.event.ActionEvent;
    import javax.faces.event.ActionListener;
    public class LoginActionListener implements ActionListener {
    public void processAction(ActionEvent event) throws AbortProcessingException {
    FacesContext context = FacesContext.getCurrentInstance();
    context.getExternalContext().getRequestMap().put("loginActionMessage",
    "Login event happened");
    }
    }




     



  4. Because this action listener is implemented in Java, you must rebuild and redeploy the application after making
    any changes to the Java file.

    Note however that Sun’s JSF 2.0 implementation snapshot, Mojarra, has the ability for any JSF artifact,
    including ActionListener implementations, to be implemented in
    Groovy. You do not have to rebuild and redeploy the
    application if the ActionListener is implemented in Groovy.
    See Ryan Lubke’s blog Groovy + Mojarra
    to learn how to use Groovy with JSF.


  5. After the application is redeployed, reload the browser page and press the Login button. You should see the page
    content shown in Figure 2.













    The Login Event Recorded on the Page

    Figure 2. The Login Event Recorded on the Page






Add Functionality: Extract Values From the Composite Component

You have now dynamically created a composite component, defined the implementation, declared the interface, and used the
component in the using page. In this final example, you'll learn one way to leverage the composite component from
within the ActionListener implementation.

Step 1. Expose the values in the contract

In this step, you'll continue to follow the best practice of information hiding by exposing only the minimum amount
of information necessary for the page author to use the component. Any useful login panel must determine what username
and password values were entered by the user. To enable the login component to get this information, you need to expose
those values to the page author. You do this by adding some entries in the <composite:interface>
section of the loginPanel.xhtml file.

Add the following lines to the <composite:interface> section of loginPanel.xhtml:

   <composite:valueHolder name="username" />
<composite:valueHolder name="password" />

This tells the page author that the composite component has two inner components that implement
javax.faces.component.ValueHolder. It means that any attached objects valid for ValueHolder
may be attached to the composite component. Note that the values of the name attribute must match the given
id values in the <composite:implementation> section.

Step 2. Use the exposed values in the LoginActionListener

Replace the implementation of the processAction() method in LoginActionListener.java file
with the following code:

   public void processAction(ActionEvent event) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
UIComponent source = event.getComponent();
ValueHolder username, password;
username = (ValueHolder) source.findComponent("username");
password = (ValueHolder) source.findComponent("password");
String message = "Login event happened" +
" userid: " + username.getValue() +
" password: " + password.getValue();
context.getExternalContext().getRequestMap().put("loginActionMessage",
message);
}

The username and password assignments in the updated implementation of the
processAction() method takes advantage of three characteristics of JSF:

  • In JSF 2.0, every composite component implements javax.faces.NamingContainer.
  • Since the beginning of JSF, the semantics of the findComponent() method on UIComponent
    state that if the argument componentId does not match any child components, then look to the closest
    ancestor component that implements NamingContainer and ask it to find the component.
  • Since the beginning of JSF, an ActionListener is passed an ActionEvent whose source
    property is the component that fired the event. That component will be a child of the composite component.

The line that begins String message = "..." constructs a message by extracting the value from the
username and password fields, and places it in request scope. The message is then printed by the
<h:outputText> tag in the using page.

Figure 3 shows what gets displayed if you enter a value in the username and password fields and then
press the Login button.













The Login Event With Captured Username and Password Values

Figure 3. The Login Event With Captured Username and Password Values



You have now dynamically created a composite component, defined the implementation, declared the interface, and used
the component in the using page. The composite component publishes a loginEvent. The ActionListener
for that event can query its source for other published components within the composite component, and values can be extracted
from those components.

Summary

JSF 2.0 provides a new taglib for creating composite components. This taglib, combined with the inclusion of Facelets
templating into JSF 2.0, gives you the ability to create composite components, declare the usage contract of composite
components, and allow the page author to use those components exactly as if they were true JSF UIComponents.

Further Reading


About the Author

Ed Burns is a senior staff engineer at Sun Microsystems. Ed has worked on a wide variety of client and server-side web
technologies since 1994, including NCSA Mosaic, Mozilla, the Sun Java Plugin, Jakarta Tomcat and, most recently
JavaServer Faces. Ed is currently the co-spec lead for JavaServer Faces. He is the coauthor of
JavaServer Faces 1.2: The Complete Reference and
the author of Secrets of the Rockstar Programmers.




Enterprise Tech Tips Survey

We'd like your feedback on the Enterprise Tech Tips -- what you like, what you don't like, what you'd like to see in future
tips. Please go to the survey and give us
your feedback. Your responses will help us make the Tech Tips better serve your needs. The survey period is September 15
through September 30.

Join the discussion

Comments ( 2 )
  • Raphael Monroe Friday, December 11, 2009

    Hi Ed,

    Firstly, nice to see you on TDC2009 in Brazil.

    About your tutorial, when you said

    "The loginPanel.xhtml file should now look like this:"

    Shouldn't the <table> tag be between <composite:implementation> and </composite:implementation> ?

    Just a little correction for some readers who did not get their example working.

    Thx


  • Ed Ort Tuesday, December 15, 2009

    Thanks for catching this. We fixed it.

    Ed Ort, Sun Developer Network


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha