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.
Page:
1
2
3