Using Child Components and Facets

   

The spinner discussed in the first half of this chapter is a simple component that nonetheless illustrates a number of useful techniques for implementing custom components. To illustrate more advanced custom component techniques, we switch to a more complicated component: a tabbed pane, as shown in Figure 9-8.

Figure 9-8. The Tabbed Pane Component

graphics/09fig08.jpg


In Chapter 7, we showed you how to create an ad hoc tabbed pane with standard JSF tags such as h:graphicImage and h:commandLink. In this chapter we show you how to implement a tabbed pane component.

Of course, the advantage of a custom component over an ad hoc implementation is that the former is reusable. For example, we can easily reuse the tabbed pane component to create a tabbed pane just like the ad hoc version, as shown in Figure 9-9.

Figure 9-9. Reusing the Tabbed Pane Component

graphics/09fig09.jpg


The tabbed pane component has some interesting features:

  • You can use CSS classes for the tabbed pane as a whole and also for selected and unselected tabs.

  • You specify tabs with f:selectItem tags (or f:selectItems), like the standard JSF menu and listbox tags specify menu or listbox items.

  • You can specify tabbed pane content (for example, the picture and description in Figure 9-9) with a URL (which the tabbed pane renderer includes) or a facet (which the renderer renders). For example, you could specify the content for the Washington tab in Figure 9-9 as /washington.jsp or washington. If you use the former, the tabbed pane renderer includes the response from the specified JSP page. If you use the latter, the renderer looks for a facet of the tabbed pane named washington. (This use of facets is similar to the use of header and footer facets in the h:dataTable tag.)

  • The tabbed pane renderer uses the servlet request dispatcher to include the content associated with a tab if that content is a URL.

  • You can add an action listener to the tabbed pane. That listener is notified whenever a tab is selected.

  • You can localize tab text by specifying keys from a resource bundle instead of the actual text displayed in the tab.

  • The tabbed pane uses hidden fields to transmit the selected tab and its content from the client to the server.

Because the tabbed pane has so many features, there are several ways in which you can use it. Here's a simple use:

 

 <corejsf:tabbedPane>    <f:selectItem itemLabel="Jefferson"  itemValue="/jefferson.jsp"/>    <f:selectItem itemLabel="Roosevelt"  itemValue="/roosevelt.jsp"/>    <f:selectItem itemLabel="Lincoln"    itemValue="/lincoln.jsp"/>    <f:selectItem itemLabel="Washington" itemValue="/washington.jsp"/> </corejsf:tabbedPane> 

The preceding code results in a rather plain-looking tabbed pane, as shown in Figure 9-10.

Figure 9-10. A Plain Tabbed Pane

graphics/09fig10.jpg


To get the effect shown in Figure 9-9, you can use CSS styles, like this:

 

 <corejsf:tabbedPane styleClass="tabbedPane"       tabClass="tab" selectedTabClass="selectedTab">    <f:selectItem itemLabel="Jefferson"  itemValue="/jefferson.jsp"/>    <f:selectItem itemLabel="Roosevelt"  itemValue="/roosevelt.jsp"/>    <f:selectItem itemLabel="Lincoln"    itemValue="/lincoln.jsp"/>    <f:selectItem itemLabel="Washington" itemValue="/washington.jsp"/> </corejsf:tabbedPane> 

You can also use a single f:selectItems tag in lieu of multiple f:selectitem tags, like this:

 

 <corejsf:tabbedPane style       tab selectedTab>    <f:selectItems value="#{tabbedPaneBean.tabs}"/> </corejsf:tabbedPane> 

The preceding items are created by a bean:

 

 public class TabbedPaneBean {    private static final SelectItem[] tabs = {       new SelectItem("/jefferson.jsp",  "Jefferson"),       new SelectItem("/roosevelt.jsp",  "Roosevelt"),       new SelectItem("/lincoln.jsp",    "Lincoln"),       new SelectItem("/washington.jsp", "Washington"),    };    public SelectItem[] getTabs() {       return tabs;    } } 

In the previous example we directly specified the text displayed in each tab as select item labels: Jefferson, Roosevelt, etc. Before the tabbed pane renderer encodes a tab, it looks to see if those labels are keys in a resource bundle if so, the renderer encodes the key's value. If the labels are not keys in a resource bundle, the renderer just encodes the labels as they are. You specify the resource bundle with the resourceBundle attribute, like this:

 

 <corejsf:tabbedPane resourceBundle="com.corejsf.messages">    <f:selectItem itemLabel="jeffersonTabKey"  itemValue="/jefferson.jsp"/>    <f:selectItem itemLabel="rooseveltTabKey"  itemValue="/roosevelt.jsp"/>    <f:selectItem itemLabel="lincolnTabKey"    itemValue="/lincoln.jsp"/>    <f:selectItem itemLabel="washingtonTabKey" itemValue="/washington.jsp"/> </corejsf:tabbedPane> 

Notice the item labels they are all keys in the messages resource bundle:

 

 ... jeffersonTabText=Jefferson rooseveltTabText=Roosevelt lincolnTabText=Lincoln washingtonTabText=Washington ... 

There's one more way to specify tabs: with a facet, like this:

 

 <corejsf:tabbedPane >     ...    <f:selectItem itemLabel="Jefferson"  itemValue="jefferson"/>     ...    <f:facet name="jefferson">       <h:panelGrid columns="2">          <h:graphicImage value="/images/jefferson.jpg"/>          <h:outputText value="#{msgs.jeffersonDiscussion}"/>       </h:panelGrid>    </f:facet> </corejsf:tabbedPane> 

Up to now we've used URLs for item values. The contents of that URL are included by the tabbed pane renderer. But in the preceding code we specify a facet instead of a URL the Jefferson select item's value is jefferson, which corresponds to a facet of the same name. Because we specified a facet, the tabbed pane renderer renders the facet instead of including content.

Finally, the tabbed pane component fires an action event when a user selects a tab. You can use the f:actionListener tag to add one or more action listeners, or you can specify a method that handles action events with the tabbed pane's actionListener attribute, like this:

 

 <corejsf:tabbedPane ... actionListener="#{tabbedPaneBean.presidentSelected}">    <f:selectItems value="#{tabbedPaneBean.tabs}"/> </corejsf:tabbedPane> 

Now that we have an overview of the tabbed pane component, let's take a closer look at how it implements advanced features. Here's what we'll cover in this section.

  • "Processing SelectItem Children" on page 410

  • "Processing Facets" on page 411

  • "Including Content" on page 413

  • "Encoding CSS Styles" on page 414

  • "Using Hidden Fields" on page 415

  • "Saving and Restoring State" on page 416

  • "Firing Action Events" on page 418

Processing SelectItem Children

The tabbed pane lets you specify tabs with f:selectItem or f:selectItems. Those tags create UISelectItem components and add them to the tabbed pane as children. Because the tabbed pane renderer has children and because it renders those children, it overrides rendersChildren() and encodeChildren().

 

 public boolean rendersChildren() {     return true; } public void encodeChildren(FacesContext context, UIComponent component)       throws java.io.IOException {    // if the tabbedpane component has no children, this method is still called    if (component.getChildCount() == 0) {       return;    }    ...    List items = com.corejsf.util.Renderers.getSelectItems(context, component);    Iterator it = items.iterator();    while (it.hasNext())       encodeTab(context, writer, (SelectItem) it.next(), component);       ...    }    ... } 

Generally, a component that processes its children contains code such as the following:

 

 Iterator children = component.getChildren().iterator(); while (children.hasNext()) {    UIComponent child = (UIComponent) children.next();    processChild(context, writer, child, component); } 

However, our situation is more complex. Recall from Chapter 4 that you can specify a single select item, a collection of select items, an array of select items, or a map of Java objects as the value for the f:selectItems tag. Whenever your class processes children that are of type SelectItem or SelectItems, you need to deal with this mix of possibilities. The com.corejsf.util.Renderers.getSelectItems method accounts for all those data types and synthesizes them into a list of SelectItem objects. Here is the code for the helper method:

 

 public static List getSelectItems(UIComponent component) {    ArrayList list = new ArrayList();    Iterator children = component.getChildren().iterator();    while (children.hasNext()) {       UIComponent child = (UIComponent) children.next();       if (child instanceof UISelectItem) {          Object value = ((UISelectItem) child).getValue();          if (value == null) {             UISelectItem item = (UISelectItem) child;             list.add(new SelectItem(item.getItemValue(),                   item.getItemLabel(),                   item.getItemDescription(),                   item.isItemDisabled()));          } else if (value instanceof SelectItem) {             list.add(value);          }       } else if (child instanceof UISelectItems) {          Object value = ((UISelectItems) child).getValue();          if (value instanceof SelectItem)             list.add(value);          else if (value instanceof SelectItem[])             list.addAll(Arrays.asList((SelectItem[]) value));          else if (value instanceof Collection)             list.addAll((Collection) value);          else if (value instanceof Map) {             Iterator entries = ((Map) value).entrySet().iterator();             while (entries.hasNext()) {                Map.Entry entry = (Map.Entry) entries.next();                list.add(new SelectItem(entry.getKey(),                      "" + entry.getValue()));             }          }       }    }    return list; } 

The encodeChildren method of the TabbedPaneRenderer calls this method and encodes each child into a tab. You will see the details in "Using Hidden Fields" on page 415.

Processing Facets

The tabbed pane lets you specify URLs or facet names for the content associated with a particular tag. The renderer accounts for that duality in its encodeEnd method:

 

    public void encodeEnd(FacesContext context, UIComponent component)                                                     throws java.io.IOException {       ResponseWriter writer = context.getResponseWriter();       UITabbedPane tabbedPane = (UITabbedPane) component;       String content = tabbedPane.getContent();       ...       if (content != null) {          UIComponent facet = component.getFacet(content);          if (facet != null) {             if (facet.isRendered()) {                facet.encodeBegin(context);                if (facet.getRendersChildren())                   facet.encodeChildren(context);                facet.encodeEnd(context);             }          }          else             includePage(context, component);       }    }    ... } 

The UITabbedPane class has a field content that stores the facet name or URL of the currently displayed tab.

The encodeEnd method checks to see whether the content of the currently selected tab is the name of a facet of this component. If so, it encodes the facet by invoking its encodeBegin, encodeChildren, and encodeEnd methods. Whenever a renderer renders its own children, it needs to take over this responsibility.

If the content of the current tab is not a facet, the renderer assumes the content is a URL and includes it, as shown in the following section.

 

graphics/api_icon.gif

 

 javax.faces.component.UIComponent 

  • UIComponent getFacet(String facetName)

    Returns a reference to the facet if it exists. If the facet does not exist, the method returns null.

  • boolean getRendersChildren()

    Returns a boolean that's true if the component renders its children, false otherwise. A component's encodeChildren method won't be called if this method does not return true. By default, getRendersChildren returns false.

  • boolean isRendered()

    Returns the rendered property. The component is only rendered if the rendered property is true.

Including Content

As you saw in the preceding section, the tabbed pane renderer's encodeEnd method calls the includePage method when the content is described by a URL.

Here's the includePage method:

 

 private void includePage(FacesContext fc, UIComponent component) {    ExternalContext ec = fc.getExternalContext();    ServletContext sc = (ServletContext) ec.getContext();    UITabbedPane tabbedPane = (UITabbedPane) component;    String content = tabbedPane.getContent();    ServletRequest request = (ServletRequest) ec.getRequest();    ServletResponse response = (ServletResponse) ec.getResponse();    try {       sc.getRequestDispatcher(content).include(request, response);    }    catch(Exception ex) {       System.out.println("Couldn't load page: " + content);    } } 

The includePage method uses the servlet request dispatcher to include the response from the specified URL. The request dispatcher reads the requested URL and writes its content to the response writer.

 

graphics/api_icon.gif

 

 javax.servlet.ServletContext 

  • RequestDispatcher getRequestDispatcher(String path)

    Returns a reference to a request dispatcher, given a path to a resource.

 

graphics/api_icon.gif

 

 javax.servlet.RequestDispatcher 

  • void include(ServletRequest request, ServletResponse response) throws IllegalStateException, IOException, ServletException

    Includes the content of some resource. The path to that resource is passed to the RequestDispatcher constructor.

Encoding CSS Styles

You can support CSS styles in two steps:

  • Add an attribute to the tag library descriptor.

  • Encode the component's attribute in your renderer's encode methods.

First, we add attributes styleClass, tabClass, and selectedTabClass to the TLD:

 

 <taglib>     ...     <tag>        ...        <attribute>          <name>styleClass</name>          <description>The CSS style for this component</description>        </attribute>       ...   </tag> </taglib> 

We then write attributes for the CSS classes:

 

 public class TabbedPaneRenderer extends Renderer {    ...    public void encodeBegin(FacesContext context, UIComponent component)          throws java.io.IOException {       ResponseWriter writer = context.getResponseWriter();       writer.startElement("table", component);       String styleClass = (String) component.getAttributes().get("styleClass");       if (styleClass != null)          writer.writeAttribute("class", styleClass, "styleClass");       writer.write("\n"); // to make generated HTML easier to read    }    public void encodeChildren(FacesContext context, UIComponent component)             throws java.io.IOException {       ...       encodeTab(context, responseWriter, selectItem, component);       ...    }    ...    private void encodeTab(FacesContext context, ResponseWriter writer,          SelectItem item, UIComponent component) throws java.io.IOException {       ...       String tabText = getLocalizedTabText(component, item.getLabel());       ...       String tabClass = null;       if (content.equals(selectedContent))          tabClass = (String) component.getAttributes().get("selectedTabClass");       else          tabClass = (String) component.getAttributes().get("tabClass");       if (tabClass != null)          writer.writeAttribute("class", tabClass, "tabClass");       ...    }    ... } 

We encode the styleClass attribute for the tabbed pane's outer table and encode the tabClass and selectedTabClass attribute for each individual tag.

 

graphics/api_icon.gif

 

 javax.faces.model.SelectItem 

  • Object getValue()

    Returns the select item's value.

Using Hidden Fields

Each tab in the tabbed pane is encoded as a hyperlink, like this:

 

 <a href="#" onclick="document.forms[formId][clientId].value=content;    document.forms[formId].submit();"/> 

When a user clicks on a particular hyperlink, the form is submitted (The href value corresponds to the current page). Of course, the server needs to know which tab was selected. This information is stored in a hidden field that is placed after all the tabs:

 

 <input type="hidden" name="clientId"/> 

When the form is submitted, the name and value of the hidden field are sent back to the server, allowing the decode method to activate the selected tab.

The renderer's encodeTab method produces the hyperlink tags. The encodeEnd method calls encodeHiddenFields(), which encodes the hidden field. You can see the details in Listing 9-18 on page 419.

When the tabbed pane renderer decodes the incoming request, it uses the request parameter, associated with the hidden field, to set the tabbed pane component's content.

 

    public void decode(FacesContext context, UIComponent component) {       Map requestParams = context.getExternalContext().getRequestParameterMap();       String clientId = component.getClientId(context);       String content = (String) (requestParams.get(clientId));       if (content != null && !content.equals("")) {          UITabbedPane tabbedPane = (UITabbedPane) component;          tabbedPane.setContent(content);       }       ...    }    ... } 

Saving and Restoring State

The UITabbedPane class has an instance field that stores the facet name or URL of the currently displayed tab. Whenever your components have instance fields and there is a possibility that they are used in a web application that saves state on the client, then you need to implement the saveState and restoreState methods of the StateHolder interface.

These methods have the following form:

 

  public Object saveState(FacesContext context) {     Object values[] = new Object[n];     values[0] = super.saveState(context);     values[1] = instance field #1;     values[2] = instance field #2;     ...     return values; } public void restoreState(FacesContext context, Object state) {     Object values[] = (Object[]) state;     super.restoreState(context, values[0]);     instance field #1 = (Type) values[1];     instance field #2 = (Type) values[2];     ... } 

Listing 9-17 shows how the UITabbedPane class saves and restores its state.

To test why state saving is necessary, run this experiment:

  • Comment out the saveState and restoreState methods.

  • Activate client-side state saving by adding these lines to web.xml:

     

     <context-param>    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>    <param-value>client</param-value> </context-param> 

  • Add a button to the index.jsp page of the bears application:

     

     <h:commandButton value="Redisplay"/> 

  • Run the application and click on a tab.

  • Click the "Redisplay" button. The current page is redisplayed, but no tab is selected!

This problem occurs because the state of the page is saved on the client, encoded as the value of a hidden field. When the page is redisplayed, a new UITabbedPane object is constructed and its restoreState method is called. If the UITabbedPane class does not override the restoreState method, the content field is not restored.

NOTE

graphics/note_icon.gif

In Chapter 6, you saw that you could save the state of converters and validators simply by making the converter or validator class serializable. This approach does not work for components you must use the StateHolder methods.


TIP

graphics/exclamatory_icon.gif

If you store all of your component state as attributes, you don't have to implement the saveState and restoreState methods because component attributes are automatically saved by the JSF implementation. For example, the tabbed pane can simply use a "content" attribute instead of the content field.

Then you don't need the UITabbedPane class at all. Simply use the UICommand superclass and declare the component class like this:

 

 <component>    <component-type>com.corejsf.TabbedPane</component-type>    <component-class>javax.faces.component.UICommand</component-class> </component> 

Frankly, that's what we do in our own code. You will find several examples in Chapters 11 and 12. The standard JSF components use the more elaborate mechanism to minimize the size of the state information.


Listing 9-17. bears/WEB-INF/classes/com/corejsf/UITabbedPane.java
  1. package com.corejsf;  2.  3. import javax.faces.component.UICommand;  4. import javax.faces.context.FacesContext;  5.  6. public class UITabbedPane extends UICommand {  7.    private String content;  8.  9.    public String getContent() { return content; } 10.    public void setContent(String newValue) { content = newValue; } 11. 12.    public Object saveState(FacesContext context) { 13.       Object values[] = new Object[2]; 14.       values[0] = super.saveState(context); 15.       values[1] = content; 16.       return values; 17.   } 18. 19.   public void restoreState(FacesContext context, Object state) { 20.       Object values[] = (Object[]) state; 21.       super.restoreState(context, values[0]); 22.       content = (String) values[1]; 23.   } 24. } 

Firing Action Events

When your component handles action events or actions, you need to take the following steps:

  • Your component should extend UICommmand.

  • You need to queue an ActionEvent in the decode method of your renderer.

The tabbed pane component fires an action event when a user selects one of its tabs. That action is queued by TabbedPaneRenderer in the decode method.

 

 public void decode(FacesContext context, UIComponent component) {    ...    UITabbedPane tabbedPane = (UITabbedPane) component;    ...    component.queueEvent(new ActionEvent(tabbedPane)); } 

This completes the discussion of the TabbedPaneRenderer class. You will find the complete code in Listing 9-18. The TabbedPaneTag class is as boring as ever, and we do not show it here.

Listing 9-18. bears/WEB-INF/classes/com/corejsf/TabbedPaneRenderer.java
   1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Iterator;   5. import java.util.List;   6. import java.util.Map;   7. import java.util.logging.Level;   8. import java.util.logging.Logger;   9. import javax.faces.component.UIComponent;  10. import javax.faces.context.ExternalContext;  11. import javax.faces.context.FacesContext;  12. import javax.faces.context.ResponseWriter;  13. import javax.faces.event.ActionEvent;  14. import javax.faces.model.SelectItem;  15. import javax.faces.render.Renderer;  16. import javax.servlet.ServletContext;  17. import javax.servlet.ServletException;  18. import javax.servlet.ServletRequest;  19. import javax.servlet.ServletResponse;  20.  21. // Renderer for the UITabbedPane component  22.  23. public class TabbedPaneRenderer extends Renderer {  24.    private static Logger logger = Logger.getLogger("com.corejsf.util");  25.  26.    // By default, getRendersChildren() returns false, so encodeChildren()  27.    // won't be invoked unless we override getRendersChildren() to return true  28.  29.    public boolean getRendersChildren() {  30.       return true;  31.    }  32.  33.    // The decode method gets the value of the request parameter whose name  34.    // is the client Id of the tabbedpane component. The request parameter  35.    // is encoded as a hidden field by encodeHiddenField, which is called by  36.    // encodeEnd. The value for the parameter is set by JavaScript generated  37.    // by the encodeTab method. It is the name of a facet or a JSP page.  38.  39.    // The decode method uses the request parameter value to set the  40.    // tabbedpane component's content attribute.  41.    // Finally, decode() queues an action event that's fired to registered  42.    // listeners in the Invoke Application phase of the JSF lifecycle. Action  43.    // listeners can be specified with the <corejsf:tabbedpane>'s actionListener  44.    // attribute or with <f:actionListener> tags in the body of the  45.    // <corejsf:tabbedpane> tag.  46.  47.    public void decode(FacesContext context, UIComponent component) {  48.       Map requestParams = context.getExternalContext().getRequestParameterMap();  49.       String clientId = component.getClientId(context);  50.  51.       String content = (String) (requestParams.get(clientId));  52.       if (content != null && !content.equals("")) {  53.          UITabbedPane tabbedPane = (UITabbedPane) component;  54.          tabbedPane.setContent(content);  55.       }  56.  57.       component.queueEvent(new ActionEvent(component));  58.    }  59.  60.    // The encodeBegin method writes the starting <table> HTML element  61.    // with the CSS class specified by the <corejsf:tabbedpane>'s styleClass  62.    // attribute (if supplied)  63.  64.    public void encodeBegin(FacesContext context, UIComponent component)  65.          throws java.io.IOException {  66.       ResponseWriter writer = context.getResponseWriter();  67.       writer.startElement("table", component);  68.  69.       String styleClass = (String) component.getAttributes().get("styleClass");  70.       if (styleClass != null)  71.          writer.writeAttribute("class", styleClass, null);  72.  73.       writer.write("\n"); // to make generated HTML easier to read  74.    }  75.  76.    // encodeChildren() is invoked by the JSF implementation after encodeBegin().  77.    // The children of the <corejsf:tabbedpane> component are UISelectItem  78.    // components, set with one or more <f:selectItem> tags or a single  79.    // <f:selectItems> tag in the body of <corejsf:tabbedpane>  80.  81.    public void encodeChildren(FacesContext context, UIComponent component)  82.          throws java.io.IOException {  83.       // if the tabbedpane component has no children, this method is still  84.       // called  85.       if (component.getChildCount() == 0) {  86.          return;  87.       }  88.  89.       ResponseWriter writer = context.getResponseWriter();  90.       writer.startElement("thead", component);  91.       writer.startElement("tr", component);  92.       writer.startElement("th", component);  93.  94.       writer.startElement("table", component);  95.       writer.startElement("tbody", component);  96.       writer.startElement("tr", component);  97.  98.       List items = com.corejsf.util.Renderers.getSelectItems(component);  99.       Iterator it = items.iterator(); 100.       while (it.hasNext()) 101.          encodeTab(context, writer, (SelectItem) it.next(), component); 102. 103.       writer.endElement("tr"); 104.       writer.endElement("tbody"); 105.       writer.endElement("table"); 106. 107.       writer.endElement("th"); 108.       writer.endElement("tr"); 109.       writer.endElement("thead"); 110.       writer.write("\n"); // to make generated HTML easier to read 111.    } 112. 113.    // encodeEnd() is invoked by the JSF implementation after encodeChildren(). 114.    // encodeEnd() writes the table body and encodes the tabbedpane's content 115.    // in a single table row. 116. 117.    // The content for the tabbed pane can be specified as either a URL for 118.    // a JSP page or a facet name, so encodeEnd() checks to see if it's a facet; 119.    // if so, it encodes it; if not, it includes the JSP page 120. 121.    public void encodeEnd(FacesContext context, UIComponent component) 122.          throws java.io.IOException { 123.       ResponseWriter writer = context.getResponseWriter(); 124.       UITabbedPane tabbedPane = (UITabbedPane) component; 125.       String content = tabbedPane.getContent(); 126. 127.       writer.startElement("tbody", component); 128.       writer.startElement("tr", component); 129.       writer.startElement("td", component); 130. 131.       if (content != null) { 132.          UIComponent facet = component.getFacet(content); 133.          if (facet != null) { 134.             if (facet.isRendered()) { 135.                facet.encodeBegin(context); 136.                if (facet.getRendersChildren()) 137.                   facet.encodeChildren(context); 138.                facet.encodeEnd(context); 139.             } 140.          } else 141.             includePage(context, component); 142.       } 143. 144.       writer.endElement("td"); 145.       writer.endElement("tr"); 146.       writer.endElement("tbody"); 147. 148.       // Close off the column, row, and table elements 149.       writer.endElement("table"); 150. 151.       encodeHiddenField(context, writer, component); 152.    } 153. 154.    // The encodeHiddenField method is called at the end of encodeEnd(). 155.    // See the decode method for an explanation of the field and its value. 156. 157.    private void encodeHiddenField(FacesContext context, ResponseWriter writer, 158.          UIComponent component) throws java.io.IOException { 159.       // write hidden field whose name is the tabbedpane's client Id 160.       writer.startElement("input", component); 161.       writer.writeAttribute("type", "hidden", null); 162.       writer.writeAttribute("name", component.getClientId(context), null); 163.       writer.endElement("input"); 164.    } 165. 166.    // encodeTab, which is called by encodeChildren, encodes an HTML anchor 167.    // element with an onclick attribute which sets the value of the hidden 168.    // field encoded by encodeHiddenField and submits the tabbedpane's enclosing 169.    // form. See the decode method for more information about the hidden field. 170.    // encodeTab also writes out a class attribute for each tab corresponding 171.    // to either the tabClass attribute (for unselected tabs) or the 172.    // selectedTabClass attribute (for the selected tab). 173. 174.    private void encodeTab(FacesContext context, ResponseWriter writer, 175.          SelectItem item, UIComponent component) throws java.io.IOException { 176.       String tabText = getLocalizedTabText(component, item.getLabel()); 177.       String content = (String) item.getValue(); 178. 179.       writer.startElement("td", component); 180.       writer.startElement("a", component); 181.       writer.writeAttribute("href", "#", "href"); 182. 183.       String clientId = component.getClientId(context); 184.       String formId = com.corejsf.util.Renderers.getFormId(context, component); 185. 186.       writer.writeAttribute("onclick", 187.       // write value for hidden field whose name is the tabbedpane's client Id 188. 189.             "document.forms['" + formId + "']['" + clientId + "'].value='" 190.                   + content + "'; " + 191. 192.                   // submit form in which the tabbedpane resides 193.                   "document.forms['" + formId + "'].submit(); ", null); 194. 195.       UITabbedPane tabbedPane = (UITabbedPane) component; 196.       String selectedContent = tabbedPane.getContent(); 197. 198.       String tabClass = null; 199.       if (content.equals(selectedContent)) 200.          tabClass = (String) component.getAttributes().get("selectedTabClass"); 201.       else 202.          tabClass = (String) component.getAttributes().get("tabClass"); 203. 204.       if (tabClass != null) 205.          writer.writeAttribute("class", tabClass, null); 206. 207.       writer.write(tabText); 208. 209.       writer.endElement("a"); 210.       writer.endElement("td"); 211.       writer.write("\n"); // to make generated HTML easier to read 212.    } 213. 214.    // Text for the tabs in the tabbedpane component can be specified as 215.    // a key in a resource bundle, or as the actual text that's displayed 216.    // in the tab. Given that text, the getLocalizedTabText method tries to 217.    // retrieve a value from the resource bundle specified with the 218.    // <corejsf:tabbedpane>'s resourceBundle attribute. If no value is found, 219.    // getLocalizedTabText just returns the string it was passed. 220. 221.    private String getLocalizedTabText(UIComponent tabbedPane, String key) { 222.       String bundle = (String) tabbedPane.getAttributes().get("resourceBundle"); 223.       String localizedText = null; 224. 225.       if (bundle != null) { 226.          localizedText = com.corejsf.util.Messages.getString(bundle, key, null); 227.       } 228.       if (localizedText == null) 229.          localizedText = key; 230.       // The key parameter was not really a key in the resource bundle, 231.       // so just return the string as is 232.       return localizedText; 233.    } 234. 235.    // includePage uses the servlet request dispatcher to include the page 236.    // corresponding to the selected tab. 237. 238.    private void includePage(FacesContext fc, UIComponent component) { 239.       ExternalContext ec = fc.getExternalContext(); 240.       ServletContext sc = (ServletContext) ec.getContext(); 241.       UITabbedPane tabbedPane = (UITabbedPane) component; 242.       String content = tabbedPane.getContent(); 243. 244.       ServletRequest request = (ServletRequest) ec.getRequest(); 245.       ServletResponse response = (ServletResponse) ec.getResponse(); 246.       try { 247.          sc.getRequestDispatcher(content).include(request, response); 248.       } catch (ServletException ex) { 249.          logger.log(Level.WARNING, "Couldn't load page: " + content, ex); 250.       } catch (IOException ex) { 251.          logger.log(Level.WARNING, "Couldn't load page: " + content, ex); 252.       } 253.    } 254. } 

Using the Tabbed Pane

The bears application shown in Figure 9-8 on page 405 uses a bean to specify the URLs for the tabs. The bean code is in Listing 9-19. Companion code also contains a presidents application that specifies the tabs with facets.

The directory structure for the application is shown in Figure 9-11. Listing 9-20 shows the index.jsp page, and Listing 9-21 shows one of the pages that make up the tab content. The other pages look similar and are omitted. Listing 9-22 through Listing 9-25 show the tag library descriptor, tag class, faces configuration file and the stylesheet for the tabbed pane application.

Figure 9-11. The Bears Directory Structure

graphics/09fig11.jpg


You have now seen how to implement custom components. We covered all essential issues that you will encounter as you develop your own components. The code in this chapter should make a good starting point for your component implementations.

Listing 9-19. bears/WEB-INF/classes/com/corejsf/TabbedPaneBean.java
  1. package com.corejsf;  2.  3. import javax.faces.model.SelectItem;  4.  5. public class TabbedPaneBean {  6.    private static final SelectItem[] tabs = {  7.       new SelectItem("/blackBears.jsp",   "blackTabText"),  8.       new SelectItem("/grizzlyBears.jsp", "grizzlyTabText"),  9.       new SelectItem("/polarBears.jsp",   "polarTabText"), 10.       new SelectItem("/pandaBears.jsp",   "pandaTabText"), 11.       new SelectItem("/teddyBears.jsp",   "teddyTabText"), 12.    }; 13. 14.    public SelectItem[] getTabs() { 15.       return tabs; 16.    } 17. } 

Listing 9-20. bears/index.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <%@ taglib uri="http://corejsf.com/tabbedpane" prefix="corejsf" %>  5.  6.    <f:view>  7.       <head>  8.          <link href="styles.css" rel="stylesheet" type="text/css"/>  9.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/> 10.          <title> 11.             <h:outputText value="#{msgs.windowTitle}"/> 12.          </title> 13.       </head> 14.       <body> 15.          <h:form> 16.             <corejsf:tabbedPane style tab 17.                selectedTab 18.                resourceBundle="com.corejsf.messages"> 19.                <f:selectItems value="#{tabbedPaneBean.tabs}"/> 20.             </corejsf:tabbedPane> 21.          </h:form> 22.       </body> 23.    </f:view> 24. </html> 

Listing 9-21. bears/blackBears.jsp
  1. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.  4. <f:subview >  5.    <h:panelGrid columns='2' columnClasses='bearDiscussionColumn'>  6.       <h:graphicImage value='/images/black-bears.jpg'/>  7.       <h:outputText value='#{msgs.blackBearDiscussion}'  8.                styleClass='tabbedPaneContent'/>  9.    </h:panelGrid> 10. </f:subview> 

Listing 9-22. bears/WEB-INF/tabbedpane.tld
  1. <?xml version="1.0" encoding="ISO-8859-1" ?>  2.  3. <!DOCTYPE taglib  4.  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"  5.  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">  6.  7. <taglib>  8.    <tlib-version>0.03</tlib-version>  9.    <jsp-version>1.2</jsp-version> 10.    <short-name>corejsf</short-name> 11.    <uri>http://corejsf/components</uri> 12.    <description>A library containing a tabbed pane</description> 13. 14.    <tag> 15.       <name>tabbedPane</name> 16.       <tag-class>com.corejsf.TabbedPaneTag</tag-class> 17.        <body-content>JSP</body-content> 18.        <description>A tag for a tabbed pane component</description> 19. 20.        <attribute> 21.          <name>id</name> 22.          <required>false</required> 23.          <rtexprvalue>false</rtexprvalue> 24.          <description>Component id of this component</description> 25.        </attribute> 26. 27.        <attribute> 28.          <name>binding</name> 29.          <required>false</required> 30.          <rtexprvalue>false</rtexprvalue> 31.          <description>Component reference expression for this component</description> 32.        </attribute> 33. 34.        <attribute> 35.          <name>rendered</name> 36.          <required>false</required> 37.          <rtexprvalue>false</rtexprvalue> 38.          <description> 39.            A flag indicating whether or not this component should be rendered. 40.           If not specified, the default value is true. 41.          </description> 42.        </attribute> 43. 44.        <attribute> 45.          <name>style</name> 46.          <required>false</required> 47.          <rtexprvalue>false</rtexprvalue> 48.          <description>The CSS style for this component</description> 49.        </attribute> 50. 51.        <attribute> 52.          <name>styleClass</name> 53.          <required>false</required> 54.          <rtexprvalue>false</rtexprvalue> 55.          <description>The CSS class for this component</description> 56.        </attribute> 57. 58.        <attribute> 59.          <name>tabClass</name> 60.          <required>false</required> 61.          <rtexprvalue>false</rtexprvalue> 62.          <description>The CSS class for unselected tabs</description> 63.        </attribute> 64. 65.        <attribute> 66.          <name>selectedTabClass</name> 67.          <required>false</required> 68.          <rtexprvalue>false</rtexprvalue> 69.          <description>The CSS class for the selected tab</description> 70.        </attribute> 71. 72.        <attribute> 73.          <name>resourceBundle</name> 74.          <required>false</required> 75.          <rtexprvalue>false</rtexprvalue> 76.          <description> 77.            The resource bundle used to localize select item labels 78.          </description> 79.        </attribute> 80. 81.        <attribute> 82.          <name>actionListener</name> 83.          <required>false</required> 84.          <rtexprvalue>false</rtexprvalue> 85.          <description> 86.            A method reference that's called when a tab is selected 87.          </description> 88.        </attribute> 89.   </tag> 90. </taglib> 

Listing 9-23. tabbedpane/WEB-INF/classes/com/corejsf/TabbedPaneTag.java
  1. package com.corejsf;  2.  3. import javax.faces.application.Application;  4. import javax.faces.context.FacesContext;  5. import javax.faces.component.UIComponent;  6. import javax.faces.el.MethodBinding;  7. import javax.faces.event.ActionEvent;  8. import javax.faces.webapp.UIComponentBodyTag;  9. 10. import com.corejsf.util.Tags; 11. 12. // This tag supports the following attributes 13. // 14. // binding (supported by UIComponentBodyTag) 15. // id (supported by UIComponentBodyTag) 16. // style (supported by UIComponentBodyTag) 17. // rendered (supported by UIComponentBodyTag) 18. // styleClass 19. // tabClass 20. // selectedTabClass 21. // resourceBundle 22. // actionListener 23. 24. public class TabbedPaneTag extends UIComponentBodyTag { 25.    private String style, styleClass, tabClass, selectedTabClass, resourceBundle, 26.                   actionListener; 27. 28.    public String getRendererType () { 29.       return "TabbedPaneRenderer"; 30.    } 31.    public String getComponentType() { 32.       return "Tabbed Pane"; 33.    } 34. 35.    // tabClass attribute 36.    public String getTabClass() { return tabClass; } 37.    public void setTabClass(String tabClass) { this.tabClass= tabClass; } 38. 39.    // selectedTabClass attribute 40.    public String getSelectedTabClass() { return selectedTabClass; } 41.    public void setSelectedTabClass(String selectedTabClass) { 42.        this.selectedTabClass= selectedTabClass; 43.    } 44. 45.    // styleClass attribute 46.    public String getStyle() { return style; } 47.    public void setStyle(String style) { this.style= style; } 48. 49.    // styleClass attribute 50.    public String getStyleClass() { return styleClass; } 51.    public void setStyleClass(String styleClass) { this.styleClass = styleClass; } 52. 53.    // resourceBundle attribute 54.    public String getResourceBundle() { return resourceBundle; } 55.    public void setResourceBundle(String resourceBundle) { 56.        this.resourceBundle = resourceBundle; 57.    } 58. 59.    // actionListener attribute 60.    public String getActionListener() { return resourceBundle; } 61.    public void setActionListener(String actionListener) { 62.        this.actionListener = actionListener; 63.    } 64. 65.    protected void setProperties(UIComponent component) { 66.       // make sure you always call the superclass 67.       super.setProperties(component); 68. 69.       com.corejsf.util.Tags.setComponentAttribute(component, "style", style); 70.       com.corejsf.util.Tags.setComponentAttribute(component, "styleClass", 71.                                                                 styleClass); 72.       com.corejsf.util.Tags.setComponentAttribute(component, "tabClass", 73.                                                                 tabClass); 74.       com.corejsf.util.Tags.setComponentAttribute(component, "selectedTabClass", 75.                                                                 selectedTabClass); 76.       com.corejsf.util.Tags.setComponentAttribute(component, "resourceBundle", 77.                                                                 resourceBundle); 78.       com.corejsf.util.Tags.setComponentAttribute(component, "actionListener", 79.                                                                 actionListener); 80.    } 81. } 

Listing 9-24. tabbedpane/WEB-INF/faces-config.xml
  1. <?xml version="1.0"?>  2.  3. <!DOCTYPE faces-config PUBLIC  4. "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"  5. "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">  6.  7. <faces-config>  8.    <managed-bean>  9.       <managed-bean-name>tabbedPaneBean</managed-bean-name> 10.       <managed-bean-class>com.corejsf.TabbedPaneBean</managed-bean-class> 11.       <managed-bean-scope>session</managed-bean-scope> 12.    </managed-bean> 13. 14.    <navigation-rule> 15.       <from-view-id>/index.jsp</from-view-id> 16.          <navigation-case> 17.             <to-view-id>/welcome.jsp</to-view-id> 18.          </navigation-case> 19.    </navigation-rule> 20. 21.    <component> 22.       <description>A tabbed pane</description> 23.       <component-type>Tabbed Pane</component-type> 24.       <component-class>com.corejsf.UITabbedPane</component-class> 25.    </component> 26. 27.    <!-- order is important within elements --> 28.    <render-kit> 29.       <renderer> 30.          <component-family>javax.faces.Command</component-family> 31.          <renderer-type>TabbedPaneRenderer</renderer-type> 32.          <renderer-class>com.corejsf.TabbedPaneRenderer</renderer-class> 33.       </renderer> 34.    </render-kit> 35. </faces-config> 

Listing 9-25. tabbedpane/styles.css
  1. body {  2.    background: #ccc;  3. }  4. .emphasis {  5.    font-size: 3.5em;  6.    font-style: italic;  7. }  8. .tabbedPane {  9.    vertical-align: top; 10.    border: thin solid Blue; 11.    width: 96%; 12.    height 96%; 13. } 14. .tab { 15.    vertical-align: top; 16.    padding: 3px; 17.    border: thin solid Red; 18.    color: Yellow; 19.    background: LightSlateGray; 20. } 21. .selectedTab { 22.    vertical-align: top; 23.    padding: 3px; 24.    border: thin solid Black; 25.    color: LightSlateGray; 26.    background: Yellow; 27. } 28. .tabbedPaneContent { 29.    vertical-align: top; 30.    width: *; 31.    height: *; 32. } 



core JavaServer Faces
Core JavaServer Faces
ISBN: 0131463055
EAN: 2147483647
Year: 2003
Pages: 121

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net