Search code examples
jsfjsf-2convertersrenderer

Converter does not seem to be used when component has a renderer


As known, we can easily write our custom conveter and register it to the component as follows:

<f:converter converterId="cId" />

or

<f:converter binding="#{mybean}" />

depneds on our needs. But now consider the source code of UIInput that's responsible for the exact conversion (comments ommited):

 protected Object getConvertedValue(FacesContext context,
                                       Object newSubmittedValue) throws ConverterException {
        Renderer renderer = getRenderer(context);
        Object newValue;

        if (renderer != null) {
            newValue = renderer.getConvertedValue(context, this,
                 newSubmittedValue); // <----------- 1
        } else if (newSubmittedValue instanceof String) {
            Converter converter = getConverterWithType(context);
            if (converter != null) {
                newValue = converter.getAsObject(context, this,
                     (String) newSubmittedValue);// <----------- 2
            } else {
                newValue = newSubmittedValue;
            }
        } else {
            newValue = newSubmittedValue;
        }
        return newValue;
    }

So, it's clear that we make a choise about what converter we should use. Moreover, as far as I understand, if a component has a separated Renderer we can't apply our custom converter specified as a tag in a facelet (because of the if(renderer != null){...} else if(newSubmittedValue instanceof String){...}). For instance <h:inputText /> the component class is UIInput, and also it has a custom com.sun.faces.renderkit.html_basic.TextRenderer, therfore getRenderer(context) returns something that's not null. So, we should skip the convertion specified by our converter tag even if we define it as

<h:inputText  value="#{myBean.prop}"/>
    <f:converter inding="#{bean}" />  
</h:inputText>

The inputText has a custom renderer, so the converter shouldn't do anything specified here:

 Converter converter = getConverterWithType(context);
 if (converter != null) {
     newValue = converter.getAsObject(context, this,
           (String) newSubmittedValue);// <----------- 2
 } else {
       newValue = newSubmittedValue;
 }

Couldn't you explain it? How does the conversion actually work and what's the difference between Renderer's converter and our custom converter


Solution

  • Actually, the renderer does the same. See also javadoc of Renderer#getConvertedValue().

    Attempt to convert previously stored state information into an object of the type required for this component (optionally using the registered Converter for this component, if there is one). If conversion is successful, the new value should be returned from this method; if not, a ConverterException should be thrown.

    In case of <h:inputText> in Mojarra, source code can be found in HtmlBasicInputRenderer.

    This design allows the renderer to take full control over the conversion step and if necessary manipulate this step. Note that it's possible to declaratively (via faces-config.xml) override the renderer of a (standard) component without changing the component itself in the view. Also note that the term "custom renderer" in your question is not entirely right. It's just the standard renderer. We talk about a "custom renderer" only when the web application provides its own, overriding the standard one.