In the process of converting an application from JSF 1.2 to JSF 2.1, I came across the following structure:
<h:inputText value="#{myBacker.myValue}"> <c:if test="#{not empty myBacker}"> <f:attribute name="binding" value="#{myBacker.myInput}"/> </c:if> </h:inputText> |
The intent of the code is clear: if the backing bean is defined, apply the binding. While it worked in JSF 1.2 with Facelets, in JSF 2.1, using the built-in implementation of Facelets, the code caused a javax.el.PropertyNotFoundException to be thrown during the Restore View phase.
So a workaround needed to be found. One possibility was to rewrite the include file to be two separate include files: one that requires the binding and one that omits it. But then we have a bunch of repeated code and a lot of callers to modify. Another possibility was to ensure that the backing bean always existed, but that requires a lot of unnecessary beans and a way to handle cases when the include was invoked multiple times.
In the end I decided to write a Facelets TagHandler that would conditionally bind the component depending on whether the binding was well-defined. Note that I am taking advantage of the fact that we are only using the binding to set the component on the backing bean; if the backing bean was providing the component to the view something else would need to be done.
public class BindingHandler extends TagHandler { private final TagAttribute parentBinding; public BindingHandler(final TagConfig config) { super(config); parentBinding = getRequiredAttribute("parentBinding"); } @Override public void apply(final FaceletContext ctx, final UIComponent parent) throws IOException { final ValueExpression bindingExpression = parentBinding.getValueExpression(ctx, UIComponent.class); try { bindingExpression.setValue(ctx, parent); parent.setValueExpression("binding", bindingExpression); } catch (final PropertyNotFoundException ignored) { } } } |
For the actual production code, I included some tests to try to keep the PropertyVetoException from being triggered. That is, I tried to suss out whether setValue would throw a PropertyVetoException before invoking it. I figured whatever tests I came up with would be more performant that depending on an exception being thrown.
All that was left was to configure the TagHandler in my taglib.xml and using it on the page. The configuration is straightforward:
<tag> <tag-name>binding</tag-name> <handler-class>org.decampo.labnotes.BindingHandler</handler-class> </tag> |
and so is the usage:
<h:inputText value="#{myBacker.myValue}"> <p:binding parentBinding="#{myBacker.myValue}" /> </h:inputText> |
Now I get conditional binding and I don’t have to change any clients of the include.