In this chapter, we will cover:
Working with implicit and explicit conversions
Standard converters for numbers
Standard converters for date and time
Converters and NULL values
Creating and using a custom converter
Using converters for
h:selectOneMenu
Binding converters to backing bean properties
RichFaces and standard converters
RichFaces and custom converters
Instance variables in converters
Client-side converters with MyFaces Trinidad
Data conversion is the process of converting/transforming one data type into another. Before going further and analyzing some aspects of JSF converters, let's see what they actually are and what they are good for.
For this, let's take an example of a web application in which the user has to fill up a simple form with some information, such as name, age, and date of birth. The server component of our application will receive this information as strings, even if we know that they are a string (the name), an integer (the age), and a date (the date of birth). This is the phase when JSF converters enter into the scene and convert the user input according to application requirements. If the submitted information is not successfully converted then the form is redisplayed (this time an attention message is also displayed) and the user can refill the form. The case repeats until the submitted information is successfully converted to the correct type.
In addition, you should know that JSF provides a set of standard converters (used for the most common conversions) and the possibility to define your own converters, known as custom converters (this kind of converters are very useful when the standard converters can't accomplish the desired conversions). Speaking of standard converters, the following are the most used converters:
Converter IDs |
Converter class |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Some JSF tags that support converters are as follows:
Speaking about a converter lifecycle, you should focus on two main phases named: Apply
Request
Values
Phase
and Render
Response
Phase
. For example, if we assume a form that is submitted with a set of values, a converter for those values, a corresponding backing bean, and a render page, then the application lifecycle will be like this (notice when and where the converter is involved!):
Restore
View
Phase
: The backing bean is created and the components are stored into theUIViewRoot
.Apply
Request
Values
Phase
: The submitted values are decoded and set in the corresponding components inUIViewRoot
.Process
Validations
Phase
: The convertergetAsObject
method receives the submitted values (eventually a potential validator is also called).Update
Model
Values
Phase
: The converted (validated) values are set in the backing bean.Invoke
Application
Phase
: The phase responsible for form processing.Render
Response
Phase
: The values that should be displayed are extracted from a backing bean. ThegetAsString
method of the converter receives these values before rendering. The conversion results are redirected to the result page.
Using the proper converter is the developer's choice. The developer is also responsible for customizing the error messages displayed when the conversion fails. When the standard converters don't satisfy the application needs, the developer can write a custom converter as you will see in our recipes.
Notice that our recipes make use of JSF 2.0 features, such as annotation, new navigation style, and no faces-config.xml
file. Especially you must notice the new @FacesConverter
annotation for indicating to a normal class that it is a JSF 2.0 converter.
Let's start with a simple recipe about working with implicit and explicit conversions.
By implicit conversions, we understand all the conversions that JSF will accomplish automatically, without the presence of an explicit converter (in other words, if you don't specify a converter, JSF will pick one for you). Actually, JSF uses implicit conversion when you map a component's value to a managed bean property of a Java primitive type or of BigInteger
and BigDecimal
objects.
In this recipe, we will see an example of an implicit and an explicit conversion. Anyway, don't forget that explicit conversion provides greater control over the conversion.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
Our recipe is based on an imaginary situation where the user should insert their age into a simple JSF form consisting of a text field and a submit button. The submitted age will be implicitly converted and displayed on another simple JSF page. The following is the JSF form (the highlighted code maps the text field's value to the userAge
managed bean property of a Java integer
type):
<h:form id="AgeForm"> <h:inputText id="userAgeID" required="true" value="#{userBean.userAge}"> </h:inputText> <h:message showSummary="true" showDetail="false" for="userAgeID" style="color: red; text-decoration:overline"/> <br /> <h:commandButton id="submit" action="response?faces- redirect=true" value="Submit Age"/> </h:form>
Note
The preceding code snippet makes uses of the new JSF 2 implicit navigation style. The {page_name}?faces-redirect=true
request parameter indicates to JSF to navigate to the {page_name}
. There is more about JSF 2 navigation in Chapter 11, JSF 2.0 Features.
The userAge
is mapped into a managed bean as shown next:
package users; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; @ManagedBean @SessionScoped public class UserBean { private int userAge; public int getUserAge(){ return this.userAge; } public void setUserAge(int userAge){ this.userAge=userAge; } }
As the userAge
is a Java integer, JSF will automatically convert the inserted age to this type (notice that we did not indicate any conversion in the previous code). This is called an implicit conversion. In the case that the inserted age is not an integer, this will be reflected by an error message exposed by the h:message
component.
Now, speaking of explicit conversion we can enforce the previous situation by using the UIComponent
converter
attribute or f:converter
tag nested within a UIComponent
. The
modifications are reflected in the next two lines:
<!-- explicit conversion using the UIComponent converter attribute --> <h:inputText id="userAgeID" required="true" value="#{userBean.userAge}" converter="javax.faces.Integer"> </h:inputText> <!-- converter tag nested within a UIComponent --> <h:inputText id="userAgeID" required="true" value="#{userBean.userAge}"> <f:converter converterId="javax.faces.Integer"/> </h:inputText>
There is no trick here! In the case of implicit conversion, JSF tries to identify which is the appropriate converter to be applied. Obviously, for explicit conversion, JSF tries to apply the indicated converter. When conversion fails, the form is redisplayed and an exception message is fired. Otherwise, the application follows its normal flow.
You can mix explicit and implicit conversion over the same managed bean property, but, in this case, you should keep in mind the Java cast rules. For example, if you try to explicitly force an integer
to a Byte
type you will get an error, as java.lang.Integer
type can't be cast to java.lang.Byte
type, while a java.lang.Integer
can be cast to java.lang.Double
.
Numbers are a generic notion used to quantify many things, such as age, salary, percent, currency, custom pattern, and so on. Also, we know that numbers can be integers, floats, doubles, and so on. Depending on what we represent, we know what kind of number to use and how to write it in the correct format and with the correct symbols attached. In this recipe you will see how to accomplish this task using JSF standard capabilities. For this we will take a generic double
number and we will output it to represent different things.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
Converting numbers and applying basic formats to them are tasks that can be accomplished by the f:convertNumber
JSF converter. This converter can be customized using a set of attributes, listed next:
Attribute name |
Description |
---|---|
|
Represents the type of number. By default this type is set to number, but you can set it to |
|
Represents the decimal format pattern used to convert this number. |
|
Represents the locale to be used for displaying this number. The user's current locale is overridden. |
|
Represents the maximum number of integer digits to display |
|
Represents the minimum number of integer digits to display. |
|
Represents the maximum number of fractional digits to display. |
|
Represents the minimum number of fractional digits to display. |
|
Represents a three-digit international currency code when the attribute |
|
Represents a symbol, like |
|
Set the value of this attribute to true, if you want to ignore the fractional part of a number. By default it is set to false. |
|
Set the value of this attribute to true, if you want to use a grouping symbol like comma or space. By default it is set to true. |
Now, let's suppose that we have the number 12345.12345 (five integer digits and five
fraction digits). The following code will output this number using the f:convertNumber
converter and the previously listed attributes:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Standard converters for numbers - format numbers</title> </h:head> <h:body> <b><h:outputText value="-Formatting the double value 12345.12345-"/></b><br /> <!-- Format as 00000.00000 --> <h:outputText value="Format as 00000.00000: "/> <h:outputText value="#{numbersBean.doubleNumber}"> <f:convertNumber type="number" maxIntegerDigits="5" maxFractionDigits="5" groupingUsed="false"/> </h:outputText> <br /> <!-- Format as 00000 --> <h:outputText value="Format as 00000: "/> <h:outputText value="#{numbersBean.doubleNumber}"> <f:convertNumber type="number" maxIntegerDigits="5" maxFractionDigits="0"/> </h:outputText> <br /> <!-- Format as currency --> <h:outputText value="Format as currency: "/> <h:outputText value="#{numbersBean.doubleNumber}"> <f:convertNumber type="currency" currencySymbol="$" maxIntegerDigits="5" maxFractionDigits="2"/> </h:outputText> <br /> <!-- Format as percent --> <h:outputText value="Format as percent: "/> <h:outputText value="#{numbersBean.doubleNumber}"> <f:convertNumber type="percent" maxIntegerDigits="5" maxFractionDigits="5"/> </h:outputText> <br /> <!-- Format as pattern #####,00% --> <h:outputText value="Format as pattern #####,00%: "/> <h:outputText value="#{numbersBean.doubleNumber}"> <f:convertNumber pattern="#####,00%"/> </h:outputText> </h:body> </html>
The NumbersBean
is the managed bean, as shown next:
package numbers; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; @ManagedBean @SessionScoped public class NumbersBean { private double doubleNumber = 12345.12345; public double getDoubleNumber(){ return this.doubleNumber; } public void setDoubleNumber(double doubleNumber){ this.doubleNumber=doubleNumber; } }
The output will be as follows:
-Formatting the double value 12345.12345
Format as 00000.00000: 12345.12345
Format as 00000: 12,345
Format as currency: $12,345.12
Format as percent: 34,512.345%
Format as pattern #####,00%: 1,23,45,12%
The number is displayed corresponding to the formatting attributes. The parts of the number that don't correspond to the conversion's restrictions are ignored or an error message is generated.
Notice that we have used the f:convertNumber
with the h:outputText
component, but you can follow the same logic to use with the h:inputText
component. These two components are the most used in conjunction with the f:convertNumber
converter.
Measuring, representing, formatting, and localizing date and time was always an important issue for developers. In this recipe, you will see how to get different formats for date and time using JSF standard converters. We will display a date/time in different formats and for different locales.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
JSF provides a dedicated converter to accomplish tasks related to date and time, named converterDateTime
. This converter can be customized through a set of attributes listed in the following table:
Attribute name |
Description |
---|---|
|
Specifies whether to display the date, time, or both. |
|
Specifies the formatting style for the date portion of the string. Supported values are |
|
Specifies the formatting style for the time portion of the string. Valid options are |
|
Specifies the time zone for the date (For example, EST). By default GMT will be used. |
|
Specifies the locale to use for displaying the date (For example, Romania - "ro", Germany - "de", England - "en". Overrides the user's current locale. |
|
Represents a date format pattern used to convert a number. |
Now, let's suppose that we have the current date (provided by a java.util.Date
instance). The next code will output this date using the f:converterDateTime
converter and the previously listed attributes:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Standard converters for date and time</title> </h:head> <h:body> <b><h:outputText value="-Formatting the current dateand time-"/></b><br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime type="date" dateStyle="medium"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime type="date" dateStyle="full"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime type="time" timeStyle="full"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime type="date" pattern="dd/mm/yyyy"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime dateStyle="full" pattern="yyyy-mm-dd"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime dateStyle="full" pattern="yyyy.MM.dd 'at' HH:mm:ss z"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime dateStyle="full" pattern="h:mm a"/> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime dateStyle="long" timeZone="EST" type="both" /> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime locale="ro" timeStyle="long" type="both" dateStyle="full" /> </h:outputText> <br /> <h:outputText value="#{datetimeBean.currentdate}"> <f:convertDateTime locale="de" timeStyle="short" type="both" dateStyle="full" /> </h:outputText> </h:body> </html>
The datetimeBean
is listed next:
package datetime; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import java.util.Date; @ManagedBean @SessionScoped public class DatetimeBean { private Date currentdate = new Date(); public Date getCurrentdate(){ return this.currentdate; } public void setCurrentdate(Date currentdate){ this.currentdate=currentdate; } }
The output will be as follows:
-Formatting the current date and time-
Jun 15, 2009
Monday, June 15, 2009
11:14:53 AM GMT
15/14/2009
2009-14-15
2009.06.15 at 11:14:53 GMT
11:14 AM
June 15, 2009 6:14:53 AM
15 iunie 2009 11:14:53 GMT
Montag, 15. Juni 2009 11:14
The idea of this recipe originates in the following JSF concept: a converter with NULL values is bypassed.
The problem occurs when we want to render a special message for a NULL property, instead of returning an empty String
or a NULL value. At first view, a custom converter should fix the problem in an elegant manner, but at second view we notice that the NULL values never get called in the converter, which means that we can't control it before the render phase. This recipe proposes a solution to this problem.
We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
The idea is to have a placebo object—an object that it is not NULL and which is passed to the converter instead of every NULL object. The converter can identify this object by a fixed property, for example its hash code, and every time it gets this object, it will return a custom message to be rendered. For example, if our objects are instances of java.util.Date
, then we can write a placebo class like the following one:
//placebo class for java.util.Date class Placebo extends java.util.Date { @Override public int hashCode() { return 0011001100; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Placebo other = (Placebo) obj; return true; } }
Notice that we have arbitrarily chosen a fixed hash code as 0011001100. This hash code will mark the NULL values in the converter's getAsString
method. However before that, we need to modify the getter method for our property as shown next (this is the entire bean, but we are focused on the getCurrentdate
method):
package nullconv; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import java.util.Date; @ManagedBean @SessionScoped public class NullBean { //valid date //private Date currentdate = new Date(); //null date private Date currentdate = null; //placebo date private Date nulldate = new Placebo(); public Date getCurrentdate() { if (currentdate == null) { return nulldate; } return this.currentdate; } public void setCurrentdate(Date currentdate) { this.currentdate = currentdate; } }
Now, the converter gets a real date, when the currentdate
property is not NULL, and it gets the placebo nulldate
, when the currentdate
property is NULL. Now, we know that the converter gets all the values, including the NULL ones. Next, the converter (getAsString
method) will check the hash code of the objects, to see which one is NULL and which one is not. The following is the source code for this converter:
package nullconv; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.ConverterException; import javax.faces.convert.DateTimeConverter; import javax.faces.convert.FacesConverter; @FacesConverter(value = "nullConverter") public class NullConverter extends DateTimeConverter { @Override public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null) {throw new NullPointerException("context");} if (arg1 == null) {throw new NullPointerException("component");} if (arg2 != null && !(arg2 instanceof java.util.Date)) { throw new ConverterException("Not valid date"); } if (arg2.hashCode() == 0011001100) { return ("Not available!"); } return super.getAsString(arg0, arg1, arg2); } @Override public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null) {throw new NullPointerException("context");} if (arg1 == null) {throw new NullPointerException("component");} return super.getAsObject(arg0, arg1, arg2); } }
Now, the NULL values will be rendered with a "Not
available!"
message!
Every time a NULL date is loaded into the bean it is replaced by the placebo date. This date has the particularity of having a well known hash code. When the placebo object gets into the converter, the getAsString
method checks for this hash code. When it finds a match it returns a custom message instead of the String
representation of the date, because it knows that the received value is actually a NULL one, which should not be rendered verbatim.
JSF custom converters run on the server/client side and can accomplish many specific business needs. Basically, JSF custom converters are created by extending the javax.faces.convert.Converter
interface or by extending a standard converter class. In this recipe, you will see both cases.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
First, let's talk about the converters that implement the Converters
interface. In this case, a converter should implement two methods, as follows:
The getAsObject
method takes the FacesContext
instance, the UI component, and the String
to be converted to a specified object. According to the official documentation, this method:
Converts the specified string value, which is associated with the specified
UIComponent
, into a model data object that is appropriate for being stored during theApply
Request
Values
phase of the request processing lifecycle.
public Object getAsObject(FacesContext context, UIComponent component, java.lang.String value){ … }
The getAsString
method takes the FacesContext
instance, the UI component, and the object to be converted to a String
. According to the official documentation, this method:
Converts the specified model object value, which is associated with the specified
UIComponent
, into a String that is suitable for being included in the response generated during theRender
Response
phase of the request processing lifecycle.
public String getAsString(FacesContext context, UIComponent component, Object value){ … }
This converter logic should use javax.faces.converter.ConverterException
to throw the appropriate exceptions and javax.faces.application.FacesMessage
to generate the corresponding error messages.
For example, the following custom converter will convert a java.util.Date
into a format of type yyyy-MM-dd
. This implementation will extend the Converter
interface, as shown next:
package datetime; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.convert.FacesConverter; @FacesConverter(value = "customDateConverterImpl") public class CustomDateConverterImpl implements Converter { public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } final Date date = (Date) arg2; String DATE_FORMAT = "yyyy-MM-dd"; SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); Calendar c1 = Calendar.getInstance(); // today c1.setTime(date); return sdf.format(c1.getTime()); } public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try { Date today = df.parse(arg2); return today; } catch (ParseException e) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Parser error!", "Cannot parse this date!"); throw new ConverterException(message); } } }
The previous converter can be called from an XHTML page as shown next (notice that we pass to the converter
attribute the value
from the @FacesConverter
annotation; this annotation defines a name for a converter and it is specific to JSF 2.0):
<h:form id="customDateTimeID"> <h:inputText id="dateID" value="#{datetimeBean.currentdate}" converter="customDateConverterImpl"> </h:inputText> <h:message showSummary="true" showDetail="false" for="dateID" style="color: red; text-decoration:overline"/> <br /> <h:commandButton value="Submit" action="selected?faces-redirect=true"/> </h:form>
Now, let's discuss converters that extend existing converters. In this case, we override the getAsString
and getAsObject
methods (mark them with the @Override
annotation) or we can call setter methods from the extended converter. For example, we can extend the DateTimeConverter
and call the setPattern
to obtain the same effect as the previous converter.
package datetime; import java.util.TimeZone; import javax.faces.convert.DateTimeConverter; import javax.faces.convert.FacesConverter; @FacesConverter(value = "customDateConverterExtend") public class CustomDateConverterExtend extends DateTimeConverter { public CustomDateConverterExtend() { super(); setTimeZone(TimeZone.getDefault()); setPattern("yyyy-MM-dd"); } }
A JSF converter is called from two directions. It is called once during the Apply
Request
Values
Phase
and once during the Render
Response
Phase
. In Apply
Request
Values
Phase
the converter is called through getAsObject
method, which is responsible to for converting the user inputs, while in the Render
Response
the converter is called through the getAsString
method, which is responsible to for converting outputs before rendering.
Keep in mind that in JSF 2.0 we don't need a faces-config.xml
descriptor, and converters need not be declared in any XML file. If you are using JSF 1.2 then you have to register converters in the faces-config.xml
document following the syntax listed next:
<converter> <converter-id>CONVERTER_ID</converter-id> <converter-class>CONVERTER_CLASS_NAME</converter-class> </converter>
A common issue regarding JSF converters and the h:selectOneMenu
component can be recreated in a simple scenario. Let's suppose that we are in the following situation: we have a database table that contains a number of rows that define cars. Each row has an Integer
value representing the car number and a string
value representing the car name. Obviously this table is wrapped into a managed bean, as shown next:
package cars; import javax.faces.bean.ManagedBean; @ManagedBean public class CarBean { private Integer carNumber; private String carName; public CarBean() {} public CarBean(Integer carNumber, String carName){ this.carNumber=carNumber; this.carName=carName; } public Integer getCarNumber(){ return this.carNumber; } public void setCarNumber(Integer carNumber){ this.carNumber=carNumber; } public String getCarName(){ return this.carName; } public void setCarName(String carName){ this.carName=carName; } }
Going further, let's have another managed bean that contains a collection of cars (we simulate the table database with a few manual instances), as shown next:
package cars; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.model.SelectItem; @ManagedBean(name = "carsBean") @SessionScoped public class CarsBean { private HashMap<Integer, CarBean> myCars = new HashMap<Integer, CarBean>(); private List<SelectItem> carItems = new LinkedList<SelectItem>(); private CarBean selectedCar; public CarsBean() { CarBean car_1 = new CarBean(1, "Ferrari"); CarBean car_2 = new CarBean(2, "Logan"); CarBean car_3 = new CarBean(3, "Fiat"); CarBean car_4 = new CarBean(4, "Kia"); CarBean car_5 = new CarBean(5, "Skoda"); carItems.add(new SelectItem(car_1, car_1.getCarName())); myCars.put(car_1.getCarNumber(), car_1); carItems.add(new SelectItem(car_2, car_2.getCarName())); myCars.put(car_2.getCarNumber(), car_2); carItems.add(new SelectItem(car_3, car_3.getCarName())); myCars.put(car_3.getCarNumber(), car_3); carItems.add(new SelectItem(car_4, car_4.getCarName())); myCars.put(car_4.getCarNumber(), car_4); carItems.add(new SelectItem(car_5, car_5.getCarName())); myCars.put(car_5.getCarNumber(), car_5); } public CarBean getCar(Integer number) { return (CarBean) myCars.get(number); } public List<SelectItem> getCarItems() { return carItems; } public void setCarItems(List<SelectItem> carItems) { this.carItems = carItems; } public CarBean getSelectedCar() { return this.selectedCar; } public void setSelectedCar(CarBean selectedCar) { this.selectedCar = selectedCar; } }
Now, we can render our car collection using an h:selectOneMenu
component, as shown next:
<h:form id="selectCarFormID"> <h:selectOneMenu id="carsID" value="#{carsBean.selectedCar}"> <f:selectItems value="#{carsBean.carItems}"/> </h:selectOneMenu> <h:commandButton value="Submit" action="selected?faces- redirect=true"/> </h:form>
Well, the car list is rendered ok, as you can see the list and make a selection. However, the problem occurs when we choose a car and we try to populate the selectedCar
property with it. As you see, the selectedCar
is a CarBean
instance, while the submitted information represents an integer (the car number). Therefore, we need to convert this integer to a CarBean
, before it gets rendered, as shown next:
<h:outputText value="Selected car number:"/> <h:outputText value="#{carsBean.selectedCar.carNumber}"/> <br /> <h:outputText value="Selected car name:"/> <h:outputText value="#{carsBean.selectedCar.carName}"/>
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
The solution came from a custom converter. In the getAsString
object, we extract and return the car number, and in the getAsObject
method, the submitted car number is converted into a CarBean
instance, as shown in the following code:
package cars; import javax.el.ValueExpression; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.convert.FacesConverter; @FacesConverter(value = "carConverter") public class CarConverter implements Converter { public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null){throw new NullPointerException("context");} if (arg1 == null){throw new NullPointerException("component");} return ((CarBean)arg2).getCarNumber().toString(); } public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null){throw new NullPointerException("context");} if (arg1 == null){throw new NullPointerException("component");} FacesContext ctx = FacesContext.getCurrentInstance(); ValueExpression vex = ctx.getApplication().getExpressionFactory().createValueExpression(ctx.getELContext(), "#{carsBean}",CarsBean.class); CarsBean cars = (CarsBean)vex.getValue(ctx.getELContext()); CarBean car; try { car = cars.getCar(new Integer (arg2)); } catch( NumberFormatException e ) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unknown value", "This is not a car number!" ); throw new ConverterException( message ); } if( car == null ) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "Unknown value", "The car is unknown!" ); throw new ConverterException( message ); } return car; } }
The mechanism is pretty simple! First, the collection of cars is rendered using a SelectItem
object. Every single car will pass through the converter's getAsString
method and is added to the list. Notice that the getAsString
method extracts and returns the car number for each car.
Second, when a car is selected and submitted, the selected car number arrives into the getAsObject
method. There we search for the corresponding car into our myCars
map. Once the car is found it is returned into the setSelectedCar
method.
You can use the same technique for h:selectManyCheckbox
or
h:selectManyListbox
. For example, in the case of h:selectManyCheckbox
, you will render the list in the following way:
<h:form id="selectCarFormID"> <h:selectManyCheckbox id="carsID" value="#{carsBean.selectedCar}" converter="carConverter"> <f:selectItems value="#{carsBean.carItems}"/> </h:selectManyCheckbox> <h:commandButton value="Submit" action="selected?faces-redirect=true"/> </h:form>
And the selections can be rendered, as shown next:
<h:dataTable value="#{carsBean.selectedCar}" var="item"> <h:column> <f:facet name="header"> <h:outputText value="Car Name:"/> </f:facet> <h:outputText value="#{item.carName}"/> </h:column> </h:dataTable>
JSF standard converter tags allow binding attributes (this is also true for listener and validator tags). This means that developers can bind converter implementations to backing bean properties. The main advantages of using the binding facility are:
The developer can allow the backing bean to instantiate the implementation
The backing bean can programmatically access the implementation's attributes
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
To successfully accomplish a binding task, you can follow the three simple steps listed next (these steps are true for converter, listener, and validator tags):
Nest the converter (listener, validator) tag in the component tag.
Put in the backing bean a property that takes and returns the converter (listener, validator) implementation class.
Reference the backing bean property using a value expression from the
binding
attribute of the converter (listener, validator) tag.
For example, let's bind the standard convertNumber
converter to a backing bean property. The idea is to let the backing bean set the formatting pattern of the user's input. First, you have to register the converter onto the component by nesting the convertNumber
tag within the component tag. Then, you have to reference the property with the binding
attribute of the convertNumber
tag, as shown next:
<h:form id="numberFormID"> <h:inputText id="numberID" value="#{numbersBean.numbery}"> <f:convertNumber binding="#{numbersBean.number}" /> </h:inputText> <h:message showSummary="true" showDetail="false" for="numberID" style="color: red; text-decoration:overline"/> <br /> <h:commandButton value="Submit" action="selected?faces-redirect=true"/> </h:form>
The number
property would be similar to the following code:
package numbers; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.convert.NumberConverter; @ManagedBean @SessionScoped public class NumbersBean { private NumberConverter number; private float numbery; public float getNumbery(){ return this.numbery; } public void setNumbery(float numbery){ this.numbery=numbery; } public NumberConverter getNumber(){ return this.number; } public void setNumber(NumberConverter number){ number.setType("currency"); number.setCurrencySymbol("$"); this.number=number; } }
In our example, the backing bean sets the formatting pattern within the convertNumber
tag, which means that the user's input will be constrained to this pattern. This time the numbers are formatted as currencies, without using specific attributes in the convertNumber
tag. Instead of this we use the binding
attribute to reference the number property, which is a NumberConverter
instance, offering us access to this class's methods.
This recipe will show you how to use one of the standard converters defined in RichFaces. First you have to know that RichFaces 3.3.3 comes with a set of converters that can be found in the following packages:
org.richfaces.convert
org.richfaces.convert.rowkey
org.richfaces.convert.seamtext
org.richfaces.convert.seamtext.tags
org.richfaces.convert.selection
org.richfaces.converter
In this recipe, we will use the org.richfaces.convert.IntegerColorConverter
for converting an RGB color from a RichFaces ColorPicker
component into an integer and vice versa.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library. In addition, we have used RichFaces 3.3.3.BETA1, which provides support for JSF 2.0. You can download this distribution from http://www.jboss.org/richfaces. The RichFaces libraries (including necessary dependencies) are in the book code bundle, under the |JSF_libs
|RichFaces
-
JSF
2.0
folder.
In RichFaces, we can use the converter
attribute or f:converter
tag nested within a UIComponent
. This is pretty similar to the JSF standard utilization of converters. For example, in the following code we have a colorPicker
component and we apply the IntegerColorConverter
converter to the selected color using the converter
attribute. The result of conversion is an integer representation of the color and it is rendered into an outputText
component:
<a4j:form> <h:outputText value="The integer version of the selected color:"/> <h:outputText id="RGBvalue" value="#{colorPickerBean.color}"/> <rich:panel header="RichFaces Color Picker" style="width: 315px"> <rich:colorPicker value="#{colorPickerBean.color}" colorMode="rgb" converter="org.richfaces.IntegerColor"> <a4j:support event="onchange" reRender="RGBvalue"/> </rich:colorPicker> </rich:panel> </a4j:form>
Notice that the IntegerColorConverter
ID is org.richfaces.IntegerColor
. You can find the converters' IDs in the Javadoc of RichFaces.
The ColorPickerBean
can be written in the following way:
package colorpicker; public class ColorPickerBean { private Integer color; /** * @return ColorPickerBean color */ public Integer getColor() { return color; } /** * @param ColorPickerBean color */ public void setColor(Integer color) { this.color = color; } }
In this recipe, we will develop and use a custom converter in RichFaces. This will convert an RGB color, extracted from a colorPicker
, into an integer similar to the result of the java.awt.Color.getRGB
method and vice versa. The result is rendered with an outputText
component.
Notice that an RGB color from a colorPicker
is a String
formatted as rgb(red, green, blue)
.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library. In addition, we have used RichFaces 3.3.3.BETA1, which provides support for JSF 2.0. You can download this distribution from http://www.jboss.org/richfaces. The RichFaces libraries (including necessary dependencies) are in the book code bundle, under the |JSF_libs|RichFaces
-
JSF
2.0
folder
.
A RichFaces custom converter follows the same principles as a JSF custom converter. We can implement the Converter
interface or extend an existing converter class. For example, in this case we will implement the Converter
interface and we will implement the getAsString
and getAsObject
methods. As the code is self-explanatory there is no need for more details:
package colorpicker; import java.awt.Color; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import java.util.StringTokenizer; public class RGBConverter implements Converter { public static final String CONVERTER_ID = "rgbConverter"; public Object getAsObject(FacesContext context, UIComponent component, String value) { if (context == null) { throw new NullPointerException("context"); } if (component == null) { throw new NullPointerException("component"); } String getRGBfromString = value.substring(4, value.length() - 1); StringTokenizer rgbComponents = new StringTokenizer(getRGBfromString,","); int r = Integer.valueOf(rgbComponents.nextToken().trim()); int g = Integer.valueOf(rgbComponents.nextToken().trim()); int b = Integer.valueOf(rgbComponents.nextToken().trim()); Color rgbColor = new Color(r, g, b); int rgbValue = rgbColor.getRGB(); Integer rgbValueInt = new Integer(rgbValue); return rgbValueInt; } public String getAsString(FacesContext context, UIComponent component, Object value) { if (context == null) { throw new NullPointerException("context"); } if (component == null) { throw new NullPointerException("component"); } Color rgbColor = new Color((Integer) value); String stringRGB = "rgb(" + rgbColor.getRed() + "," + rgbColor.getGreen() + "," + rgbColor.getBlue() + ")"; return stringRGB; } }
Calling this converter is a simple task that we have accomplished as shown next:
<a4j:form> <h:outputText value="The integer version of the selected color:"/> <h:outputText id="RGBvalue" value="#{colorPickerBean.color}"/> <rich:panel header="RichFaces Color Picker" style="width: 315px"> <rich:colorPicker value="#{colorPickerBean.color}" colorMode="rgb" converter="rgbConverter"> <a4j:support event="onchange" reRender="RGBvalue"/> </rich:colorPicker> </rich:panel> </a4j:form>
The ColorPickerBean
can be written in the following way:
package colorpicker; public class ColorPickerBean { private Integer color; /** * @return ColorPickerBean color */ public Integer getColor() { return color; } /** * @param ColorPickerBean color */ public void setColor(Integer color) { this.color = color; } }
If you are making a simple attempt to declare an instance variable in a converter, you will notice that you can't store the variable state over time. This may look like a strange behavior, but the truth is that the getAsObject
and getAsString
are called on different instances. This is the simple explanation of why the instance variable doesn't have persistence over these methods calls.
We can fix this using UIComponent
set/getAttribute
or using a session variable instead. In this recipe, we will use a session variable to simulate an instance variable of a converter. For this, let's suppose that we have two numbers, one inserted by the user and one is selected by the user from a selectOneMenu
component. The inserted value is multiplied with the selected value, inside of a custom converter, in the getAsObject
method. In the backing bean we keep the multiplied result. Before the result is rendered, its value is divided by the same value in the getAsString
method. If everything works fine, then we will not notice these operations over the inserted value.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
Storing the selected value in the session is a simple task. First, the backing bean associated to this value is marked with the annotation @SessionScoped
, indicating that the instance of this bean should be stored in session. Second, we pass the selected value in the traditional way (this code is from the multiply.xhtml
page of the application), as shown next:
<h:form id="MultiplyForm"> <h:outputText value="Select the multiply factor:" /> <h:selectOneMenu id="factorID" value="#{factorBean.selectedFactor}"> <f:selectItems value="#{factorBean.factors}"/> </h:selectOneMenu> <h:commandButton id="submit" action="number?faces-redirect=true" value="Submit"/> </h:form>
The selectedFactor
property belongs to the next backing bean:
package multiply; import java.util.LinkedList; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.model.SelectItem; @ManagedBean @SessionScoped public class FactorBean { private List<SelectItem> factors = new LinkedList<SelectItem>(); private double selectedFactor; public FactorBean(){ factors.add(new SelectItem("1.0", "1.0")); factors.add(new SelectItem("2.0", "2.0")); factors.add(new SelectItem("3.0", "3.0")); factors.add(new SelectItem("4.0", "4.0")); factors.add(new SelectItem("5.0", "5.0")); } public List<SelectItem> getFactors() { return factors; } public void setFactors(List<SelectItem> factors) { this.factors = factors; } public double getSelectedFactor() { return this.selectedFactor; } public void setSelectedFactor(double selectedFactor) { this.selectedFactor = selectedFactor; } }
Now, the multiplication factor is on session and we can request the user to insert a value to be multiplied by this factor (number.xhtml
), as shown next:
<h:form id="NumberForm"> <h:outputText value="Insert the value to be multiplied:"/> <h:inputText id="valueID" required="true" value="#{multiplyBean.value}" converter="multiplyConverter" /> <h:message showSummary="true" showDetail="false" for="valueID" style="color: red; text-decoration:overline"/> <br /> <h:commandButton id="submit" action="number?faces- redirect=true" value="Submit"/> </h:form>
The value
is stored in the MultiplyBean
, as shown next:
package multiply; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; @ManagedBean @SessionScoped public class MultiplyBean { private double value = 0.0d; public double getValue() { return this.value; } public void setValue(double value) { this.value = value; } }
As you can see the operations are taking place in a converter. Now, the converter has access to the multiplication factor in a very easy approach, as shown next:
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } FacesContext ctx = FacesContext.getCurrentInstance(); ValueExpression vex = ctx.getApplication().getExpressionFactory(). createValueExpression(ctx.getELContext(), "#{factorBean}", FactorBean.class); FactorBean c = (FactorBean) vex.getValue(ctx.getELContext()); try { Double dividedVal = (Double) arg2 / c.getSelectedFactor(); return dividedVal.toString(); } catch (Exception e) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error!", "Cannot accomplish this operation (DIVIDE) !"); throw new ConverterException(message); } } public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) { if (arg0 == null) { throw new NullPointerException("context"); } if (arg1 == null) { throw new NullPointerException("component"); } FacesContext ctx = FacesContext.getCurrentInstance(); ValueExpression vex = ctx.getApplication().getExpressionFactory(). createValueExpression(ctx.getELContext(), "#{factorBean}", FactorBean.class); FactorBean c = (FactorBean) vex.getValue(ctx.getELContext()); try { Double val = new Double(arg2); Double multiplyVal = val * c.getSelectedFactor(); return multiplyVal; } catch (NumberFormatException e) { FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error!","Connot accomplish this operation (MULTIPLY)!"); throw new ConverterException(message); } }
A great facility of Apache MyFaces Trinidad is that it supports client-side versions of JSF converters and validators. This means that errors are detected on the client machine, and the server is not involved. In this recipe, we will create such a converter for converting a number into an IP address. Our restrictions will be as follows:
The IP address should have exactly 12 digits
The IP will always have a pattern of 000.000.000.000
The IP can be supplied like 000000000000 or 000.000.000.000
The idea of Apache Trinidad client conversion is that it works on the client in a very similar way to how it works on the server, but in this case the language on the client is JavaScript instead of Java. By convention, JavaScript objects are prefixed in Trindad with the tr
prefix, in order to avoid name collisions. There are JavaScript converter objects that support the methods getAsString
and getAsObject
. A TrConverter
can throw a TrConverterException
.
Let's see what are the steps that should be accomplished to create such a converter.
We developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library. In addition, we have used Apache Trinidad 2.0.0, which provides support for JSF 2.0. You can download this distribution from http://myfaces.apache.org/trinidad/index.html. The Apache Trinidad libraries (including necessary dependencies) are in the book code bundle, under the |JSF_libs|Apache
Trinidad
-
JSF
2.0
folder.
We will develop a complete application, including the client-side converter by following the four listed steps:
Develop a JavaScript version of the converter. Before doing this you have to be aware of the Trindad API, which is listed next (this can also be found on the Trinidad website http://myfaces.apache.org/trinidad/index.html):
/** * Converter "interface" similar to javax.faces.convert.Converter, * except that all relevant information must be passed to the constructor * as the context and component are not passed to the getAsString or getAsObject method * */ function TrConverter() { } /** * Convert the specified model object value, into a String for display * * @param value Model object value to be converted * @param label label to identify the editableValueHolder to the user * * @return the value as a string or undefined in case of no converter mechanism is * available (see TrNumberConverter). */ TrConverter.prototype.getAsString = function(value, label){} /** * Convert the specified string value into a model data object * which can be passed to validators * * @param value String value to be converted * @param label label to identify the editableValueHolder to the user * * @return the converted value or undefined in case of no converter mechanism is * available (see TrNumberConverter). */ TrConverter.prototype.getAsObject = function(value, label){}
TrConverters
can throw aTrConverterException
, which should contain aTrFacesMessage
. Here is the signature forTrFacesMessage
: /** * Message similar to javax.faces.application.FacesMessage * * @param summary - Localized summary message text * @param detail - Localized detail message text * @param severity - An optional severity for this message. Use constants * SEVERITY_INFO, SEVERITY_WARN, SEVERITY_ERROR, and * SEVERITY_FATAL from the FacesMessage class. Default is * SEVERITY_INFO */ function TrFacesMessage( summary, detail, severity )The signature for the
TrConverterException
is as follows:/** * TrConverterException is an exception thrown by the getAsObject() or getAsString() * method of a Converter, to indicate that the requested conversion cannot be performed. * * @param facesMessage the TrFacesMessage associated with this exception * @param summary Localized summary message text, used to create only if facesMessage is null * @param detail Localized detail message text, used only if facesMessage is null */ function TrConverterException( facesMessage, summary, detail )
Another useful API that can be used to format messages is shown next:
/** * TrFastMessageFormatUtils is a greatly reduced version * of the java.text.MessageFormat class, but delivered as a utility. * <p> * The only syntax supported by this class is simple index-based * replacement, namely: * <pre> * some{1}text{0}here{2}andthere * </pre> * as well as escaping using single quotes. Like MessageFormat, * a single quote must be represented using two consecutive single * quotes, but the contents of any text between single quotes * will not be interpreted. So, the following pattern could * be used to include a left bracket: * <pre> * some'{'text{0} * </pre> */ function TrFastMessageFormatUtils() /** * Formats the given array of strings based on the initial * pattern. * @param {String} String to format * @param {any...:undefined} Varargs objects to substitute for positional parameters. * Each parameter will be converted to a String and substituted into the format. */ TrFastMessageFormatUtils.format = function( formatString, // error format string with embedded indexes to be replaced parameters // {any...:undefined} Varargs objects to substitute for positional parameters. )
Based on this API, we have developed the JavaScript version of our IP converter as follows (
IPConverter.js
):function ipGetAsString(value, label) { return value.substring(0,3) + '.' + value.substring(3,6) + '.' + value.substring(6,9) + '.' + value.substring(9,12); } function ipGetAsObject(value, label) { if (!value)return null; var len=value.length; var messageKey = IPConverter.NOT; if (len < 12 ) messageKey = IPConverter.SHORT; else if (len > 15) messageKey = IPConverter.LONG; else if ((len == 12)||(len == 15)) { return value; } if (messageKey!=null && this._messages!=null) { // format the detail error string var detail = this._messages[messageKey]; if (detail != null) { detail = TrFastMessageFormatUtils.format(detail, label, value); } var facesMessage = new TrFacesMessage( this._messages[IPConverter.SUMMARY], detail, TrFacesMessage.SEVERITY_ERROR) throw new TrConverterException(facesMessage); } return null; } function IPConverter(messages) { this._messages = messages; } IPConverter.prototype = new TrConverter(); IPConverter.prototype.getAsString = ipGetAsString; IPConverter.prototype.getAsObject = ipGetAsObject; IPConverter.SUMMARY = 'SUM'; IPConverter.SHORT = 'S'; IPConverter.LONG = 'L'; IPConverter.NOT = 'N';
Next we bind the JavaScript converter with the Java converter. For this we have to implement the
org.apache.myfaces.trinidad.converter.ClientConverter
interface. The methods of this interface are:getClientLibrarySource()
: returns a library that includes an implementation of the JavaScriptConverter
object.getClientConversion()
: returns a JavaScript constructor, which will be used to instantiate an instance of the converter.getClientScript()
: can be used to write out inline JavaScript.getClientImportNames()
: is used to import the built-in scripts provided by Apache MyFaces Trinidad.
Now, the Java version of our
IPConverter
looks like this (notice the constructor used to instantiate the JavaScript version):package converterJSF; import java.util.Collection; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import org.apache.myfaces.trinidad.convert.ClientConverter; import org.apache.myfaces.trinidad.util.LabeledFacesMessage; public class IPConverter implements Converter, ClientConverter { private static final String _SHORT_ERROR_TEXT = "The value is to short for an IP of type 000.000.000.000!"; private static final String _LONG_ERROR_TEXT = "The value is to long for an IP of type 000.000.000.000!"; private static final String _INVALID_ERROR_TEXT = "The value is not a valid IP number"; public static final String CONVERTER_ID = "converterJSF.IP"; //getAsObject public Object getAsObject(FacesContext context, UIComponent component, String value) { if ( value == null || value.trim().length() == 0) return null; String ipValue = value.trim(); int length = ipValue.length(); if ( length < 12 ) { throw new ConverterException(_getMessage(component, _SHORT_ERROR_TEXT)); } if ( length > 15 ) { throw new ConverterException(_getMessage(component, _LONG_ERROR_TEXT)); } //12 if (length == 12) { try { return Long.valueOf(ipValue); } catch(NumberFormatException e) { throw new ConverterException(_getMessage(component, _INVALID_ERROR_TEXT)); } } //15 if (length == 15) { try { String extractIP = ipValue.substring(0,3) + ipValue.substring(4,7) + ipValue.substring(8,11) + ipValue.substring(12,15); return Long.valueOf(extractIP); } catch(NumberFormatException e) { throw new ConverterException(_getMessage(component, _INVALID_ERROR_TEXT)); } } throw new ConverterException(_getMessage(component, _INVALID_ERROR_TEXT)); } //getAsString public String getAsString(FacesContext context, UIComponent component, Object value) { if ( value == null || !(value instanceof Long)) return null; Long longValue=(Long)value; String valueString = longValue.toString(); String ip="000.000.000.000"; if (valueString.length() == 12) { ip = valueString.substring(0,3) + '.' + valueString.substring(3,6) + '.' + valueString.substring(6,9) + '.' + valueString.substring(9,12); } return ip; } //implement the ClientConverter's getClientImportNames public Collection<String> getClientImportNames() { return null; } //implement the ClientConverter's getClientLibrarySource public String getClientLibrarySource( FacesContext context) { return context.getExternalContext().getRequestContextPath() + "/jsLibs/IPConverter.js"; } //implement the ClientConverter's getClientConversion public String getClientConversion(FacesContext context, UIComponent component) { return ("new IPConverter({" + "SUM:'Invalid IP.'," + "S:'Value \"{1}\" is too short for an 000.000.000.000 IP.'," + "L:'Value \"{1}\" is too long for an 000.000.000.000 IP.'," + "N:'Value \"{1}\" is not a valid IP of type 000.000.000.000 .'})" ); } //implement the ClientConverter's getClientScript public String getClientScript(FacesContext context, UIComponent component) { return null; } private LabeledFacesMessage _getMessage(UIComponent component, String text) { // Using the LabeledFacesMessage allows the <tr:messages> component to // properly prepend the label as a link. LabeledFacesMessage lfm = new LabeledFacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", text); if (component != null) { Object label = null; label = component.getAttributes().get("label"); if (label == null) label = component.getValueExpression("label"); if (label != null) lfm.setLabel(label); } return lfm; } }
Next we need to create a tag for this converter. For example, let's name this tag
converterIP
:<tag> <name>convertIP</name> <tag-class>converterJSF.IPConverterTag</tag-class> <body-content>empty</body-content> <description> The convertIP tag converts a number to/from an IP address. </description> </tag>
The
IPConverterTag
is as follows:package converterJSF; import javax.faces.application.Application; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.webapp.ConverterELTag; import javax.servlet.jsp.JspException; public class IPConverterTag extends ConverterELTag { public IPConverterTag() { } @Override protected Converter createConverter() throws JspException { Application app = FacesContext.getCurrentInstance().getApplication(); IPConverter converter = (IPConverter)app.createConverter(IPConverter.CONVERTER_ID); return converter; } }
Call the converter from a JSP page, as shown next:
<tr:inputText value="#{ipBean.ip}" label="Insert a number of type 000000000000/000.000.000.000:"> <trip:convertIP /> </tr:inputText>
The submitted values are first evaluated by the JavaScript converter. As this converter runs on the client side, it can return potential errors almost immediately. If the submitted values successfully pass the JavaScript converter, then they arrive into the Java converter (on the server side) and after that in the backing bean. Reversing the process, the result values pass first through the Java converter and after that, through the JavaScript converter.
Speaking about another release of Apache MyFaces you should know that Apache MyFaces Tomahawk project contains several custom objects that do not implement UIComponent
. Some of these include objects that implement the Converter
interface.