Implementing Custom Component Tags

Decoding: Processing Request Values

To understand the decoding process, keep in mind how a web application works. The server sends an HTML form to the browser. The browser sends back a POST request that consists of name/value pairs. That POST request is the only data that the server can use to interpret the user's actions inside the browser.

If the user clicks the increment or decrement button, the ensuing POST request includes the names and values of all text fields, but only the name and value of the clicked button. For example, if the user clicks the month spinner's increment button in the application shown in Figure 9-1 on page 356, the following request parameters are transferred to the server from the browser:

Name Value
_id1:monthSpinner 1
_id1:yearSpinner 12
_id1:monthSpinner.more >


When our spinner decodes an HTTP request, it looks for the request parameter names that match its client ID and processes the associated values. The spinner's decode method is listed below.

  public void decode(FacesContext context) {      Map requestMap = context.getExternalContext().getRequestParameterMap();      String clientId = getClientId(context);      int increment;      if (requestMap.containsKey(clientId + MORE)) increment = 1;      else if (requestMap.containsKey(clientId + LESS)) increment = -1;      else increment = 0;      try {         int submittedValue            = Integer.parseInt((String) requestMap.get(clientId));         int newValue = getIncrementedValue(submittedValue, increment);         setSubmittedValue("" + newValue);         setValid(true);      }      catch(NumberFormatException ex) {         // let the converter take care of bad input, but we still have         // to set the submitted value or the converter won't have         // any input to deal with         setSubmittedValue((String) requestMap.get(clientId));      }   }     

The decode method looks at the request parameters to determine which of the spinner's buttons, if any, triggered the request. If a request parameter named clientId.less exists, where clientId is the client ID of the spinner we are decoding, then we know that the decrement button was activated. If the decode method finds a request parameter named clientId.more, then we know that the increment button was activated.

If neither parameter exists, we know that the request was not initiated by the spinner, so we set the increment to zero. We still need to update the value the user might have typed a value into the text field and clicked the "Next" button.

Our naming convention works for multiple spinners in a page because each spinner is encoded with the spinner component's client ID, which is guaranteed to be unique. If you have multiple spinners in a single page, each spinner component decodes its own request.

Once the decode method determines that one of the spinner's buttons was clicked, it increments the spinner's value by 1 or 1, depending on which button the user activated. That incremented value is calculated by a private getIncrementedValue method:

  private int getIncrementedValue(int submittedValue, int increment) {      Integer minimum = (Integer) getAttributes().get("minimum");      Integer maximum = (Integer) getAttributes().get("maximum");      int newValue = submittedValue + increment;      if ((minimum == null || newValue >= minimum.intValue()) &&         (maximum == null || newValue <= maximum.intValue()))         return newValue;      else         return submittedValue;   }

The getIncrementedValue method checks the value the user entered in the spinner against the spinner's minimum and maximum attributes. Those attributes are set by the spinner's tag handler class.

After it gets the incremented value, the decode method calls the spinner component's setSubmittedValue method. That method stores the submitted value in the component. Subsequently, in the JSF life cycle, that submitted value will be converted and validated by the JSF framework.

Caution

You must call setValid(true) after setting the submitted value. Otherwise, the input is not considered valid, and the current page is redisplayed.


javax.faces.component.UIComponent

  • void decode(FacesContext context)

    The method called by JSF at the beginning of the JSF life cycle only if the component's renderer type is null, signifying that the component renders itself.

    The decode method decodes request parameters. Typically, components transfer request parameter values to component properties or attributes. Components that fire action events queue them in this method.


javax.faces.context.FacesContext

  • ExternalContext getExternalContext()

    Returns a reference to a context proxy. Typically, the real context is a servlet or portlet context. If you use the external context instead of using the real context directly, your applications can work with servlets and portlets.


javax.faces.context.ExternalContext

  • Map getRequestParameterMap()

    Returns a map of request parameters. Custom components typically call this method in decode() to see if they were the component that triggered the request.


javax.faces.component.EditableValueHolder

  • void setSubmittedValue(Object submittedValue)

    Sets a component's submitted value input components have editable values, so UIInput implements the EditableValueHolder interface. The submitted value is the value the user entered, presumably in a web page. For HTML-based applications, that value is always a string, but the method accepts an Object reference in deference to other display technologies.

  • void setValid(boolean valid)

    Custom components use this method to indicate their value's validity. If a component cannot convert its value, it sets the valid property to false.


Using Converters

The spinner component uses the standard JSF integer converter to convert strings to Integer objects, and vice versa. The UISpinner constructor simply calls setConverter, like this:

  public class UISpinner extends UIInput {      ...      public UISpinner() {         setConverter(new IntegerConverter()); // to convert the submitted value         setRendererType(null);                // this component renders itself      }     

The spinner's decode method traps invalid inputs in the NumberFormatException catch clause. However, instead of reporting the error, it sets the component's submitted value to the user input. Later on in the JSF life cycle, the standard integer converter will try to convert that value and will generate an appropriate error message for bad input.

Listing 9-1 contains the complete code for the UISpinner class.

Listing 9-1. spinner/src/java/com/corejsf/UISpinner.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Map;   5. import javax.faces.component.UIInput;   6. import javax.faces.context.FacesContext;   7. import javax.faces.context.ResponseWriter;   8. import javax.faces.convert.IntegerConverter;   9.  10. public class UISpinner extends UIInput {  11.    private static final String MORE = ".more";  12.    private static final String LESS = ".less";  13.  14.    public UISpinner() {  15.       setConverter(new IntegerConverter()); // to convert the submitted value  16.       setRendererType(null); // this component renders itself  17.    }  18.  19.    public void encodeBegin(FacesContext context) throws IOException {  20.       ResponseWriter writer = context.getResponseWriter();  21.       String clientId = getClientId(context);  22.  23.       encodeInputField(writer, clientId);  24.       encodeDecrementButton(writer, clientId);  25.       encodeIncrementButton(writer, clientId);  26.    }  27.  28.    public void decode(FacesContext context) {  29.       Map<String, String> requestMap  30.          = context.getExternalContext().getRequestParameterMap();  31.       String clientId = getClientId(context);  32.  33.       int increment;  34.       if (requestMap.containsKey(clientId + MORE)) increment = 1;  35.       else if(requestMap.containsKey(clientId + LESS)) increment = -1;  36.       else increment = 0;  37.  38.       try {  39.          int submittedValue  40.             = Integer.parseInt((String) requestMap.get(clientId));  41.  42.          int newValue = getIncrementedValue(submittedValue, increment);  43.          setSubmittedValue("" + newValue);  44.          setValid(true);  45.       }  46.       catch(NumberFormatException ex) {  47.          // let the converter take care of bad input, but we still have  48.          // to set the submitted value, or the converter won't have  49.          // any input to deal with  50.          setSubmittedValue((String) requestMap.get(clientId));  51.       }  52.    }  53.  54.    private void encodeInputField(ResponseWriter writer, String clientId)  55.          throws IOException {  56.       writer.startElement("input", this);  57.       writer.writeAttribute("name", clientId, "clientId");  58.  59.       Object v = getValue();  60.       if (v != null)  61.          writer.writeAttribute("value", v.toString(), "value");  62.  63.       Integer size = (Integer)getAttributes().get("size");  64.       if(size != null)  65.          writer.writeAttribute("size", size, "size");  66.  67.       writer.endElement("input");  68.    }  69.  70.    private void encodeDecrementButton(ResponseWriter writer, String clientId)  71.          throws IOException {  72.       writer.startElement("input", this);  73.       writer.writeAttribute("type", "submit", null);  74.       writer.writeAttribute("name", clientId + LESS, null);  75.       writer.writeAttribute("value", "<", "value");  76.       writer.endElement("input");  77.    }  78.    private void encodeIncrementButton(ResponseWriter writer, String clientId)  79.          throws IOException {  80.       writer.startElement("input", this);  81.       writer.writeAttribute("type", "submit", null);  82.       writer.writeAttribute("name", clientId + MORE, null);  83.       writer.writeAttribute("value", ">", "value");  84.       writer.endElement("input");  85.    }  86.  87.    private int getIncrementedValue(int submittedValue, int increment) {  88.       Integer minimum = (Integer) getAttributes().get("minimum");  89.       Integer maximum = (Integer) getAttributes().get("maximum");  90.       int newValue = submittedValue + increment;  91.  92.       if ((minimum == null || newValue >= minimum.intValue()) &&  93.          (maximum == null || newValue <= maximum.intValue()))  94.          return newValue;  95.       else  96.          return submittedValue;  97.    }  98. }     

javax.faces.component.ValueHolder

  • void setConverter(Converter converter)

    Input and output components both have values and, therefore, both implement the ValueHolder interface. Values must be converted, so the ValueHolder interface defines a method for setting the converter. Custom components use this method to associate themselves with standard or custom converters.



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