I'm trying to create a paginated, sortable datatable using MyFaces. The sorting works fine; I can click on a column header and it will sort the data based on the column. Also the pagination works fine for the most part. The datatable will split itself appropriately with some number of items per page. In addition, I want the user to be able to change the number of items displayed per page. Again, this seems to be working until I want all of the items displayed on one page.
According to this reference (also here), if you set the "rows" attribute of t:datatable to "0", it will display the remaining rows in the table. However, when I try this, I get an exception that includes this message:
javax.faces.FacesException - You need to set a value to the 'rows' attribute of component 'myComponent'
I'm trying to set the number of items per page using an attribute in a backing bean. My t:datatable looks like this:
<t:dataTable id="myComponent" var="cur"
value="#{backingBean.list}" sortAscending="#{backingBean.ascending}"
sortColumn="#{backingBean.sortColumn}" sortable="true"
styleClass="myClass" rowClasses="oddRow,evenRow"
rows="#{backingBean.itemsPerPage}" preserveDataModel="false">
<!-- data here -->
</t:datatable>
Later, I have a t:dataScroller to control the pagination:
<t:dataScroller id="pageNavigation" for="myComponent"
paginatorActiveColumnStyle="font-weight:bold;"
renderFacetsIfSinglePage="false"
binding="#{backingBean.scroller}"
paginator="true" >
<!-- facets here -->
</t:dataScroller>
Then, I have a h:selectOneMenu to select the number of items per page
<h:selectOneMenu id="myScroller"
value="#{backingBean.itemsPerPage}"
required="true" onchange="this.form.submit();"
valueChangeListener="#{backingBean.updateItemsPerPage}">
<f:selectItems value="#{backingBean.itemsPerPageArray}" />
</h:selectOneMenu>
My backing bean looks something like this:
public class BackingBean {
private boolean ascending;
private Long itemsPerPage;
private String sortColumn;
private ArrayList<SelectItem> itemsPerPageArray;
private ArrayList<SomeObject> list; // data for table
private HtmlDataScroller scroller;
// constructors, getters, setters, and other stuff here
public void updateItemsPerPage(ValueChangeEvent valueChangeEvent) {
itemsPerPage = (Long) valueChangeEvent.getNewValue();
resetScrollerIndex();
}
private void resetScrollerIndex() {
if (scroller!=null && scroller.isPaginator())
scroller.getUIData().setFirst(0);
}
// called in constructor
private void constructItemsPerPageArray() {
itemsPerPageArray = new ArrayList<SelectItem>();
itemsPerPageArray.add(new SelectItem(new Long(10), "10"));
itemsPerPageArray.add(new SelectItem(new Long(50), "50"));
itemsPerPageArray.add(new SelectItem(new Long(100), "100"));
itemsPerPageArray.add(new SelectItem(new Long(0), "All"));
}
}
To sum up, when I select the "All" item from the h:selectOneMenu, I get the exception mentioned above. Hopefully, I've included an appropriate level of detail. Thanks!
Alright, I think I found the answer...
If I remove the first line from the updateItemsPerPage method, the problem is fixed. The method now looks like:
public void updateItemsPerPage(ValueChangeEvent valueChangeEvent) {
resetScrollerIndex();
}
It thought I had tried that...
Related
I have a Facelets page with a <h:dataTable>. In each row there is a <h:selectBooleanCheckbox>. If the checkbox is selected the object behind the corresponding row should be set in the bean.
How do I do this?
How to get the selected rows or their data in a backing bean?
Or would it be better to do it with <h:selectManyCheckbox>?
Your best bet is to bind the <h:selectBooleanCheckbox> value with a Map<Item, Boolean> property where Item represents the object behind the corresponding row.
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:selectBooleanCheckbox value="#{bean.checked[item]}" />
</h:column>
...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
private Map<Item, Boolean> checked = new HashMap<Item, Boolean>();
private List<Item> items;
public void submit() {
List<Item> selectedItems = checked.entrySet().stream()
.filter(Entry::getValue)
.map(Entry::getKey)
.collect(Collectors.toList());
checked.clear(); // If necessary.
// Now do your thing with selectedItems.
}
// ...
}
You see, the map is automatically filled with all table items as key and the checkbox value is automatically set as map value associated with the item as key.
This only requires that the Item#equals() and Item#hashCode() is properly implemented as per their contracts.
If you can't guarantee that, then you'd better use a Map<RowId, Boolean> instead where RowId represents the type of the row identifier. Let's take an example that you've a Item object whose identifier property id is a Long:
public class Item {
private Long id;
// ...
}
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:selectBooleanCheckbox value="#{bean.checked[item.id]}" />
</h:column>
...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
private List<Item> items;
public void submit() {
List<Item> selectedItems = items.stream()
.filter(item -> checked.get(item.getId()))
.collect(Collectors.toList());
checked.clear(); // If necessary.
// Now do your thing with selectedItems.
}
// ...
}
In the following example I am using checkboxes to select two or more products to allow the user to compare product specifications on a new web page using JSF 2.0.
It took me a good while to find the following problem (totally obvious now of course) so thought it worth a mention for those trying to use pagination with BalusC's code above (nice answer BalusC, much simpler than I ever imagined it would be).
If you are using pagination you will get nullpointers at the line:
if (checked.get(item.getId()))
-in BalusC's code above.
This is because only displayed check boxes are added to the Map (doh; slap forehead).
For those products whose check boxes are never displayed, due to pagination, this line will result in a null pointer error and a check needs to be added to ignore these null pointers (assuming that all check boxes are unchecked on page load). In order for the user to tick a check box then they need to display the pagination page so all works well there after.
If some or all of the check boxes are required to be ticked on first page load then this will be of no help to you...you will have to manually add those to the Map in order for them to be displayed correctly on page load.
Note: because I am using a JPA 'Entity class from database' object I also needed to use #Transient for the id in my ProductTbl Entity Class as all variables are considered columns in the database by JPA, by default, unless prefixed with #Transient. Also I am using a second link to reset the check boxes, which calls clearSelections(), and my 'submit' is a link calling compareSelectedProducts() rather than a Submit button.
The full code is as follows:
In the 'ProductTbl' Entity class derived from the database :
#Transient
private Long id;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
In the backing bean 'ProductSelection':
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
private String errorMessage = "";
// List of all products.
private List<ProductTbl> products;
// List of products to compare.
private List<ProductTbl> compareProducts;
// Setters and getters for above...
public String compareSelectedProducts()
{
// Reset selected products store.
compareProducts = new ArrayList();
for (ProductTbl item: products)
{
// If there is a checkbox mapping for the current product then...
if(checked.get(item.getId()) != null)
{
// If checkbox is ticked then...
if (checked.get(item.getId()))
{
// Add product to list of products to be compared.
compareProducts.add(item);
}
}
}
if(compareProducts.isEmpty())
{
// Error message that is displayed in the 'ErrorPage.xhtml' file.
errorMessage = "No Products selected to compare specifications. Select two or more products by ticking the check box in the second column 'Cmpr'";
return "process_ErrorPage";
}
// Rest of code to get product specification data ready to be displayed.
return "process_CompareSelected";
}
public String clearSelections()
{
// Untick all checkbox selections.
checked.clear();
return "process_MainSearchResult";
}
In the JSF Web page 'MainSearchResult.xhtml':
<h:commandLink action="#{productSelection.compareSelectedProducts()}" value="Cmpr Specification Comparison Table" />
<h:commandLink action="#{productSelection.clearSelections()}" value="Clear Selected" />
<h:dataTable value="#{productSelection.products}" rows="#{productSelection.numberRowsToDisplay}" first="#{productSelection.rowStart}" var="item" headerClass="table-header" >
<h:column>
<f:facet name="header">
<h:outputText style="font-size:12px" value="Cmpr" />
</f:facet>
<div style="text-align:center;" >
<h:selectBooleanCheckbox value="#{productSelection.checked[item.id]}" />
</div>
</h:column>
</h:dataTable>
In the 'faces-config.xml' file:
<navigation-rule>
<navigation-case>
<from-outcome>process_MainSearchResult</from-outcome>
<to-view-id>/MainSearchResult.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>process_CompareSelected</from-outcome>
<to-view-id>/CompareSelected.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>process_ErrorPage</from-outcome>
<to-view-id>/ErrorPage.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
One way to send in a parameter via <h:selectBooleanCheckbox> is to send it in via the title of the Checkbox. In the ValueChangeListener, you can get it from the component using a getAttributes().get("title"). This helps in cases where you want to send an id value as a parameter (as opposed to the selected row index).
I'm trying to implement a <p:selectManyCheckbox> but I'm having no success.
Now I have the following architecture:
Course - have many Disciplines
Discipline - belongs to none, one or many Courses.
In Course class I have two ArrayList<Discipline>:
public class CourseMBean{
(...)
// Stores all disciplines
private static ArrayList<Discipline> allDisciplines;
// Stores only the disciplines that's already associated with this course.
private static ArrayList<Discipline> courseDisciplines;
(get and set for the arraylists)
(...)
}
All data comes from a MYSQL DB, but that isn't the question. Now I want create a new Course, so I don't have anything in courseDisciplines.
I want show allDisciplines in checkboxes, and want that when user select one checkbox, the object Discipline of this checkbox be added in courseDisciplines - and when unselect one checkbox, remove the discipline from the courseDsiciplines.
My JSF 2.0 code is following:
<p:selectManyCheckbox id="disciplines" value="#{courseMBean.allDisciplines}" layout="grid" columns="2">
<f:selectItems value="#{courseMBean.courseDisciplines}" />
</p:selectManyCheckbox>
This actually shows all disciplines without any selected checkboxes, what's right. But when I select some checkboxes and submit the form I try to print the elements inside courseDisciplines, and this don't show anything in console.
What I'm doing wrong?
when I select some checkboxes and submit the form I try to print the elements inside courseDisciplines
As the courseDisciplines actually represents the available items not the selected items, it seems that you misunderstood some basic concepts around the UISelectMany and UISelectItem(s) components.
The <f:selectItem(s)> (from the UISelectItem(s) family) represent the available items. It are exactly those items which are shown in the UI and which the enduser has to choose from.
The value attribute of <p:selectManyCheckbox> (from the UISelectMany family, like <h:selectManyCheckbox> and <h:selectManyMenu>) represent the (pre)selected items. If this is null or empty during first display of the form, then nothing is preselected. Or if this contains some preselected items, then only those available items which are equal() will be preselected.
When the enduser has changed the selection in the UI and submits the form, then all selected items will end up in the value attribute of UISelectMany component. The UISelectItem(s) remains unchanged.
Here's a basic kickoff example:
<p:selectManyCheckbox value="#{bean.selectedItems}">
<f:selectItems value="#{bean.availableItems}" />
</p:selectManyCheckbox>
<p:commandButton value="Submit" action="#{bean.submit}" />
<p:messages autoUpdate="true" />
private List<String> selectedItems; // +getter +setter
private List<String> availableItems; // +getter (no setter necessary!)
#PostConstruct
public void init() {
availableItems = new ArrayList<String>();
availableItems.add("one");
availableItems.add("two");
availableItems.add("three");
}
public void submit() {
System.out.println("Selected items: " + selectedItems);
}
(all other <p:selectManyXxx> and <h:selectManyXxx> components work exactly the same)
When a complex Javabean object like Discipline comes into the picture, then you need to make sure that there's a Converter for that so that JSF can properly convert between it and String for usage in generated HTML output and as HTTP request parameter (HTML and HTTP namely can't pass around nor hold Java objects, but only character sequences which are in Java represented by String).
This is perhaps your root problem. You said that nothing is printed to the console on submit. But it could be as good the case that the whole submit() method is actually never being invoked. You're not explicit enough on this. If the whole action method is indeed never invoked (i.e. a debug breakpoint doesn't hit there, or another System.out.println() printing a static string is never shown in console), then you've actually most likely a conversion error. If you have used <h|p:message(s)> the right way, or have paid attention to server log about queued but undisplayed faces messages, then you should have noticed it.
In that case, you need to implement a Converter which converts between Discipline and String.
#FacesConverter(forClass=Discipline.class)
public class DisciplineConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
// Write code here to convert from String to Discipline.
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
// Write code here to convert from Discipline to String.
}
}
More than often the DB ID is being used as String representation. See also the section "Complex object as selected item" of this answer on a related question: How to populate options of h:selectOneMenu from database?
I'm working on an application with Spring, JSF, Hibernate following the example in this link.
When I run this code with some values populated in the database, this should have the datatable header name along with all the rows retrieved. But when there is no value in the database and when I run the code, this shouldn't display the datatable header name. The datatable header name should only be displayed only when there is some values retrieved from the database and not during startup. How do I do this?
Add a integer property to your managed bean which gets size of list
private int sizeOfTable;
public int getSizeOfTable()
{
return customerBo.findAllCustomer().size();
}
public int setSizeOfTable(int sizeOfTable(int sizeOfTable)
{
this.sizeOfTable = sizeOfTable;
}
You can control the data table by rendered attribute.
<h:dataTable value="#{customer.getCustomerList()}" var="c" rendered="#{customer.sizeOfTable == 0}"
styleClass="order-table"
headerClass="order-table-header"
rowClasses="order-table-odd-row,order-table-even-row"
>
If the size of the data table is 0 then the data table will not be rendered. If you want to hide the header only you can add the rendered value to h:column
However, Database rows are retreived for a single count operation. It is not a good practice. You should handle database accesses according to your needs. A database access is a IO operation for a computer which is the most costy part of operations.
My suggestion is to store the list in a data property and work on it.
private List<Customer> customerList;
public List<Customer> getCustomerList(){
if(customerList == null)
{
customerList = customerBo.findAllCustomer();
}
return customerList;
}
and you should edit your size property accordingly.
public int getSizeOfTable()
{
return customerList.size();
}
Just let the rendered attribute of the <h:dataTable> check if the data model is not empty.
<h:dataTable value="#{customer.customerList}" ... rendered="#{not empty customer.customerList}">
No need to clutter the model with an irrelevant property, like as suggested by the other answer.
See also:
How do I display a message if a jsf datatable is empty?
I use <h:dataTable> to list data from database. We have many records in page, now I would like to select multiple records with a checkbox in each row. How can I achieve this?
I assume that your entity is that well-designed that it has an unique technical identifier, for example the auto increment sequence from the DB.
public class Entity {
private Long id;
// ...
}
If not, you'll need to add it.
Then, add a Map<Long, Boolean> property to the bean which is tied to the table.
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
(preinitialization can also happen in (post)constructor, take your pick, at least JSF won't do it for you; oh, give it a getter as well, a setter is not necessary)
Then, add a column with a checkbox which maps to the boolean value by entity ID as key.
<h:dataTable value="#{bean.entities}" var="entity">
<h:column>
<h:selectBooleanCheckbox value="#{bean.checked[entity.id]}" />
</h:column>
...
</h:dataTable>
<h:commandButton value="Delete" action="#{bean.delete}" />
Now, in the action method associated with the delete button, you can collect and delete the checked items as follows:
public void delete() {
List<Entity> entitiesToDelete = new ArrayList<Entity>();
for (Entity entity : entities) {
if (checked.get(entity.getId())) {
entitiesToDelete.add(entity);
}
}
entityService.delete(entitiesToDelete);
checked.clear();
loadEntities();
}
I have a Facelets page with a <h:dataTable>. In each row there is a <h:selectBooleanCheckbox>. If the checkbox is selected the object behind the corresponding row should be set in the bean.
How do I do this?
How to get the selected rows or their data in a backing bean?
Or would it be better to do it with <h:selectManyCheckbox>?
Your best bet is to bind the <h:selectBooleanCheckbox> value with a Map<Item, Boolean> property where Item represents the object behind the corresponding row.
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:selectBooleanCheckbox value="#{bean.checked[item]}" />
</h:column>
...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
private Map<Item, Boolean> checked = new HashMap<Item, Boolean>();
private List<Item> items;
public void submit() {
List<Item> selectedItems = checked.entrySet().stream()
.filter(Entry::getValue)
.map(Entry::getKey)
.collect(Collectors.toList());
checked.clear(); // If necessary.
// Now do your thing with selectedItems.
}
// ...
}
You see, the map is automatically filled with all table items as key and the checkbox value is automatically set as map value associated with the item as key.
This only requires that the Item#equals() and Item#hashCode() is properly implemented as per their contracts.
If you can't guarantee that, then you'd better use a Map<RowId, Boolean> instead where RowId represents the type of the row identifier. Let's take an example that you've a Item object whose identifier property id is a Long:
public class Item {
private Long id;
// ...
}
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:selectBooleanCheckbox value="#{bean.checked[item.id]}" />
</h:column>
...
</h:dataTable>
<h:commandButton value="submit" action="#{bean.submit}" />
public class Bean {
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
private List<Item> items;
public void submit() {
List<Item> selectedItems = items.stream()
.filter(item -> checked.get(item.getId()))
.collect(Collectors.toList());
checked.clear(); // If necessary.
// Now do your thing with selectedItems.
}
// ...
}
In the following example I am using checkboxes to select two or more products to allow the user to compare product specifications on a new web page using JSF 2.0.
It took me a good while to find the following problem (totally obvious now of course) so thought it worth a mention for those trying to use pagination with BalusC's code above (nice answer BalusC, much simpler than I ever imagined it would be).
If you are using pagination you will get nullpointers at the line:
if (checked.get(item.getId()))
-in BalusC's code above.
This is because only displayed check boxes are added to the Map (doh; slap forehead).
For those products whose check boxes are never displayed, due to pagination, this line will result in a null pointer error and a check needs to be added to ignore these null pointers (assuming that all check boxes are unchecked on page load). In order for the user to tick a check box then they need to display the pagination page so all works well there after.
If some or all of the check boxes are required to be ticked on first page load then this will be of no help to you...you will have to manually add those to the Map in order for them to be displayed correctly on page load.
Note: because I am using a JPA 'Entity class from database' object I also needed to use #Transient for the id in my ProductTbl Entity Class as all variables are considered columns in the database by JPA, by default, unless prefixed with #Transient. Also I am using a second link to reset the check boxes, which calls clearSelections(), and my 'submit' is a link calling compareSelectedProducts() rather than a Submit button.
The full code is as follows:
In the 'ProductTbl' Entity class derived from the database :
#Transient
private Long id;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
In the backing bean 'ProductSelection':
private Map<Long, Boolean> checked = new HashMap<Long, Boolean>();
private String errorMessage = "";
// List of all products.
private List<ProductTbl> products;
// List of products to compare.
private List<ProductTbl> compareProducts;
// Setters and getters for above...
public String compareSelectedProducts()
{
// Reset selected products store.
compareProducts = new ArrayList();
for (ProductTbl item: products)
{
// If there is a checkbox mapping for the current product then...
if(checked.get(item.getId()) != null)
{
// If checkbox is ticked then...
if (checked.get(item.getId()))
{
// Add product to list of products to be compared.
compareProducts.add(item);
}
}
}
if(compareProducts.isEmpty())
{
// Error message that is displayed in the 'ErrorPage.xhtml' file.
errorMessage = "No Products selected to compare specifications. Select two or more products by ticking the check box in the second column 'Cmpr'";
return "process_ErrorPage";
}
// Rest of code to get product specification data ready to be displayed.
return "process_CompareSelected";
}
public String clearSelections()
{
// Untick all checkbox selections.
checked.clear();
return "process_MainSearchResult";
}
In the JSF Web page 'MainSearchResult.xhtml':
<h:commandLink action="#{productSelection.compareSelectedProducts()}" value="Cmpr Specification Comparison Table" />
<h:commandLink action="#{productSelection.clearSelections()}" value="Clear Selected" />
<h:dataTable value="#{productSelection.products}" rows="#{productSelection.numberRowsToDisplay}" first="#{productSelection.rowStart}" var="item" headerClass="table-header" >
<h:column>
<f:facet name="header">
<h:outputText style="font-size:12px" value="Cmpr" />
</f:facet>
<div style="text-align:center;" >
<h:selectBooleanCheckbox value="#{productSelection.checked[item.id]}" />
</div>
</h:column>
</h:dataTable>
In the 'faces-config.xml' file:
<navigation-rule>
<navigation-case>
<from-outcome>process_MainSearchResult</from-outcome>
<to-view-id>/MainSearchResult.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>process_CompareSelected</from-outcome>
<to-view-id>/CompareSelected.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>process_ErrorPage</from-outcome>
<to-view-id>/ErrorPage.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
One way to send in a parameter via <h:selectBooleanCheckbox> is to send it in via the title of the Checkbox. In the ValueChangeListener, you can get it from the component using a getAttributes().get("title"). This helps in cases where you want to send an id value as a parameter (as opposed to the selected row index).