Client-side converters with MyFaces Trinidad

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.

Getting ready

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.

How to do it...

We will develop a complete application, including the client-side converter by following the four listed steps:

  1. 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 a TrConverterException, which should contain a TrFacesMessage. Here is the signature for TrFacesMessage:
    /**
    * 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';
    
  2. 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 JavaScript Converter 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;
    }
    }
    
  3. 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;
    }
    }
    
  4. 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>
    

How it works...

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.

There's more...

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.

See also

The code bundled with this book contains a complete example of this recipe. The project can be opened with NetBeans 6.8 and is named: Client_side_converters_with_Apache_Trinidad.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset