JSF Central - Developing Unwired JSF Components
JSF Central

 
 Home 
 
 News 
 
 FAQ 
 
 Products 
 
 Articles & Books 
 
 Resources 
 
Developing Unwired JSF Components
(continued)
by Marcel Urbanek
20 Sep 2005 02:45 EDT

Learn how to develop JSF components that render for WAP and desktop browsers.


The return value of the getComponentType() method specifies which component class is to be instantiated and the faces-config.xml file determines which component class corresponds to which return value. This is illustrated in the following excerpt from the faces-config.xml file:

<component>
        <component-type>UrbiworxButton</component-type>
        <component-class>de.urbiworx.jsf.button.ButtonComponent
        </component-class>
</component>

In this example, if the return value of the getComponent() method is "UrbiworxButton", the component class de.urbiworx.jsf.button.ButtonComponent is instantiated.

A tag also needs a tag descriptor, so it can be accessed from a JSP, as follows:

<!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>
        <short-name>JavaServerFaces Example Components Tag Library
        </short-name>
        <uri>http://urbiworx.de/jsfcomponents</uri>
        <description>
                This Taglibary contains tags for the Urbiworx JSF button
        </description>
        <tag>
                <name>button</name>
                <tag-class>de.urbiworx.jsf.button.ButtonTag
                </tag-class>
                <description>
                        Component representing a Table wich can be 
                        rendered for HTML and HTMLMP. Also shows 
                        forward and backward arrrows if there are 
                        to much rows.
                </description>
                <attribute>
                        <name>action</name>
                        <required>true</required>
                        <rtexprvalue>false</rtexprvalue>
                        <description>
                                Action which will be invoked when clicked
                        </description>
                </attribute>
[...]

The tag class ("<tag-class>") to be used, which attributes have to be given, and which may be given is defined in the tag descriptor. Each parameter ("attribute>") specified here requires a corresponding set and get methods in the tag class.

After the creation of the tag class and the corresponding configuration files, component classes also must to be created. Because our component will delegate the rendering to a renderer, the components implementation is trivial. The only really interesting method is getRenderer(), which will determine which renderer is to be used.

As the HTML code for WAP and web browsers would be exactly the same, we would normally need only one renderer, which would render the following:

<input type="img" src="[imageurl]" [...]/>

This would display an image button on both web and WAP browsers. However, a problem emerges in that series60 and series40 devices don't fulfil the W3C standards. If you use the HTML snippet above, the IDs of all buttons will be submitted regardless of which button is clicked. However, this issue can be resolved by setting a micro text button (without text, of course) in front of the button image so that the user then clicks the small text button instead of the image. The generation of a small non-text button works differently on series60 and series40 devices, and there might be some other mobiles which also work differently. Therefore, the HTML to be generated for series60 devices is:

<input type="submit" value=""
        style="margin-left:0px;margin-right:0px;height:6px;width:
        1px;border-style:none;background-color:transparent;
        font-size:xx-small;"[...]/>
<img src="[imageurl]"/>

The corresponding HTML for series40 devices is:

<input type="submit" value=" "
        style="margin-left:0px;margin-right:0px;height:4px;width:1 px;
        border-style:none;background-color:transparent;font-size:xx-small;"
        [...]/>
<img src="[imageurl]"/>

Therefore, 3 renderers are required: one for normal WAP and web browsers (W3C conforming), one for series60 devices and one for series40 devices. If your mobile works differently, you may want to implement a renderer yourself.

Let’s have a look at how to determine which renderer JSF should use. This is implemented in the getRenderer() method of our component class.

public class ButtonComponent extends UICommand {
[...]

        public String getRendererType() {
                FacesContext context=FacesContext.getCurrentInstance();
                String compatibility=getCompatibility(context);

                if (compatibility!=null)
                {
                        if (compatibility.equalsIgnoreCase("series40"))
                                return "UrbiworxButtonHtmlSeries40";
                        else if (compatibility.equalsIgnoreCase("series60"))
                                return "UrbiworxButtonHtmlSeries60";
                }
                //If this component is not rendererd in an servlet
                //enivornment, broeser identification is skipped.
                if (!(context.getExternalContext().getRequest()
                        instanceof HttpServletRequest))
                {
                        return "UrbiworxButtonHtml";
                }
                HttpServletRequest request=
                  (HttpServletRequest)context.getExternalContext().
                        getRequest();
                //The user-agent string contains information about which
                //browser is used to view the pages
                String useragent= request.getHeader("user-agent");
                useragent=useragent.toLowerCase();
                if (useragent.indexOf("opera")!=-1) 
                        return "UrbiworxButtonHtml";
                if (useragent.indexOf("netscape")!=-1) 
                        return "UrbiworxButtonHtml";
                if (useragent.indexOf("msie")!=-1) 
                        return "UrbiworxButtonHtml";
                if (useragent.indexOf("firefox")!=-1) 
                        return "UrbiworxButtonHtml";
                if (useragent.indexOf("nokia")!=-1)
                if (useragent.indexOf("symbianos")!=-1)
                        return "UrbiworxButtonHtmlSeries60";
                else
                        return "UrbiworxButtonHtmlSeries40";
                return "UrbiworxButtonHtml";
        }
[...]
}
Another way to use different renderer is to use a ViewHandler decorator or a PhaseListener. This logic would then select the render kit instead of the renderer type. However, in this case you would not have the ability to use a compatibility attribute, because the decision about which renderer to take is made in the ViewHandler or PhaseListener.

Thus, the browser identification is analysed, and depending on what browser is being used, the corresponding string will be returned. If the compatibility attribute is set to "series40" or "series60", identification is skipped and the corresponding value is returned.

This return string can be looked up in the faces-config.xml file to determine which render class has to be instantiated, as illustrated below:

<render-kit>
        <renderer>
                <description>
                        Renderer for the credit card component.
                </description>
                <component-family>UrbiworxButtonFamily
                </component-family>
                <renderer-type>
                        UrbiworxButtonHtml
                </renderer-type>
                <renderer-class>
                        de.urbiworx.jsf.button.ButtonRendererHtml
                </renderer-class>
        </renderer>
[...]        

The renderer implementation will be discussed later. First, let’s look at how to implement the component's properties, such as the getTheme() method:

public String getTheme(FacesContext context)
{

        ValueBinding vb=this.getValueBinding("theme");
        //If there is a value binding, the value of the value binding will
        //be returned
        if (vb!=null)
                return (String)vb.getValue(context);//
        //If there is no value binding, the value can be returned as-is
        return((String)this.getAttributes().get("theme"));
}

The first step it to check if the value is a ValueBinding. If it is, the ValueBinding's value will be returned and if not, only the value is returned. This technique can be used for any value. In the case of a different return type (not a string), the casting for the ValueBinding's value must, of course, be different. If there is no ValueBinding, but there is a normal value, it is necessary to convert it to the return type (i.e. return new Integer(Integer.parseInt(...));) when using return types other than strings. (Alternatively, you can simply store the property as an instance variable.)

Now, let’s discuss the renderer implementation -- in this case the renderer for the web, ButtonRendererHtml.java:

public class ButtonRendererHtml extends ButtonRenderer {
        public void encodeEnd(FacesContext context, UIComponent component)
                        throws IOException {
                //Get the responsewriter. In the following source you will see
                //how to use it
                ResponseWriter writer=context.getResponseWriter();

                //This is the component that we will render
                ButtonComponent buttonComponent=(ButtonComponent)component;

                //This tag is used to start an html element, here the writer
                //will produce \'< input\' as output
                writer.startElement("input",buttonComponent);
                try
                {
                        //TabGen.gen() will generate a button and return the
                        //HTTP path to it. The writer.writeAttribute() method
                        //will add an attribute to the open HTML tag (in this
                        //case the open tag is input). The //writer\'s output
                        //is : //src=\'HTTP://[...]\'.The methods like 
                        //getText(context) get the attributes which were 
                        //given by the user. Look at the component source 
                        //code for details on this.
                        writer.writeAttribute("src",
                            TabGen.gen(context.getExternalContext().
                                getSession(false),
                                buttonComponent.getTheme(context),
                                buttonComponent.getActive(context).
                                        booleanValue(),
                                buttonComponent.getText(context)),null);
                }
                catch (Exception e){e.printStackTrace();}

                //render typical attributes
                writer.writeAttribute("type","image",null);

                //We use the method getClientId() of the component to get a
                //unique id. We will use this id to identify our component in
                //the whole request. Because every component gets a different
                //id we can be sure that everything in the response which has
                //this id is caused by us.
                writer.writeAttribute("name",
                        buttonComponent.getClientId(context),null);
                writer.writeAttribute("style","border-style:none",null);
                writer.writeAttribute("alt",
                        buttonComponent.getDescription(context),null);
                writer.writeAttribute("title",
                        buttonComponent.getDescription(context),null);

                // if JavaScript is needed, render JavaScript attributes
                if (buttonComponent.getOnClick(context)!=null)
                {
                        writer.writeAttribute("onClick",
                                buttonComponent.getOnClick(context),
                                        null);
                }
                if (buttonComponent.getOnMouseOver(context)!=null)
                {
                        writer.writeAttribute("onMouseOver",
                                buttonComponent.getOnMouseOver(context),
                                        null);
                }

                writer.endElement("input");
        }
}

The other renderers are similar. For instance, the series40 renderer uses following style:

writer.writeAttribute("style","margin-left:0px;margin-right:0px;" + 
        "height:4 px;width:1px;border-style:none;background-color:" + 
        "transparent;font-size:xx-small;",null);

The series60 renderer uses this style:

writer.writeAttribute("style","margin-left:0px;margin-right:0px",null);

Further, the "value" tag is different for both renderers as can be seen on examination of the full source code.


Download: source code (ZIP; 2MB)


RSS feed(all feeds)

Inside Facelets
Learn how to use Facelets, an alternative to JSP.
In the Trenches
Read about real-world JSF projects!

Sponsored links

Site version 1.83  Report web site problems

Copyright (C) 2003-2007 Virtua, Inc. All Rights Reserved. Java, JavaServer Faces, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. Virtua, Inc. is independent of Sun Microsystems, Inc. All other trademarks are the sole property of their respective owners.