Revisiting the Spinner

Implementing Custom Component Tags

Now that you have seen how to implement the spinner component, there is one remaining chore: to supply a tag handler. The process is somewhat byzantine, and you may find it helpful to refer to Figure 9-5 as we discuss each step.

Figure 9-5. Locating a custom component


Note

Custom tags use the JavaServer Pages tag library mechanism. For more information on JSP custom tags, see Chapter 7 of the Java EE 5 tutorial at http://java.sun.com/javaee/5/docs/tutorial/doc/index.html.


The TLD File

You need to produce a TLD (tag library descriptor) file that describes one or more tags and their attributes. Place that file into the WEB-INF directory. Listing 9-2 shows the TLD file that describes our spinner custom tag.

The purpose of the file is to specify the class name for the tag handler (com.core-jsf.SpinnerTag) and the permitted attributes of the tag (in our case, id, rendered, minimum, maximum, size, and value).

Note the uri tag that identifies the tag library:

  <uri>http://corejsf.com/spinner</uri>

This is the URI that you reference in a taglib directive of the JSF page, such as

  <%@ taglib uri="http://corejsf.com/spinner" prefix="corejsf" %>

This taglib directive is the analog to the directives that define the standard f and h prefixes in every JSF page.

Note

You can choose arbitrary names for the TLD files only the .tld extension matters. The JSF implementation searches for TLD files in the following locations:

  • The WEB-INF directory or one of its subdirectories

  • The META-INF directory or any JAR file in the WEB-INF/lib directory

The latter is useful if you want to package your converters as reusable JAR files.


Note

In the following, we describe TLD files and tag handlers for JSF 1.2. The details are quite different for JSF 1.1. See page 383 for details.


Most attribute definitions in the TLD file contain a deferred-value child element, like this:

  <attribute>      <description>The spinner minimum value</description>      <name>minimum</name>      <deferred-value>         <type>int</type>      </deferred-value>   </attribute>

This syntax indicates that the attribute is defined by a value expression. The attribute value can be a constant string or a string that contains #{...} expressions. The type element specifies the Java type of the value expression. In our example, minimum is defined by a value expression of type int.

Some attributes are specified by method expressions instead of value expressions. For example, to define an action listener, you use the following tag:

  <attribute>      <name>actionListener</name>      <deferred-method>         <method-signature>            void actionListener(javax.faces.event.ActionEvent)         </method-signature>      </deferred-method>   </attribute>

Generally, you want to allow value or method expressions for attributes. One exception is the id attribute that is defined as a runtime expression value that is, a JSP expression but not a JSF value expression.

  <attribute>      <description>The client id of this component</description>      <name>id</name>      <rtexprvalue>true</rtexprvalue>   </attribute>

Listing 9-2. spinner/web/WEB-INF/spinner.tld

  1. <?xml version="1.0" encoding="UTF-8"?>   2. <taglib xmlns="http://java.sun.com/xml/ns/javaee"   3.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   4.    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   5.       http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"   6.    version="2.1">   7.    <tlib-version>1.1</tlib-version>   8.    <short-name>spinner</short-name>   9.    <uri>http://corejsf.com/spinner</uri>  10.  11.    <tag>  12.       <name>spinner</name>  13.       <tag-class>com.corejsf.SpinnerTag</tag-class>  14.       <body-content>empty</body-content>  15.       <attribute>  16.          <description>A value binding that points to a bean property</description>  17.          <name>binding</name>  18.          <deferred-value>  19.             <type>javax.faces.component.UIComponent</type>  20.          </deferred-value>  21.       </attribute>  22.  23.       <attribute>  24.          <description>The client id of this component</description>  25.          <name>id</name>  26.          <rtexprvalue>true</rtexprvalue>  27.       </attribute>  28.  29.       <attribute>  30.          <description>Is this component rendered?</description>  31.          <name>rendered</name>  32.          <deferred-value>  33.             <type>boolean</type>  34.          </deferred-value>  35.       </attribute>  36.  37.       <attribute>  38.          <description>The spinner minimum value</description>  39.          <name>minimum</name>  40.          <deferred-value>  41.             <type>int</type>  42.          </deferred-value>  43.       </attribute>  44.  45.       <attribute>  46.          <description>The spinner maximum value</description>  47.          <name>maximum</name>  48.          <deferred-value>  49.             <type>int</type>  50.          </deferred-value>  51.       </attribute>  52.  53.       <attribute>  54.          <description>The size of the input field</description>  55.          <name>size</name>  56.          <deferred-value>  57.             <type>int</type>  58.          </deferred-value>  59.       </attribute>  60.  61.       <attribute>  62.          <description>The value of the spinner</description>  63.          <name>value</name>  64.          <required>true</required>  65.          <deferred-value>  66.             <type>int</type>  67.          </deferred-value>  68.       </attribute>  69.    </tag>  70. </taglib>     

The Tag Handler Class

Together with the TLD file, you need to supply a tag handler class for each custom tag. For a component tag, the tag handler class should be a subclass of UIComponentELTag. As you will see later, the tag handlers for custom converters need to subclass ComponentELTag, and custom validator tag handlers need to subclass ValidatorELTag.

Component tag classes have five responsibilities:

  • To identify a component type

  • To identify a renderer type

  • To provide setter methods for tag attributes

  • To store tag attribute values in the tag's component

  • To release resources

Now we look at the implementation of the SpinnerTag class:

  public class SpinnerTag extends UIComponentELTag {      private ValueExpression minimum;      private ValueExpression maximum;      private ValueExpression size;      private ValueExpression value;      ...   }

The spinner tag class has an instance field for each attribute. The tag class should keep all attributes as ValueExpression objects.

Note

The id, binding, and rendered attributes are handled by the UIComponentELTag superclass.


The SpinnerTag class identifies its component type as com.corejsf.Spinner and its renderer type as null. A null renderer type means that a component renders itself or nominates its own renderer.

   public String getComponentType() { return "com.corejsf.Spinner"; }    public String getRendererType()  { return null; }

Caution

When the getRendererType method of the tag handler returns null, you must call setRendererType in the component constructor. If the component renders itself, call setRendererType(null).


SpinnerTag provides setter methods for the attributes it supports: minimum, maximum, value, and size, as follows:

  public void setMinimum(ValueExpression newValue) { minimum = newValue; }   public void setMaximum(ValueExpression newValue) { maximum = newValue; }   public void setSize(ValueExpression newValue) { size = newValue; }   public void setValue(ValueExpression newValue) { value = newValue; }

When the tag is processed in the JSF page, the tag attribute value is converted to a ValueExpression object, and the setter method is called. Getter methods are not needed.

Tag handlers must override a setProperties method to copy tag attribute values to the component. The method name is somewhat of a misnomer because it usually sets component attributes or value expressions, not properties:

  public void setProperties(UIComponent component) {      // always call the superclass method      super.setProperties(component);      component.setValueExpression("size", size);      component.setValueExpression("minimum", minimum);      component.setValueExpression("maximum", maximum);      component.setValueExpression("value", value);   }

Later, you can evaluate the value expression simply by using the attributes map. For example,

   Integer minimum = (Integer) component.getAttributes().get("minimum");

The get method of the attributes map checks that the component has a value expression with the given key, and it evaluates it.

Finally, you need to define a release method that resets all instance fields to their defaults:

  public void release() {      // always call the superclass method      super.release();      minimum = null;      maximum = null;      size = null;      value = null;   }

This method is necessary because the JSF implementation may cache tag handler objects and reuse them for parsing tags. If a tag handler is reused, it should not have leftover settings from a previous tag.

Note

Tag classes must call superclass methods when they override setProperties and release.


Listing 9-3 contains the complete code for the SpinnerTag tag handler.

Listing 9-3. spinner/src/java/com/corejsf/SpinnerTag.java

  1. package com.corejsf;   2.   3. import javax.el.ValueExpression;   4. import javax.faces.component.UIComponent;   5. import javax.faces.webapp.UIComponentELTag;   6.   7. public class SpinnerTag extends UIComponentELTag {   8.    private ValueExpression minimum = null;   9.    private ValueExpression maximum = null;  10.    private ValueExpression size = null;  11.    private ValueExpression value = null;  12.  13.    public String getRendererType() { return null; }  14.    public String getComponentType() { return "com.corejsf.Spinner"; }  15.  16.    public void setMinimum(ValueExpression newValue) { minimum = newValue; }  17.    public void setMaximum(ValueExpression newValue) { maximum = newValue; }  18.    public void setSize(ValueExpression newValue) { size = newValue; }  19.    public void setValue(ValueExpression newValue) { value = newValue; }  20.  21.    public void setProperties(UIComponent component) {  22.       // always call the superclass method  23.       super.setProperties(component);  24.  25.       component.setValueExpression("size", size);  26.       component.setValueExpression("minimum", minimum);  27.       component.setValueExpression("maximum", maximum);  28.       component.setValueExpression("value", value);  29.    }  30.  31.    public void release() {  32.       // always call the superclass method  33.       super.release();  34.  35.       minimum = null;  36.       maximum = null;  37.       size = null;  38.       value = null;  39.    }  40. }     

javax.faces.webapp.UIComponentELTag JSF 1.2

  • void setProperties(UIComponent component)

    Transfers tag attribute values to component properties, attributes, or both. Custom components must call the superclass setProperties method to make sure that properties are set for the attributes UIComponentELTag supports: binding, id, and rendered.

  • void release()

    Clears the state of this tag so that it can be reused.


javax.faces.component.UIComponent

  • void setValueExpression(String name, ValueExpression expr) JSF 1.2

    If the expression is a constant, without #{...} expressions, then it is evaluated and the (name, value) pair is put into the component's attribute map. Otherwise, the (name, expr) pair is put into the component's value expression map.


The Spinner Application

After a number of different perspectives of the spinner component, it is time to take a look at the spinner example in its entirety. This section lists the code for the spinner test application shown in Figure 9-1 on page 356. The directory structure is shown in Figure 9-6 and the code is shown in Listings 9-4 through 9-9.

Figure 9-6. Directory structure for the spinner example


Listing 9-4. spinner/web/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/spinner" prefix="corejsf" %>   5.    <f:view>   6.       <head>   7.          <link href="styles.css" rel="stylesheet" type="text/css"/>   8.          <title><h:outputText value="#{msgs.windowTitle}"/></title>   9.        </head>  10.  11.        <body>  12.           <h:form >  13.              <h:outputText value="#{msgs.creditCardExpirationPrompt}"  14.                 style/>  15.              <p/>  16.              <h:panelGrid columns="3">  17.                 <h:outputText value="#{msgs.monthPrompt}"/>  18.                 <corejsf:spinner value="#{cardExpirationDate.month}"  19.                     minimum="1" maximum="12" size="3"/>  20.                 <h:message for="monthSpinner"/>  21.                 <h:outputText value="#{msgs.yearPrompt}"/>  22.                 <corejsf:spinner value="#{cardExpirationDate.year}"  23.                     minimum="1900" maximum="2100" size="5"/>  24.                 <h:message for="yearSpinner"/>  25.              </h:panelGrid>  26.              <p/>  27.              <h:commandButton value="#{msgs.nextButtonPrompt}" action="next"/>  28.           </h:form>  29.        </body>  30.     </f:view>  31. </html>     

Listing 9-5. spinner/web/next.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.   5.    <f:view>   6.       <head>   7.          <link href="styles.css" rel="stylesheet" type="text/css"/>   8.          <title><h:outputText value="#{msgs.windowTitle}"/></title>   9.       </head>  10.       <body>  11.          <h:form>  12.            <h:outputText value="#{msgs.youEnteredPrompt}" style/>  13.            <p>  14.            <h:outputText value="#{msgs.expirationDatePrompt}"/>  15.               <h:outputText value="#{cardExpirationDate.month}"/> /  16.               <h:outputText value="#{cardExpirationDate.year}"/>  17.            <p>  18.            <h:commandButton value="Try again" action="again"/>  19.         </h:form>  20.      </body>  21.   </f:view>  22.</html>     

Listing 9-6. spinner/src/java/com/corejsf/CreditCardExpiration.java

  1. package com.corejsf;   2.   3. public class CreditCardExpiration {   4.    private int month = 1;   5.    private int year = 2000;   6.   7.    // PROPERTY: month   8.    public int getMonth() { return month; }   9.    public void setMonth(int newValue) { month = newValue; }  10.  11.    // PROPERTY: year  12.    public int getYear() { return year; }  13.    public void setYear(int newValue) { year = newValue; }  14. }

Listing 9-7. spinner/web/WEB-INF/faces-config.xml

  1. <?xml version="1.0"?>   2. <faces-config xmlns="http://java.sun.com/xml/ns/javaee"   3.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   4.    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   5.         http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"   6.    version="1.2">   7.    <navigation-rule>   8.       <from-view-id>/index.jsp</from-view-id>   9.       <navigation-case>  10.          <from-outcome>next</from-outcome>  11.          <to-view-id>/next.jsp</to-view-id>  12.      </navigation-case>  13.   </navigation-rule>  14.  15.   <navigation-rule>  16.      <from-view-id>/next.jsp</from-view-id>  17.      <navigation-case>  18.         <from-outcome>again</from-outcome>  19.         <to-view-id>/index.jsp</to-view-id>  20.      </navigation-case>  21.   </navigation-rule>  22.  23.   <component>  24.      <component-type>com.corejsf.Spinner</component-type>  25.      <component-class>com.corejsf.UISpinner</component-class>  26.   </component>  27.  28.   <managed-bean>  29.      <managed-bean-name>cardExpirationDate</managed-bean-name>  30.      <managed-bean-class>com.corejsf.CreditCardExpiration</managed-bean-class>  31.      <managed-bean-scope>session</managed-bean-scope>  32.   </managed-bean>  33.  34.   <application>  35.      <resource-bundle>  36.         <base-name>com.corejsf.messages</base-name>  37.         <var>msgs</var>  38.      </resource-bundle>  39.   </application>  40. </faces-config>     

Listing 9-8. spinner/src/java/com/corejsf/messages.properties

  1. windowTitle=Spinner Test   2. creditCardExpirationPrompt=Please enter your credit card expiration date:   3. monthPrompt=Month:   4. yearPrompt=Year:   5. nextButtonPrompt=Next   6. youEnteredPrompt=You entered:   7. expirationDatePrompt=Expiration Date   8. changes=Changes:     

Listing 9-9. spinner/web/styles.css

  1. body {   2.    background: #eee;   3. }   4. .pageTitle {   5.    font-size: 1.25em;   6. }

Defining Tag Handlers in JSF 1.1

In JSF 1.1, support for tag handlers was less elegant than in JSF 1.2. This section contains the details. Feel free to skip it if you do not have to write components that are backward compatible with JSF 1.1.

JSF 1.1 provides two separate tag superclasses, UIComponentTag and UIComponentBodyTag. You extend the former if your component does not process its body (that is, the child tags and text between the start and end tag), and the latter if it does. Only four of the standard tags in JSF 1.1 extend UIComponentBodyTag: f:view, f:verbatim, h:commandLink, and h:outputLink. A spinner component does not process its body, so it would extend UIComponentTag.

Note

A tag that implements UIComponentTag can have a body, provided that the tags inside the body know how to process themselves. For example, you can add an f:attribute child to a spinner.


Before JSF 1.2, TLD files used a DOCTYPE declaration instead of a schema declaration, like this:

  <?xml version="1.0" encoding="ISO-8859-1" ?>   <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"      "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">   <taglib>      <tlib-version>0.03</tlib-version>      <jsp-version>1.2</jsp-version>      ...   </taglib>     

More important, you cannot use any deferred-value or deferred-method child elements for attributes. Instead, the tag handler class gets passed the expression strings. It must convert them to objects of type ValueBinding or MethodBinding. These are the JSF 1.1 analogs of the ValueExpression and MethodExpression classes.

Thus, the tag handler class must define setters for strings, like this:

  public class SpinnerTag extends UIComponentTag {      private String  minimum;      private String  maximum;      ...      public void setMinimum(String  newValue) { minimum = newValue; }      public void setMaximum(String  newValue) { maximum = newValue; }      ...   }

In the setProperties method, you check whether the string is in fact a value binding. If so, you convert it to an object of type ValueBinding, the precursor to the ValueExpression class. Otherwise, you convert the string to the appropriate type and set it as a component attribute.

This conversion is rather tedious, and it is useful to define a helper method, such as the following:

  public void setInteger(UIComponent component, String name, String expr) {      if (expr == null) return null;      else if (UIComponentTag.isValueReference(expr)) {         FacesContext context = FacesContext.getCurrentInstance();         Application app = context.getApplication();         ValueBinding binding = app.createValueBinding(expr);         component.setValueBinding(name, binding);      }      else         component.getAttributes().put(name, new Integer(expr));   }

Note

The map returned by the UIComponent.getAttributes method is smart: It accesses component properties and attributes. For example, if you call the map's get method with an attribute whose name is "value", the getValue method is called. If the attribute name is "minimum", and there is no getMinimum method, the component's attribute map is queried for the entry with key "minimum".


You call the helper method in the setProperties method, like this:

public void setProperties(UIComponent component) {    super.setProperties(component);    setInteger(component, "minimum", minimum);    setInteger(component, "maximum", maximum);    ... }

Our helper method assumes that the expression evaluates to an integer. You would need other helper methods setString, setBoolean, and so on, for other types.

Attributes that define method bindings such as the four commonly used attributes in Table 9-1 require additional work. You create a MethodBinding object by calling the createMethodBinding method of the Application class. That method has two parameters: the method binding expression and an array of Class objects that describe the method's parameter types. For example, this code creates a method binding for a value change listener:

  FacesContext context = FacesContext.getCurrentInstance();   Application app = context.getApplication();   Class[] paramTypes = new Class[] { ValueChangeListener.class };   MethodBinding mb = app.createMethodBinding(attributeValue, paramTypes);

Table 9-1. Method Binding Attributes
Attribute Name Method Parameters Method for Setting the Binding
valueChangeListener ValueChangeEvent EditableValueHolder.setValueChangeListener
validator FacesContext, UIComponent, Object EditableValueHolder.setValidator
actionListener ActionEvent ActionSource.setActionListener
action none ActionSource.setAction


You then store the MethodBinding object with the component:

  ((EditableValueHolder) component).setValueChangeListener(mb);

Action listeners and validators follow exactly the same pattern. However, actions are slightly more complex. An action can be either a method binding or a fixed string, for example

  <h:commandButton value="Login" action="#{loginController.verifyUser}"/>

or

  <h:commandButton value="Login" action="login"/>

But the setAction method of the ActionSource interface requires a MethodBinding in all cases. If the action is a fixed string, you must construct a MethodBinding object whose getExpressionString method returns that string:

  if (UIComponentTag.isValueReference(attributeValue))      ((ActionSource) component).setMethodBinding(component, "action", attributeValue,            new Class[] {});   else {      FacesContext context = FacesContext.getCurrentInstance();      Application app = context.getApplication();      MethodBinding mb = new ActionMethodBinding(attributeValue);      component.getAttributes().put("action", mb);   }     

Here, ActionMethodBinding is the following class (which you must supply in your code):

   public class ActionMethodBinding extends MethodBinding implements Serializable {       private String result;       public ActionMethodBinding(String result) { this.result = result; }       public Object invoke(FacesContext context, Object params[]) { return result; }       public String getExpressionString() { return result; }       public Class getType(FacesContext context) { return String.class; }    }     

Handling method expressions is much simpler in JSF 1.2 (see "Supporting Method Expressions" on page 396 for details).

javax.faces.context.FacesContext

  • static FacesContext getCurrentInstance()

    Returns a reference to the current FacesContext instance.

  • Application getApplication()

    Returns the Application object associated with this web application.


Note

The following methods are all deprecated. You should only use them to implement components that are backward compatible with JSF 1.1.


javax.faces.application.Application

  • ValueBinding createValueBinding(String valueBindingExpression)

    Creates a value binding and stores it in the application. The string must be a value binding expression of the form #{...}.

  • MethodBinding createMethodBinding(String methodBindingExpression, Class[] arguments)

    Creates a method binding and stores it in the application. The methodBinding-Expression must be a method binding expression. The Class[] represents the types of the arguments passed to the method.


javax.faces.component.UIComponent

  • void setValueBinding(String name, ValueBinding valueBinding)

    Stores a value binding by name in the component.


javax.faces.webapp.UIComponentTag

  • static boolean isValueReference(String expression)

    Returns true if expression starts with "#{" and ends with "}".


javax.faces.component.EditableValueHolder

  • void setValueChangeListener(MethodBinding m)

    Sets a method binding for the value change listener of this component. The method must return void and is passed a ValueChangeEvent.

  • void setValidator(MethodBinding m)

    Sets a method binding for the validator of this component. The method must return void and is passed a ValueChangeEvent.


javax.faces.component.ActionSource

  • void setActionListener(MethodBinding m)

    Sets a method binding for the action change listener of this component. The method must return void and is passed an ActionEvent.

  • void setAction(MethodBinding m)

    Sets a method binding for the action of this component. The method can return an object of any type, and it has no parameters.



Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

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