The litmus test for a component model is: Can you buy components from third-party vendors and plug them into your application? Similar to the way you can buy visual Swing components, you can buy Java ServerFaces (JSF) components! Need a fancy calendar? You have your choice among open source implementations and commercial components. You now have the option of buying instead of building complex Web-based GUI components.
JSF has a component model similar to AWT's GUI component model. You can create reusable components with JSF. Unfortunately, there is a misconception that creating components with JSF is difficult. Don't believe the FUD from people that have never tried it! Developing JSF components is not difficult. You can save time by not repeating the same code over and over. Once you create a component, it is easy to drop that component onto any JSP, or rather into any JSF form, which is important if you are working on a 250-page site. Most of JSF's functionality comes from base classes. JSF makes creating components easy because all the heavy lifting is done by API and base classes.
Throughout this series I've tried to help you overcome the FUD that has caused many Java developers to shy away from using JSF technology. I've discussed the basic misconceptions about the technology and served as a tour guide to both its underlying framework and its most worthy development features. With all the background work out of the way, I think you're ready to take the plunge and develop your own custom JSF components. As with all things JSF, I promise it will be easier than you think and far too rewarding, in terms of time and headache saved, to ignore.
Examples in this article have been developed using JDK 1.5 and Tomcat. Click on Sample code at the top of the page to download the example source. Note that there is no build file associated with this article, unlike the previous ones; I've left it to you as an exercise. Simply set your IDE or compiler to compile the classes in /src to /webapp/WEB-INF/classes and include all of the JAR files in /webapp/WEB-INF/lib (along with servlet-api.jar and jsp-api.jar, which are included with Tomcat).
The JSF component model is similar to the AWT GUI component model. It has events and properties just like the Swing component model. It also has containers that contain components, and that also are components that can be contained by other containers. In theory, the JSF component model is divorced from HTML and JSP. The standard set of components that ships with JSF has JSP bindings and generates HTML renderings.
Examples of JSF components include calendar input components and rich HTML input components. You may never have time to write such components, but what if they already existed? The component model lowers the barrier to entry to add more functionality to Web applications by making a commodity of common functionality.
Component functionality typically centers around two actions: decoding and encoding data. Decoding is the process of converting incoming request parameters to the values of the component. Encoding is converting the current values of the component into the corresponding markup, that is, HTML.
The JSF framework gives you two options for encoding and decoding data. With a direct implementation approach, the component itself implements decoding and encoding. With a delegated implementation approach, the component delegates to a renderer that does the encoding and decoding. If you choose a delegated implementation you can associate your component with different renderers that will represent it in different ways on the page; for example a multi-select list box versus a list of check boxes.
Thus, JSF components consist of two parts: the component and the renderer. The JSF Component class defines the state and behavior of a UI component; a renderer defines how the component will be read from the request and how it will be displayed -- usually though HTML.The renderer converts the values of the component to appropriate markup. Event queuing and performance validation happen inside the component.
In Figure 1 you can see where data encoding and decoding occur in the (by now, I hope, familiar!) JSF lifecycle.
Figure 1. JSF lifecycle and JSF components
The base class for all JSF components is UIComponent. When you develop your own components you will subclass UIComponentBase,
which extends UIComponent, and provides default implementations of the all of the abstract methods in UIComponent.
Components have parents and identifiers. Each components is associated with a component type, which is used to register the component in the faces context configuration file (faces-config.xml). You can use the JSF-EL (expression language) to bind JSF components to managed bean properties. You can associate expressions to any property on a component, thus allowing the component's property value to be set with JSF-EL. When you create component properties that are bound with JSF-EL, you need to create a value binding expression. When the getter method of the bound property is called it must use the value binding to get the value unless the setter method has set the value already.
A component can be a ValueHolder or an EditableValueHolder. A ValueHolder is associated with one or more Validators and a Converter; thus, JSF UI components are associated with Validators and a Converter (see Resources for more on JSF validation and conversion.)
Components like form field components have a ValueBinding that must be bound to a JavaBean read-write property. Components can access their parent by calling the getParent method, and can access their children by calling getChildren. Components can also have facet components, which are subcomponents of the current component and can be accessed by calling getFacets, which returns a map. Facets are named subcomponents.
Many of the component concepts outlined here will be part of the hands-on examples that follow, so keep them in mind!
Let's start your foray into JSF component development with a nice and easy example: I'll show you how to render the Label tag, (example:
<label>Form Test</label>).
Here are the steps I'll walk you through:
-
Extend a UIComponent
- Create a class that extends
UIComponent - Save the component state
- Register the component with faces-config.xml
- Create a class that extends
-
Define the renderer or implement inline
- Override encode
- Override decode
- Register the renderer with faces-config.xml
-
Create a custom tag that subclasses UIComponentTag
- Return the renderer type
- Return the component type
- Set up properties that might use JSF expressions
The Label example will demonstrates the following aspects of JSF component development:
- Creating a component
- Directly implementing a renderer
- Encoding output
- Associating a custom tag with a component
Referring back to Figure 1 you can see the two lifecycle properties that will be active in this example. They are Apply Request Values and Render Response.
In Figure 2 you can see how the Label tag (<label>Form Test</label>) would be used in a JSP.
Figure 2. Using a JSF tag in a JSP
The first step is to create a component that subclasses UIOutput, which is a subclass of UIComponent.
In addition to subclassing the class, I will add a label property, which the component will display as shown in Listing 1:
Listing 1. Subclass UIComponent and add label
import java.io.IOException;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
public class LabelComponent extends UIOutput{
private String label;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
...
|
The next thing to do is save the component state. JSF does the actual storage and state management, typically though a session, a hidden form field, cookies, etc. (This is usually a setting that you configure.). To save the component state override the component's saveState and
restoreState methods, as shown in Listing 2:
Listing 2. Saving component state
@Override
public Object saveState(FacesContext context) {
Object values[] = new Object[2];
values[0] = super.saveState(context);
values[1] = label;
return ((Object) (values));
}
@Override
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[])state;
super.restoreState(context, values[0]);
label = (String)values[1];
}
|
You'll notice that I'm using JDK 1.5. I set up the compiler so I have to specify the override annotation so as to clarify which methods are overriding base class methods. Doing this makes it easier to identify where the hooks for JSF exist.
The final step of creating the component is to register it with the faces-config.xml, as shown below:
<faces-config>
<component>
<component-type>simple.Label</component-type>
<component-class>
arcmind.simple.LabelComponent
</component-class>
</component>
...
|
The next thing to do is define the renderer functionality inline. Later I'll show you how to create a separate renderer. For now, you start by encoding the Label component output to display the label, as shown in Listing 3:
Listing 3. Encode the component output
public class LabelComponent extends UIOutput{
...
public void encodeBegin(FacesContext context)
throws IOException {
ResponseWriter writer =
context.getResponseWriter();
writer.startElement("label", this);
writer.write(label);
writer.endElement("label");
writer.flush();
}
...
}
|
Notice that the response writer (javax.faces.context.ResponseWriter) makes it easy to work with markup languages like HTML. The code in Listing 3 outputs the value of the label in the body of the <label> element.
The family property shown below is used to associate the Label component with a renderer. While you don't need this property for the Label component per se (because it doesn't have a separate renderer) you will need it later in the article, when I show you how to create a separate renderer.
public class LabelComponent extends UIOutput{
...
public String getFamily(){
return "simple.Label";
}
...
}
|
A side step: Hacking the JSF-RI
If you're using the JSF reference implementation from Sun Microsystems (and not the MyFaces implementation), you'll have to add the following to your component-creation code:
public void encodeEnd(FacesContext context)
throws IOException {
return;
}
public void decode(FacesContext context) {
return;
}
|
Sun's JSF RI expects a renderer will send a null pointer exception if your component doesn't have a renderer. The MyFaces implementation doesn't require you to work around this requirement, but it's still a good idea to include the above methods in your code, so that your component works in both the MyFaces and JSF RI environments.
JSF components are not inherently tied to JSP. To bridge from the JSP world to the JSF world you need a custom tag that returns the component type (which you'll then register in the faces-context file) and a renderer, as Figure 3 demonstrates.
Figure 3. Bridging JSF and JSP
Note that since you don't have a separate renderer, you can return a null value for getRendererType(). Note also that you have to have set the value of the label property from the custom tag to the component, as shown here:
[LabelTag.java]
public class LabelTag extends UIComponentTag {
…
protected void setProperties(UIComponent component) {
/* you have to call the super class */
super.setProperties(component);
((LabelComponent)component).setLabel(label);
}
|
Remember that the Tag class sets up the binding from the JSP to the Label component, as shown in Figure 4.
Figure 4. Binding JSF and JSP
Now all you have to do is create a TLD (Tag Library Descriptor) file to register your custom tag, as shown in Listing 4:
Listing 4. Registering a custom tag
[arcmind.tld]
<taglib>
<tlib-version>0.03</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>arcmind</short-name>
<uri>http://arcmind.com/jsf/component/tags</uri>
<description>ArcMind tags</description>
<tag>
<name>slabel</name>
<tag-class>arcmind.simple.LabelTag</tag-class>
<attribute>
<name>label</name>
<description>The value of the label</description>
</attribute>
</tag>
...
|
Once you've defined a TLD file, you can start using the tag in JSPs, as in the following example:
[test.jsp]
<%@ taglib prefix="arcmind"
uri="http://arcmind.com/jsf/component/tags" %>
...
<arcmind:slabel label="Form Test"/>
|
And that's it -- not much more is needed to develop a simple JSF component. But what if you wanted to create a slightly more complex component, for a slightly more complex usage scenario? Keep reading, then.
For the next example I'll show you how to create a component (and tag) that picks up where the last one left off. The Field component combines the work of several components into one. Composite components are a mainstay of JSF component development and will save you lots of time!
The Field component combines label, text input, and message functionality into one component. Field's text input functionality allows users to input text. Its label functionality shows up red if there is a problem (such as incorrect input) and also presents an asterisk (*) to denote required fields. Its message functionality lets it write out error messages when required.
The Field component example demonstrates the following:
- The UIInput component
- Working with value bindings and component attributes
- Decoding values from request parameters
- Working with error messages
Unlike the Label component, the Field component uses a separate renderer. If you are developing components for one HTML-based application, don't bother with separate renderers. Doing so is extra work without extra bang for your buck. If you're developing a lot of JSF components that you plan on selling to clients that want to target to more than one client, then you do want a separate renderer. In short, renderers are good for commercial-frameworks developers and not so good for application developers developing in-house Web applications.
Since I've already gone over the basic steps of creating a component, defining the renderer, and creating a custom tag, this time I'll let the code speak for itself and only point out the more important details. In Listing 5 you can see how the Field tag is used in a typical application example:
Listing 5. The Field tag
<f:view>
<h2>CD Form</h2>
<h:form id="cdForm">
<h:inputHidden id="rowIndex" value="#{CDManagerBean.rowIndex}" />
<arcmind:field id="title"
value="#{CDManagerBean.title}"
label="Title:"
errorStyleClass="errorText"
required="true" /> <br />
<arcmind:field id="artist"
value="#{CDManagerBean.artist}"
label="Artist:"
errorStyleClass="errorText"
required="true" /> <br />
<arcmind:field id="price"
value="#{CDManagerBean.price}"
label="CD Price:"
errorStyleClass="errorText"
required="true">
<f:validateDoubleRange maximum="1000.0" minimum="1.0"/>
</arcmind:field>
|
The above tags output the following HTML:
<label style="" class="errorText">Artist*</label>
<input type="text" id="cdForm:artist "
name=" cdForm:artist " />
Artist is blank, it must contain characters
|
And Figure 5 shows what all this might look like in your browser.
Figure 5. The Field component
Listing 6 shows the code to create the Field component. Because this component inputs text rather than just outputting it (as Label did)
you start by subclassing UIInput rather than UIOutput.
Listing 6. Field subclasses UIInput
package com.arcmind.jsfquickstart;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
/**
* @author Richard Hightower
*
*/
public class FieldComponent extends UIInput {
private String label;
@Override
public Object saveState(FacesContext context) {
Object values[] = new Object[2];
values[0] = super.saveState(context);
values[1] = label;
return ((Object) (values));
}
@Override
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[])state;
super.restoreState(context, values[0]);
label = (String)values[1];
}
public FieldComponent (){
this.setRendererType("arcmind.Field");
}
/**
* @return Returns the label.
*/
public String getLabel() {
return label;
}
/**
* @param label
* The label to set.
*/
public void setLabel(String label) {
this.label = label;
}
@Override
public String getFamily() {
return "arcmind.Field";
}
public boolean isError() {
return !this.isValid();
}
}
|
You'll notice the encoding method is missing from this snippet. That's because the encoding and decoding occur in a separate renderer. I'll cover this a bit later.
Value bindings and component attributes
Whereas the Label component had only one property (JSP attribute) the Field component has several, namely label, errorStyle, errorStyleClass, and value. The label and value properties are at the core of the Field component, but errorStyle and errorStyleClass are HTML specific. Because these attributes are HTML specific, you do not need to have them as properties in the Field component; instead you will pass them around as component attributes that only the renderer knows about.
As with the Label component, you will need a custom tag to bind the Field component to a JSP, as shown in Listing 7:
Listing 7. Creating a custom tag for FieldComponent
/*
* Created on Jul 19, 2004
*
*/
package com.arcmind.jsfquickstart;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
/**
* @author Richard Hightower
*
*/
public class FieldTag extends UIComponentTag {
private String label;
private String errorStyleClass="";
private String errorStyle="";
private boolean required;
private String value="";
/**
* @return Returns the label.
*/
public String getLabel() {
return label;
}
/**
* @param label The label to set.
*/
public void setLabel(String label) {
this.label = label;
}
/**
* @see javax.faces.webapp.UIComponentTag#setProperties
* (javax.faces.component.UIComponent)
*/
@Override
protected void setProperties(UIComponent component) {
/* You have to call the super class */
super.setProperties(component);
((FieldComponent)component).setLabel(label);
component.getAttributes().put("errorStyleClass",
errorStyleClass);
component.getAttributes().put("errorStyle",errorStyle);
((FieldComponent)component).setRequired(required);
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
ValueBinding binding = application.createValueBinding(value);
component.setValueBinding("value", binding);
}
/**
* @see javax.faces.webapp.UIComponentTag#getComponentType()
*/
@Override
public String getComponentType() {
return "arcmind.Field";
}
/**
* @see javax.faces.webapp.UIComponentTag#getRendererType()
*/
@Override
public String getRendererType() {
return "arcmind.Field";
}
/**
* @return Returns the errorStyleClass.
*/
public String getErrorStyleClass() {
return errorStyleClass;
}
/**
* @param errorStyleClass The errorStyleClass to set.
*/
public void setErrorStyleClass(String errorStyleClass) {
this.errorStyleClass = errorStyleClass;
}
/**
* @return Returns the errorStyle.
*/
public String getErrorStyle() {
return errorStyle;
}
/**
* @param errorStyle The errorStyle to set.
*/
public void setErrorStyle(String errorStyle) {
this.errorStyle = errorStyle;
}
/**
* @return Returns the required.
*/
public boolean isRequired() {
return required;
}
/**
* @param required The required to set.
*/
public void setRequired(boolean required) {
this.required = required;
}
/**
* @return Returns the value.
*/
public String getValue() {
return value;
}
/**
* @param value The value to set.
*/
public void setValue(String value) {
this.value = value;
}
}
|
Conceptually, you will not find much difference between the above code and
the Label component. The setProperties method does differ slightly in this instance, however:
protected void setProperties(UIComponent component) {
/* You have to call the super class */
super.setProperties(component);
((FieldComponent)component).setLabel(label);
component.getAttributes().put("errorStyleClass",
errorStyleClass);
component.getAttributes().put("errorStyle",errorStyle);
((FieldComponent)component).setRequired(required);
|
Whereas the label property is passed through just as it was in the previous example, the errorStyleClass and errorStyle properties are not passed through. Instead, they are added to the attributes map of the JSF component. The Renderer class will then use the attributes map to render class and style attributes. This setup allows the HTML-specific code to be absent from your component.
The actual value binding code for this revised setProperties method is also somewhat different, as shown here.
protected void setProperties(UIComponent component) {
...
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
ValueBinding binding = application.createValueBinding(value);
component.setValueBinding("value", binding);
|
This code allows the value property of the Field component to be bound to a backing bean. For the sake of example, I've bound the CDManagerBean's title property to a Field component as follows: value="#{CDManagerBean.title}. Value bindings are created with the Application object. The Application object is the factory that creates value bindings. The component has a special method to store value bindings, namely setValueBinding; you can have more than one value binding.
Last but not least is the renderer. The main thing the separate renderer has to worry about is decoding (input) and encoding (output). The Field component does a lot more encoding than decoding, so its renderer has many encoding methods but just one decoding method. In Listing 8 you can see the Field component's renderer:
Listing 8. FieldRenderer extends Renderer
package com.arcmind.jsfquickstart;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.ValueBinding;
import javax.faces.render.Renderer;
/**
* @author Richard Hightower
*
*/
public class FieldRenderer extends Renderer {
@Override
public Object getConvertedValue(FacesContext facesContext, UIComponent component,
Object submittedValue) throws ConverterException {
//Try to find out by value binding
ValueBinding valueBinding = component.getValueBinding("value");
if (valueBinding == null) return null;
Class valueType = valueBinding.getType(facesContext);
if (valueType == null) return null;
if (String.class.equals(valueType)) return submittedValue;
if (Object.class.equals(valueType)) return submittedValue;
Converter converter = ((UIInput) component).getConverter();
converter = facesContext.getApplication().createConverter(valueType);
if (converter != null ) {
return converter.getAsObject(facesContext, component, (String) submittedValue);
}else {
return submittedValue;
}
}
@Override
public void decode(FacesContext context, UIComponent component) {
/* Grab the request map from the external context */
Map requestMap = context.getExternalContext().getRequestParameterMap();
/* Get client ID, use client ID to grab value from parameters */
String clientId = component.getClientId(context);
String value = (String) requestMap.get(clientId);
FieldComponent fieldComponent = (FieldComponent)component;
/* Set the submitted value */
((UIInput)component).setSubmittedValue(value);
}
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
FieldComponent fieldComponent = (FieldComponent) component;
ResponseWriter writer = context.getResponseWriter();
encodeLabel(writer,fieldComponent);
encodeInput(writer,fieldComponent);
encodeMessage(context, writer, fieldComponent);
writer.flush();
}
private void encodeMessage(FacesContext context, ResponseWriter writer,
FieldComponent fieldComponent) throws IOException {
Iterator iter = context.getMessages(fieldComponent.getClientId(context));
while (iter.hasNext()){
FacesMessage message = (FacesMessage) iter.next();
writer.write(message.getDetail());
}
}
private void encodeLabel(ResponseWriter writer, FieldComponent
fieldComponent) throws IOException{
writer.startElement("label", fieldComponent);
if (fieldComponent.isError()) {
String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");
String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");
writer.writeAttribute("style", errorStyle, "style");
writer.writeAttribute("class", errorStyleClass, "class");
}
writer.write("" + fieldComponent.getLabel());
if (fieldComponent.isRequired()) {
writer.write("*");
}
writer.endElement("label");
}
private void encodeInput(ResponseWriter writer, FieldComponent
fieldComponent) throws IOException{
FacesContext currentInstance = FacesContext.getCurrentInstance();
writer.startElement("input", fieldComponent);
writer.writeAttribute("type", "text", "type");
writer.writeAttribute("id", fieldComponent.getClientId(currentInstance), "id");
writer.writeAttribute("name", fieldComponent.getClientId(currentInstance), "name");
if(fieldComponent.getValue()!=null)
writer.writeAttribute("value", fieldComponent.getValue().toString(), "value");
writer.endElement("input");
}
}
|
As previously mentioned, the main thing this renderer does is decode input and encode output. I'll start with the decoding because it's the easiest.
The FieldRenderer's decode method is as follows:
@Override
public void decode(FacesContext context, UIComponent component) {
/* Grab the request map from the external context */
Map requestMap = context.getExternalContext().getRequestParameterMap();
/* Get client ID, use client ID to grab value from parameters */
String clientId = component.getClientId(context);
String value = (String) requestMap.get(clientId);
FieldComponent fieldComponent = (FieldComponent)component;
/* Set the submitted value */
((UIInput)component).setSubmittedValue(value);
}
|
The Label component didn't need to decode because it was a UIOutput component. The Field component is a UIInput component, which means it accepts input so it does have to decode. A decode method could read values from session, cookies, headers, request, etc. In most cases, the decode method will just read values from request parameters like the above. The Field Renderer's decode method grabs the clientId from the component to identify the request parameter to be looked up. The clientId is calculated as the fully qualified name of the component given its container path. Thus, because the example component is in a form (a container), its clientid would be as follows: nameOfForm:nameOfComponent or in the example cdForm:artist, cdForm:price, cdForm:title. The last step of the decode method is to store the submitted value in the component (later it will be converted then validated; see Resources for more on validation and conversion).
The encoding methods offer no real surprises. They are similar to what you saw with the Label component. The first method, encodeBegin, delegates to the three helper methods encodeLabel, encodeInput, and encodeMessage, as shown here:
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
FieldComponent fieldComponent = (FieldComponent) component;
ResponseWriter writer = context.getResponseWriter();
encodeLabel(writer,fieldComponent);
encodeInput(writer,fieldComponent);
encodeMessage(context, writer, fieldComponent);
writer.flush();
}
|
The encodeLabel method is responsible for changing the color of the label to red (or whatever color you specify in your style sheet styles) in the case of errors and using asterisks (*) to denote required fields, as you can see below:
private void encodeLabel(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{
writer.startElement("label", fieldComponent);
if (fieldComponent.isError()) {
String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");
String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");
writer.writeAttribute("style", errorStyle, "style");
writer.writeAttribute("class", errorStyleClass, "class");
}
writer.write("" + fieldComponent.getLabel());
if (fieldComponent.isRequired()) {
writer.write("*");
}
writer.endElement("label");
}
|
First, the encodeLabel method checks to see if there are errors, and if so it outputs the errorStyle and errorStyleClass (a better version would only output them if they were not blank -- but I'll leave that as an exercise for you!). The helper method then checks to see if the component is a required field, and if so it outputs an asterisk. The encodeMessages and encodeInput methods do what you would expect; namely output error messages and generate the HTML input text field for the Field component.
You may have noticed that there is a method I haven't covered. This method is the dark horse of the methods in this class. If you read the javadocs
for Renderer (an abstract class that all renderers extend), you may feel that such a method was not needed, which would be fair enough: That's what I thought at first, too. But you, like me, would be wrong!
In fact, the base class Renderer
does not automatically invoke the associated converters of the subclass Renderer -- even though the javadocs for Renderer and the JSF specification suggest that it does. MyFaces and the JSF RI have classes (specific to their implementation) that perform this magic for their renderers, but the functionality is not covered in the core JSF API.
Instead, you will need to use the method getConvertedValues to look up the associated converter and invoke it. The method shown in Listing 9 grabs the right converter based on the type of the value binding:
Listing 9. The getConvertedValues method
@Override
public Object getConvertedValue(FacesContext facesContext,
UIComponent component, Object submittedValue) throws ConverterException {
//Try to find out by value binding
ValueBinding valueBinding = component.getValueBinding("value");
if (valueBinding == null) return null;
Class valueType = valueBinding.getType(facesContext);
if (valueType == null) return null;
if (String.class.equals(valueType)) return submittedValue;
if (Object.class.equals(valueType)) return submittedValue;
Converter converter = ((UIInput) component).getConverter();
converter = facesContext.getApplication().createConverter(valueType);
if (converter != null ) {
return converter.getAsObject(facesContext, component, (String) submittedValue);
}else {
return submittedValue;
}
}
|
The code in Listing 9 adds the functionality that both the Render javadoc and the JSF specification would have you
believe is automatic: It isn't. On the other hand, note that if you do not have a separate Renderer, you
do not need the above (getConvertedValues) method. The UIComponentBase class
(a superclass of the Field component), provides this functionality in the case of a direct renderer. Take my advice and only bother with renderers
if you have to target many devices or if you're writing a commercial framework. In other cases they're just not worth the extra headache.
If you're wondering how the component is associated with the renderer, simply take a look at Figure 6.
Figure 6. Mapping a renderer to a component
The custom tag has two methods that return the component type and the renderer type. These are used to look up the correct renderer and the component that is configured in faces-config.xml. Note (although it is not pictured) that the component must return the right family type.
And with that you really do know the essentials of JSF component development. Of course, there are plenty of other topics to be covered in this area -- including emitting component events, internationalizing components, creating UICommand-style components, and more. See Resources for a JSF reading list!
In the writing of this article I encountered a technical snag with the Renderer, which resulted in my discovering the workaround of the getConvertedValues method. While I had encountered the problem with the Converter and worked through it before, I'd done so on a tight (production) schedule. Hacks that work for production aren't necessarily the same as the ones that get proliferated in how-to articles; so this time I had to learn to not only fix the problem but how to do it right. In the course of all that I ended up learning and experiencing how JSF component handling works at a very deep level; so sometimes the detour is worth the scenery.
I hope that in this four-part series you've learned enough about the advantages of using JSF, as well as the basics of how it works, that you'll feel comfortable going deeper into the technology on your own. And when you get lost -- because you probably sometimes will -- don't give in to FUD. Instead, just remember what I said about detours and scenery, and keep going.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | j-jsf4comps.zip | 4 MB | HTTP |
Information about download methods
- Visit the JSF project page to download the JavaServer Faces APIs, custom tag library,
and related documentation.
- See the MyFaces home page
to learn more about MyFaces.
- Jackwind Li Guojie's "UI
development with JavaServer Faces" (developerWorks, September
2003) is an early-bird's look at the technology.
- Roland Barcia's five-part "Developing JSF Applications using WebSphere
Studio V5.1.1" (developerWorks, January 2004) tutorial is a
hands-on introduction to programming with JSF.
- Srikanth Shenoy and Nithin Mallya show you how to integrate the
features of Struts, Tiles, and JavaServer Faces in the advanced article,
"Integrating Struts, Tiles, and JavaServer Faces"
(developerWorks, September 2003).
- See Rick Hightower's blog to
keep up with his thoughts about JSF and other Java technologies.
- David Geary's
Core JavaServer Faces
(Prentice Hall, June
2004) is the best possible book-length introduction to JavaServer
Faces technology, including a discussion about creating custom
messages.
- Learn more about IBM
developer kits for the Java platform (downloads).
- You'll find articles about every aspect of Java programming in
the developerWorks Java technology
zone.





