Setting a validator attribute using EL based on ui:repeat var
Asked Answered
A

1

6

I am looking for a little bit of guidance today with the issue I am running into.

What I am trying to accomplish is build a page on the fly with validation and all. The end result is to allow the user to configure the fields on the page through administrative functions. Below is a copy of the code that I am using as the test page where I loop through the "Configured" fields and write out the fields using the defined criteria.

<ui:repeat var="field" value="#{eventMgmt.eventFields}" varStatus="status">
  <div class="formLabel">
    <h:outputLabel value="#{field.customName}:"></h:outputLabel>
  </div>
  <div class="formInput">
    <h:inputText id="inputField" style="width:# {field.fieldSize gt 0 ? field.fieldSize : 140}px;">
      <f:validateRegex  disabled="#{empty field.validationPattern}" pattern="#{field.validationPattern}"></f:validateRegex>
    </h:inputText>
    <h:message for="inputField" showDetail="true" errorClass="errorText"></h:message>
  </div>
</ui:repeat>

After the page renders and I attempt to submit any values for the field, I get the following message "Regex pattern must be set to non-empty value." which obviously means that the expression is not populated. What makes it interesting to me is that fields that do not have an expression for them will be disabled when the EL is evaluated. I can also take the same code #{field.validationPattern} and put it in the page and the correct value will be written on the page.

So, my question(s) are: 1. Is this possible? 2. At what point does the JSF container look at binding the Pattern for the validate regex? 3. What am I doing wrong or What is the right way to do this ??

I am running Tomcat 7.0.22, Mojarra 2.1.5, and Eclipse as my IDE.

Anthropomorphic answered 8/2, 2012 at 14:6 Comment(0)
T
15

This is caused by using <f:validateRegex> whose properties depend on the currently iterated item of <ui:repeat>.

The <f:xxx> tags are tag handlers, not UI components. Tag handlers are parsed and evaluated when the UI component tree is to be built during view build time. All EL is evaluated during the view build time. The <h:xxx> tags and some <ui:xxx> tags like <ui:repeat> are UI components. All their EL is evaluated during view render time.

So in your case, when <f:validateRegex> get parsed and executed, the #{field} is not available in the current EL scope and thus evaluates as null.

There are several ways to get it to work.

  • Move the validator to the class representing Field and reference it as follows:

    <h:inputText ... validator="#{field.validate}" />
    

    with in Field class wherein you manually instantiate it:

    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        if (pattern != null) {
            RegexValidator regexValidator = new RegexValidator();
            regexValidator.setPattern(pattern);
            regexValidator.validate(context, component, value);
        }
    }
    

  • Or, wrap #{eventMgmt.eventFields} in a ListDataModel<Field> and bind the validator to the #{eventMgmt} bean. This way you will be able to set the validator's properties based on the row data:

    <h:inputText ... validator="#{eventMgmt.validate}" />
    

    with in the backing bean class behind #{eventMgmt}:

    private DataModel<Field> model;
    private RegexValidator regexValidator;
    
    @PostConstruct
    public void init() {
        regexValidator = new RegexValidator();
    }
    
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String pattern = model.getRowData().getPattern();
    
        if (pattern != null) {
            regexValidator.setPattern(pattern);
            regexValidator.validate(context, component, value);
        }
    }
    

  • Or, create a custom Validator which extends RegexValidator and set the pattern as a custom attribute of the component by <f:attribute> and let the Validator intercept on that. The <f:attribute> basically adds a new attribute to the component with an unevaluated ValueExpression, so it will be re-evaluated when you call it. E.g.:

    <h:inputText ...>
        <f:validator validatorId="extendedRegexValidator" />
        <f:attribute name="pattern" value="#{field.pattern}" />
    </h:inputText>
    

    with

    @FacesValidator("extendedRegexValidator")
    public class ExtendedRegexValidator extends RegexValidator {
    
        @Override
        public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
            String pattern = (String) component.getAttributes().get("pattern");
    
            if (pattern != null) {
                setPattern(pattern);
                super.validate(context, component, value);
            }
        }
    
    }
    

  • Or, if you happen to use JSF utility library OmniFaces, use its <o:validator>. E.g.

    <h:inputText ...>
        <o:validator validatorId="javax.faces.RegularExpression" pattern="#{field.pattern}" />
    </h:inputText>
    

    Yes, that's all. The <o:validator> will make sure that all attributes are evaluated as deferred expressions instead of immediate expressions.

See also:

Twoway answered 8/2, 2012 at 14:36 Comment(3)
You totally ROCK!! Thanks you very much for the long healthy answers. If this were on your plate which approach would you choose as the best one. I am tied between #1 and #3 as the path for me.Anthropomorphic
You're welcome. I'd opt for #1. In this kind of "dynamic form" approach, it should be the responsibility of the Field class itself and it also puts doors open for better abstraction.Twoway
I went with #1 and took just a few minutes to get everything running. The only change that I had to make was to initialize the RegexValidator. Fantastic! Thanks again for the help.Anthropomorphic

© 2022 - 2024 — McMap. All rights reserved.