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.

1
2
3
4
5
6
7
8
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;
}
1
2
3
4
5
<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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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:

1
2
3
4
5
6
7
<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>

Comments