0

I am running into a very odd issue where the selectOneMenu jsf control is showing the wrong value as selected one (even though the model gets updated with the correct value) after there was a validation error on a completely different field on a previous (not current) submit.

How to replicate (minimum code below):

  1. load the form and leave the "Required text field" text box blank.
  2. Set some values to Active and Inactive in a couple of dropdowns below.
  3. Submit the form. There will be an error message about the "Required text field" being blank and the drop downs you set will still have the same value you gave them (expected). The printout of what the model has will still show the old value and not the value you selected (expected).
  4. Put some text into the "Required text field" and submit the form.
  5. The error message goes away and the form gets "saved". The printouts of what the model has next to the dropdowns have the value you set (expected), however, the dropdowns themselves no longer have the value you selected as the selected option (NOT EXPECTED).

enter image description here

Sample code:

    <?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form id="myForm">
            <div>
                <h:outputLabel value="Required text field" for="reqField" />
                <h:inputText value="#{myBeanController.requiredField}" id="reqField" label="Required Text Field" required="true" />
            </div>
            <div>
                <h:outputLabel value="some objects" />
                <ui:repeat value="#{myBeanController.bunchOfObjects}" var="obj">
                    <div>
                        value on obj model: #{obj}
                        <h:selectOneMenu value="#{obj.type}">
                            <f:selectItems value="#{myBeanController.availableRequiredTypeOptions}" />
                        </h:selectOneMenu>
                    </div>
                </ui:repeat>
            </div>
            <div>
                <h:commandButton value="Submit" action="#{myBeanController.save}" />
            </div>
        </h:form>
    </h:body>
</html>

Controller bean:

package com.mycompany.reproducefieldnotsavingaftervalidation;

import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import org.apache.commons.lang3.text.WordUtils;

@ManagedBean
@SessionScoped
public class MyBeanController {
    private String requiredField;
    private Collection<MyDomainClass> bunchOfObjects;

    @PostConstruct
    public void init(){
        // create some sample objects
        bunchOfObjects = new LinkedList<>();
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.ACTIVE));
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.INACTIVE));
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.UNKNOWN));
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.UNKNOWN));
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.UNKNOWN));
        bunchOfObjects.add(new MyDomainClass(MyDomainClass.PossibleTypes.UNKNOWN));
    }

    public void save() {
        System.out.println("SAVING FORM");
        System.out.println("requiredField: " + getRequiredField());
        System.out.println("Bunch of objects: ");
        for(MyDomainClass obj : getBunchOfObjects()) {
            System.out.println("/tObj: " + obj);
        }
    }

    public Map<String, String> getAvailableRequiredTypeOptions() {
        Map<String, String> options = new TreeMap<>();

        for(MyDomainClass.PossibleTypes type : MyDomainClass.PossibleTypes.values()) {
            // make the text of the option pretty by removing all caps and replacing underscores with space
            options.put(WordUtils.capitalizeFully(type.name(), new char[]{'_'}).replaceAll("_", " "), type.name());
        }

        return options;
    }

    public String getRequiredField() {
        return requiredField;
    }

    public void setRequiredField(String requiredField) {
        this.requiredField = requiredField;
    }

    public Collection<MyDomainClass> getBunchOfObjects() {
        return bunchOfObjects;
    }

    public void setBunchOfObjects(Collection<MyDomainClass> bunchOfObjects) {
        this.bunchOfObjects = bunchOfObjects;
    }    
}

Model:

package com.mycompany.reproducefieldnotsavingaftervalidation;

import java.util.Objects;

public class MyDomainClass {
    private String type;

    public MyDomainClass() { }

    public MyDomainClass(PossibleTypes type) {
        this.type = type.name();
    }

    public MyDomainClass(String type) {
        this.type = type;
    }

    public static enum PossibleTypes {
        UNKNOWN, ACTIVE, INACTIVE
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "MyDomainClass{" + "type=" + type + '}';
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.getType());
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof MyDomainClass)) {
            return false;
        }
        final MyDomainClass other = (MyDomainClass) obj;
        if (!Objects.equals(this.getType(), other.getType())) {
            return false;
        }
        return true;
    }

}
Creature
  • 994
  • 1
  • 12
  • 27
  • Start here https://stackoverflow.com/questions/2090033/why-jsf-calls-getters-multiple-times#2090062 and https://stackoverflow.com/questions/6848970/how-to-populate-options-of-hselectonemenu-from-database – Kukeltje Sep 29 '19 at 20:21
  • @Kukeltje, I don't see how those relate to my case. My select values are not coming from the database. Nor am I concerned with the getters and setters being called multiple times. – Creature Sep 29 '19 at 20:40
  • I was not saying it was related, I just pointed you to something that is not good. And it might not be a problem here, but if you have a large bunch of objects and a large number of types, you construct the list "large bunch * large number" times. If it is constructing from a database (supppose someone with less JSF knowledge changes the implementation) that is not good and if it is not a string but objects, it might cause other problems. Just pointing you to things... – Kukeltje Sep 30 '19 at 06:04

1 Answers1

0

Switching the

<ui:repeat> 

to

<c:forEach>

Seems to have fixed the issue. I don't know why the ui:repeat was causing issues only on a save after previously failed validation and not on a first valid save (if anyone know the answer to this, please post it, I would be interested to know). Based on some research, ui:repeat happens at a different phase than f:selectItem or f:selectItems, but then I would expect the issue to happen on every save and it's not.

Regardless, if you're seeing something similar and your selects are inside of a ui:repeat, try switching that out with a c:forEach to see if that fixes it.

Creature
  • 994
  • 1
  • 12
  • 27