-1

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.

10101101
  • 193
  • 1
  • 13
  • [mcve].... always... And I bet it is the first line in number 1 of the soultions: _"Ensure that exactly the same list is been preserved during the subsequent request, particularly in case of multiple cascading menus. Making the bean `@ViewScoped` instead of `@RequestScoped` should fix it in most cases. "_ – Kukeltje Mar 06 '20 at 07:56
  • The problem is in your java code which you haven't posted. Most likely `repairManagerBean.devices` for some reason gets reset back to `Apple`'s list of devices. – Vsevolod Golovanov Mar 06 '20 at 12:14
  • @VsevolodGolovanov I have updated my question please. – 10101101 Mar 06 '20 at 14:14
  • [mcve]... Please.... This is not one by a long shot – Kukeltje Mar 06 '20 at 14:17
  • @VsevolodGolovanov Do you suggesting the problem is in this line: devices = deviceDao.findByManufacturerId(manufacturers.get(0).getId()); – 10101101 Mar 06 '20 at 14:35
  • @Kukeltje Sorry but I really dont know what else can be simpler here... all code I posted is only related to this problem, nothing more. – 10101101 Mar 06 '20 at 14:48
  • It is not only about simple, it is also about complete, runnable. Create a new empty jsf project. Copy only (and really only) the code above into it. Does it work? It does not even compile. Are all custom classes needed for the issue? Most likely not... Remove them... Make it more simple and more complete... Cheers – Kukeltje Mar 06 '20 at 15:50
  • @Kukeltje Yes Sir please see now and feel free to answer I will give ya a plus (I deleted my answer). ;) – 10101101 Mar 06 '20 at 16:31

1 Answers1

0

So the problem was my @PostConstruct init method reinitialized all fields after every form action. So after Submit button was hit, it first set devices and manufacturers back to default (Apple), and then tried to save it to db with wrong id. This is how it should be:

@PostConstruct
public void init() {
    try {
        readManufacturersDevices();
    } catch(Exception e) { System.err.println("manufacturers read fail");}
}

And I also forget about getter for manufacturers list. And also changed @ViewScoped from javax.faces.view to javax.faces.bean.

10101101
  • 193
  • 1
  • 13