0
votes

I have a <p:dataTable> with incell editing, it contains 2 columns each with a <h:selectOneMenu> plus a third Edit column. The second <h:selectOneMenu> is dependent on the selection in the first <h:selectOneMenu>. When a row is in input/edit mode, changing the value in the first <h:selectOneMenu> should update the list in the second <h:selectoneMenu>, I should then be able to continue to change the value in the second and then save changes on the row.

My problem is, if the value in the first <h:selectOneMenu> is changed, the list in second <p:selectOneMenu> is updated correctly, but nothing happen when I click on the check icon (save changes) in <p:rowEditor> column, the row stays in editing mode. The RowEditEvent listener in the backing bean is (obviously) not invoked as well. I'm still able to click on the close icon to switch the row back to output mode however, but then nothing is saved.

If I only change the value in the second <h:selectOneMenu> without changing the value in the first <h:selectOneMenu>, then there is no problem saving the changes.

The followings are code snippets, simplified, and still "dirty" due to days of testing codes:

xhtml:

<h:form id="dataListForm">
<p:dataTable id="dataTable" value="#{bean.rdcList}" var="rdc" >
    <p:ajax event="rowEdit" listener="#{bean.onRowEdit}" />
    <f:facet name="header">
        Data List
    </f:facet>
    <p:column headerText="Agency">
        <p:cellEditor>
            <f:facet name="output">
                <h:outputText value="#{rdc.agency.descr}" />
            </f:facet>
            <f:facet name="input">
                <h:selectOneMenu value="#{rdc.agency.id}" valueChangeListener="#{bean.agChanged}">
                    <f:selectItems value="#{bean.agenciestab}" var="ag" itemValue="#{ag.id}" itemLabel="#{ag}" />
                    <p:ajax event="change" update="zoneSelect" />
                </h:selectOneMenu>
            </f:facet>
        </p:cellEditor>
    </p:column>
    <p:column headerText="Zone">
        <p:cellEditor>
            <f:facet name="output">
                <h:outputText value="#{rdc.zone.id} - #{rdc.zone.name}" />
            </f:facet>
            <f:facet name="input">
                <h:selectOneMenu id="zoneSelect" value="#{rdc.zone.id}" valueChangeListener="#{bean.zChanged}">
                    <f:selectItems value="#{bean.zonestab}" var="z" itemValue="#{z.id}" itemLabel="#{z}" />
                </h:selectOneMenu>
            </f:facet>
        </p:cellEditor>
    </p:column>
    <p:column headerText="Edit">
        <p:rowEditor />
    </p:column>
</p:dataTable>

bean:

@ManagedBean(name = "bean")
@SessionScoped

public class Bean implements Serializable {
.
.
/* Init */
@PostConstruct
public void init() {
    aChanged = false;
    zChanged = false;
    edited = false;
    agenciestab = new ArrayList<SelectItem>();
    for (Agency agency : agencyDao.all()) {
        agenciestab.add(new SelectItem(agency.getId(), agency.getDescr()));
    }
}

/* Listeners */
public void agChanged(ValueChangeEvent event) {
    aChanged = true;
    selectedAgency = agencyDao.get(Integer.valueOf(event.getNewValue()
            .toString()));
    zonestab = new ArrayList<SelectItem>();
    if (selectedAgency != null) {
        for (Zone zone : zoneDao.getByDistrict(selectedAgency.getDistrict())) {
            zonestab.add(new SelectItem(zone.getId(), zone.getZone()+ " - " + zone.getName()));
        }
    } else {
        for (Zone zone : zoneDao.getByDistrict(rdcList.getRowData().getAgency().getDistrict())) {
            zonestab.add(new SelectItem(zone.getId(), zone.getZone()+ " - " + zone.getName()));
        }
    }
}

public void zChanged(ValueChangeEvent event) {
    zChanged = true;
    selectedZone = zoneDao.get(Integer.valueOf(event.getNewValue().toString()));
}

public void onRowEdit(RowEditEvent event) {
    edited = true;
    Rdc rdc = (Rdc) event.getObject();
    if (aChanged) {
        rdc.setAgency(selectedAgency);
        aChanged = false;
    }
    if (zChanged) {
        rdc.setZone(selectedZone);
        zChanged = false;
    }
    try {
        rdcDao.saveOrUpdate(rdc);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/* Getters & Setters */
public DataModel<Rdc> getRdcList() {
    if (rdcList == null) {
        rdcList = new ListDataModel<Rdc>(rdcDao.getBatchRdc(batchBean.getCurBatch().getId()));
    } else {
        rdcList.setRowIndex(0);
        if (rdcList.isRowAvailable() && rdcList.getRowData().getBatch().getId().equals(batchBean.getCurBatch().getId())) {
            if (edited) {
                edited = false;
                rdcList = new ListDataModel<Rdc>(
                        rdcDao.getBatchRdc(batchBean.getCurBatch().getId()));
            }
        } else {
            rdcList = new ListDataModel<Rdc>(rdcDao.getBatchRdc(batchBean.getCurBatch().getId()));
        }
    }
    return rdcList;
}

public void setRdcList(DataModel<Rdc> rdcList) {
    this.rdcList = rdcList;
}

public ArrayList<SelectItem> getAgenciestab() {
    return agenciestab;
}

public void setAgenciestab(ArrayList<SelectItem> agenciestab) {
    this.agenciestab = agenciestab;
}

public ArrayList<SelectItem> getZonestab() {
    zonestab = new ArrayList<SelectItem>();
    if (aChanged && selectedAgency != null) {
        for (Zone zone : zoneDao.getByDistrict(selectedAgency.getDistrict())) {
            zonestab.add(new SelectItem(zone.getId(), zone.getZone()+ " - " + zone.getName()));
        }
    } else if (!aChanged) {
        for (Zone zone : zoneDao.getByDistrict(rdcList.getRowData().getAgency().getDistrict())) {
            zonestab.add(new SelectItem(zone.getId(), zone.getZone()+ " - " + zone.getName()));
        }
    }

    return zonestab;
}

public void setZonestab(ArrayList<SelectItem> zonestab) {
    this.zonestab = zonestab;
}
/* other getters & setters */
}

I'm using primefaces-3.1.1, mojarra-2.1.3-FCS and Tomcat 7.0.26

Where did I do wrong? And is there a cleaner and better way of achieving what I'm trying to achieve?

1

1 Answers

2
votes

You're performing business logic in a getter method. In your particular case you're setting the datatable row index to 0 everytime the getter of the datatable is called. This is most likely the root cause of your concrete problem. This way JSF won't be able to find any actions which are performed on rows other than the first row. You should absolutely not be performing business logic in a getter method. The getter method is called everytime when EL needs to evaluate the expression which can possibly go up to a couple of hundred times in a moderately sized datatable.

Getter methods should be designed that way that they only return the data, nothing more.

public List<Rdc> getRdcList() {
    return rdcList;
}

Any business logic should be performed in the (post)constructor of (action)listener methods instead. First-time initialization should go in (post)constructor and one-time changes based on enduser (ajax) actions should go in (action)listener methods. In edge cases, you should use at highest lazy loading.

See also:


Unrelated to the concrete problem, the valueChangeListener isn't the right tool for the purpose which you've there. Rather use <p:ajax listener="#{bean.changed}">. The model is already updated with the submitted values at that point.