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 null
value (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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With