Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Using JSF technology for XForms applications

Faheem Khan (fkhan872@yahoo.com), Software Consultant, Independent
Faheem Khan is an independent software consultant specializing in enterprise application integration (EAI) and B2B solutions.

Summary:  Interested in learning how JavaServer Faces and XForms technology can work together in a server-side application? This comprehensive hands-on tutorial by Java enterprise developer Faheem Khan examines how XForms authoring requirements fit into the JSF architecture. The author begins with a solid overview of JSF technology, then identifies the tasks you must perform if you plan to use JSF technology for XForms authoring, and demonstrates the development of a JSF tag library capable of authoring XForms markup. Throughout the tutorial, he guides your learning with a sample application to help you see the concepts put to work in a real-world application. Note: This tutorial is comprehensive and will take a significant time investment to complete.

Date:  03 Feb 2005
Level:  Introductory PDF:  A4 and Letter (1440 KB | 200 pages)Get Adobe® Reader®

Activity:  9971 views
Comments:  

Implementing the XForms-JSF shopping cart

Implementing the NameValuePair, Category, and Product classes

In Components of the XForms-JSF shopping cart application, we discussed three JSF tags: xcart:category, xcart:product, and xcart:cart. These tags form the XForms-based shopping cart tag library. We wish to make this tag library independent of our shopping cart application to ensure that you can integrate our shopping cart tag library into your own shopping cart application.

To make our shopping cart tag library independent of the shopping cart application, we need to implement three classes: NameValuePair, Category, and Product. These classes will be part of the shopping cart tag library. Application-specific model beans initialize and use these classes.

The NameValuePair class just wraps a name-value pair. The Category class holds the subcategories and products corresponding to a particular category. The Product class holds the details of an individual product (name, ID, description, price, etc.).

Let's look at the implementation details of the NameValuePair, Category, and Product classes in detail.

The NameValuePair class contains two fields -- name and value -- and their respective setter and getter methods. The following code shows the implementation of the NameValuePair class:

public class NameValuePair{
  protected String name;
  protected String value;
  public void setName(String name) {
    this.name = name;
  }
  public String getName(){
    return name;
  }
  public void setValue(String value){
    this.value = value;
  }
  public String getValue(){
    return value;
  }
}//NameValuePair

Many of the classes in our shopping cart tag library and shopping cart application use the NameValuePair class.

Any application that wants its data to be rendered using the xcart:category tag instantiates a Category object and populates it with application-specific data. The xcart:category component uses this Category object to fetch the application-specific data.

The Category class holds the category data in id, products, ancestors, and subcategories properties.

The id property of the Category class is a String object that holds the ID of the current category. The products, ancestors, and subCategories properties are arrays of NameValuePair objects. The products property contains the information of each product in the category. The ancestors property tracks the ancestors of the current category. The subCategories property stores the list of subcategories in the current category.

The following code shows the implementation of the Category class:

public class Category{
  protected String id;
  protected NameValuePair[] products;
  protected NameValuePair[] ancestors; 
  protected NameValuePair[] subCategories;
  public void setAncestors(NameValuePair[] nameId) {
    ancestors = nameId;
  }
  public NameValuePair[] getAncestors(){
    return ancestors; 
  }
  public void setSubCategories(NameValuePair[] nameId) {
    subCategories = nameId;
  }
  public NameValuePair[] getSubCategories(){
    return subCategories;
  }   
  public void setProducts (NameValuePair[] nameId) {
    products = nameId;
  }
  public NameValuePair[] getProducts(){
    return products;
  }  public void setId(String id) {
    this.id = id;
  }
  public String getId(){
    return id;
  }
}//Category

Now let's discuss the implementation of the Product class.

The Product class holds the product data in seven properties:

  • id: Stores the ID of a particular product the user clicked in the catalog view

  • name: Stores the name of the product

  • price: Stores the price of the product

  • description: Stores the description of the product

  • features: Stores the features of a product in name-value pair form

  • optional-features: Stores the optional features of the product

  • selectedOptionalFeatures: Stores those optional features the user selected in the product specification view

The following code shows the implementation of the ProductData class:

public class Product{
  protected String id;
  protected String name;
  protected String price;
  protected String[] options;
  protected String description;
  protected NameValuePair[] features;
  protected NameValuePair[] optionalFeatures;
  protected NameValuePair[] selectedOptionalFeatures;
  public void setName ( String name) {
    this.name = name;
  }  
  public String getName (){
    return name;
  }  
  public void setId (String id) {
    this.id = id;
  }  
  public String getId(){
    return id;
  }  
  public void setDescription ( String description) {
    this.description = description;
  }  
  public String getDescription (){
    return description;
  }  
  public void setPrice( String price) {
    this.price = price;
  }  
  public String getPrice(){
    return price;
  } 
  public void setFeatures(NameValuePair[] features) {
    this.features = features;
  }  
  public NameValuePair[] getFeatures (){
    return features;
  }  
  public void setSelectedOptionalFeatures(NameValuePair[] 
selOptFeatures){
    this.selectedOptionalFeatures = selectedOptionalFeatures;
  }  
  public NameValuePair[] getSelectedOptionalFeatures(){
    return selectedOptionalFeatures;
  }
  public void setOptionalFeatures (NameValuePair[] optionalFeatures){
    this.optionalFeatures = optionalFeatures;
  }  
  public NameValuePair[] getOptionalFeatures (){
    return optionalFeatures;
  }
  public void setOptions (String[] options) {
    this.options = options;
  }  
  public String[] getOptions(){
    return options;
  }  
}//Product

Now let's start implementing our sample XForms-JSF shopping cart. We have to implement three JSP pages (catalogView.jsp, productView.jsp, and cartView.jsp), three JSF components (UICategory, UIProduct, and UICart), two event handler classes (UICategoryActionListener and UICartActionListener), and three model beans (CategoryData, ProductData, and CartData).

The catalogView.jsp page, UICategory component, UICategoryActionListener event handler, and CartData model bean form a set. We will explain the working of this set in the next four sections. For the rest of the JSP pages, components, event handlers, and model beans, we will only explain the differences from the first set.


Using the xcart:category tag to generate the catalog view

Look at the following catalogView.jsp page, which generates the catalog view:

<?xml version="1.0" encoding="iso-8859-1"?> 
<html
  xmlns:ev="http://www.w3.org/2001/xml-events" 
  xmlns:xforms="http://www.w3.org/2002/xforms">
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XCart" 
  prefix="xcart" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XForms-JSF" 
  prefix="xforms-jsf" %>
<f:view>
 <head>
   <xforms-jsf:model value="#{categoryData.model" />
 </head>
 <body>
   <xcart:category value="#{categoryData.category}" 
     action="#{categoryData.getAction}">    
     <f:action_listener type="model.UICategoryActionListener"/>
   </xcart:category>
   <xforms-jsf:commandButton label="Show Cart" immediate="true" 
     action="#{categoryData.getAction}"
     actionListener="#{categoryData.showCartView}">
   </xforms-jsf:commandButton> </body>
 </body>
</f:view>
</html>

Note the following points:

  • We have used the xforms-jsf:model tag from the XForms-JSF tag library in the head element. We explained this tag in Implementing the xforms-jsf:model component.

  • We have included the xcart:category tag in the body element, which contains two attributes: value and action.

  • The JSP author provides the xcart:category tag with a value attribute. The value attribute contains the property of the model bean (categoryData.category), which contains the application data associated with the catalog view. The categoryData.category property is an object of the Category class explained earlier. Note that the model bean (categoryData) can be an object of any class, but the property that contains the application data should be an object of the Category class.

  • The method of the model bean specified in the action attribute ("categoryData.getAction") controls navigation. In Navigation process, we explained how navigation works and how the JSF framework uses the action attribute for navigation.

  • We have associated an action listener (UICategoryActionListener) with the xcart:category component. When the user clicks any category or product in the catalog view, the UICategoryActionListener class handles the generated action event, which we will discuss in Implementing the UICategoryActionListener class.

  • We have used the xforms-jsf:commandButton tag to show the cart view. The actionListener attribute of the xforms-jsf:commandButton tag refers to the event handling method of the model bean. When the user clicks this button, the JSF framework calls this method to handle the generated action event.

Implementing the UICategory component

The xcart:category tag renders the list of categories and products available in the catalog view. Each category or product is rendered as a button. If the user clicks on any category, its subcategories and products are displayed. If the user clicks on a product, the product-specification view appears to show the details of the product.

The first step toward the development of any JSF tag is to make an entry in the TLD file. The TLD entry for the xcart:category tag looks like the following code:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib>
  ......
  <tag>
    <name>category</name>
    <tag-class>xcart.CategoryTag</tag-class>
    <attribute>
       <name>value</name>
       <required>false</required>
       <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
       <name>id</name>
       <required>false</required>
       <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
       <name>action</name>
       <required>false</required>
       <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <name>binding</name>
      <required>false</required>
      <rtexprvalue> false </rtexprvalue>
    </attribute>
    <attribute>
      <name>actionListener</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <name>immediate</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
      <name>rendered</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
    </attribute>
  </tag>
  ..... 
  <!--other tag instances-->
<taglib>

In the above TLD entry, the name element inside the tag element wraps the name of the JSF tag (category), and the tag-class element wraps the complete qualified name of the tag handler class (xcart.CategoryTag).

The xcart:category tag contains two attributes: value and action. The two attribute elements in the above TLD entry correspond to these two attributes.

Here's how we implement the xcart.CategoryTag tag handler class:

public class CategoryTag extends UIComponentTag {
  private String value;
  private String action;
  private String immediate;
  private String actionListener;
  public void setValue(Object value){
    this.value = value;
  }
  public Object getValue(){
    return this.value;
  }
  public void setAction(Object action) {
    this.action = action;
  }
  public Object getAction(){
    return this.action;
  }
  public String getComponentType(){
    return "category";
  }
  public String getRendererType(){
     return null;
  }
  public String getImmediate(){
    return immediate;
  }
  public void setImmediate(String newImmediate) {
     immediate = newImmediate;
  }
  public String getActionListener(){
     return actionListener;
  }
  public void setActionListener(String newActionListener) {
    actionListener = newActionListener;
  }
  public void setProperties(UIComponent component) {
    super.setProperties(component);
    UICategory catComp = (UICategory)component;

    FacesContext fc = FacesContext.getCurrentInstance();
    Application app = fc.getApplication();

    if(action != null)    {
      if(UIComponentTag.isValueReference(action)) {
        MethodBinding mb = app.createMethodBinding(action, null);
        catComp.setAction(mb);
      } 
      else{
        MethodBinding mb = new ConstantMethodBinding(action);
        catComp.setAction(mb);
      }
    }
    if(value != null) {
      if(UIComponentTag.isValueReference(value))
      {
        ValueBinding vb = app.createValueBinding(value);
        catComp.setValueBinding("value", vb);
      } 
      else
        catComp.setValue(value);
    }
    if(actionListener != null) {
      if(UIComponentTag.isValueReference(actionListener)) {
        Class args[] = {
          javax.faces.event.ActionEvent.class
        };
        MethodBinding mb = 
          app.createMethodBinding(actionListener, args);
        catComp.setActionListener(mb);
      } 
    }  
    if(immediate != null) {
      if(UIComponentTag.isValueReference(immediate)) {
        ValueBinding vb = app.createValueBinding(immediate);
        catComp.setValueBinding("immediate", vb);
      } 
      else{
        boolean boolImmediate = 
          (new Boolean(immediate)).booleanValue();
        catComp.setImmediate(boolImmediate);
      }
    }
  }//setProperties 
}//CategoryTag

The CategoryTag class extends the UIComponentTag class to act as a tag class. Notice the following points from the CategoryTag class implementation above:

  • The getComponentType() method returns the type of the component (category), which tells the JSF framework about the component class used with this tag. Later, we will implement the matching component class.

  • The getRendererType() method returns null, which indicates that there is no renderer class associated with this JSF tag.

  • There are two setter methods (setValue() and setAction()) in the tag class, which the JSF framework uses to pass the attribute values from the JSP page to the CategoryTag tag handler class.

  • The setProperties() method is used to pass on the values of value and action attributes from the tag handler class to the matching component class.

Here's how to implement the component class associated with the xcart:category tag. Look at the following entry in the faces-config.xml file:

<?xml version="1.0"?>
<faces-config>
  ..... 
  <component>
    <component-type>category</component-type>
    <component-class>xcart.UICategory</component-class>
  </component>
  <!-- other component instances-->
</faces-config>

The component-type element wraps the type of the component (category). The name of the component class is specified in the accompanying component-class element (xcart.UICategory). Now we will implement this UICategory component class.

The UICategory component renders the categories and products as buttons. An action event is generated whenever the user clicks on a category or a product button. We will extend our UICategory component class from the UICommand class, which helps us handle action events.

The following code shows the fields and methods of the UICategory class:

public class UICategory extends UICommand{
  private String btnId = null;
  private void renderValueAsButton (ResponseWriter writer, String name,
     String id, String clientId) {
  }
  public void encodeBegin(FacesContext context) 
    throws IOException {
  }
  private String getActionValueFromRequest(FacesContext fc, String ref, 
    String tagName) {
  }
  private Object getModelBeanObject(String ref, FacesContext fc){
  }
  public void decode (FacesContext context) 
    throws IOException {
  }
  public void setBtnId(String id) {
  }
  public String getBtnId(){
  }
}//UICategory

The UICategory class contains only one property (btnId), its setter and getter methods, two public methods (encodeBegin() and decode()), and three private helper methods (renderValueAsButton(), getActionValueFromRequest(), and getModelBeanObject()).

The btnId property contains the ID of the category or product button that the user clicks. When the UICategory component fires an action, the action event handling class calls the getter method of this property to identify the category or product the user clicked in the catalog view.

Let's discuss the methods of the UICategory class one by one.

The renderValueAsButton() method writes the markup to render a single product or category as a button. The encodeBegin() method of the UICategory class calls this method once for every category and product available in the catalog view.

The renderValueAsButton() method takes the following four parameters:

  • writer: An object of the ResponseWriter class. The renderValueAsButton() method writes XForms markup on this ResponseWriter object.

  • name: The name of a product or category.

  • id: The ID of a product or category.

  • clientId: uniquely identifies a JSF component on the client side.

The code for the renderValueAsButton() method:

private void renderValueAsButton(ResponseWriter writer, String name,
  String id, String clientId){ 
  try{
    writer.write("<xforms:submit submission=\"submit\" >");
    
writer.write("<xforms:label>"+name.trim()+"</xforms:label>");
    writer.write("<xforms:action ev:event=\"DOMActivate\">");
    writer.write("<xforms:setvalue ref=\"action-performed\">"+ 
      clientId+"@"+id+"</xforms:setvalue>");
    writer.write("</xforms:action>");
    writer.write("</xforms:submit>");
  } 
  catch (java.io.IOException ie){
    ie.printStackTrace();
  }
}//renderValueAsButton

The above code authors the following markup:

<xforms:submit submission="submit" 
  xmlns:xforms="http://www.w3.org/2002/xforms" 
  xmlns:ev="http://www.w3.org/2001/xml-events">
  <xforms:label>Computer</xforms:label>
  <xforms:action ev:event="DOMActivate">
    <xforms:setvalue 
ref="action-performed">_id1@1</xforms:setvalue>
  </xforms:action>
</xforms:submit>

This markup renders a button. The whole markup is hard-coded in the renderValueAsButton() method, except the strings "Computer" and "_id1@1" (boldface in the markup above). The string "Computer" is the value of the second parameter (name). The second string "_id1@1" is the result of concatenating the third and fourth parameters (id and clientId). This data uniquely identifies a product or category. It consists of two parts separated by an "@" symbol. The first part (_id1, value of the third parameter) is the ID of the UICategory component. The second part (1, value of the fourth parameter) identifies a particular category or product in the catalog view.

Recall from Implementing the xforms-jsf:model component, where we explained the purpose of inserting an extra action-performed tag in the application-specific XML. The renderValueAsButton() method wraps the component and category (or product) ID in the action-performed tag. When a user clicks the button corresponding to a particular category or product, the data in boldface (_id1@1) is sent back to the server, where it is used to identify which button was clicked.

The encodeBegin() method of the UICategory class is lengthy, so we will implement this method incrementally.

The encodeBegin() method first checks whether the FacesContext instance is null. If it is null, it simply throws an exception.

Then it extracts the component ID by calling the getClientId() method. Next, it gets the ResponseWriter object so it can render the markup for this component. These steps are shown in the following code:

public void encodeBegin(FacesContext context) 
  throws IOException 
{
if (context == null) 
    throw new NullPointerException();
  String clientId = getClientId(context);
  ExternalContext externalContext = context.getExternalContext();
  ResponseWriter writer = context.getResponseWriter();
  .....
}//encodeBegin

Now the encodeBegin() method calls the getModelBeanObject() method. We explained how the getModelBeanObject() method works in Implementing the UISelect1 component. The getModelBeanObject() method returns an object of the Category class, which contains the data to be rendered, as shown in boldface below:

public void encodeBegin(FacesContext context) 
  throws IOException 
{
  if (context == null) 
    throw new NullPointerException();
  String clientId = getClientId(context);
  ExternalContext externalContext = context.getExternalContext();
  ResponseWriter writer = context.getResponseWriter();
  categoryData = (Category) getModelBeanObject(context,
    value);
  .....
}//encodeBegin

Now we check if the Category instance returned by the getModelBeanObject() method is null. If it is null, it means that we have no data (that is, categories and products) to render in the catalog view.

public void encodeBegin(FacesContext context) 
  throws IOException 
{
  if (context == null) 
    throw new NullPointerException();
  String clientId = getClientId(context);
  ExternalContext externalContext = context.getExternalContext();
  ResponseWriter writer = context.getResponseWriter();
  categoryData = (Category) getModelBeanObject(context,
    value);
  if (categoryData == null){
    writer.write ("catalog is empty...");
    return;
  }
  .....
}//encodeBegin

Recall Views of the shopping cart, where we discussed that the catalog view contains three boxes: ancestors, subcategories, and products. Now we will code to display these boxes one by one.

The encodeBegin() method calls the getAncestors() method of the Category object to check whether the current category has any ancestors. If the category contains ancestors, the encodeBegin() method writes markup to display the ancestors in the ancestors box.

public void encodeBegin(FacesContext context) 
  throws IOException 
{
  ......
  if (categoryData == null){
    writer.write ("category data is null...");
    return;
  }
  if (categoryData.getAncestors() != null){
    writer.write("<table border=\"1\" width=\"100%\">");
    writer.write("<tr>");
    writer.write("<td>");         
    NameValuePair[] ancestors = categoryData.getAncestors();
    if (ancestors != null){
      for(int x = 0; x < ancestors.length-1; x++){
        renderValueAsButton (writer, ancestors[x].getValue(),
          ancestors[x].getName(), clientId);
        writer.write(" > ");
      }
    }//if (ancestors != null)
    writer.write (ancestors[ancestors.length-1].getValue());
    writer.write("</td>");
    writer.write("</tr>");
    writer.write ("</table>");
  }//if (categoryData.getAncestors() != null)
  .....
}//encodeBegin

Next it calls the getSubCategories() and getProducts() methods of the Category object to check for the subcategories and products in the current category. If it contains any subcategory or product, the encodeBegin() method writes markup to display subcategories and products as buttons in their respective boxes. For this purpose, it calls the renderValueAsButton() method explained earlier.

If the category does not contain a subcategory or product, the encodeBegin() method writes the markup to display that there is no subcategory or product in the current category.

public void encodeBegin(FacesContext context) 
  throws IOException 
{
  ......
  if (categoryData.getAncestors() != null){
    ......
  }
  if (categoryData.getSubCategories() != null){
    writer.write("<table border=\"1\" width=\"100%\">");
    writer.write("Sub-Categories");
    writer.write("<tr>");
    writer.write("<td>");
    writer.write("<ul>");
    NameValuePair[] categories = categoryData.getSubCategories();
    for (int x =0; x < categories.length; x++){
      writer.write("<li>");
      renderValueAsButton (writer, categories[x].getValue(), 
        categories[x].getName(), clientId);
      writer.write("</li>");
    }//for
    writer.write("</ul>");
    writer.write("</td>");
    writer.write("</tr>");
    writer.write("</table>");
  }//if
  else{
    NameValuePair[] ancestors = categoryData.getAncestors();
    writer.write("<table border=\"1\" width=\"100%\">");
    writer.write("Sub-Categories");
    writer.write("<tr>");
    writer.write("</tr>");
    writer.write("<tr><td>");
    writer.write("<h3> There is no sub-category in the ");
    writer.write (ancestors[ancestors.length-1].getValue());
    writer.write(" category. . . </h3> ");
    writer.write("</td></tr>");
    writer.write("<tr>");
    writer.write("</tr>");
    writer.write("</table>");
  }//else
  if (categoryData.getProducts() != null){
    writer.write("<table border=\"1\" width=\"100%\" >");
    writer.write("Products");
    writer.write("<tr>");
    writer.write("<td>");
    writer.write("<ul>");
    NameValuePair[] products = categoryData.getProducts();
    for (int x =0; x < products.length; x++){
      writer.write("<li>");
      renderValueAsButton(writer, products[x].getValue(), 
        products[x].getName(), clientId);
      writer.write("</li>");
    }//for
    writer.write("</ul>");
    writer.write("</td>");
    writer.write("</tr>");
    writer.write("</table>");
  }//if
  else{
    NameValuePair[] ancestors = categoryData.getAncestors();
    writer.write("<table border=\"1\" width=\"100%\">");
    writer.write("Products");
    writer.write("<tr> ");
    writer.write("</tr>");
    writer.write("<tr><td>");
    writer.write("<h3> There is no product in the ");
    writer.write (ancestors[ancestors.length-1].getValue());
    writer.write(" category. . . </h3> ");
    writer.write("</td></tr>");
    writer.write("<tr>");
    writer.write("</tr>");
    writer.write("</table>");
  }//else
  writer.write("</tr>");
  writer.write("</table>");
}//encodeBegin

Now let's see the implementation of the getActionValueFromRequest() method. This method parses the incoming request and extracts an identifier from the request (the identifier used to identify which category or product was clicked by the user). The decode method uses this method.

The getActionValueFromRequest() method takes the following parameters:

  • fContext: The FacesContext instance.

  • ref: Contains the model bean (IncomingXMLInstanceRequest) name and the property that contains the incoming XML request from the client side. See Parsing the incoming XML instance data for details about this model bean.

  • tagName: The name of the tag (action-performed) that contains the information about the button clicked by the user.
private String getActionValueFromRequest(
              FacesContext fContext, 
              String ref,
              String tagName){
  Document doc = (Document) getModelBeanObject(fContext, ref);
  String value = new String();
  if(doc != null) {
    NodeList nl = doc.getElementsByTagName(tagName);
    if (nl != null) {
      for (int i=0; i < nl.getLength(); i++){
        if(nl.item(i).getFirstChild().getNodeType() == 
          Node.TEXT_NODE ) {
          value = nl.item(i).getFirstChild().getNodeValue();
          if (value.indexOf('@') != -1) {
            int index = value.indexOf('@');
            String id = value.substring(0,index);
            if (id.equals(getClientId(fContext)))
              return value.substring(index+1);
            else
              return null;
          }//if (value.indexOf('@') != -1)
        }//if(nl.item(i).getFirstChild()
      }//for
    }//if(nl != null)
  }//if(doc != null)
  return null;
}//getActionValueFromRequest()

Notice the following points from the getActionValueFromRequest() method above:

  1. It first calls the getModelBeanObject() method, which returns a DOM Document object. The DOM Document object contains DOM representation of the application-specific XML from the user's request.

  2. Then it extracts the action-performed tag from the DOM document and extracts its contents. The contents of the action-performed tag consist of two parts separated by the "@" symbol. The first part identifies the component; the second part identifies the category or the product that was clicked. The getActionValueFromRequest() method identifies both the values and returns the product and category ID.

Next, we will discuss the implementation details of the decode() method.

public void decode (FacesContext context){
  if (context == null) 
    throw new NullPointerException();
  String id = getActionValueFromRequest(context,
    "#{incomingXMLInstanceRequest.DOMDocument}", "action-performed");
  if (id != null){
    this.btnId = id;
    queueEvent(new ActionEvent(this));
  }//if
}//decode

You are already familiar with the functionality of the decode() method. Here, the decode() method is performing the following steps:

  1. It checks whether the FacesContext object is null. If it is null, the decode() method throws an exception.

  2. It calls the getActionValueFromRequest() method explained above.

  3. The call in step 2 returns a string value. This value can tell which product or category was clicked.

  4. if the ID returned by the getActionValueFromRequest() method is not null, the decode() method will set this ID in the btnId property of the component (which the event handling logic will use to identify the category or product ID the user clicked).

  5. Finally, the decode() method fires an action event. To fire an action event, the decode() method first instantiates an ActionEvent object, passing it the component instance. It then calls the queueEvent() method, passing it the ActionEvent object along with the method call. Note that the event handler class will extract the component object from the ActionEvent object, then call the getBtnId() method of the component to identify the category or product the user clicked.

Implementing the UICategoryActionListener class

The UICategoryActionListener class is associated with the UICategory component. When the user clicks any category or product in the catalog view, the UICategory component fires an action event. this action event is handled by UICategoryActionListener. The following code shows the methods in the UICategoryActionListener class:

public class UICategoryActionListener implements 
ActionListener {
  public void processAction(ActionEvent event) {
  }
}//UICategoryActionListener

UICategoryActionListener implements the processAction() method of the ActionListener interface.

The processAction() method first creates an instance of the FacesContext object. It then gets an instance of the model bean from the context by calling the getModelBeanObject() method. The getModelBeanObject() method returns an object of the CategoryData class, which contains complete catalog data, as shown in boldface below:

public void processAction(ActionEvent event) 
{
FacesContext context = FacesContext.getCurrentInstance();
  CategoryData categoryData = (CategoryData) getModelBeanObject
    ("#{categoryData}", context);
  .......
}//processAction

Next, the processAction() method calls the getComponent() method of the event object, which returns the instance of the component that fires the action event. Then it calls the getBtnId() method, which returns the ID of the category or product that identifies the category or product button clicked by the user in the catalog view. The processAction() method stores the ID in a String type variable named actionId:

public void processAction(ActionEvent event){
  FacesContext context = FacesContext.getCurrentInstance();
  CategoryData categoryData = (CategoryData) getModelBeanObject
    ("#{categoryData}", context);
UICategory uic = (UICategory) event.getComponent();
  String actionId = (String) uic.getBtnId();
  .......
}//processAction

Next, we check whether the user clicked a category or product. If the user clicked a category, there can be two further subcases: The user can click the root category or any subcategory.

If the user clicked the root category, we will set the category ID as null (as the root category does not have an ID). If the user clicked any subcategory in the catalog view, we will set the category ID as the actionId value. We then call the setAction() method of the model bean, passing the string "category" to tell the model bean that the user clicked a category.

public void processAction(ActionEvent event){
  .......
  UICategory uic = (UICategory) event.getComponent();
  String actionId = (String) uic.getBtnId();
if(actionId.equals("null"))
    category = true;
  else
    category = categoryData.isCategory(actionId);
  if (category){
    if(actionId.equals("null"))
      categoryData.getCategoryData().setId(null);
    else
      categoryData.getCategoryData().setId(actionId);
    categoryData.setAction("category");
  }//if(category)
  .......
}//processAction

Now the processAction() method handles the case when the user clicks a product in the catalog view. Clicking a product shows the product-specification view. First, we have to prepare the ProductData model bean, then we have to tell the CategoryData model bean that the user clicked a product.

So we call the getModelBeanObject() method, which returns an instance of the ProductData class. The ProductData model bean was initialized when the first request was made of the shopping cart, but it does not contains any data. Now we populate the ProductData object with the data of the product that the user wants. The product-specification view uses this data to render the specifications of the product.

Finally, we call the setAction() method of the CategoryData model bean, passing the string "product" to tell the CategoryData model bean that the user clicked a product:

public void processAction(ActionEvent event){
  .......
  if(actionId.equals("null"))
    category = true;
  else
    category = categoryData.isCategory(actionId);
  if (category){
    .......
  }
else {
    ProductData productData = (ProductData) getModelBeanObject
      ("#{ProductData}", context);
    productData.setDocument(categoryData.getDocument());
    productData.setProductId(actionId);
    categoryData.setAction("product");
  }//else
}//processAction


Implementing the CategoryData model bean

The CategoryData bean is an application-specific model bean that performs the following actions:

  • It parses the XML file, which holds the complete catalog data comprising all products and categories in our shopping cart. Note that we are using a simple XML file for back-end data storage. If you want to use some other type of back-end data storage in your shopping cart application, you will only need to reimplement the CategoryData model bean, and the rest of the application will work fine.

  • It also instantiates and populates the Category class required by the components to render the catalog view.

  • The CategoryData model bean also implements action event handling methods to handle the events fired by the xforms-jsf:commandButton components in the catalog view.

The following code shows the properties and methods of the CategoryData class:

public class CategoryData{
  private String model = null;
  private String outcome = null;
  private Category categoryData;
  private Document document = null;

  public CategoryData() {}
  public void setModel(String model) {}
  public String getModel() {}
  public void setDocument (Document doc) {}
  public Document getDocument() {}
  public void setAction(String action) {}
  public Action getAction() {}
  public void setCategoryData(Category categoryData) {}
  public Category getCategoryData() {}
  public boolean isCategory(String id) {}
  private Category setCategoryDataValues(String id, Document doc) {}
  private void setSubCategoriesAndProducts (Element element, 
    Category categoryData) {}
  private void setNameValuePair(Element[] elements, NameValuePair[] 
    nameValuePair) {}
  private Element[] getElementsByTagName(Element element, String 
    tagName) {}
  private Element getElementById(Element element, String tagName,
    String id) {}
  private void loadXML(String fileName) {}

  public void showCartView(ActionEvent ae) {
  } 
}//CategoryData

The CategoryData class has a constructor, four properties (document, model, outcome, and categoryData), setter and getter methods for these properties, some private helper methods, and a public showCartView() action event handling method.

The document property contains the complete catalog of our shopping cart. The UICategoryActionListener class calls the getter method (getDocument()) to pass on the document to the ProductData model bean.

The model property contains the application-specific XML, which is used to track the category or product the user clicked in the catalog view. The xforms-jsf:model component renders the application-specific XML, so the UIModel component explained in Implementing the xforms-jsf:model component calls the getter method of the model property to fetch the application-specific XML.

The outcome property specifies the string used to navigate to the next page. This property can have any of the following three values: "category," "product," and "cart." If the value of the outcome property is "category" (which tells that the user clicked a category in the catalog view), the next page will be the same: catalogView.jsp. If the value of the outcome property is "product" (which tells that the user clicked a product), the next page will be productView.jsp. If the value of the outcome property is "cart" (which tells that the user clicked the Show cart button), the next page will be cartView.jsp.

The categoryData property is an instance of the Category class, which this model bean instantiates and populates for the UICategory component.

Let's discuss the methods of the CategoryData class one by one.

The CategoryData constructor first initializes the Category object. It then calls the loadXML() method, passing along the name of the XML file that contains the complete catalog data of our shopping cart. The constructor also sets the ID property of the newly created Category object to null. The loadXML() method parses the XML file and then loads it in the document property of the bean discussed above.

public CategoryData(){
  categoryData = new Category();
  categoryData.setId(null);
  String path = fc.getApplication().getViewHandler().getResourceURL
    (fc, "/shoppingcart.xml");
  String url = fc.getExternalContext().encodeResourceURL(path);
  this.loadXML(url);
}//CategoryData

The CategoryData class implements the setAction() method. The setAction() method takes a string parameter and sets it in the outcome property of the model bean. The decode() method of the UICategory class knows the ID of the category or product that the user clicked in the catalog view. Before firing the action event, the decode() method sets this ID in the btnId property of the component.

The event handler class (UICategoryActionListener) calls the getter method of the btnId to check if the ID belongs to a category. Then it calls the setAction() method and passes it a string value "category."

public void setAction(String action){
  this.outcome = action;
}//setAction

The getAction() method returns a string value, which the JSF framework uses for navigation. In Using the xcart:category tag to generate the catalog view, we provided the catalogView.jsp page. Notice the action attribute in the xcart:category tag shown in the JSP page. The action attribute specifies the categoryData.getAction() method name that decides the navigation. The following code shows the implementation of this method in the model bean:

public String getAction(){
  return outcome; 
}//getAction

The getAction() method returns the value of the outcome property (which UICategoryActionListener has already set by calling the setAction() method).

The setCategoryData() method simply takes an instance of the Category object and assigns it to the categoryData property.

public void setCategoryData(Category categoryData){
  this.categoryData = categoryData;
}//setCategoryData

The decode() method extracts the ID of the category or product the user clicked in the catalog view and passes it to the event handler class (the UICategoryActionListener class). When UICategoryActionListener receives the ID, it instantiates the Category class and sets the id property of the Category object. It then calls the setCategoryData() method of the model bean and passes the Category object along with the method call.

The getCategoryData() method simply calls a helper method named getCategoryDataValues(). The getCategoryDataValues() method takes the same ID that the UICategoryActionListener supplied to the model bean. The getCategoryDataValues() helper method returns another Category object that contains the same ID, as well as all the data related to the same category (list of subcategories, ancestors, and products).

public Category getCategoryData(){
  Category catData = getCategoryDataValues(categoryData.getId());
  return catData;
}//getCategoryData

The CategoryData model bean implements an action event handling method named showCartView(). Recall the screen shot for the catalog view from Views of the shopping cart, which contains a Show cart button. When the user clicks this, the JSF framework calls the showCartView() method of the model bean, which simply sets the action property with a "cart" string and causes the navigation to the cartView.jsp page.

public void showCartView(ActionEvent ae){
  this.action = "cart";
}//showCartView 

In the past four sections, we have seen the implementation of the catalogView.jsp page, UICategory component, UICategoryActionListener, and the CategoryData model bean. These four components together form a set, which renders the catalog view.

In the next two sections, we will implement similar kinds of sets to render the product-specification view and catalog view. But this time, we will not go into low-level details like we did for first set.


Implementing the productView.jsp page, UIProduct component, and ProductData model bean

In the productView.jsp page, we have used three tags from our XForms-JSF tag library (xforms-jsf:model, xforms-jsf:selectManyCheckbox, and xforms-jsf:commandButton) and one tag (xcart:product) from our shopping cart tag library.

The JSP author provides the xcart:product tag with a value attribute, whose value refers to a Product class type property of a ProductData model bean. For example, look at the following xcart:product tag in the JSP page:

<?xml version="1.0" encoding="iso-8859-1"?>
<html
  xmlns:ev="http://www.w3.org/2001/xml-events" 
  xmlns:xforms="http://www.w3.org/2002/xforms">
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XCart" 
prefix="xcart" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XForms-JSF" 
  prefix="xforms-jsf" %>
<f:view>
 <head>
  <xforms-jsf:model value="#{productData.model}"/>
 </head>
 <body>
  <xcart:product value="#{productData.product}"/>
  <xforms-jsf:selectManyCheckbox label="Optional Features: "
    value="#{productData.selectedOptionalFeatures}" ref="options">
    <f:selectItems value="#{productData.optionalFeatures}"/>
  </xforms-jsf:selectManyCheckbox>
  <xforms-jsf:commandButton label="Add to cart" immediate="false"
     action="#{productData.getAction}"
     actionListener="#{productData.addProductToCart}">
  </xforms-jsf:commandButton>
  <xforms-jsf:commandButton label="Back to catalog view" 
     action="#{productData.getAction}" immediate="true" 
     actionListener="#{productData.showCatalogView}">
  </xforms-jsf:commandButton> 
 </body>
</f:view>
</html>

The xcart:product tag takes only one attribute: value. You are already familiar with the functionality of the value attribute. In the above JSP, the value attribute contains the product property of the ProductData model bean. The xcart:product component implementation calls the getter method of the productData.product property and gets the Product instance that contains the complete details of a single product.

The xcart:product tag renders the details of the product the user clicked in the catalog view. These details of a product consist of the name, price, description, and features of a particular product.

Now let's implement the component class mentioned in the component-class element of the faces-config.xml file above: the UIProduct class.

The following code shows the properties and methods of the UIProduct class:

public class UIProduct extends UIComponentBase{
  private String value;
  public void encodeBegin(FacesContext context) 
    throws IOException  {
  }
  public void setValue(Object value) {
  }
}//UIProduct

The UIProduct class contains only one property (value), two public methods (encodeBegin() and setValue()), and four private helper methods (renderProductSpec(), renderFeatures(), renderNameValue(), and getModelBeanObject()).

The value property contains the name of the Product object associated with the UIProduct component. The UIProduct component uses the value attribute to get the Product object. It then uses the Product object to fetch the application-specific data (name, id, price, and description of a product).

Let's look at the encodeBegin() method of the UIProduct class.

public void encodeBegin(FacesContext context) 
  throws IOException 
{
  if (context == null)
    throw new NullPointerException();
  Product productData = (Product) getModelBeanObject (context, 
    value);
  ResponseWriter writer = context.getResponseWriter();
  if (productData == null)
    writer.write("<br/>product data is null"); //show nothing
  else
  {
    writer.write("<h3> X-Cart Product Detail </h3>");
    writer.write("<table width=\"100%\">");
    writer.write("<tr>");
    writer.write("<table colspan=\"3\" border=\"1\" width=\"70%\""); 
    String[] spec = new String[4];
    spec[0] = productData.getId();
    spec[1] = productData.getName(); 
    spec[2] = productData.getDescription();
    spec[3] = productData.getPrice();
    String[] labels = {"Product_ID","Product_Name:",
      "Product_Description:", "Product_Price:"};
    renderProductSpec(writer, spec, labels);
    NameValuePair[] features = productData.getFeatures();
    if (features != null)
      renderFeatures(writer, "Product Features",features);
    writer.write("</table>");
    writer.write("</tr>");
    writer.write("</table>"); //colspan=\"4\" border=\"1\" 
  }//else
}//encodeBegin

The following points explain the above code:

  • It first verifies the FacesContext instance passed to it. If it is null, it throws a NullPointerException.

  • After verifying the FacesContext object, it calls the getModelBeanObject() method, which returns an instance of the Product object.

  • It fetches the ResponseWriter object.

  • It then determines whether the Product instance is null. If so, it hard-codes the markup to display that the product is null.

  • If the Product instance retrieved in step 2 is not null, it uses the Product instance to get the name, description, price, and features of the product.

  • Finally, it calls the renderProductSpec() and renderFeatures() private helper methods to display the product specifications (product name, description, price, etc.) and product features.

Now we will implement the ProductData model bean.

The ProductData model bean is associated with the product-specification view. This model bean basically populates the Product class used by UIProduct and UISelect components to display the product details like name, price, features, and optional features. The ProductData model bean also implements the action event handling methods to handle the action events generated in the product-specification view and in the edit product view.

The following code shows the implementation of the ProductData model bean:

public class ProductData{
  private Product productData;
  private Document document = null;
  private String model = null;
  private String productId = null;  
  private boolean isProductSet = false;
  private ArrayList optionalFeatures;
  private String[] selectedOptionalFeatures = null;
  
  public ProductData() {}
  public String getModel() {}
  public void setModel(String model){}
  public void setDocument(Document doc) {}
  public Document getDocument(){}
  public String getProductId(){}
  public void setProductId(String id) {}
  public Product getProductData(){}
  public void setProductData(Product pData) {}
  public void setOptionalFeatures (String[] optFeatures, Product pd) {}
  public Collection getOptionalFeatures (){}
  public void setSelectedOptionalFeatures (String[] 
    selectedOptionalFeatures) {}
  public String[] getSelectedOptionalFeatures(){}
  public void showCatalogView(ActionEvent ae){
  }
  public void showCartView(ActionEvent ae){
  }
  public void addProductToCart(ActionEvent ae){
  }
  public void saveToCart(ActionEvent ae){
  }
}//ProductData

The UIProduct class contains a constructor, six properties (document, model, productData, productId, optionalFeatures, and selectedOptionalFeatures), setter and getter methods for these properties, and four event handler methods (showCatalogView(), addProductToCart(), showCartView(), and saveToCart()).

The document property contains the complete catalog of our shopping cart. UICategoryActionListener calls the setter method (setDocument()) to pass on the document to the ProductData model bean.

The model property contains the application-specific XML. The UIModel component calls the getter method of the model property to fetch the application-specific XML.

The productId property contains the ID of the product the user clicked in the catalog view. UICategoryActionListener calls the setter method (setProductId()) to pass on the product ID the user clicked.

The optionalFeatures property of bean contains the optional feature of the product the user clicked in the catalog view. The UISelect component calls the getOptionalFeatures() method to render the optional features of the product as check boxes.

The selectedOptionalFeatures property stores those optional features the user selects in the product-specification view. The updateModel() method of the UISelect component calls the setSelectedOptionalFeatures() method to set the user's selected optional features that its respective decode() method retrieved from the request.

The product property of the ProductData model bean is an instance of the Product class. This property only stores the record of a single product. The UIProduct and UISelect components call the getProduct() method to render the product details.

Let's discuss the implementation of the event handling methods in the ProductData model bean one by one.

The showCatalogView() method takes an instance of the ActionEvent class along with the method call. Recall the product-specification view from Views of the shopping cart, where a Back to catalog view button is shown. When the user clicks this, an action event occurs. The JSF framework calls the showCatalogView() method to handle the event. This method simply sets the action property with the "catalogView" string:

public void showCatalogView(ActionEvent ae){
  this.action = "catalogView";
}//showCatalogView

The showCartView() method takes an instance of the ActionEvent class along with the method call. Recall the edit product view from Views of the shopping cart, where a Back to cart button is shown. When the user clicks this, an action event occurs. The JSF framework calls the showCartView() method to handle the event. This method simply sets the action property with the "cart" string.

public void showCartView(ActionEvent ae){
  this.action = "cart";
}//showCartView

The addProductToCart() event handling method takes an instance of the ActionEvent object along with the method call. When the user clicks the Add to cart button in the product-specification page, the JSF calls the addProductToCart() method to handle the event. The following code shows the implementation of the addProductToCart() method:

public void addProductToCart(ActionEvent ae) {
  FacesContext context = FacesContext.getCurrentInstance();
  Application app = context.getApplication();
  ValueBinding vb = app.createValueBinding("#{cartData}");
  CartData cdmb = (CartData) vb.getValue(context);
  Product pd = getProduct();
  String[] selectedFeatures = getSelectedOptionalFeatures();
  if (selectedFeatures != null) {
    NameValuePair[] optionalFeatures =  pd.getOptionalFeatures();
    NameValuePair[] new_optionalFeatures = new 
          NameValuePair[selectedFeatures.length];
    for (int x = 0; x < selectedFeatures.length; x++){
      for (int y = 0; y < optionalFeatures.length; y++){
        NameValuePair pair = optionalFeatures[y];
        if (selectedFeatures[x].equals(pair.getName()))
          new_optionalFeatures[x] = pair;
      }//for
    }
    pd.setSelectedOptionalFeatures(new_optionalFeatures);
  }//if(selectedFeatures != null)
  cdmb.addProduct(pd);
  this.action = "cart";
}//addProductToCart

Note the following points:

  • The addProductToCart() method first gets an instance of the FacesContext object.

  • It fetches an instance of the CartData model bean from the application context.

  • The addProductToCart() method then calls the getProduct() method, which returns the Product class instance that wraps the details of the product the user clicked in the catalog view.

  • Next, the addProductToCart() method calls the getSelectedOptionalFeatures() method, which returns the list of features the user selected in the product-specification view.

  • It checks if the selected optional features are not null, then it sets the selected optional features in the selectedOptionalFeatures property of the Product object.

  • After setting the selected features, it adds the product to the cart. It calls the addProduct() method of the CartData model bean to add the product to the cart.

  • Finally, the addProductToCart() method sets the action property of the model bean with the "cart" string.

When the user clicks the Save edited product button in the edit product view, the JSF calls the saveToCart() method to handle the event. The following code shows the implementation of the saveToCart() method:

public void saveToCart(ActionEvent ae){
  FacesContext context = FacesContext.getCurrentInstance();
  Application app = context.getApplication();
  ValueBinding vb = app.createValueBinding("#{cartData}");
  CartData cd = (CartData) vb.getValue(context);
  Product pd = getProduct();
  String[] selectedFeatures = getSelectedOptionalFeatures();
  if (selectedFeatures == null || selectedFeatures.length == 0)
    pd.setSelectedOptionalFeatures(new NameValuePair[0]);
  else{
    NameValuePair[] optionalFeatures =  pd.getOptionalFeatures();
    NameValuePair[] new_optionalFeatures = 
        new NameValuePair[selectedFeatures.length];
    for (int x = 0; x < selectedFeatures.length; x++){
      for (int y = 0; y < optionalFeatures.length; y++){
        NameValuePair pair = optionalFeatures[y];
        if (selectedFeatures[x].equals(pair.getName()))
          new_optionalFeatures[x] = pair;
      }//for(int y = 0; y < optionalFeatures.length; y++)
    }//for(int x = 0; x < selectedFeatures.length; x++)
    pd.setSelectedOptionalFeatures(new_optionalFeatures);
  }//else
  Product[] pData = cd.getProducts();
  for(int i = 0; i< pData.length; i++){
    if( pData[i].getId().equals(pd.getId()) )
      cd.setProductAt(pd, i);
  }//for
  this.action = "cart";
}//saveToCart

Note the following code points:

  • The saveToCart() method first gets an instance of the FacesContext object.

  • It then fetches an instance of the CartData model bean from the application context.

  • Then the saveToCart() method calls the getProduct() method, which returns the Product class instance that wraps the details of the product the user clicked in the catalog view.

  • The saveToCart() method calls the getSelectedOptionalFeatures() method, which returns the list of features the user selected in the product-specification view.

  • It then checks if the selected optional features are null and sets the selectedOptionalFeatures property of the Product object as null.

  • If the selected optional features are not null, it sets the selected optional feature in the selectedOptionalFeatures property of the ProductData model bean class.

  • After adding the selected features, it calls the getProducts() method of CartData, which returns all products in the cart as an array of the Product object.

  • After iterating through all the products until the current product (edited) is found, it overwrites the edited product.

  • Finally, the saveToCart() method sets the action property of the model bean with the "cart" string.

Implementing the cartView.jsp page, UICart component, UICartActionListener, and CartData model bean

Look at the following cartView.jsp page, which generates the cart view:

<?xml version="1.0" encoding="iso-8859-1"?> 
<html
  xmlns:ev="http://www.w3.org/2001/xml-events" 
  xmlns:xforms="http://www.w3.org/2002/xforms">
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XCart" 
prefix="xcart" %>
<%@ taglib uri="http://afictitiousshoppingcart.com/XForms-JSF" 
  prefix="xforms-jsf" %>
<f:view>
 <head>
   <xforms-jsf:model value="#{cartData.model}"/>
 </head>
 <body>
  <xcart:cart value="#{cartData.products}" 
    action="#{cartData.getAction}">
    <f:actionListener type="model.UICartActionListener"/>      
  </xcart:cart>
  <xforms-jsf:commandButton label="Buy Cart" 
      action="#{cartData.getAction}"
      actionListener="#{cartData.buy}" immediate="true">
  </xforms-jsf:commandButton>
  <xforms-jsf:commandButton label="Back to catalog view"
      action="#{cartData.getAction}"
      actionListener="#{cartData.showCatalogView}" immediate="true">
  </xforms-jsf:commandButton>
 </body>
</f:view>
</html>

Note the following points:

  • We have included the xcart:cart tag in the body element, which contains two attributes: value and action.

  • The xcart:cart tag renders the list of all the products a user added to the cart. The xcart:cart tag also renders Remove and Edit buttons with each product in the cart.

  • The method of the model bean specified in the action attribute (cart.getAction) controls navigation.

  • We have associated an action listener (UICartActionListener) with the xcart:cart component. When the user clicks the edit or Remove button of any product in the cart view, the UICartActionListener class handles the generated action event, which we will discuss shortly.

  • We have used two xforms-jsf:commandButton tags. The first is used to go back to the catalog view; the second is to buy the cart.

Next, we'll implement the component class associated with the xcart:cart tag.

The following code shows the properties and methods in the UICart component:

public class UICart extends UICommand{
  private String btnId = null;
  private String productId = null;
  private void renderValueAsButton (ResponseWriter writer, String name, 
    String id, String clientId) {}
  public void encodeBegin(FacesContext context) 
    throws IOException  {}
  public void decode (FacesContext context) {}
  private String getActionValueFromRequest(String retValue, 
    FacesContext fc, String ref, String tagName) {}
}//UICart

The UICart class contains two properties (btnId, productId), setter and getter methods for the properties, two public methods (encodeBegin() and decode()), and three private helper methods (renderValueAsButton(), getModelBeanObject(), and getActionValueFromRequest()).

The btnId property contains the ID of the Edit or Remove button the user clicks in the cart view. When the UICart component fires an action, the action event handling class calls the getter method of this property to compare the button the user clicked against a particular product in the cart view.

The productId property contains the ID of the product whose Edit or Remove button the user clicks in the cart view. When the UICart component fires an action, the action event handling class calls the getter method of the productId property to get the product ID.

Let's discuss the methods of the UICart class one by one.

The renderValueAsButton() method writes the markup to render a single product as a button. The encodeBegin() method of the UICategory class calls this method once for every product available in the cart view.

The renderValueAsButton() method takes the following four parameters:

  • writer: An object of the ResponseWriter class. Its write() method is used to write the response markup.

  • name: The name of a product.

  • id: The ID of a product.

  • clientId: Uniquely identifies a component on the client side.

The code for the renderValueAsButton() method:

private void renderValueAsButton(ResponseWriter writer, String name, 
  String id, String clientId){
  try {
    writer.write("<xforms:submit submission=\"submit\""); 
    writer.write(" 
xmlns:xforms=\"http://www.w3.org/2002/xforms\">");
    
writer.write("<xforms:label>"+name.trim()+"</xforms:label>");
    writer.write("<xforms:action ev:event=\"DOMActivate\"");
    writer.write(" 
xmlns:ev=\"http://www.w3.org/2001/xml-events\">");
    writer.write("<xforms:setvalue ref=\"action-performed\">"+
      clientId+"@"+id+"@"+name.trim()+"</xforms:setvalue>");
    writer.write("</xforms:action>");
    writer.write("</xforms:submit>");
  } 
  catch (java.io.IOException ie){
    ie.printStackTrace();
  }
}//renderValueAsButton

The above code authors the following markup:

<xforms:submit submission="submit" 
  xmlns:xforms="http://www.w3.org/2002/xforms" 
  xmlns:ev="http://www.w3.org/2001/xml-events">
  <xforms:label>Intel_Pentinum4</xforms:label>
  <xforms:action ev:event="DOMActivate">
    <xforms:setvalue ref="action-performed">
 _id1@1@edit
    </xforms:setvalue>
  </xforms:action>
</xforms:submit>

This markup renders a button. The whole markup is hard-coded in the renderValueAsButton() method, except the strings "Intel_Pentinum4" and "_id1@1@edit" (boldface in the markup above). The first string "Intel_Pentinum4" is the value of the second parameter (name). The second string "_id1@1@edit" uniquely identifies the product whose Remove and Edit button the user clicks. The second string consists of three parts, each of which is separated by an "@" symbol. The first part (_id1) is the ID of the component. The second part (1) identifies a particular product in the catalog view. The third part (edit) identifies the button clicked.

Recall Implementing the xforms-jsf:model component, where we explained the purpose of inserting an extra action-performed tag in the application-specific XML. The renderValueAsButton() method wraps the component, product ID, and button name (edit or remove) in the action-performed tag. When a user clicks the button corresponding to a particular product in the cart view, the string "_id1@1@edit" is sent back to the server.

The encodeBegin() method of the UICart class is lengthy, so we will implement this method incrementally.

The encodeBegin() method first checks whether the FacesContext instance is null. If it is null, it simply throws an exception. Then it extracts the component ID by calling the getClientId() method. Next, it gets the ResponseWriter object so that it can render the markup for this component. See these steps in the following code:

public void encodeBegin(FacesContext context) throws IOException {
if (context == null)
    throw new NullPointerException();
  String clientId = getClientId(context);    
  ExternalContext externalContext = context.getExternalContext();
  ResponseWriter writer = context.getResponseWriter();
  double totalPrice = 0;
  .....
}//encodeBegin

Now the encodeBegin() method calls the getModelBeanObject() method. We explained how this method works in Implementing the UISelect1 component. The getModelBeanObject() method returns an array-type object Product class, which contains the data to be rendered. See the boldface code below:

public void encodeBegin(FacesContext context) 
  throws IOException {
  if (context == null)
    throw new NullPointerException();
  String clientId = getClientId(context);    
  ExternalContext externalContext = context.getExternalContext();
  ResponseWriter writer = context.getResponseWriter();
  double totalPrice = 0;
Product[] products = (Product[]) getModelBeanObject(context,
    value);
  .....
}//encodeBegin

Now we check if the Product instance returned by the getModelBeanObject() method is null. If it is null, it means that we have no data (products) to render in the cart view.

public void encodeBegin(FacesContext context) 
  throws IOException {
  ......
  Product[] products = (Product[]) getModelBeanObject(context,
    value);
if (products == null){
    writer.write ("Cart is empty......");
    return;
  }//if(products == null)
  .....
}//encodeBegin

Now the encodeBegin() method writes the markup to display each product and its price in tabular format from the Product object, as shown in boldface below:

public void encodeBegin(FacesContext context) 
  throws IOException {
  ......
  if (products == null){
    writer.write ("<br><h3> Cart is 
empty......</h3>");
    return;
  }//if (products == null)
writer.write("\r\n X-Cart View <br/> ");
  writer.write("<table border=\"1\">");
  writer.write("<ol type=\"1\">");
  for (int x = 0; x<products.length; x++){
    Product product = products[x];
    double price = Integer.parseInt(product.getPrice());
    NameValuePair[] pair = product.getSelectedOptionalFeatures();
    if(pair != null ){
      for(int y = 0; y < pair.length; y++){
        price += Integer.parseInt(pair[y].getValue());
      }
    }//if(pair != null )
    totalPrice += price;
    writer.write("<tr>");
    writer.write("<td>");
    writer.write("<li>"+product.getName()+"</li>");
    writer.write("</td>");           
    writer.write("<td>");
    renderValueAsButton(writer, " Edit ",  
      new Integer(x).toString(), clientId);
    writer.write("</td>");           
    writer.write("<td>");
    renderValueAsButton(writer, " Remove ", 
      new Integer(x).toString(), clientId);
    writer.write("</td>");
    writer.write("<td>");
    writer.write("" + price);
    writer.write("</td>");           
    writer.write("</tr>");
  }//for (int x = 0; x<products.length; x++)
  writer.write("</ol>");
  writer.write("</table>");
  writer.write("<table border=\"1\">");
  writer.write("<ol>");
  writer.write("<tr>");
  writer.write("<td>");
  writer.write("<li> Total Cart Price </li>");
  writer.write("</td>");           
  writer.write("<td>");
  writer.write(""+totalPrice);
  writer.write("</td>");           
  writer.write("</tr>");
  writer.write("</ol>");
  writer.write("</table>");
}//encodeBegin

Now we'll implement the getActionValueFromRequest() method. The decode() method calls this method to get the ID of the product whose Edit or Remove button the user clicked in the cart view.

The getActionValueFromRequest() method takes the following four parameters:

  • retValue: Contains the string value "id" or "action," which decides the return value of the method.

  • fc: The FacesContext instance.

  • ref: Contains the model bean (that is, IncomingXMLInstanceRequest) name and the property that contains the incoming XML request from the client side. See Parsing the incoming XML instance data for details about this model bean.

  • tagName: The name of the tag (that is, action-performed) that contains the clicked button information.
private String getActionValueFromRequest(String retValue, 
   FacesContext fc, String ref, String tagName){
  String value = new String();
  Document doc = (Document) getModelBeanObject(fc, ref);
  if(doc != null){
    NodeList nl = doc.getElementsByTagName(tagName);
    if (nl != null){
      for (int i=0; i < nl.getLength(); i++){
        if (nl.item(i).getFirstChild().getNodeType() == 
          Node.TEXT_NODE ){
          value = nl.item(i).getFirstChild().getNodeValue();
          if (retValue.equals("id")){
            if (value.indexOf('@') != -1){
              int index = value.indexOf('@');
              String id = value.substring(0,index);
              if (id.equals(getClientId(fc)))
                return value.substring(index+1, 
                  value.lastIndexOf('@'));
              else
                return null;  
            }//if(value.indexOf('@') != -1)
          }//if(retValue.equals("id"))
          else
            return value.substring(value.lastIndexOf('@')+1);
        }//if
      }//for
    }//if(nl != null)
  }//if(doc != null)
  return null;
}//getActionValueFromRequest

Note the following points from the getActionValueFromRequest() method:

  • The method first gets the property from the model bean that contains the client request as DOM Document object.

  • Then it extracts the action-performed tag from the DOM document and extracts its value.

  • Finally, it checks the value of the retValue parameter. If it is "id," then it returns the product ID whose Edit or Remove button is clicked. Otherwise, it returns the ID of the button -- edit or Remove -- from the request.

Next, we will discuss the implementation details of the decode() method.

public void decode(FacesContext context){
  if (context == null)
    throw new NullPointerException();
  String id = getActionValueFromRequest("id", context, 
     "#{incomingXMLInstanceRequest.DOMDocument}", "action-performed");
  String action = getActionValueFromRequest("action", context, 
     "#{incomingXMLInstanceRequest.DOMDocument}","action-performed");
  if(id != null){
    this.btnId = action;
    this.productId = id;
    queueEvent(new ActionEvent(this));
  }//if(id != null)
}//decode

Note the following points in the decode() method implementation:

  • It first verifies the FacesContext instance passed to it by the JSF framework. If it is null, it throws a NullPointerException.

  • If the FacesContext instance is not null, the decode() method calls the getActionValueFromRequest() method, passing it the string "id" in the retValue parameter, as explained above. This method returns the ID of the product that the user clicked.

  • The decode() method again calls the getActionValueFromRequest() method, passing it the string "action." It returns the name of the button the user clicked (edit or Remove).

  • If the product ID returned by the getActionValueFromRequest() method is not null, it sets the product name ID in the btnId property and the product ID in the productId property of the component (that the event handling logic uses to identify the Edit or Remove button of a particular product the user clicked).

  • Finally, the decode() method fires an action event by instantiating an ActionEvent object, passing it the component instance. It then calls the queueEvent() method, passing it the ActionEvent object along with the method call. Note that the event handler class extracts the component object from the ActionEvent object and calls the getBtnId() method of the component to identify the product the user clicked.

Next, we will implement the UICartActionListener.

The UICartActionListener class is associated with the UICart component. When the user clicks the Edit or Remove button of a particular product in the cart view, the UICart component fires an action event. This action event is handled by the UICartActionListener class. The implementation of the UICartActionListener class:

public class UICartActionListener implements ActionListener {
  public void processAction(ActionEvent event)  {
  }
  private Object getModelBeanObject(FacesContext context,String value){
  }
}//UICartActionListener

The UICartActionListener class contains two methods (processAction() and getModelBeanObject()). We explained the getModelBeanObject() method in Implementing the UISelect1 component. The processAction() method processes the action event fired by the component:

public void processAction(ActionEvent event) {
  FacesContext context = FacesContext.getCurrentInstance();
  CartData cd = (CartData) getModelBeanObject("#{cartData}", 
    context);
  ProductData pdm = (ProductData) 
    getModelBeanObject("#{editProductData}", context);
  UICart uic = (UICart) event.getComponent();
  String productId = uic.getProductId();
  String btn = uic.getBtnId();
  if(btn.equals("Edit")){
    Product pd = cd.getProductAt(productId);
    if (pd != null)
      pdm.setProduct(pd);
    cd.setAction("edit");
  }//if(btn.equals("Edit"))
  else{
    cd.removeProduct(productId);
    cd.setAction("remove");
  }//else
}//processAction

Note the following points:

  • The processAction() method first retrieves the FacesContext instance.

  • It then fetches the CartData and ProductData model bean instances from the application context.

  • Next, it calls the getComponent() method of the event object, which returns an object of the component class that fired the event.

  • Then it calls the getBtnId() and getProductId() methods of the component class, which returns the ID of the button the user clicked and the product ID against that button, respectively.

  • It then checks if the button ID is Edit. if it is, it fetches the product from the cart against the product ID and sets it in the ProductData model bean by calling its setProduct() method.

  • If the button ID is not Edit, it simply calls the removeProduct() method of the CartData model bean, passing it the product ID. This method call simply removes the product from the cart.

Now let's implement the CartData model bean.

The CartData model bean stores all the products added by the user in the cart. The UICart component uses this CartData model bean to fetch the list of products added to the cart and also to handle some action events that occurred in the cart view.

The following code shows the implementation of the CartData class:

public class CartData{
  protected String model = null;
  protected String outcome = null;
  protected Product[] products = null;  

  public String getAction(){
  }
  public void setAction (String outcome){
  }
  public String getModel(){
  }
  public void setModel(String model){
  }
  public Product [] getProducts(){
  }
  public Product getProductAt(String index){
  }    
  public void setProductAt(Product pd, int index) 
  }    
  public void removeProduct(String index){
  }
  public void addProduct(ProductData productData){ 
  }
  public void showCatalogView(ActionEvent ae){
  }
  public void buy(ActionEvent ae){
  }
}//CartData

The CartData class has three properties (model, outcome, and products), setter and getter methods for these properties, some private helper methods, four public methods (getProductAt(), setProductAt(), addProduct(), and removeProduct()), and two action event handling methods (showCatalogView() and buy()).

The model property contains the application-specific XML, which is used to track the product the user clicked in the cart view. The xforms-jsf:model component renders the application-specific XML, so the UIModel component explained in Implementing the xforms-jsf:model component calls the getter method of the model property to fetch the application-specific XML.

The outcome property specifies the string used for navigation. This property can have one of three values: "catalogView," "buy," or "edit." If the value of the outcome property is "catalogView" (which indicates that the user clicked the Back to catalog view button in the cart view), the next page will be the catalogView.jsp. If the value is "edit" (which indicates that the user clicked the Edit button of particular product), the next page will be editProductView.jsp. If the value is "buy" (which indicates that the user clicked the Buy button), the next page will be checkout.jsp.

The products property is an array of the Product object, which stores all the products added by the user to the cart. The UICart component calls its getter method to fetch the list of products added to the cart.

Let's discuss the methods of the CartData class one by one.

The CartData class implements the setAction() method. The setAction() method takes a string parameter and sets it in the outcome property of the model bean. The action event handling logic calls the setAction() method, passing it a string value.

public void setAction(String action){
  this.outcome = action;
}//setAction

The getAction() method returns a string value, which the JSF framework uses for the navigation. In the cartView.jsp page above, notice the action attribute in the xcart:cart tag. The action attribute specifies the cartData.getAction method name that decides the navigation. The following code shows the implementation of this method in the model bean:

  public String getAction(){
    return outcome; 
  }//getAction

The getAction() method returns the value of the outcome property (which the event handling logic has already set by calling the setAction() method).

The getProductAt() method returns a product from the array of Product objects at some specific index. It takes the product index along with the method call. The UICartActionListener calls this method to fetch the product at some specific location in the cart.

public Product getProductAt(String index) {
  if ( productsVector.size() > 0 )
    return (Product)productsVector.elementAt(Integer.parseInt(index));
  return null;
}//getProductAt

The setProductAt() method sets a product at some specific index in the array of the Product object. It takes the Product object and index along with the method call. The UICartActionListener calls this method to set the product at a particular location:

  public void setProductAt(Product pd, int index) {
    if ( pd != null )
      productsVector.setElementAt(pd, index);
  }//setProductAt

The addProduct() method adds the product to the cart that is passed to it along with the method call. The event handling logic behind the Add to cart button in the product-specification view calls the addProduct() method, passing it the Product object along with the method call.

public void addProduct(Product productData) {
  if (productData != null) {
    productsVector.addElement(productData);
  }     
}//addProduct

The removeProduct() method removes the product from the array of Product objects at the given index. When the user clicks the Remove button in the cart view, the UICartActionListener calls the removeProduct() method, passing it the index of the product in the cart.

public void removeProduct(String index) {
  productsVector.removeElementAt(Integer.parseInt(index));
}//removeProduct

The CartData model bean implements an action event handling method named showCatalogView(). Recall the screenshot for the cart view from Views of the shopping cart, which contains a Back to catalog view button. When the user clicks this, the JSF framework calls the showCatalogView() method of the model bean, which sets the action property with a "catalogView" string and causes the navigation to the catalogView.jsp page.

public void showCatalogView(ActionEvent ae){
  this.outcome = "catalogView";
}//showCatalogView

The screenshot for the cart view shown in Views of the shopping cart contains a Buy button. When the user clicks this, the JSF framework calls the buy() method of the model bean, which sets the action property with a "buy" string and causes the navigation to the checkout.jsp page.

public void buy(ActionEvent ae){
  if(getProducts().length == 0){
    return;
  }
  this.outcome = "buy";
}//buy


Trying out the shopping cart

We have placed the source code for our complete XForms-JSF shopping cart application in the section9.zip file available in the source code download of this tutorial; see Resources. When you unzip the section9.zip file, you will find that it contains an xcart.jar file (that is, the shopping cart-specific tag library developed in this section), ShoppingCart.war file (that is, the XForms-JSF Shopping cart application developed in this section), and a folder named ShoppingCart. The ShoppingCart folder contains the complete source code for our XForms-JSF shopping cart application, including all the JSP pages, model beans, and action listeners developed for our sample shopping cart application.

To try our XForms-JSF shopping cart application, you deploy the ShoppingCart.war file in your application server. Be sure to use the following URL in the address bar of your XForms browser:

    http://localhost:8080/ShoppingCart

Once the catalog view displays, you can browse through our shopping cart, as we discussed in Views of the shopping cart.

9 of 12 | Previous | Next

Comments



Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=131918
TutorialTitle=Using JSF technology for XForms applications
publish-date=02032005
author1-email=fkhan872@yahoo.com
author1-email-cc=jaloi@us.ibm.com

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).