3
votes

I have an OmniFaces <o:validateMultiple> set to two <p:selectOneMenu>. The one dropdown box is for the home, the other for the away team (of a sports game).

The validator checks, if the combination of the two teams already exists in the schedule, if so, the validator method return false. (see table above in the example: combination already exists... the single item in the list)

Now the problem:

The two select boxes are reset to their null values, but in the background, they seem to keep the value.

The UI tells the user "selection has been cleared", but this is not what I or the users expect.

Example

Before: validation:

enter image description here

After validation:

enter image description here

QUESTION:

How can I restore the select boxes' values after validation fail or rather how do I simply keep the values?

I guess resetting the inputs is just JSF specified behavior?? ????‍♂️

Is it a problem with PrimeFaces (I guess not)?

Can it be done? If so, how?

PS: I don't think posting the JSF code for the select boxes could help here, but if it could, please leave a comment

1

1 Answers

2
votes

JSF should preserve the state of the component tree as defined by the life cycle of the framework.

However, depending on how your JSF page functions you may choose to for example only partially process a page whenever an event occurs. In this case only the processed components will retain their state.

Let's take your use case as an example and first define a view:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui"
      xmlns:of="http://omnifaces.org/functions">
    <h:head>
        <title>SelectOneMenu Validation</title>
    </h:head>
    <h:body>
        <h:form>
            <o:validateMultiple id="validateMatch" components="home away"
                        validator="#{selectBackingBean.onValidateMatch}"
                        message="The combination already exists or is invalid!" />

            <p:dataList value="#{selectBackingBean.matches}" var="match">
                <f:facet name="header">Home / Away</f:facet>
                #{match.home} vs #{match.away}
            </p:dataList>
            <br/><br/>
            <p:selectOneMenu id="home" label="Please select home..." value="#{selectBackingBean.selectedHome}">
                <f:selectItems value="#{selectBackingBean.teams}" var="team" 
                           itemValue="#{team}" itemLabel="#{team}" />
            </p:selectOneMenu>
            <p:selectOneMenu id="away" label="Please select away..." value="#{selectBackingBean.selectedAway}">
                <f:selectItems value="#{selectBackingBean.teams}" var="team" 
                           itemValue="#{team}" itemLabel="#{team}" />
            </p:selectOneMenu>
            <h:panelGroup>
                <br/>
                <h:message for="validateMatch"  />
                <h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
            </h:panelGroup>
            <br/><br/>
            <p:commandButton value="Save" action="#{selectBackingBean.onSave}" update="@form" />
        </h:form>
    </h:body>
</html>

Let's also define the backing bean holding the view state and the model:

@Data
@Named
@ViewScoped
public class SelectBackingBean implements Serializable {
    private List<String> teams;
    private List<Match> matches;

    private String selectedHome;
    private String selectedAway;

    @Data
    @AllArgsConstructor
    public class Match {
        private String home;
        private String away;
    }

    @PostConstruct
    private void init() {
        teams = Arrays.asList("Malmö FF", "GAIS", "IFK Göteborg", "AIK");
        matches = new ArrayList<>(Arrays.asList(new Match("Malmö FF", "AIK")));
    }

    public boolean onValidateMatch(FacesContext context, List<UIInput> components,
                                List<String> values) {
        return values.get(0) != values.get(1) && !matches.contains(new Match(values.get(0), values.get(1)));
    }

    public void onSave() {
        matches.add(new Match(selectedHome, selectedAway)); 
    }
}

If we now run this we get the following results:

enter image description here

If we keep going and eventually run into a validation error we get the following:

enter image description here

Notice how it constantly retains the state of all components throghout. There are components in OmniFaces and JSF that allow you to control the state of selected parts of the component tree to get away from this behavior.

I'm guessing there is something going on in your code or that you are somehow resetting the backing bean values connected to the components.