Cong Wang
Cong Wang

Reputation: 206

Primefaces <f:selectItems> attribute got set several times with or without values

I have a problem debugged for all half day. I still could not figure it out. Basically, I use datatable expandable feature to show some extra options for each row. User could check some or none of them, then user click on update button to update database. So one row will have many options.

<f:selectItems value="#{adminBean.allTabNames}" /> is to use collect users' selected options, then managed bean will save them into database once user clicks update.

Then problem is that public void setSelectedTabsNames(List<String> selectedTabsNames) { this.selectedTabsNames = selectedTabsNames; } method is called several times with expected values or null values(empty list). The values are passed randomly, sometimes there are no values.

View:

<ui:composition 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"
xmlns:p="http://primefaces.org/ui">
<h:form id="form1">
    <p:growl id="growl" showDetail="true"/> 

    <p:dataTable var="user" value="#{adminBean.users}" scrollable="false"
        >

        <p:ajax event="rowToggle" listener="#{adminBean.onRowToggle(user.id)}" update=":form:tabView:form1:growl" /> 

        <f:facet name="header">
            All Users
        </f:facet>

        <p:column style="width:2%">
            <p:rowToggler />
        </p:column>

        <p:column headerText="First Name">
            <h:outputText value="#{user.firstname}" />
        </p:column>

        <p:column headerText="Last Name">
            <h:outputText value="#{user.lastname}" />
        </p:column>

        <p:column headerText="Password">
            <h:outputText value="#{user.password}" />
        </p:column>

        <p:column headerText="Active">
            <h:outputText value="#{user.active}" />
        </p:column>

        <p:column headerText="Last Login">
            <h:outputText value="#{user.timestamp}" />
        </p:column>

        <p:column headerText="Notes">
            <h:outputText value="#{user.notes}" />
        </p:column>

        <p:rowExpansion>
            <h:panelGrid id="display" columns="1" cellpadding="4">

                <h:outputText value="Tabs: " />
                <p:selectManyCheckbox id="grid" value="#{adminBean.selectedTabsNames}"
                    layout="pageDirection" >
                    **<f:selectItems value="#{adminBean.allTabNames}" />**
                </p:selectManyCheckbox>
            </h:panelGrid>

            <br/>

            <p:commandButton value="Update" id="submit" actionListener="#{adminBean.updateTabsForUser(user.id)}" ajax="true" />

        </p:rowExpansion>

    </p:dataTable>

</h:form>

Managed Bean:

setSelectedTabsNames(List selectedTabsNames)

package org.userlogin.view;

import java.io.Serializable;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.bean.ViewScoped;

import org.userlogin.db.entity.FopsUser;
import org.userlogin.service.UserService;

@ManagedBean
@ViewScoped
public class AdminBean implements Serializable {

private static final long serialVersionUID = -9002632063713324598L;

private List<FopsUser> users;

private List<String> selectedTabsNames;
private List<String> allTabNames;

private UserService us;

public AdminBean() {
    us = new UserService();
    users = us.getAllUsers();
    allTabNames = us.getAllTabs();
}

public List<FopsUser> getUsers() {
    return users;
}

public void setUsers(List<FopsUser> users) {
    this.users = users;
}

public void setSelectedTabsNames(List<String> selectedTabsNames) {
    this.selectedTabsNames = selectedTabsNames;
}

public List<String> getSelectedTabsNames() {
    return selectedTabsNames;
}

public List<String> getAllTabNames() {
    return allTabNames;
}

public void setAllTabNames(List<String> allTabNames) {
    this.allTabNames = allTabNames;
}

public void updateTabsForUser(Long uid) {
    us.updateTabsUser(selectedTabsNames);
}

public void onRowToggle(Long uid) {
    //set current selected user
    us.setCurrent(uid);
    this.selectedTabsNames = us.getTabNamesByUserId(uid);
}

}

---------------update-------

Remove the nested 'form', but still not working. I found the the issue is not affecting the last row of data table. Suppose I have three rows in data table, the setters are called multiple times and set to null at last time when I manipulate the first two rows. But for the last row, the setter is still called multiple times. The last call sets the expected value. Now I just add

public void setSelectedOptions(List<String> selectedOptions) {
    if (selectedOptions == null || selectedOptions.size() == 0) {
        return;
    }
    this.selectedOptions = selectedOptions;
}

It is still ugly ...

------------update----------

<p:selectManyCheckbox id="grid" value="#{user.selectedTabsNames}"
     layout="pageDirection" >
     **<f:selectItems value="#{adminBean.allTabNames}" />**
</p:selectManyCheckbox>

Should design like this: put selectedTabsNames into User object. But still not working. since I have this ajax submit button, this requests each selectedTabsNames got called with empty list passed in.

<p:rowExpansion>
<h:panelGrid id="display" columns="1" cellpadding="4">

    <h:outputText value="Tabs: " />
    <p:selectManyCheckbox id="grid" value="#  {user.selectedTabsNames}"
                    layout="pageDirection" >
        <f:selectItems value="#{adminBean.allTabNames}" />
    </p:selectManyCheckbox>
    <p:commandButton value="Update" id="submit"  ajax="true" />     
</h:panelGrid>

<br/>
</p:rowExpansion>

----------------update with my own solution (not graceful one, but works) -----

Every time an ajax buttom has been clicked, the whole data table is updated. That means each setSelectedItem method will be called with expected value or empty value. I don't know how to change that.

So I modify my save() method called from ajax button with following logic:

public void save(Long userId, List<String> selectedItem) {
    for (User user: users) {
        if (user.getId() == userId) {
             //update selectedItem in db for this user.
        } else {
            // read selectedItems in db
            // update selectedItem in user object.
        }
    }

}

Upvotes: 0

Views: 2408

Answers (2)

kolossus
kolossus

Reputation: 20691

I may be wrong, but I don't think rowToggle event is the kind of ajax event that can handle row-level parameters. Think about it this way: var="user" is a row-iteration level variable, available for each row in the datatable. The rowToggle event on the other hand is a single level tag, applicable to the entire table as one component. So there probably isn't a reliable way for the datatable to know which row you're referring to when you use adminBean.onRowToggle(user.id), it'll just select the last row that was rendered

A more effective way to get hold of the details of the row that was toggled is using the ToggleEvent listener in the backing bean, where you don't have to pass a variable:

     public void onRowToggle(ToggleEvent te){
        User theSelectedUser = (User)te.getData();
        int id = theSelectedUser.getId();
      }

In your view, you'll now have:

<p:ajax event="rowToggle" listener="#{adminBean.onRowToggle}" update=":form:tabView:form1:growl"/>

Upvotes: 0

Pablo
Pablo

Reputation: 3673

When the ajax event is fired, all the input elements in the form are sent. That means that all the selectManyCheckbox (one for each row) are sent. That's why setSelectedTabsNames is called several times.

You have to change how you have designed your implementation. One way woud be to store the selected options in the FopsUser object, so you could do value="#{user.selectedTabsNames}":

<p:selectManyCheckbox id="grid" value="#{user.selectedTabsNames}"
     layout="pageDirection" >
     **<f:selectItems value="#{adminBean.allTabNames}" />**
</p:selectManyCheckbox>

This way the selected tabs for each row are stored separately.

Upvotes: 1

Related Questions