Component Implementation for Mobile Clients

   

The JSF specification specifies a standard set of presentation-neutral components such as UIInput, UIForm, and UICommand, and an HTML-specific set of tags and renderers. There is no standard implementation for non-HTML clients. Thus, we need to develop our own set of tags and renderers to communicate with a midlet.

Let us first review what exactly happens when you put an HTML component tag such as h:inputText into your JSF page.

  • The standard tag library file html_basic.tld maps the inputText tag to the class com.sun.faces.taglib.html_basic.InputTextTag. The tag handler class is called whenever the JSF file parser encounters an inputText tag. The tag handler stashes away the tag attributes (such as value and validator) inside the associated component object.

  • The getComponentType method of that class returns the string "javax.faces.HtmlInputText", and the getRendererType method returns the string "javax.faces.Text".

  • The file jsf-ri-config.xml maps these strings to classes javax.faces.component.html.HtmlInputText and com.sun.faces.renderkit.html_basic.TextRenderer.

  • The HtmlInputText class is a subclass of UIInput. It merely adds getter and setter methods for the "pass through" attributes (such as onMouseOver, alt, and so on). The UIInput class carries out validation, manages value change events, and updates model values.

  • The com.sun.faces.renderkit.html_basic.TextRenderer class does a fair amount of heavy lifting. Its decode method fetches the value that the user supplied and sets it as the current value of the component. Its encode methods produce a string of the form <input type="text" .../>. Moreover, these methods handle data conversion and value binding lookup.

When we implement our own components, we can reuse component classes such as UIInput. However, the JSF framework gives very limited support for tags and renderers. Much of the essential work is carried out by classes in the com.sun.faces packages. We need to replicate that work in our own tags and renderers.

We designed a tag library with five tags: input, select, output, form, and command. On the JSF side, they correspond to the UIInput, UISelectOne, UIOutput, UIForm, and UICommand classes. On the MIDP side, the first two correspond to TextField and ChoiceGroup items. The form tag identifies the form that the client should display. The client uses the command tag to invoke JSF actions.

Let's walk through the input tag in detail. Consider the following tag:

 

 <j2me:input  value="#{user.name}"    validator="#{loginform.authenticate}"/> 

The tag class for the input tag processes the id, value, and validator attributes and set the appropriate values in the UIInput component.

When the client requests this page for the first time, the renderer for this tag produces a string such as

 

 uname=John+Q%2e+Public 

The renderer simply looks up the ID and the value of the UIInput component.

When the MIDP client receives this string, it places the value of the uname key inside the matching text field. Note again that the client does its own rendering. The server doesn't tell it where to place the text field, only what value it should contain.

When the client posts the form contents to the server, it sends POST data that contain the new value of the text field, such as

 

 ...&uname=Jane+Doe&... 

Now the renderer for the input tag kicks in again, fetching the request parameter value and setting the value of the UIInput component.

We don't need to worry about invoking the validator or evaluating the value binding expression those tasks are handled by the JSF framework.

Now let's look at the code in detail.

As always, we start with a tag library descriptor file (see Listing 11-1). That file lists the tags, their valid attributes, and the handler classes.

For simplicity, all tag handlers extend the J2meComponentTag class shown in Listing 11-3. That class processes the value, action, and validator attributes. The id and binding attributes are handled by the UIComponentTag superclass.

The InputTag class (Listing 11-4) merely defines the getComponentType and getRendererType methods. The first method returns the string "Input", which is mapped by the standard JSF configuration to the UIInput component. The second method returns the string J2meText, which we will map to the TextRenderer class of Listing 11-5.

The decode method of the renderer simply sets the new value of the component:

 

 Map requestMap = context.getExternalContext()    .getRequestParameterMap(); if (requestMap.containsKey(id)) {    String newValue = (String) requestMap.get(id);    ((ValueHolder) component).setValue(newValue); } 

The encodeBegin method writes out the current value:

 

 ResponseWriter writer = context.getResponseWriter(); String id = component.getId(); String value = ((ValueHolder) component).getValue().toString(); writer.write(id + "=" + URLEncoder.encode(value, "UTF8") + "\n"); 

Moreover, the encodeBegin method produces a name/value pair with all messages that may have queued up for this component. For example, if during validation, an error was found in the uname field, the resulting output would be

 

 uname.messages=No+such+user 

It is up to the client to decide what to do with these messages.

The remaining component implementations are similar. A few issues are worth noting.

  • The renderer for the command tag queues an action event instead of setting a component value. The associated UICommand component automatically processes the event, using its action property. See Listing 11-6.

  • The renderer for the selectOne tag (shown in Listing 11-7) encodes the labels for the choices in the following format:

     

     direction.label.0=horizontal direction.label.1=vertical 

    It needs to carry out this in the encodeEnd method rather than encodeBegin since the enclosed f:selectItem or f:selectItems values are not available at the beginning!

  • Finally, the form renderer needs to call the setSubmitted method of the UIForm and to indicate whether the form is requested for the first time or whether it is posted again with new values. (See Listing 11-8.) There is a technical reason for this requirement. When the form is rendered for the first time, the component values may be defaults that do not pass validation. Validation should only occur when the renderer processes form data that are posted from the client. The submitted property regulates this behavior. We simply require the client to include the form ID when posting form data.

As you can see, implementing these renderers is fairly straightforward. Moreover, you gain some insight into the rendering process that is helpful for understanding any JSF application.

To separate the configuration information for these renderers from the application-specific configuration, we use an auxiliary file j2me-config.xml (see Listing 11-2). To add this file to your web application, include the following parameter definition in your web.xml file:

 

 <context-param>    <param-name>       javax.faces.application.CONFIG_FILES</param-name>    <param-value>       /WEB-INF/faces-config.xml,       /WEB-INF/j2me-config.xml    </param-value> </context-param> 

In the next section, we put our tags to work in a complete application.

Listing 11-1. phonebattle/WEB-INF/j2me.tld
  1. <?xml version="1.0" encoding="ISO-8859-1" ?>  2. <!DOCTYPE taglib  3.   PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"  4.   "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">  5. <taglib>  6.    <tlib-version>0.03</tlib-version>  7.    <jsp-version>1.2</jsp-version>  8.    <short-name>j2me</short-name>  9.    <uri>http://corejsf.com/j2me</uri> 10.    <description> 11.       This tag library contains J2ME component tags. 12.    </description> 13. 14.    <tag> 15.       <name>form</name> 16.       <tag-class>com.corejsf.j2me.FormTag</tag-class> 17.       <attribute> 18. 19.          <name>id</name> 20.       </attribute> 21.    </tag> 22.    <tag> 23.       <name>input</name> 24.       <tag-class>com.corejsf.j2me.InputTag</tag-class> 25.       <attribute> 26.          <name>id</name> 27.       </attribute> 28.       <attribute> 29.          <name>value</name> 30.       </attribute> 31.       <attribute> 32.          <name>validator</name> 33.       </attribute> 34.    </tag> 35.    <tag> 36.       <name>output</name> 37.       <tag-class>com.corejsf.j2me.OutputTag</tag-class> 38.       <attribute> 39.          <name>id</name> 40.       </attribute> 41.       <attribute> 42.          <name>value</name> 43.       </attribute> 44.    </tag> 45.    <tag> 46.       <name>selectOne</name> 47.       <tag-class>com.corejsf.j2me.SelectOneTag</tag-class> 48.       <attribute> 49.          <name>id</name> 50.       </attribute> 51.       <attribute> 52.          <name>binding</name> 53.       </attribute> 54.       <attribute> 55.          <name>value</name> 56.       </attribute> 57.    </tag> 58.    <tag> 59.       <name>command</name> 60.       <tag-class>com.corejsf.j2me.CommandTag</tag-class> 61.       <attribute> 62.          <name>id</name> 63.       </attribute> 64.       <attribute> 65.          <name>action</name> 66.       </attribute> 67.    </tag>  1. </taglib> 

Listing 11-2. phonebattle/WEB-INF/j2me-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.    <render-kit>  9.       <renderer> 10.          <component-family>javax.faces.Input</component-family> 11.          <renderer-type>com.corejsf.j2me.Text</renderer-type> 12.          <renderer-class>com.corejsf.j2me.TextRenderer</renderer-class> 13.       </renderer> 14.       <renderer> 15.          <component-family>javax.faces.Output</component-family> 16.          <renderer-type>com.corejsf.j2me.Text</renderer-type> 17.          <renderer-class>com.corejsf.j2me.TextRenderer</renderer-class> 18.       </renderer> 19.       <renderer> 20.          <component-family>javax.faces.Form</component-family> 21.          <renderer-type>com.corejsf.j2me.Form</renderer-type> 22.          <renderer-class>com.corejsf.j2me.FormRenderer</renderer-class> 23.       </renderer> 24.       <renderer> 25.          <component-family>javax.faces.SelectOne</component-family> 26.          <renderer-type>com.corejsf.j2me.Choice</renderer-type> 27.          <renderer-class>com.corejsf.j2me.ChoiceRenderer</renderer-class> 28.       </renderer> 29.       <renderer> 30.          <component-family>javax.faces.Command</component-family> 31.          <renderer-type>com.corejsf.j2me.Command</renderer-type> 32.          <renderer-class>com.corejsf.j2me.CommandRenderer</renderer-class> 33.       </renderer> 34.    </render-kit> 35. </faces-config> 

Listing 11-3. phonebattle/WEB-INF/classes/com/corejsf/j2me/J2meComponentTag.java
  1. package com.corejsf.j2me;  2.  3. import javax.faces.component.UIComponent;  4. import javax.faces.webapp.UIComponentTag;  5.  6. public abstract class J2meComponentTag extends UIComponentTag {  7.    private String value;  8.    private String action;  9.    private String validator; 10. 11.    // PROPERTY: value 12.    public void setValue(String newValue) { value = newValue; } 13. 14.    // PROPERTY: action 15.    public void setAction(String newValue) { action = newValue; } 16. 17.    // PROPERTY: validator 18.    public void setValidator(String newValue) { validator = newValue; } 19. 20.    public void setProperties(UIComponent component) { 21.       super.setProperties(component); 22.       com.corejsf.util.Tags.setString(component, "value", value); 23.       com.corejsf.util.Tags.setAction(component, action); 24.       com.corejsf.util.Tags.setValidator(component, validator); 25.    } 26. 27.    public void release() { 28.       value = null; 29.       validator = null; 30.       action = null; 31.    } 32. } 

Listing 11-4. phonebattle/WEB-INF/classes/com/corejsf/j2me/InputTag.java
 1. package com.corejsf.j2me; 2. 3. 4. public class InputTag extends J2meComponentTag { 5.    public String getComponentType() { return "javax.faces.Input"; } 6.    public String getRendererType() { return "com.corejsf.j2me.Text"; } 7. } 

Listing 11-5. phonebattle/WEB-INF/classes/com/corejsf/j2me/TextRenderer.java
  1. package com.corejsf.j2me;  2.  3. import java.io.IOException;  4. import java.net.URLEncoder;  5. import java.util.Map;  6. import javax.faces.component.UIComponent;  7. import javax.faces.component.ValueHolder;  8. import javax.faces.context.FacesContext;  9. import javax.faces.context.ResponseWriter; 10. import javax.faces.render.Renderer; 11. 12. public class TextRenderer extends Renderer { 13.    public void encodeBegin(FacesContext context, UIComponent component) 14.       throws IOException { 15.       ResponseWriter writer = context.getResponseWriter(); 16.       String id = component.getId(); 17.       String value = "" + ((ValueHolder) component).getValue(); 18.       writer.write(id + "=" + URLEncoder.encode(value, "UTF8") + "\n"); 19.    } 20. 21.    public void decode(FacesContext context, UIComponent component) { 22.       if (context == null || component == null) return; 23. 24.       String id = component.getId(); 25.       Map requestMap 26.          = context.getExternalContext().getRequestParameterMap(); 27.       if (requestMap.containsKey(id) 28.          && component instanceof ValueHolder) { 29.          String newValue = (String) requestMap.get(id); 30.          ((ValueHolder) component).setValue(newValue); 31.       } 32.    } 33. } 

Listing 11-6. phonebattle/WEB-INF/classes/com/corejsf/j2me/CommandRenderer.java
  1. package com.corejsf.j2me;  2.  3. import java.util.Map;  4. import javax.faces.component.UIComponent;  5. import javax.faces.context.FacesContext;  6. import javax.faces.event.ActionEvent;  7. import javax.faces.render.Renderer;  8.  9. public class CommandRenderer extends Renderer { 10.    public void decode(FacesContext context, UIComponent component) { 11.       if (context == null || component == null) return; 12. 13.       String id = component.getId(); 14.       Map requestMap 15.          = context.getExternalContext().getRequestParameterMap(); 16.       if (requestMap.containsKey(id)) { 17.          component.queueEvent(new ActionEvent(component)); 18.       } 19.    } 20. } 

Listing 11-7. phonebattle/WEB-INF/classes/com/corejsf/j2me/ChoiceRenderer.java
  1. package com.corejsf.j2me;  2.  3. import java.io.IOException;  4. import java.net.URLEncoder;  5. import java.util.List;  6. import java.util.Map;  7. import javax.faces.component.UIComponent;  8. import javax.faces.component.EditableValueHolder;  9. import javax.faces.component.ValueHolder; 10. import javax.faces.context.FacesContext; 11. import javax.faces.context.ResponseWriter; 12. import javax.faces.model.SelectItem; 13. import javax.faces.render.Renderer; 14. 15. public class ChoiceRenderer extends Renderer { 16.    public void encodeEnd(FacesContext context, UIComponent component) 17.       throws IOException { 18.       ResponseWriter writer = context.getResponseWriter(); 19.       EditableValueHolder input = (EditableValueHolder) component; 20.       String id = component.getId(); 21.       List items = com.corejsf.util.Renderers.getSelectItems(component); 22.       String value = input.getValue().toString(); 23.       String label = findLabel(items, value); 24.       writer.write(id + "=" + URLEncoder.encode(label, "UTF8") + "\n"); 25.       for (int i = 0; i < items.size(); i++) { 26.          SelectItem item = (SelectItem) items.get(i); 27.          writer.write(id + ".label." + i 28.             + "=" + URLEncoder.encode(item.getLabel(), "UTF8") + "\n"); 29.       } 30.    } 31. 32.    public void decode(FacesContext context, UIComponent component) { 33.       if (context == null || component == null) return; 34. 35.       String id = component.getId(); 36.       Map requestMap 37.          = context.getExternalContext().getRequestParameterMap(); 38.       if (requestMap.containsKey(id) 39.          && component instanceof ValueHolder) { 40.          String label = (String) requestMap.get(id); 41.          List items = com.corejsf.util.Renderers.getSelectItems(component); 42.          Object value = findValue(items, label); 43.          ((ValueHolder) component).setValue(value); 44.       } 45.    } 46. 47.    private static Object findValue(List list, String label) { 48.       for (int i = 0; i < list.size(); i++) { 49.          SelectItem item = (SelectItem) list.get(i); 50.          if (item.getLabel().equals(label)) return item.getValue(); 51.       } 52.       return null; 53.    } 54. 55.    private static String findLabel(List list, Object value) { 56.       for (int i = 0; i < list.size(); i++) { 57.          SelectItem item = (SelectItem) list.get(i); 58.          if (item.getValue().equals(value)) return item.getLabel(); 59.       } 60.       return null; 61.    } 62. } 

Listing 11-8. phonebattle/WEB-INF/classes/com/corejsf/j2me/FormRenderer.java
  63. package com.corejsf.j2me;  64.  65. import java.io.IOException;  66. import java.net.URLEncoder;  67. import java.util.Iterator;  68. import java.util.Map;  69. import javax.faces.application.FacesMessage;  70. import javax.faces.component.UIComponent;  71. import javax.faces.component.UIForm;  72. import javax.faces.context.FacesContext;  73. import javax.faces.context.ResponseWriter;  74. import javax.faces.render.Renderer;  75.  76. public class FormRenderer extends Renderer {  77.    public void encodeBegin(FacesContext context,  78.       UIComponent component) throws IOException {  79.       ResponseWriter writer = context.getResponseWriter();  80.       writer.write("form=" + component.getId() + "\n");  81.  82.       Iterator ids = context.getClientIdsWithMessages();  83.       while (ids.hasNext()) {  84.          String id = (String) ids.next();  85.          Iterator messages = context.getMessages(id);  86.          String msg = null;  87.          while (messages.hasNext()) {  88.             FacesMessage m = (FacesMessage) messages.next();  89.             if (msg == null) msg = m.getSummary();  90.             else msg = msg + "," + m.getSummary();  91.          }  92.          if (msg != null) {  93.             writer.write("messages");  94.             if (id != null) writer.write("." + id);  95.             writer.write("=" + URLEncoder.encode(msg, "UTF8") + "\n");  96.          }  97.       }  98.    }  99. 100.    public void decode(FacesContext context, UIComponent component) { 101.       Map map = context.getExternalContext().getRequestParameterMap(); 102.       ((UIForm)component).setSubmitted( 103.          component.getId().equals(map.get("form"))); 104.    } 



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