Search code examples
jsfjsf-2datatable

JSF Datatable: Multiple row submit via a single command button


I'm facing some troubles with JSF and Datatables. In my bean I have a list of objects that are showed in the view by relying on a Datatable. The object are very simple, each of them is composed by an integer (the id) and a String (the comment).

The final aim is to print the full list of objects to the user that will edit one or more rows (via an h:inputtext field) at the same time and then submit his changes via a button. And this is the problem. I already used "successfully" Datatables by editing one row per time by relying on Edit/Save button on each row, but in this case is not applicable as the final user will face with more that 500 rows in totals.

Here is the code that I used (simplified and extracted)

Bean:

@ManagedBean 
@RequestedScope
public class VlanBean {

private DataModel<VlanWrapper> model;

private List<VlanWrapper> vlans = new ArrayList<VlanWrapper>();

public VlanBean() {
    vlans.add(new VlanWrapper(1, ""));
    vlans.add(new VlanWrapper(2, "Prova2-2"));
    vlans.add(new VlanWrapper(3, ""));
    vlans.add(new VlanWrapper(4, ""));
    vlans.add(new VlanWrapper(5, ""));
    vlans.add(new VlanWrapper(6, "Prova3"));
    vlans.add(new VlanWrapper(7, ""));
    vlans.add(new VlanWrapper(8, ""));
}

public void commitChanges()
{
    System.out.println("Before Model ");

    for (VlanWrapper vw : model)
    {
        System.out.println("ModelOF " + vw.getId() + " - " + vw.getProject() + " - changed: " + vw.changed + ";");
    }
}

public DataModel<VlanWrapper> getModel() {
    if (model == null)
        model = new ListDataModel<VlanWrapper>(vlans);
    return model;
}

public void setModel(DataModel<VlanWrapper> model) {
    this.model = model;
}

public class VlanWrapper
{
    private boolean changed;
    private int id;
    private String project;

    public VlanWrapper(int id, String prj)
    {
        this.id = id;
        this.project = prj;
        changed = false;
    }

    public String getProject() {
        return project;
    }

    public void setProject(String project) {
        System.out.println("Setting PRJ..");
        changed = true;
        this.project = project;
    }

    public int getId() {
        return id;
    }

    public boolean isChanged() {
        return changed;
    }
}
}

View:

<!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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
<f:view>
    <h:panelGroup>
    <h:form>
        <h:commandButton value="Commit changes to DB" action="#{VlanBean.commitChanges}">
        </h:commandButton>
    </h:form>


    <h:dataTable value="#{VlanBean.model}" var="vlans">
        <h:column><f:facet name="header">ID</f:facet>#{vlans.id}</h:column>
        <h:column><f:facet name="header">Project</f:facet>
            <h:inputText  value="#{vlans.project}" immediate="true"/>
        </h:column>
    </h:dataTable>
    </h:panelGroup>

</f:view>
</h:body>
</html>

The problem is that all the changes I do on the view are not reflected in the bean and I haven't find any useful hint/post around (some where suggesting ajax forced update, other javascript, but I didn't make any of them working and no one was really close to the use I desired).

I also tried to go for ui:repeat, but I have the same behavior. Any ideas/suggestion? I'm not obliged to used Datatable, so I'm opened to other solution/approach!

Thanks in advace, Alberto


Solution

  • From your code, you should be getting a warning saying that your <h:inputText should have a <f:form as parent; there is no form surrounding it, and the form you have is just surrounding the <h:commandButton

    Using any type of Iteration UIComponent you can have the behavior you want, although normally you use <h:datatable. A way that you can do it is the following although you have to select the rows you want to edit first but I'm sure you can find the solution by yourself with this:

    I added another property to your entity VlanWrapper:

    private boolean changed;
    private boolean edit;
    private int id;
    private String project;
    

    // setters & getters ....

    Your facelet could be something like this:

    <h:form>
        <h:dataTable value="#{meod.vlans}" var="v" >
    
            <h:column>
                <f:facet name="header">Id</f:facet>
                <h:outputLabel value="#{v.id}" />                    
            </h:column>
    
            <h:column>
                <f:facet name="header">Project</f:facet>
                <h:outputLabel rendered="#{not v.edit}" value="#{v.project}" />
                <h:inputText   rendered="#{v.edit}" value="#{v.project}" >
                    <f:ajax event="blur" execute="@this" listener="#{meod.addToEditedList(e)}" render="@all">
                        <f:param name="vlanedit" value="#{v.id}" />
                        <f:param name="vlanproject" value="#{v.project}" />
                    </f:ajax>
                </h:inputText>
            </h:column>
    
            <h:column>
                <f:facet name="header">Edit</f:facet>
                <h:commandButton value="Edit" action="#{meod.edit(v)}">
                    <f:ajax render="@form" />
                </h:commandButton>
            </h:column>
        </h:dataTable>            
        <h:commandButton value="Finalize Edit" action="#{meod.editSubmit()}" />
    </h:form>
    

    The reason I said you have to select the rows you want to edit is because when you press the edit button, the the whole form will be updated so just play around with what you want updated and this should do it.

    The backingbean :

    @ManagedBean
    @ViewScoped
    public class Meod {
    
        private List<VlanWrapper> vlans;
    
        public Meod() {
    
            vlans = new ArrayList<>();
    
            for (int i = 0; i < 11; i++) {
    
                vlans.add(new VlanWrapper(i, "Project " + i));
            }
        }
    
        public void addToEditedList(AjaxBehaviorEvent e) {
    
            Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
    
            int id = Integer.valueOf(params.get("vlanedit"));
            String newProject = params.get("vlanproject");
    
            updateVlanInView(id, newProject);
        }
    
        private void updateVlanInView(int id, String proj) {
    
            vlans.stream().filter(v -> v.getId() == id).forEach(v -> v.setProject(proj));
        }
    
        public void edit(VlanWrapper vlan2Edit) {
            vlan2Edit.setEdit(true);
        }
    
        public void editSubmit() {
            vlans.forEach((v) -> v.setEdit(false));
        }
    
        public List<VlanWrapper> getVlans() {
            return vlans;
        }
    
        public void setVlans(List<VlanWrapper> vlans) {
            this.vlans = vlans;
        }
    }
    

    There are multiple ways in which you can achieve this... probably better ways to but to summarize:

    • Create a datatable to iterate over you objects
    • Make sure your <h:commandXXX and EditableValueHolders are in a form.
    • The "new" property edit is to determine whether or not to render the input instead of the output text components
    • Use a DBService to update the values after you have been changed.

    Hope this helps.