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):
- load the form and leave the "Required text field" text box blank.
- Set some values to Active and Inactive in a couple of dropdowns below.
- 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).
- Put some text into the "Required text field" and submit the form.
- 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).
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;
}
}