1

I have this select:

<p:selectOneMenu  value="#{bean.val}">
    <f:selectItem itemValue="X" itemLabel="Select" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="wrapper" />
</p:selectOneMenu>

And this is the wrapper it updates:

<p:panel id="wrapper">
    <p:panel rendered="#{bean.val == 'A' or bean.val == 'B'}">
        <!-- insert your code here -->
    </p:panel> 
</p:panel>

The wrapper is outside the select, at the same level.

At the start, it's all ok. The wrapper is hidden.

If I select 'A' and then 'C', for example, the wrapper disappear. BUT, if I select 'A' or 'B' and 'X' again (the first "choice", Select), the wrapper does NOT disappear!

I have put a breakpoint inside the setter of bean.val. The setter in invoked for all the choices BUT for the first one the value inside the debugger is the same as the previous one!

BUT! If I remove the validator, it works!

This is the validator:

@FacesValidator(value="requiredSelect")
public class RequiredSelect implements Validator {
    protected MessageUtil messageUtil = new MessageUtil();
    
    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String val = (String) value;
        
        if ("X".equals(val)) {
            FacesMessage msg = this.messageUtil.getDefaultValidationError();
            throw new ValidatorException(msg);
        }
    }
}

Previously I used another validator:

@FacesValidator(value="requiredString")
public class RequiredString implements Validator {
    protected MessageUtil messageUtil = new MessageUtil();
    
    @Override
    public void validate(
        FacesContext context, 
        UIComponent component, 
        Object value
    ) throws ValidatorException {
        String val = (String) value;
        
        if (val == null || val.trim().isEmpty()) {
            FacesMessage msg = this.messageUtil.getDefaultValidationError();
            throw new ValidatorException(msg);
        }
    }
}

but it did not work, because the empty string was not saved to the backing bean at menu change. So, if, for example, you selected 'A' and then back '' (Select) and you try to submit the form, the select is signaled with errors, but the value of the select returns to be 'A'! So the bogus 'X' value.

I'm using Primefaces 3.4.1 and Mojarra 2.1.7

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100

3 Answers3

2

Your issue is a bit of a XY-problem. You are trying to hack your way out of creating a required p:selectOneMenu using the X select item. This requires you to create a validator. If you simply make the p:selectOneMenu required, and would use null as the itemValue of the placeholder, it prevents the need to handle the X, so you don't need a validator.

Selecting null will cause validation to fail. So, once a selection is made you no longer want to render the null placeholder. Normally one would use the hideNoSelectionOption attribute of the p:selectOneMenu. But this is not available in PrimeFaces 3.4.1. As you don't want to upgrade, you can add this behavior by creating a custom renderer for the SelectOneMenu.

Create a new class, say my.custom.MySelectOneMenuRenderer, and extend SelectOneMenuRenderer. In this you want to @Override the encodeInput method to something like:

protected SelectItem selectMeOption = new SelectItem(null, "Select");

@Override
protected void encodeInput(
    FacesContext context, SelectOneMenu menu, String clientId,
    List<SelectItem> selectItems, Object values, Object submittedValues,
    Converter converter
) throws IOException {
    String classes = menu.getStyleClass();
    Pattern pattern = Pattern.compile("\\bnormalSelect\\b");
    Matcher matcher = pattern.matcher(classes);
    boolean isNormal = matcher.find();
    Object value = menu.getValue();
    
    // If there's not a class "normalSelect" and no value is set, add the
    // null option as the first item
    if (!isNormal) {
        if (value == null) {
            selectItems.add(0, selectMeOption);
        }
        else {
            SelectItem firstOption = selectItems.get(0);
            
            if (selectMeOption.equals(firstOption)) {
                selectItems.remove(0);
            }
        }
    }
    
    super.encodeInput(
        context, menu, clientId, selectItems, values, submittedValues,
        converter
    );
}

Add your custom renderer to the render-kit section in your faces-config.xml like:

<render-kit>
  <renderer>
    <component-family>org.primefaces.component</component-family>
    <renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type>
    <renderer-class>my.custom.MySelectOneMenuRenderer</renderer-class>
  </renderer>
</render-kit>

View example:

<p:selectOneMenu value="#{bean.val}" layout="pageDirection">
    <f:selectItems value="#{bean.vals()}" />
    <p:ajax update="#{empty bean.val ? '@this' : ''}" />
</p:selectOneMenu>

For a working example see: https://github.com/jepsar/primefaces-test/blob/so_68457571/src/main/webapp/test.xhtml

See also:

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
2

Please have a look at the JSF Life-Cycle for example here: https://www.torsten-horn.de/img/JSF-Lifecycle.gif

And you will note, that if you throw "Validator-Exceptions" the "Update Model Values" Phase is skipped. (Process Events forwards to render response).

Hence your value "X", which you consider invalid in your validator is never send to the bean, and therefore the wrapper won't vanish.

What you actually want is:

  • Define a proper "NoSelect-Option"
  • Make the field "required".

Then - upon submission - the required field will be considered empty and throw a validator-exception where you need it.

ps.: A neat way to just bypass this "Issue" is to use hideNoSelectionOption on the selectOneMenu - that way, the form starts with "Please select", but the user can't switch back to "Please select", once he made an choice:

<p:selectOneMenu  value="#{bean.val}" hideNoSelectionOption="#{not empty bean.val}">
    <f:selectItem itemValue="#{null}" itemLabel="Select" noSelectionOption="true" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="@this,wrapper" process="@this" />
</p:selectOneMenu>

Updated, added @this to p:ajax.

PS: this does not work with old versions of Primefaces that does not have hideNoSelectionOption

Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
dognose
  • 20,360
  • 9
  • 61
  • 107
  • @JasperdeVries No, the NoSelect-Option is valid to be selected. It just will cause validation-Errors, when the form is submitted and the field is "required". That's how it's supposed to work. – dognose Jul 21 '21 at 15:36
  • The problem is I can't customize the `require="true"` message – Marco Sulla Jul 21 '21 at 15:47
  • @MarcoSulla you can. Use `required="true" requiredMessage="This field is required, bro!"` – dognose Jul 21 '21 at 15:49
  • @JasperdeVries No, "Required" validation does happen up on submission, not on Postback. Reason "his" validator is causing this issue, because he is evaluating his "own designed" required-validation ON postback, where it only should happen on submission. – dognose Jul 21 '21 at 16:55
  • I do not want to add the same requiredMessage="This field is required, bro!" for ***ALL*** inputs, for the One Source of Truth principle. Can't I change the default message? – Marco Sulla Jul 22 '21 at 07:31
  • Found it: https://stackoverflow.com/questions/9155684/change-the-default-message-validation-error-value-is-required-to-just-value I'll try – Marco Sulla Jul 22 '21 at 07:35
  • PS: the "neat way" is not neat, because the user could never change the select. – Marco Sulla Jul 22 '21 at 08:27
  • Please take a look at my elaboration[1] of your answer and the latest comments: the problem is even if I put null and/or noSelectionOption, the wrapper is NOT updated if I select an option and then back the noSelectionOption. PS: someone can call BalusC? X-D [1]: https://stackoverflow.com/a/68483393/1763602 – Marco Sulla Jul 23 '21 at 08:59
  • @MarcoSulla if it's a mandatory field, the user has no need to switch back to "Please select..." – dognose Jul 28 '21 at 14:00
  • The problem is I can't use your "trick", because I have Primefaces 3.4.1 and no `hideNoSelectionOption` – Marco Sulla Jul 29 '21 at 07:21
1

Ok, this is my solution, thanks to the dognose's answer first part:

  1. Change the input to:
<p:selectOneMenu value="#{bean.val}" required="true">
    <f:selectItem itemValue="" itemLabel="Select" />
    <f:selectItems value="#{bean.getVals()}" />
    <p:ajax update="wrapper" />
</p:selectOneMenu>

No need to itemValue="#{none}", since null strings are automatically transformed to empty string by default, by jboss or primefaces, I do not remember. No need of noSelectionOption="true" too (I do not understand its scope... the validation works even if I don't put it)

  1. added to a package my.package the files messages.properties, with this content:
javax.faces.component.UIInput.REQUIRED = Error
javax.faces.component.UIInput.REQUIRED_detail = One or more fields are missing or wrong. Please check the form
  1. Then I added a p:messages like this one:
<p:messages id="messages" closable="true" showDetail="true" />
  1. Then, I used this trick to remove the duplicate messages.
Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
  • Try selecting a value first and then select the empty "Select" option. – Jasper de Vries Jul 22 '21 at 14:44
  • @JasperdeVries I tried, it works. I submitted with "Select" and it was red. I Changed to a value, I submitted and it was "white". Then I changed again to "Select" and submitted and it returned red. – Marco Sulla Jul 22 '21 at 21:08
  • Is the wrapper also updated in that case? – Jasper de Vries Jul 23 '21 at 07:54
  • @JasperdeVries Uops. No.... I try null and/or noSelectionOption :D – Marco Sulla Jul 23 '21 at 08:48
  • @JasperdeVries: I tried with all the combinations of null and noSelectionOption, nothing. The wrapper does not udated, probably because the setter is NOT invoked – Marco Sulla Jul 23 '21 at 08:54
  • @JasperdeVries but this problem is only with my version of Primefaces, or was fixed? – Marco Sulla Jul 26 '21 at 15:08
  • It's not a bug. If you provide an empty value for a required field, processing will fail. That's why I remove the "Select" option once a valid selection is made in my answer. See also https://stackoverflow.com/questions/25339056/understanding-primefaces-process-update-and-jsf-fajax-execute-render-attributes – Jasper de Vries Jul 26 '21 at 15:29
  • In an HTML page, it's possible to do it. So making this complicated code that does not work for me for something so simple sounds to me a bug to report. And I never get so much votes for a question on JSF :D – Marco Sulla Jul 27 '21 at 08:25
  • HTML does not do server side validation. – Jasper de Vries Jul 27 '21 at 08:31
  • @JasperdeVries No, but you can use Spring and Hibernate validators. More easy. – Marco Sulla Jul 27 '21 at 08:46