Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resetting child p:selectOneMenus inside a p:dataTable to empty, when a labelled item in their parent list is selected

There are three tables in MySQL database, category, sub_category and brand (manufacturer) where category is a parent of the rest i.e. sub_category and brand. I hope, the relationship between menus can be clearer based on the table relationships.

All of three <p:selectOneMenu>s are placed inside a <p:dataTable> in three respective columns as identified by <p:column>. I am ignoring <p:column>, <p:cellEditor>, <f:facet name="output">, <f:facet name="input">, <p:rowEditor> and all such nuisances for brevity.

row corresponds to a JPA managed entity which is product in this case as specified by var="row" in the <p:dataTable> associated.

This is the actual question mark : When an item (the first one) with a null value in the categoryList (parent) is selected, its child lists subCategoryList and brandList should be rest to empty.

Category List:

<p:selectOneMenu id="categoryList"
                 value="#{row.category}"
                 required="#{param['javax.faces.source'] ne component.clientId}">

    <f:selectItem itemLabel="Select"
                  itemValue="#{null}"/>
    <!-- When this item is selected, its children below should be reset to empty. -->

    <f:selectItems var="category"
                   value="#{productManagedBean.categories}"
                   itemLabel="Select"
                   itemValue="#{category}"/>

    <p:ajax update="subCategoryList brandList"/>
    <!-- The listener functionality is left incomplete here. -->
</p:selectOneMenu>

Subcategory List :

<p:selectOneMenu id="subCategoryList"
                 value="#{row.subCategory}">

    <f:selectItem itemLabel="Select"
                  itemValue="#{null}"/>

    <f:selectItems var="subCategory"
                   value="#{productManagedBean.getSubCategories(row.category)}"
                   itemLabel="#{subCategory.subCatName}"
                   itemValue="#{subCategory}"
                   rendered="true"/>
</p:selectOneMenu>

Brand (manufacturer) List :

<p:selectOneMenu id="brandList"
                 value="#{row.brand}">

    <f:selectItem itemLabel="Select"
                  itemValue="#{null}"/>

    <f:selectItems var="brand"
                   value="#{productManagedBean.getBrands(row.category)}"
                   itemLabel="#{brand.brandName}"
                   itemValue="#{brand}"
                   rendered="true"/>
</p:selectOneMenu>

The managed bean (lazy data model can be ignored in the context of this question) :

@Named
@ViewScoped
public class ProductManagedBean extends LazyDataModel<Product> implements Serializable {

    @Inject
    private Service service;

    // Associated with <p:selectOneMenu id="categoryList">.
    private List<Category> categories; // Getter & setter.

    // These are merely helper maps to reduce possible database calls.
    private Map<Category, List<SubCategory>> subCategoriesByCategory;
    private Map<Category, List<Brand>> brandByCategory;

    public ProductManagedBean() {}

    @PostConstruct
    private void init() {
         // This can be application scoped somewhere else as per business requirement.
        categories = service.getCatgeoryList();

        subCategoriesByCategory = new HashMap<Category, List<SubCategory>>();
        brandByCategory = new HashMap<Category, List<Brand>>();
    }

    // This method populates <f:selectItems> associated with <p:selectOneMenu id="brandList">.

    public List<SubCategory> getSubCategories(Category category) {
        // category is never null here unless something is broken deliberately.

        if (category == null) {
            return null;
        }

        List<SubCategory> subCategories = subCategoriesByCategory.get(category);

        if (subCategories == null) {
            subCategories = service.findSubCategoriesByCategoryId(category.getCatId());
            subCategoriesByCategory.put(category, subCategories);
        }

        return subCategories;
    }

    // This method populates <f:selectItems> associated with <p:selectOneMenu id="brandList">.
    public List<Brand> getBrands(Category category) {
        // category is never null here unless something is broken deliberately.

        if (category == null) {
            return null;
        }

        List<Brand> brands = brandByCategory.get(category);

        if (brands == null) {
            brands = service.findBrandsByCategoryId(category.getCatId());
            brandByCategory.put(category, brands);
        }

        return brands;
    }
}

In any case, the selected value in any of these menus is not supplied to the corresponding backing bean. It is only available in the model backed by JPA (value="#{row.category}", value="#{row.subCategory}" and value="#{row.brand}" respectively).

► How to signal the backing bean that the first item with a nullvalue (labelled "Select") in the parent menu is selected so as to resetting its child lists to empty? This should happen in any feasible way, if this is not feasible.

I am using PrimeFaces 5.2 final (community release) and Mojarra 2.2.12.


This is not needed unless there is a null foreign key in the underlying database table specifically using the vendor specific ON DELETE SET NULL option allowing an optional parent in each (or some) corresponding child row.

like image 514
Tiny Avatar asked Jul 31 '15 01:07

Tiny


1 Answers

To the point, you need to make sure that the <f:selectItem> getter is called with a null argument. In other words, the #{row.category} must be null. Given that you're for #{row.category} using the model als shown in this answer, Populate p:selectOneMenu based on another p:selectOneMenu in each row of a p:dataTable, most likely as below,

@Transient
private Category category;

public Category getCategory() {
    return (category == null && subCategory != null) ? subCategory.getCategory() : category;
}

then the #{row.category} will actually never be null when there's a subCategory. This will be the case when an existing data entry is presented in view.

You basically need to explicitly null out the subCategory (and brand) when the transient category property is explicitly set to null. This oversight has in the meanwhile been fixed in the mentioned answer. Here's how your new setCategory() method should look like:

public void setCategory(Category category) {
    this.category = category;

    if (category == null) {
        subCategory = null;
        brand = null;
    }
}

This way, the getCategory() will properly return null and thus the passed-in #{row.category} also.

like image 159
BalusC Avatar answered Oct 22 '22 08:10

BalusC