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

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.

Comments:

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

Posted by Raphael Monroe on December 10, 2009 at 09:39 PM PST #

Thanks for catching this. We fixed it.

Ed Ort, Sun Developer Network

Posted by Ed Ort on December 15, 2009 at 01:36 AM PST #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« July 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today