In my previous post I showed how simple it is to control rendering of UI elements based on validation results.

Today I want to put this idea a little bit further and add another convenicent facelets tag that will allow to assign a style class to any UI element based on validation results. In order to create an appealing form that clearly indicates the position where validation fails very often you see that input elements change their visual appearance based on the validation state. For example any invalid input element is shown with red background. With no component you end up with tedious (or complex) EL expressions that are assigned to the styleClass attribute.

To make that concept reusable and simple to apply I show how to create a facelets tag setStyleClassIfMessages that evaluates the faces messages available for a specific component, looks for the message with highest severity and uses the severity to apply a CSS style class to any component.

1
2
3
4
5
6
<h:outputLabel for="amount" value="Amount" />
<h:inputText label="Amount" id="amount" value="#{mbean.amount}" required="true">
  <f:convertNumber maxFractionDigits="2" minFractionDigits="2" currencySymbol="€" />
  <b:setStyleClassIfMessages for="amount"
      infoClass="validationInfo" warnClass="validationWarn"     errorClass="validationError" fatalClass="validationFatal" />
</h:inputText>

The tag implementation is quite straightforward, I have stripped some obvious details from the code.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class SetStyleClassIfMessagesTagHandler extends MessagesTagHandler {

  protected final TagAttribute[] classAttributes = new TagAttribute[5];

  public SetStyleClassIfMessagesTagHandler(TagConfig config) {
      super(config);
      classAttributes[0] = getAttribute("passedClass");
      classAttributes[FacesMessage.SEVERITY_INFO.getOrdinal() + 1] = getAttribute("infoClass");
      classAttributes[FacesMessage.SEVERITY_WARN.getOrdinal() + 1] = getAttribute("warnClass");
      classAttributes[FacesMessage.SEVERITY_ERROR.getOrdinal() + 1] = getAttribute("errorClass");
      classAttributes[FacesMessage.SEVERITY_FATAL.getOrdinal() + 1] = getAttribute("fatalClass");
  }

  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) {
              setMaximumSeverityStyleClass(ctx, forComponent);
          } else {
              LOGGER.warn("No component {} found.", forAttribute.getValue(ctx));
          }
      } else if (parent != null) {
          setMaximumSeverityStyleClass(ctx, parent);
      }
  }

  private void setMaximumSeverityStyleClass(FaceletContext ctx, UIComponent forComponent) {
      FacesMessage message = findMostSevereMessage(ctx.getFacesContext(), forComponent);
      if (PropertyUtils.isWriteable(forComponent, STYLE_CLASS_PROPERTY_NAME)) {
          TagAttribute messageStyleClass = message == null ? classAttributes[0] : classAttributes[message
                  .getSeverity().getOrdinal() + 1];
          try {
              StringBuilder newStyleClass = new StringBuilder(messageStyleClass == null ? "" : messageStyleClass
                      .getValue(ctx));
              // read style and remove style class from previous post-back,
              // then set new style class
              String oldStyleClass = (String) PropertyUtils
                      .getSimpleProperty(forComponent, STYLE_CLASS_PROPERTY_NAME);

              Set<String> classNames = new HashSet<String>();
              for (TagAttribute classAttribute : classAttributes) {
                  if (classAttribute != null) {
                      classNames.add(classAttribute.getValue());
                  }
              }
              StringTokenizer tokenizer = new StringTokenizer(oldStyleClass == null ? "" : oldStyleClass, " ");
              while (tokenizer.hasMoreElements()) {
                  String styleClass = tokenizer.nextToken();
                  if (classNames.contains(styleClass)) {
                      continue;
                  }
                  newStyleClass.append(' ').append(styleClass);
              }
              PropertyUtils.setSimpleProperty(forComponent, STYLE_CLASS_PROPERTY_NAME, newStyleClass.toString());
          } catch (Exception e) {
              throw new FaceletException(e);
          }
      }
  }
}

Comments