Lets say I ahve this on xhtml page:
<h:selectOneMenu value="#{repairManagerBean.manufacturerName}"
id="manufacturer_selection">
<f:ajax listener="#{repairManagerBean.changeDevice}"
render="device_selection" />
<f:selectItems value="#{repairManagerBean.manufacturers}"
var="manufacturer" itemValue="#{manufacturer.name}"/>
</h:selectOneMenu>
<h:message for="manufacturer_selection" style="color: red"/>
<h:selectOneMenu value="#{repairManagerBean.deviceName}"
id="device_selection">
<f:selectItems value="#{repairManagerBean.devices}"
var="device" itemValue="#{device.name}"/>
</h:selectOneMenu>
<h:message for="device_selection" style="color: red"/>
<p><h:commandButton value="Submit" action="#{repairManagerBean.storeRepairInfo}" /></p>
In database there are 2 manufacturers: "Apple"(id=1), "myPhone"(id=2); and also 4 devices: "iPhone 4"(id=4), "iPad mini 4"(id=3), "SmartView 7"(id=2), "Pocket 2"(id=1).
When choose manufacturer, ajax updates devices. If I choose Apple, I can select any of 2 devices, hit Submit, and alls well and saved to db. If however I choose myPhone, no matter which device I select, I get:
j_id_4:device_selection: Value is not valid option
and storeRepairInfo method doesnt gets called.
This is my backing RepairManagerBean class:
@ManagedBean(name = "repairManagerBean")
@ViewScoped
public class RepairManager implements Serializable {
private Repair repair;
private List<Manufacturer> manufacturers;
private String manufacturerName;
private String deviceName;
private List<Device> devices;
/**
* Creates a new instance of RepairManagerBean
*/
public RepairManager() {
}
// getters and setters for all fields except daos
// Action Methods
public String storeRepairInfo() {
try {
Manufacturer m = manufacturerDao.findByName(manufacturerName);
Device d = deviceDao.findByName(deviceName);
repair.setDevice(d);
repair.setManufacturer(m);
saveRepair();
return "success";
} catch(Exception e) {
System.err.println("write fail");
return "error";
}
}
public void changeDevice(AjaxBehaviorEvent event) {
for(Manufacturer m : manufacturers) {
if(m.getName().equals(manufacturerName)) {
devices = deviceDao.findByManufacturerId(m.getId());
break;
}
}
}
@PostConstruct
public void init() {
repair = new Repair();
manufacturers = new ArrayList<>();
manufacturerName = "";
devices = new ArrayList<>();
deviceName = "";
try {
readManufacturersDevices();
} catch(Exception e) { System.err.println("manufacturers read fail");}
}
public void saveRepair() throws Exception {
// save repair object to db
}
public void readManufacturersDevices() throws Exception {
// normally ofc its read from db, but result is same here so you can compile it
Manufacturer apple = new Manufacturer(1l, "Apple");
Manufacturer myphone = new Manufacturer(2l, "myPhone");
manufacturers = new ArrayList<>();
manufacturers.add(apple);
manufacturers.add(myphone);
Device pocket2 = new Device(1l, "Pocket 2");
Device smartview7 = new Device(2l, "SmartView 7");
Device ipad4mini = new Device(3l, "iPad 4 mini");
Device iphone4 = new Device(4l, "iPhone 4");
devices = new ArrayList<>();
devices.add(pocket2);
devices.add(smartview7);
devices.add(ipad4mini);
devices.add(iphone4);
}
}
And here is simple Device entity class:
@Entity
@Table(name = "Device")
public class Device extends AbstractEntity {
@Column(name = "name")
private String name;
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "manufacturer_id")
private Manufacturer manufacturer;
public Device() {
}
public Device(String name, Manufacturer manufacturer) {
this.name = name;
this.manufacturer = manufacturer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Manufacturer getManufacturer() {
return manufacturer;
}
public void setManufacturer(Manufacturer manufacturer) {
this.manufacturer = manufacturer;
}
@Override
public String toString() {
return "Device{" + "id=" + id + "name=" + name + ", manufacturer="
+ manufacturer + '}';
}
@Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + Objects.hashCode(this.name);
hash = 71 * hash + Objects.hashCode(this.manufacturer.getName());
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Device other = (Device) obj;
if (!this.name.equals(other.name)) {
return false;
}
if (!this.manufacturer.getName().equals(other.manufacturer.getName())) {
return false;
}
return true;
}
}
And here is simple Manufacturer entity class:
@Entity
@Table(name = "Manufacturer")
public class Manufacturer extends AbstractEntity {
@Column(name = "name")
private String name;
public Manufacturer() {
}
public Manufacturer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Manufacturer{" + "id=" + id + "name=" + name + '}';
}
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + Objects.hashCode(this.name);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Manufacturer other = (Manufacturer) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
}
And simple abstract entity here:
@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
I have implemented equals and hashcode in Device entity class as you can see. I use @ViewScoped on backing bean class. I read also this and that posts. I dont use any custom converter. Thanks for help.