1

the problem

I'm trying to work with form validation in jsf 1.2. I have a form with rows of two input text fields.

I enter two rows of data, with one bad cell, like this:

| :) | :/ |
| :) | :) |

The validator is called once for each row, but checks both fields. Each UIInput that fails validation is added to a list of failed UIComponents. The method for the submit action finally gets to run. First it restores any saved styles. Then it loops over the failed UIComponents. Inside the loop, it saves the current style, then sets the style to "badInput".

But when the page loads, both end-cells have the "badInput" style:

| :) | :/ |
| :) | :/ |

my code

This is my validator, a method on the managed bean that handles this page:

public void validateTime(FacesContext context, UIComponent component, Object value)
{
  UIInput out = (UIInput) component.findComponent("out");

  for (UIComponent uic : Arrays.asList(component, out))
  {
    String time = (String) ((UIInput)uic).getSubmittedValue();

    if (!StringToTime.isValid(time))
    {
      // mark that we found invalid times
      validTimes = false;

      // save the failed component
      // the click method will change the style during the render phase
      failedUics.add(uic);  // List<UIComponent>
      badComps.put(uic.getClientId(context), uic);  // Map<String, UIComponent>
    }
  }
}

And here's the table of input fields:

<h:dataTable binding="#{entryHandler.tableAttends}" value="#{entryHandler.attends}" var="range">
  <h:column>
    <div>
      <h:outputLabel>
        <h:outputText value="In: " />
        <h:inputText value="#{range.start}" id="in" validator="#{entryHandler.validateTime}" />
      </h:outputLabel>

      <h:outputLabel>
        <h:outputText value="Out: " />
        <h:inputText value="#{range.end}" id="out" />
      </h:outputLabel>

      <h:commandLink action="#{entryHandler.delAttend}" value="X" styleClass="removeTime" />
    </div>
  </h:column>
</h:dataTable>

I've tried applying the bad input style these two ways:

for (UIComponent target : failedUics)
{
  log.debug("target client id: " + target.getClientId(context));

  Map<String, Object> attr = target.getAttributes();

  // save the style before changing it
  String style = (String) attr.get("styleClass");
  originalStyle.put(target.getClientId(context), style);

  // add the badInput css class
  if (style == null) style = "";
  attr.put("styleClass", "badInput " + style);
}
failedUics = new ArrayList<UIComponent>();

and the second:

UIComponent root = context.getViewRoot();
for (String clientId : badComps.keySet())
{
  root.invokeOnComponent(context, clientId, new BadInputCallback(originalStyle));
}
badComps = new HashMap<String, UIComponent>();

where this is the callback function:

private static class BadInputCallback implements ContextCallback
{
  private final Map<String, String> originalStyle;

  public BadInputCallback(Map<String, String> originalStyle)
  {
    this.originalStyle = originalStyle;
  }

  @Override
  public void invokeContextCallback(FacesContext context, UIComponent target)
  {
    Map<String, Object> attr = uic.getAttributes();

    // save the style before changing it
    String style = (String) attr.get("styleClass");
    originalStyle.put(target.getClientId(context), style);

    // add the badInput css class
    if (style == null) style = "";
    attr.put("styleClass", "badInput " + style);
  }
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
djeikyb
  • 4,470
  • 3
  • 35
  • 42

1 Answers1

1

Your concrete problem is caused because there is physically only one input component in the component tree, whose state changes whenever the parent UIData component iterates over every item of the model. When you want to set the styleClass dynamically, you basically need to let it depend on the currently iterated item, like so:

<h:dataTable ... var="item">
    <h:column>
        <h:inputText ... styleClass="#{item.bad ? 'badInput' : ''}" />
    </h:column>
</h:dataTable>

Or when you're already on JSF 2.x, then check UIInput#isValid() instead whereby the UIInput is referenced via implicit EL variable #{component}, like so:

<h:dataTable ... var="item">
    <h:column>
        <h:inputText ... styleClass="#{component.valid ? '' : 'badInput'}" />
    </h:column>
</h:dataTable>

This problem is already identified before and taken into account in among others the JSF 1.2 targeted SetFocusListener phase listener on The BalusC Code and the JSF 2.0 <o:highlight> component of JSF utility library OmniFaces.

Both have under the covers the same approach: they collect the client IDs of all invalidated input components and pass them as an array to JavaScript code which in turn sets the desired class name via HTML DOM.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for solving both my practical problem and my wrong-thinking. I was definitely expecting the `UIData` loop to create many `UIComponents`. For anyone else passing by, [this answer about taghandlers](http://stackoverflow.com/a/3343681/659715) also helped my thinking. – djeikyb Aug 08 '13 at 20:52