Control rendering of UI elements based on validation results
In this post I describe how I tackled a problem that I faced quite often in the past. I need control over rendering a part of the UI based on validation results. E.g. when there is a validation message available for a certain input component then render a tooltip (containing a validation message) for the component.
JSF components offer a boolean rendered attribute that allows controling the rendering process. For complex decisions that means that a method on a managed bean would calculate the resulting state.
Exactly that was the solution that I was starting with for the problem described above.
public Map<String, Boolean> getHasMessages() {
HashMap<String, Boolean> flags = new HashMap<String, Boolean>(1);
Iterator<String> messages = FacesContext.getCurrentInstance().getClientIdsWithMessages();
while (messages.hasNext()) {
flags.put(messages.next(), Boolean.TRUE);
}
return flags;
}
<h:inputText label="Amount" id="amount" >
<p:tooltip for="amount" showEvent="focus" hideEvent="blur" rendered="#{mbean.hasMessages['amount']}">
<h:message for="amount" />
</p:tooltip>
</h:inputText>
So I had a simple solution working fairly quickly. But let’s face it: It’s a clumsy solution that is hard to reuse and breaks when the id of the ui container around the input is changed.
To improve that, I decided that this requires a component that would be simple to reuse. As I’m using Facelets for the view I updated my knowledge of their support for custom components.
public class IfMessagesTagHandler extends TagHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(IfMessagesTagHandler.class);
private static final String FOR_ATTRIBUTE_NAME = "for";
private final TagAttribute forAttribute;
public IfMessagesTagHandler(TagConfig config) {
super(config);
forAttribute = getAttribute(FOR_ATTRIBUTE_NAME);
}
public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
ELException {
if (forAttribute != null) {
UIComponent forComponent = getForComponent(ctx.getFacesContext(), forAttribute.getValue(ctx), parent);
if (forComponent != null) {
if (ctx.getFacesContext().getMessages(forComponent.getClientId(ctx.getFacesContext())).hasNext()) {
this.nextHandler.apply(ctx, parent);
}
} else {
LOGGER.warn("No component {} found.", forAttribute.getValue(ctx));
}
} else if (parent != null) {
if (ctx.getFacesContext().getMessages(parent.getClientId(ctx.getFacesContext())).hasNext()) {
this.nextHandler.apply(ctx, parent);
}
}
}
}
The above implementation accepts the optional attribute for that refers to a component id for which it tests if messages are available. In case the for attribute was not passed the tag would assume that the component to test for messages is the enclosing parent tag.
To register the tag handler a facelets tag library descriptor in META-INF is required.
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://martinahrer.at/jsf/facelets/blueprint</namespace> <tag> <tag-name>ifMessages</tag-name> <handler-class>at...view.facelets.IfMessagesTagHandler</handler-class> </tag> </facelet-taglib>
A client would use that tag as:
<h:inputText label="Amount" id="amount" value="#{expenseSlipFormController.entity.amount}" required="true">
<b:ifMessages >
<p:tooltip for="amount" showEvent="focus" hideEvent="blur" style="myStyle">
<h:message for="amount" />
</p:tooltip>
</b:ifMessages>
</h:inputText>

3 Comments
Dominik Dorn - 2009/10/21
Nice one martin, although I would probably tried it with extending the h:message tag of the jsf-api/-ri.
Martin Ahrer - 2009/10/21
@Dominik Dorn
I don’t think that is a smart idea to do. h:message is just reponsible for rendering validation messages. Extending from h:messages would imply a hard dependency on that implementation. If anything changes with h:messages you eventually would have to modify your extension.
Composition is a better approach. That allows to compose any content from any set of tags (including h:messages). So you get more value!
Wagner Borges - 2010/07/29
@Dominik Dorn, really, I believe also that this make a large coupling. Is very code to write to a simple result, and we can also improve the format of just changing the CSS h: messages.
[]s
2 Trackbacks