3

I have an input text that have required=true attribute, like below

<h:panelGrid columns=2>
   <h:panelGroup id="ccm">
      <p:inputText id="txtCCMNumber" value="#{setupView.selectedCCM}"
                 required="true" requiredMessage="Required">
          <p:ajax event="blur" listener="#{setupView.handleLooseFocusCCMTextbox()}"
              update=":setupForm:ccm :setupForm:ccmMsg"/>
      </p:inputText>
      <h:outputText value="Duplicated" id="ccmExisted"
                 styleClass="ui-message-error ui-widget ui-corner-all"
                 rendered="#{setupView.ccmNameExisted}"/>
      <h:graphicImage id="ccmNotExist" url="resources/images/check-icon.png"
                    rendered="#{setupView.ccmNameUnique}"
                    width="18"/>
   </h:panelGroup>
   <p:message for="txtCCMNumber" id="ccmMsg" display="text"/>
</h:panelGrid>

So my requirement is, if the value is empty, then it will display Required, since required=true, it should fail at Process Validation phase. If the value is unique, then display a check image, if duplicated, then display Duplicated text. The problem that I run into is, after I type something and tab away (let say I type something unique), it displays the check image, I then erase the text, and tab away again, now the Required text appear, but so is the check image. My theory is that, at the Process validation phase, it fail due to the value is empty, so at the update component phase, it does not invoke that method handleLooseFocusCCMTextbox() that will set the boolean ccmNameUnique to false. Is there a way to fix this?

NOTE: handleLooseFocusCCMTextbox() just turn the boolean value on and off to display the check image or Duplicated text.

Answered. Create Validator class, take out required=true

public void validate(FacesContext fc, UIComponent uic, Object value)
        throws ValidatorException {
     FacesContext context = FacesContext.getCurrentInstance();
     SetupView setupView = (SetupView) context.getApplication().
           evaluateExpressionGet(context, "#{setupView}", SetupView.class);
     if (value == null || value.toString().isEmpty()) {
        setupView.setCcmNameUnique(false);
        FacesMessage message = new FacesMessage();
        message.setSeverity(FacesMessage.SEVERITY_ERROR);
        message.setSummary("Error");
        message.setDetail("Required");
        //This will end up in <p:message>
        throw new ValidatorException(message);
     }
     String rootPath = setupView.getRootPath();
     File rootFolder = new File(rootPath);
     if (rootFolder.exists() && rootFolder.canRead()) {
        List<String> folderNames = Arrays.asList(new File(rootPath).list());
        if (folderNames.contains(value.toString())) {
           setupView.setCcmNameUnique(false);
           FacesMessage message = new FacesMessage();
           message.setSeverity(FacesMessage.SEVERITY_ERROR);
           message.setSummary("Error");
           message.setDetail("Duplicate");
           //This will end up in <p:message>
           throw new ValidatorException(message);
        } else {
           setupView.setCcmNameUnique(true);
        }
     } else {
        logger.log(Level.SEVERE, "Please check the root folder path as "
              + "we cannot seems to see it. The path is {0}", rootPath);
     }
}
Thang Pham
  • 38,125
  • 75
  • 201
  • 285

1 Answers1

2

You want to use a validator instead of an action listener.

<p:inputText id="txtCCMNumber" value="#{setupView.selectedCCM}"
    required="true" requiredMessage="Required"
    validator="#{setupView.validateDuplicateCCM}">
    <p:ajax event="blur" update="ccm ccmMsg" />
</p:inputText>

with

public void validateDuplicateCCM(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    if (value == null || value.toString().isEmpty()) {
        return; // Let required="true" handle.
    }

    // ...

    if (duplicate) {
        ((UIInput) component).setValid(false);
        ccmNameExisted = true;
        // I'd rather throw ValidatorException instead of above two lines here so that it ends up in <p:message>
    } else {
        ccmNameUnique = true;
    }
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I love your suggestion about `throw new ValidatorException()`. However, I still having the same problem. That if I dont type in anything, and tab away, it does not invoke `validateDuplicateCCM`. Therefore, if I type in something unique, and tab away, tick mark show up, and if I erase what I just type, and tab away the `Required` message show up, but the tick mark still there, because `validateDuplicateCCM` did not get invoked. Any idea BalusC? Same behavior if I switch to h:inputText – Thang Pham Feb 28 '12 at 14:16
  • Oh yes, you've hit a bug in `validator` attribute. See this related question/answer: http://stackoverflow.com/questions/9415059/validating-empty-fields-with-inline-validation-method-and-validator-class It will indeed not be invoked when the value is null. I'd move it into a normal `Validator` class which you declare by ``. – BalusC Feb 28 '12 at 14:23
  • I see, however, I need to set `ccmNameUnique = true;` if no duplication found. `ccmNameUnique` is a variable in my view scoped managed bean `SetupView`. Can we inject managed bean into validator, BalusC? – Thang Pham Feb 28 '12 at 16:02
  • You can only inject it if the validator class is registered as `@ManagedBean` and is been used as ``. Alternatively it's available by `Application#evaluateExpressionGet()`. As another alternative you could also try `rendered="#{facesContext.postback and not facesContext.validationFailed}"` on the check icon (this may only have undesireable side effects when also rendered by another inputs, so be careful with this). – BalusC Feb 28 '12 at 16:06
  • I put the Validator as managed bean, but I still experienced two issues: 1. It still not get into my validator when the field is null. and 2. it does not set my `ccmNameUnique` to true. I post the code in my original post. can u take a look, BalusC – Thang Pham Feb 28 '12 at 16:41
  • 1) That will happen when `javax.faces.VALIDATE_EMPTY_FIELDS` context param is set to `false` in `web.xml`. Make sure that it's not `false` or just remove it. It defaults to `true`. 2) The `#{setupView}` seems to be a view scoped bean. The validator is created during restore view, when the view scoped beans aren't available yet. Instead, you get a brand new instance which is later overridden by the real restored view scoped beans. Use `Application#evaluateExpressionGet()` instead of `@ManagedProperty`. – BalusC Feb 28 '12 at 17:01
  • Thank you, BalusC, `Application#evaluateExpressionGet()` works great. I have updated my code from my original post, however, I still having problem with invoking the validator method when the field is empty, I also post my web.xml on my original post as well, if you have some free time, will you please take a look at them. Thank you BalusC – Thang Pham Feb 28 '12 at 18:27
  • I think I figure it out. After I remove `require=true` from my inputText, the it invoke my Validator even though the field is empty. Thank you for your help. It was your comment here that help me http://stackoverflow.com/questions/8719739/jsf-converter-causes-validators-to-be-ignored – Thang Pham Feb 28 '12 at 18:42
  • Oh right, totally overlooked that bit :) Yes, you'd need to take over the `required="true"` work in the validator, or to go ahead with `rendered="#{facesContext.postback and not facesContext.validationFailed}"` instead. – BalusC Feb 28 '12 at 18:48