Jumat, 24 Januari 2014

Using datatables



WARNING - OUTDATED CONTENT!


This article is targeted on JSF 1.2. For JSF 2.0, using datatables can be approached much more elegantly with help of a @ViewScoped managed bean. Please checkout this article which contains two basic CRUD examples, one for JSF 2.0 on Servlet 2.5 and other for JSF 2.0 on Servlet 3.0 which supports passing method arguments in EL 2.2.







Introduction


This whole JSF datatable howto is based on the use of a backing bean with the request scope. You can use it in a session scoped bean, but you have to do some finetuning and those will be explained in the text. It's recommended to use at least JSF RI 1.1_02 or JSF RI 1.2_02 (and thus not 1.x_01 or older; newer is always better, pick the newest if you can), because the 1.x_02 has some important bugfixes. The code examples given below are based on JSF RI 1.2 (Mojarra) with at least Java EE 5.0. At the bottom of this article you can download a WAR containing all examples in Request as well as Session scope using Sun JSF Mojarra 1.2.





Create DTO class


The h:dataTable dynamic tables are nice, you can put any List or DataModel of DTO or Map objects in it. In this article we'll use a List of DTO's.



Imagine a SQL database containing a table with three fields: ID, Name and Value. First create a data wrapper class which represents each single row of the table, this is just a simple class with the table fields (columns) definied as encapsulated private variables which can be accessed by public getters and setters. Such a class is also known as a DTO (Data Transfer Object).



Here is an example called MyData.java:



package mymodel;

public class MyData {

// Init --------------------------------------------------------------------------------------

private Long id;
private String name;
private String value;

// Getters -----------------------------------------------------------------------------------

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getValue() {
return value;
}

// Setters -----------------------------------------------------------------------------------

public void setId(Long id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setValue(String value) {
this.value = value;
}

}


It's a good practice to use wrapper datatype objects (Long, Boolean, Integer, etc) instead of primitive datatypes (long, boolean, int, etc) for the properties, because the database fields can contain null values which can not be put into a primitive at all.


Back to top


Retrieve and store data


You can use Hibernate or just plain JDBC to retrieve SQL data. It must return a List of MyData objects. This has to be stored in a private variable dataList which can be accessed by a getter and setter. The setter of the dataList is actually not used by JSF pages (only UIInput values require setters). You can safely remove it if you don't use it somewhere else in the backing bean logic.


The data layer and the DAO pattern is explained in this tutorial: DAO tutorial - the data layer.


Here is the relevant java code for the backing bean. You can load the dataList in either the constructor or initialization block of the bean:




package mypackage;

import java.util.List;

import javax.faces.context.FacesContext;

import mydao.DAOException;
import mydao.DAOFactory;
import mydao.MyDataDAO;
import mymodel.MyData;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private MyDataDAO dataDAO = DAOFactory.getInstance("javabase").getMyDataDAO();
private List dataList;

{
loadDataList(); // Preload in initialization block.
}

// Constructor -------------------------------------------------------------------------------

public MyBean() {
loadDataList(); // OR preload in constructor.
}

// Actions -----------------------------------------------------------------------------------

private void loadDataList() {

// Do your "SELECT * FROM mydata" thing.
try {
dataList = dataDAO.list();
} catch (DAOException e) {
setErrorMessage(e.getMessage() + " Cause: " + e.getCause());
e.printStackTrace();
}
}

// Getters -----------------------------------------------------------------------------------

public List getDataList() {
return dataList;
}

}



If you're going to use this bean for other purposes than only showing the datalist, then you can better move the loading to the getter so that the data won't be loaded everytime when the bean is constructed. Also use lazy loading in the getter so that the data won't be loaded everytime when the getter is called during the bean's life. Here is an example:





public List getDataList() {
if (dataList == null) {
loadDataList(); // Preload by lazy loading.
}
return dataList;
}



Session scope: once loaded the dataList will persist during the session and won't be garbaged after one request in the same session. If you want to reload the session data every view to get the most recent data, then do as follows:





public List getDataList() {
if (FacesContext.getCurrentInstance().getRenderResponse()) {
loadDataList(); // Reload to get most recent data.
}
return dataList;
}



The FacesContext#getRenderResponse() returns true when this getter is invoked during the render response phase. This is to prevent duplicate reloads as this getter can also be invoked during the apply request values phase or the process validations phase. Also see Debug JSF lifecycle.


You can also consider to use two beans: one request-scoped bean for the views and form actions and one session-scoped bean for the session data. You can inject the session scoped bean in the request scoped bean as managed property using faces-config.xml. And in the request scoped bean you can invoke reload of the data after save/update/delete action of the session data. Also see Communication in JSF.


Storing the data in the session scope has not only an impact on the server memory usage, but also on the user experience when the data is not for display only. Imagine what would happen with the data when the user manages (edit/delete) the data in multiple browser windows/tabs in the same session.


Instead of using the session scope you can also consider using the Tomahawk t:dataTable component. It has an extra attribute 'preserveDataModel' which you should set to true. This will store the datatable value in the component tree state in session and restore it during the next request. This way you can also avoid unforeseen concurrent updates on the datamodel in between the requests.


Back to top


Show data contents in datatable


First define the backing bean as a request scoped managed bean myBean in the faces-config.xml.




myBean
mypackage.MyBean
request


Session scope: if you want to use the session scope, just change request to session.


Now you can use the h:dataTable to show the contents of the retrieved list.


The relevant JSF code looks like:



 value="#{myBean.dataList}" var="dataItem">

name="header">
value="ID" />

value="#{dataItem.id}" />



name="header">
value="Name" />

value="#{dataItem.name}" />



name="header">
value="Value" />

value="#{dataItem.value}" />



The h:dataTable value should contain the retrieved list from the backing bean definied as managed bean myBean. The h:dataTable var is just a holder for every MyData object of the list. This can be used to access the getters and setters of MyData.java.


The f:facet components are optional. In this case the f:facet name="header" represents the table header (in HTML terms, the element). If you don't need it, you can just leave it away.


Back to top


Add backing bean action to every row


You can use h:commandLink or h:commandButton in one or more columns to invoke a backing bean action and pass the appropriate MyData object to MyBean. This is faster than passing a f:attribute tag with an ID to retrieve the selected item by ID straight from the database. To get the selected row, use the h:dataTable binding to dynamically bind the state of the datatable to the backing bean, so that it knows which row was clicked.


Update the JSP as follows:




binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">

name="header">
value="ID" />

value="#{dataItem.id}" action="#{myBean.editDataItem}" />


...



Another way is using f:setPropertyActionListener to set the currently iterated MyData object as a property of MyBean.




value="#{myBean.dataList}" var="dataItem">

name="header">
value="ID" />

value="#{dataItem.id}" action="#{myBean.editDataItem}">
target="#{myBean.dataItem}" value="#{dataItem}" />



...



Back to top


Get selected datatable row


Clicking at the h:commandLink of every row will invoke the editDataItem() method of the backing bean. The MyData item belonging to the row can be retrieved using the getRowData() method of the HtmlDataTable class, which is bound by h:dataTable binding. Finally store the MyData item as dataItem.


As we aren't going to make the ID of the MyData item editable, we need to store its value in a HtmlInputHidden component bound to the page so that it can be retained in the next request, even if the validation phase fails.


So extend the backing bean with the following code to retrieve the MyData item of the row selected:



package mypackage;

import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlInputHidden;

import mymodel.MyData;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private HtmlDataTable dataTable;
private MyData dataItem = new MyData();
private HtmlInputHidden dataItemId = new HtmlInputHidden();

// Actions -----------------------------------------------------------------------------------

public String editDataItem() {

// Get selected MyData item to be edited.
dataItem = (MyData) dataTable.getRowData();

// Store the ID of the data item in hidden input element.
dataItemId.setValue(dataItem.getId());

return "edit"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public HtmlDataTable getDataTable() {
return dataTable;
}

public MyData getDataItem() {
return dataItem;
}

public HtmlInputHidden getDataItemId() {
return dataItemId;
}

// Setters -----------------------------------------------------------------------------------

public void setDataTable(HtmlDataTable dataTable) {
this.dataTable = dataTable;
}

public void setDataItem(MyData dataItem) {
this.dataItem = dataItem;
}

public void setDataItemId(HtmlInputHidden dataItemId) {
this.dataItemId = dataItemId;
}

}


Session scope: you don't need to pre-instantiate the dataItem with new MyData() as this won't be garbaged after one request. That same way you don't need to retain the ID of the MyData item, so you can safely remove the dataItemId property along with all related logic.



When you're using the f:setPropertyActionListner approach, then the backing bean class should rather look like:



package mypackage;

import javax.faces.component.html.HtmlInputHidden;

import mymodel.MyData;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private MyData dataItem;
private HtmlInputHidden dataItemId = new HtmlInputHidden();

// Actions -----------------------------------------------------------------------------------

public String editDataItem() {

// The dataItem is already set by f:setPropertyActionListener.
// Store the ID of the data item in hidden input element.
dataItemId.setValue(dataItem.getId());

return "edit"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public MyData getDataItem() {
return dataItem;
}

public HtmlInputHidden getDataItemId() {
return dataItemId;
}

// Setters -----------------------------------------------------------------------------------

public void setDataItem(MyData dataItem) {
this.dataItem = dataItem;
}

public void setDataItemId(HtmlInputHidden dataItemId) {
this.dataItemId = dataItemId;
}

}


Back to top


Show selected data item


The contents of the MyData item retrieved can be shown in a new JSP file for edit or just for view. In this example we will show the item for edit.


The relevant JSF code:




columns="2">
value="ID" />
value="#{myBean.dataItem.id}" />

value="Name" />
value="#{myBean.dataItem.name}" />

value="Value" />
value="#{myBean.dataItem.value}" />


binding="#{myBean.dataItemId}" />
value="Save" action="#{myBean.saveDataItem}" />


Session scope: as mentioned in the previous chapter you don't need the h:inputHidden element, so just keep it away.


Back to top


Save edited data item


Saving is simple, the JSF lifecycle already has updated the MyData item for you. Just do your DAO update thing.


The relevant java code of the backing bean:



package mypackage;

public class MyBean {

// Actions -----------------------------------------------------------------------------------

public String saveDataItem() {

// Retain the ID of the data item from hidden input element.
dataItem.setId(Long.valueOf(dataItemId.getValue().toString()));

// Do your "UPDATE mydata SET values WHERE id" thing.
try {
dataDAO.save(dataItem);
} catch (DAOException e) {
setErrorMessage(e.getMessage() + " Cause: " + e.getCause());
e.printStackTrace();
}

return "list"; // Navigation case.
}

}


Session scope: the hidden input element is not needed, so you can safely remove the related code lines.



Back to top


Editable datatable


You can also use the datatable to mass-edit the datalist. Also here saving the edited datalist is simple, the JSF lifecycle already has updated the dataList for you. You just need to do your DAO update thing.


The relevant JSF code looks like:




value="#{myBean.dataList}" var="dataItem">

name="header">
value="ID" />

value="#{dataItem.id}" />



name="header">
value="Name" />

value="#{dataItem.name}" />



name="header">
value="Value" />

value="#{dataItem.value}" />




value="Save" action="#{myBean.saveDataList}" />


The relevant java code of the backing bean:



package mypackage;

public class MyBean {

// Actions -----------------------------------------------------------------------------------

public String saveDataList() {

// Do your "UPDATE mydata SET values WHERE id" thing for each data item.
try {
dataDAO.save(dataList);
} catch (DAOException e) {
setErrorMessage(e.getMessage() + " Cause: " + e.getCause());
e.printStackTrace();
}

return "list"; // Navigation case.
}

}


Back to top


Add new rows to datatable


Adding new rows to the datatable is in fact easy, just add a new and empty MyData item to the dataList and let the rendered attribute of the components intercept on the ID being null. Also add a counter to inform the bean how many rows are been added so that it knows how to prepare the list and how many of the last rows have to be saved.


But in the request scope you'll have to store the counter in a HtmlInputHidden component bound to the page so that its value can be retained in the next request, regardless the outcome of the validations phase.


The relevant JSF code looks like:




value="#{myBean.dataList}" var="dataItem">

name="header">
value="ID" />

value="#{dataItem.id}" rendered="#{dataItem.id != null}" />
value="new" rendered="#{dataItem.id == null}" />



name="header">
value="Name" />

value="#{dataItem.name}" rendered="#{dataItem.id != null}" />
value="#{dataItem.name}" rendered="#{dataItem.id == null}" />



name="header">
value="Value" />

value="#{dataItem.value}" rendered="#{dataItem.id != null}" />
value="#{dataItem.value}" rendered="#{dataItem.id == null}" />




binding="#{myBean.addCount}" converter="javax.faces.Integer" />
value="Add" action="#{myBean.addNewDataItem}" />
value="Save" action="#{myBean.saveNewDataItems}" />


The relevant java code of the backing bean (note that you should preload the dataList by lazy loading in the getter and that the loadDataList() method is updated):



package mypackage;

import javax.faces.component.html.HtmlInputHidden;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private HtmlInputHidden addCount = new HtmlInputHidden();

// Constructors ------------------------------------------------------------------------------

public MyBean() {
// Preset it to 0 to avoid NullPointerExceptions.
addCount.setValue(0);
}

// Actions -----------------------------------------------------------------------------------

private void loadDataList() {

// Do your "SELECT * FROM mydata" thing.
try {
dataList = dataDAO.list();
} catch (DAOException e) {
setErrorMessage(e.getMessage() + " Cause: " + e.getCause());
e.printStackTrace();
}

// If any, prepare the list with the new added items.
for (int i = 0; i < (Integer) addCount.getValue(); i++) {
dataList.add(new MyData());
}
}

public String addNewDataItem() {

// Add new MyData item to the data list.
dataList.add(new MyData());
addCount.setValue(((Integer) addCount.getValue()) + 1);

return null; // Postback to same view. You can declare method void as well.
}

public String saveNewDataItems() {

// Do your "UPDATE mydata SET values WHERE id" thing for each new data item.
try {
int size = dataList.size();
dataDAO.save(dataList.subList(size - ((Integer) addCount.getValue()), size));
} catch (DAOException e) {
setErrorMessage(e.getMessage() + " Cause: " + e.getCause());
e.printStackTrace();
}

// Reset the amount of newly added items.
addCount.setValue(0);

return "list"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public List getDataList() {
if (dataList == null) {
loadDataList(); // Preload by lazy loading.
}
return dataList;
}

public HtmlInputHidden getAddCount() {
return addCount;
}

// Setters -----------------------------------------------------------------------------------

public void setAddCount(HtmlInputHidden addCount) {
this.addCount = addCount;
}

}


Session scope: as you don't need to retain the amount of newly added items in the next request -because the complete list already been kept in the session-, you can just remove the addCount property and its getter and setter. Finally replace the property by a simple int property:





private int addCount;



Update the code in the action methods accordingly. Use it to increment the value in the addNewDataItem() method, to get the sublist in the saveNewDataItems() method and set its value to 0. No getter and setter is needed. Also the h:inputHidden in the JSF page is not needed.



If you're using the FacesContext.getCurrentInstance().getRenderResponse() trick in the getDataList(), then realize that it would override the newly added data items. So remove it or change the code accordingly that it will only reload the datalist in the action methods, e.g. after the save action.



Back to top


Select multiple rows


You also can select multiple rows using checkboxes. There are in general two ways to achieve this: one by adding a boolean property to the DTO and another by adding a Map to the backing bean.


Here is the first way: just add a boolean property to the MyData.java DTO class and use h:selectBooleanCheckbox to add a checkbox column which triggers this property.



package mypackage;

public class MyData {

// Init --------------------------------------------------------------------------------------

private boolean selected;

// Getters -----------------------------------------------------------------------------------

public boolean isSelected() {
return selected;
}

// Setters -----------------------------------------------------------------------------------

public void setSelected(boolean selected) {
this.selected = selected;
}

}


Extend the datatable with a checkbox column and add a commandbutton to the form:




value="#{myBean.dataList}" var="dataItem">

name="header">
value="Select" />

value="#{dataItem.selected}" />


...


value="Get selected items" action="#{myBean.getSelectedItems}" />


Finally add the commandbutton action to the backing bean to get the selected items:



package mypackage;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private List selectedDataList;

// Actions -----------------------------------------------------------------------------------

public String getSelectedItems() {

// Get selected items.
selectedDataList = new ArrayList();
for (MyData dataItem : dataList) {
if (dataItem.isSelected()) {
selectedDataList.add(dataItem);
dataItem.setSelected(false); // Reset.
}
}

// Do your thing with the MyData items in List selectedDataList.

return "selected"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public List getSelectedDataList() {
return selectedDataList;
}

}


Here is the another way. If you do not want to extend the MyData object with the boolean property selected for some reasons, then you can also consider the following approach with a Map where the SomeIDType must represent the same type as the unique ID of the DAO, in this case Long:




value="#{myBean.dataList}" var="dataItem">

name="header">
value="Select" />

value="#{myBean.selectedIds[dataItem.id]}" />


...


value="Get selected items" action="#{myBean.getSelectedItems}" />


The appropriate backing bean code:



package mypackage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private Map selectedIds = new HashMap();
private List selectedDataList;

// Actions -----------------------------------------------------------------------------------

public String getSelectedItems() {

// Get selected items.
selectedDataList = new ArrayList();
for (MyData dataItem : dataList) {
if (selectedIds.get(dataItem.getId()).booleanValue()) {
selectedDataList.add(dataItem);
selectedIds.remove(dataItem.getId()); // Reset.
}
}

// Do your thing with the MyData items in List selectedDataList.

return "selected"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public Map getSelectedIds() {
return selectedIds;
}

public List getSelectedDataList() {
return selectedDataList;
}

}



Back to top


Select row by radio button


Using h:selectOneRadio in a JSF datatable is a bit tricky. When putting just a h:selectOneRadio component in a h:column column, the radio buttons gets rendered, but they are not grouped together. Each radio button is attached to one row object and not to the datatable itself, resulting the other radiobuttons not getting unselected when you select one radiobutton. To get it work, use JavaScript to unselect all other radio buttons when one radio button is selected. This is only good for the human eye.


The real work is done by the valuechangelistener attribute of the h:selectOneRadio which fires a ValueChangeEvent. When you clicks a radio button inside a row and then clicks the command button outside the row, the row index of the selected row will be lost. The fired ValueChangeEvent is processed during the third phase of the JSF lifecycle, where the row index is still available. The row index is reset during the fourth phase and the command button action is invoked during the fifth phase. So the trick is to store the object of the selected row during the processing of the ValueChangeEvent and finally handle with it during the command button action.


Add a h:selectOneRadio column to the datatable and supply a fake selectItem to it:




binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">

name="header">
value="Select" />

valueChangeListener="#{myBean.setSelectedItem}"
onclick="dataTableSelectOneRadio(this);">
itemValue="null" />



...


value="Get selected item" action="#{myBean.getSelectedItem}" />


Add the valuechangelistener and the commandbutton action to the backing bean to get the selected item and to handle with it:



package mypackage;

import javax.faces.event.ValueChangeEvent;

public class MyBean {

// Actions -----------------------------------------------------------------------------------

public void setSelectedItem(ValueChangeEvent event) {

// Catch the MyData item during the third phase of the JSF lifecycle.
dataItem = (MyData) dataTable.getRowData();
}

public String getSelectedItem() {

// The MyData item is already catched in setSelectedItem().
selectedDataList = new ArrayList();
selectedDataList.add(dataItem);

// Do your thing with the MyData item in List selectedDataList.

return "selected"; // Navigation case.
}

}


Here is the JavaScript function which resets all other radio buttons of the same column.



function dataTableSelectOneRadio(radio) {
var id = radio.name.substring(radio.name.lastIndexOf(':'));
var el = radio.form.elements;
for (var i = 0; i < el.length; i++) {
if (el[i].name.substring(el[i].name.lastIndexOf(':')) == id) {
el[i].checked = false;
}
}
radio.checked = true;
}


Back to top


Sorting datatable


Update: there's a newer article out for more effective sorting at DAO level. You may find it useful: Effective datatable paging and sorting.


As datatables can be filled with List objects, you can easily sort a datatable using Collections.sort(). The best approach is to add a h:commandLink tag with one f:attribute tag to each column header. The h:commandLink tag should invoke the sorting in the backing bean. The f:attribute tag should pass the column name/type, or in this case, the name of the field getter of the DTO class. And it should be nice to reverse the sorting when clicking again at the same column header.


Add commandlinks with params to the column headers of the datatable:




binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">

name="header">
actionListener="#{myBean.sortDataList}">
name="sortField" value="getId" />
value="ID" />


value="#{dataItem.id}" />



name="header">
actionListener="#{myBean.sortDataList}">
name="sortField" value="getName" />
value="Name" />


value="#{dataItem.name}" />



name="header">
actionListener="#{myBean.sortDataList}">
name="sortField" value="getValue" />
value="Value" />


value="#{dataItem.value}" />



value="#{myBean.sortField}" />
value="#{myBean.sortAscending}" />


Session scope: you don't need the h:inputHidden elements as the current sortField and sortAscending won't be garbaged after one request.


Now extend the backing bean with sortDataList() to set the sort field and the sort order. Use the f:attribute value to indicate which MyData field should be sorted. The DTOComparator is described later.



package mypackage;

import java.util.Collections;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import myutil.DTOComparator;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private String sortField = null;
private boolean sortAscending = true;

// Actions -----------------------------------------------------------------------------------

public void sortDataList(ActionEvent event) {
String sortFieldAttribute = getAttribute(event, "sortField");

// Get and set sort field and sort order.
if (sortField != null && sortField.equals(sortFieldAttribute)) {
sortAscending = !sortAscending;
} else {
sortField = sortFieldAttribute;
sortAscending = true;
}

// Sort results.
if (sortField != null) {
Collections.sort(dataList, new DTOComparator(sortField, sortAscending));
}
}

// Getters -----------------------------------------------------------------------------------

public String getSortField() {
return sortField;
}

public boolean getSortAscending() {
return sortAscending;
}

// Setters -----------------------------------------------------------------------------------

public void setSortField(String sortField) {
this.sortField = sortField;
}

public void setSortAscending(boolean sortAscending) {
this.sortAscending = sortAscending;
}

// Helpers -----------------------------------------------------------------------------------

private static String getAttribute(ActionEvent event, String name) {
return (String) event.getComponent().getAttributes().get(name);
}

}


Session scope: you don't need to pre-instantiate the sortField and sortAscending with null and true as those won't be garbaged after one request. You also have to move the piece of Java code after the "// Sort results." comment at the end of sortDataList to the end of loadDataList. This is because the reloading of the dataList in the getDataList() method will override the sorting otherwise.



    public void loadDataList() {

...

// Sort results.
if (sortField != null) {
Collections.sort(dataList, new DTOComparator(sortField, sortAscending));
}
}


Here is the DTOComparator.java which can be very useful to sort data transfer objects in a List. It implements the Comparator interface.



package myutil;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
* Sorts a collection of data transfer objects (DTO's) based on the name of the getter of the field
* to sort on. For example: the DTO MyData has three fields ID, Name and Value, all with appropriate
* getters and setters. If you want to sort on the field Name, which has a getter called "getName",
* then invoke the sorting as follows:
*

* Collections.sort(dataList, new DTOComparator("getName", true));
*

* The following call is also supported. The "get" will be prefixed automatically if not present:
*

* Collections.sort(dataList, new DTOComparator("name", true));
*

* You can use "deep" getters when a DTO contains one or more nested DTO's. For example: MyData1
* contains a MyData2 property which contains a String property "name". If you want to sort
* myData1List on the "name" property of MyData2, then separate the getters by dots and invoke the
* sorting as follows:
*

* Collections.sort(myData1List, new DTOComparator("myData2.name", true));
*

* The boolean 2nd parameter indicates the sort order. If true, then the collection will be sorted
* ascending at natural sort order. If false, then the collection will be sorted descending at
* natural sort order. The default value is true.
*


* Very useful for lists of DTO's used in JSF datatables.
*
* @author BalusC
* @link http://balusc.blogspot.com/2006/06/using-datatables.html
*/
public class DTOComparator implements Comparator {

// Init --------------------------------------------------------------------------------------

private List getters;
private boolean ascending;

// Constructor -------------------------------------------------------------------------------

/**
* @param getter The name of the getter of the field to sort on.
* @param ascending The sort order: true = ascending, false = descending.
*/
public DTOComparator(String getter, boolean ascending) {
this.getters = new ArrayList();
for (String name : getter.split("\\.")) {
if (!name.startsWith("get")) {
name = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
}
this.getters.add(name);
}
this.ascending = ascending;
}

/**
* @param getter The name of the getter of the field to sort on.
*/
public DTOComparator(String getter) {
this(getter, true);
}

// Actions -----------------------------------------------------------------------------------

/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
try {
Iterator iter = getters.iterator();
while (o1 != null && o2 != null && iter.hasNext()) {
String getter = iter.next();
o1 = o1.getClass().getMethod(getter, new Class[0]).invoke(o1, new Object[0]);
o2 = o2.getClass().getMethod(getter, new Class[0]).invoke(o2, new Object[0]);
}
} catch (Exception e) {
// If this exception occurs, then it is usually a fault of the DTO developer.
throw new RuntimeException("Cannot compare " + o1 + " with " + o2 + " on " + getters, e);
}

if (ascending) {
return (o1 == null) ? -1 : ((o2 == null) ? 1 : ((Comparable) o1).compareTo(o2));
} else {
return (o2 == null) ? -1 : ((o1 == null) ? 1 : ((Comparable) o2).compareTo(o1));
}
}

}

Back to top


Paging datatable


Update: there's a newer article out for a more effective paging at DAO level. You may find it useful: Effective datatable paging and sorting.


If you have a plenty of rows, then you can use the following datatable attributes to page through the datatable: first and rows. The first attribute identifies the row number of the starting row which should be displayed. The rows attribute identifies the amount of rows to be shown at once, from the first row on. For pagination we need to set the first attribute dynamically in the backing bean. You can use commandlinks or commandbuttons to change this value.


So extend the datatable with the rows attribute and a set of commandbuttons in the footer. In this example we will page on every 3 rows.




binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem" rows="3">

...


...

name="footer">

value="first" action="#{myBean.pageFirst}"
disabled="#{myBean.dataTable.first == 0}" />
value="prev" action="#{myBean.pagePrevious}"
disabled="#{myBean.dataTable.first == 0}" />
value="next" action="#{myBean.pageNext}"
disabled="#{myBean.dataTable.first + myBean.dataTable.rows
>= myBean.dataTable.rowCount}"
/>
value="last" action="#{myBean.pageLast}"
disabled="#{myBean.dataTable.first + myBean.dataTable.rows
>= myBean.dataTable.rowCount}"
/>





Please note the disabled attribute of the commandbuttons. The 'first' and the 'prev' buttons should be disabled at the first page. The 'next' and the 'last' buttons should be disabled when a next page cannot exist.


And add the following pagination methods to the backing bean:



package mypackage;

public class MyBean {

// Actions -----------------------------------------------------------------------------------

public void pageFirst() {
dataTable.setFirst(0);
}

public void pagePrevious() {
dataTable.setFirst(dataTable.getFirst() - dataTable.getRows());
}

public void pageNext() {
dataTable.setFirst(dataTable.getFirst() + dataTable.getRows());
}

public void pageLast() {
int count = dataTable.getRowCount();
int rows = dataTable.getRows();
dataTable.setFirst(count - ((count % rows != 0) ? count % rows : rows));
}

}


With the following getters you can get the current page number and the total number of pages.



package mypackage;

public class MyBean {

// Special getters ---------------------------------------------------------------------------

public int getCurrentPage() {
int rows = dataTable.getRows();
int first = dataTable.getFirst();
int count = dataTable.getRowCount();
return (count / rows) - ((count - first) / rows) + 1;
}

public int getTotalPages() {
int rows = dataTable.getRows();
int count = dataTable.getRowCount();
return (count / rows) + ((count % rows != 0) ? 1 : 0);
}

}


Note that you need to handle ArithmeticException / by zero when the rows is 0 and you still want to display the table.



Back to top


Nesting datatables


You can use multidimensional lists in a nested datatable: just put the inner datatable in a h:column of the outer datatable and add the inner list to the DTO of the outer list.


The basic JSF code:



 value="#{myBean.dataList}" var="dataItem">

value="#{dataItem.name}" />



value="#{dataItem.value}" />



value="#{dataItem.innerList}" var="innerItem">

value="#{innerItem.name}" />



value="#{innerItem.value}" />





The updated Java code of MyData.java which represents each dataItem:



package mypackage;

public class MyData {

// Init --------------------------------------------------------------------------------------

private List innerList;

// Getters -----------------------------------------------------------------------------------

public List getInnerList() {
return innerList;
}

// Setters -----------------------------------------------------------------------------------

public void setInnerList(List innerList) {
this.innerList = innerList;
}

}


Back to top


Populate datatable


You can also programmatically populate the datatable in the backing bean. This may be useful if you don't know the expected state of the datatable in the JSF page, but only in the backing bean.


Here is the basic JSF code:





binding="#{myBean.dataTableGroup}" />



Fairly simple, isn't it? We're using a h:panelGroup as placeholder, because when you want to bind the datatable itself, then you have to specify the var attribute in the JSF page. Using a h:panelGroup without any style doesn't output anything to the HTML, so this won't harm that much. This approach is not needed anymore if you're using JSF 1.2_10 or newer, since this bug is been fixed.


And here is the relevant Java code of the backing bean, it might look like a lot of code, but it is actually rather simple:



package mypackage;

import java.util.List;

import javax.el.ValueExpression;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlPanelGroup;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private HtmlPanelGroup dataTableGroup; // Placeholder.

// Actions -----------------------------------------------------------------------------------

private void populateDataTable() {

// Create .
HtmlDataTable dataTable = new HtmlDataTable();
dataTable.setValueExpression("value",
createValueExpression("#{myBean.dataList}", List.class));
dataTable.setVar("dataItem");

// Create for 'ID' column.
HtmlColumn idColumn = new HtmlColumn();
dataTable.getChildren().add(idColumn);

// Create for of 'ID' column.
HtmlOutputText idHeader = new HtmlOutputText();
idHeader.setValue("ID");
idColumn.setHeader(idHeader);

// Create for the body of 'ID' column.
HtmlOutputText idOutput = new HtmlOutputText();
idOutput.setValueExpression("value",
createValueExpression("#{dataItem.id}", Long.class));
idColumn.getChildren().add(idOutput);

// Create for 'Name' column.
HtmlColumn nameColumn = new HtmlColumn();
dataTable.getChildren().add(nameColumn);

// Create for of 'Name' column.
HtmlOutputText nameHeader = new HtmlOutputText();
nameHeader.setValue("Name");
nameColumn.setHeader(nameHeader);

// Create for the body of 'Name' column.
HtmlOutputText nameOutput = new HtmlOutputText();
nameOutput.setValueExpression("value",
createValueExpression("#{dataItem.name}", String.class));
nameColumn.getChildren().add(nameOutput);

// Create for 'Value' column.
HtmlColumn valueColumn = new HtmlColumn();
dataTable.getChildren().add(valueColumn);

// Create for of 'Value' column.
HtmlOutputText valueHeader = new HtmlOutputText();
valueHeader.setValue("Value");
valueColumn.setHeader(valueHeader);

// Create for the body of 'Value' column.
HtmlOutputText valueOutput = new HtmlOutputText();
valueOutput.setValueExpression("value",
createValueExpression("#{dataItem.value}", String.class));
valueColumn.getChildren().add(valueOutput);

// Finally add the datatable to .
dataTableGroup = new HtmlPanelGroup();
dataTableGroup.getChildren().add(dataTable);
}

// Getters -----------------------------------------------------------------------------------

public HtmlPanelGroup getDataTableGroup() {
// This will be called once in the first RESTORE VIEW phase.
if (dataTableGroup == null) {
populateDataTable(); // Populate datatable.
}
return dataTableGroup;
}

// Setters -----------------------------------------------------------------------------------

public void setDataTableGroup(HtmlPanelGroup dataTableGroup) {
this.dataTableGroup = dataTableGroup;
}

// Helpers -----------------------------------------------------------------------------------

private ValueExpression createValueExpression(String valueExpression, Class valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createValueExpression(
facesContext.getELContext(), valueExpression, valueType);
}

}


This creates exactly the same datatable as shown in Show data contents in datatable.


Back to top


Populate action datatable


You can also programmatically populate a datatable with a binding and a backing bean action in every row in the backing bean. This may be useful if you don't know the expected state of the datatable in the JSF page, but only in the backing bean.


Here is the basic JSF code:





binding="#{myBean.actionDataTableGroup}" />



Fairly simple, isn't it? We're using a h:panelGroup as placeholder, because when you want to bind the datatable itself, then you have to specify the var attribute in the JSF page. Using a h:panelGroup without any style doesn't output anything to the HTML, so this won't harm that much. This approach is not needed anymore if you're using JSF 1.2_10 or newer, since this bug is been fixed.


And here is the relevant Java code of the backing bean, it might look like a lot of code, but it is actually rather simple:



package mypackage;

import java.util.List;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlPanelGroup;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private HtmlPanelGroup actionDataTableGroup; // Placeholder.
private HtmlDataTable actionDataTable;

// Actions -----------------------------------------------------------------------------------

private void populateActionDataTable() {

// Create // value="#{myBean.dataList}" var="dataItem">.
HtmlDataTable actionDataTable = new HtmlDataTable();
actionDataTable.setValueExpression("binding",
createValueExpression("#{myBean.actionDataTable}", HtmlDataTable.class));
actionDataTable.setValueExpression("value",
createValueExpression("#{myBean.dataList}", List.class));
actionDataTable.setVar("dataItem");

// Create for 'ID' column.
HtmlColumn idColumn = new HtmlColumn();
actionDataTable.getChildren().add(idColumn);

// Create for of 'ID' column.
HtmlOutputText idHeader = new HtmlOutputText();
idHeader.setValue("ID");
idColumn.setHeader(idHeader);

// Create
// for the body of 'ID' column.
HtmlCommandLink idLink = new HtmlCommandLink();
idLink.setId("edit"); // Custom ID is required in dynamic UIInput and UICommand.
idLink.setValueExpression("value",
createValueExpression("#{dataItem.id}", Long.class));
idLink.setActionExpression(
createActionExpression("#{myBean.editPopulatedDataItem}", String.class));
idColumn.getChildren().add(idLink);

// Create for 'Name' column.
HtmlColumn nameColumn = new HtmlColumn();
actionDataTable.getChildren().add(nameColumn);

// Create for of 'Name' column.
HtmlOutputText nameHeader = new HtmlOutputText();
nameHeader.setValue("Name");
nameColumn.setHeader(nameHeader);

// Create for the body of 'Name' column.
HtmlOutputText nameOutput = new HtmlOutputText();
nameOutput.setValueExpression("value",
createValueExpression("#{dataItem.name}", String.class));
nameColumn.getChildren().add(nameOutput);

// Create for 'Value' column.
HtmlColumn valueColumn = new HtmlColumn();
actionDataTable.getChildren().add(valueColumn);

// Create for of 'Value' column.
HtmlOutputText valueHeader = new HtmlOutputText();
valueHeader.setValue("Value");
valueColumn.setHeader(valueHeader);

// Create for the body of 'Value' column.
HtmlOutputText valueOutput = new HtmlOutputText();
valueOutput.setValueExpression("value",
createValueExpression("#{dataItem.value}", String.class));
valueColumn.getChildren().add(valueOutput);

// Finally add the datatable to .
actionDataTableGroup = new HtmlPanelGroup();
actionDataTableGroup.getChildren().add(actionDataTable);
}

public String editPopulatedDataItem() {

// Get selected MyData item to be edited.
dataItem = (MyData) actionDataTable.getRowData();

return "edit"; // Navigation case.
}

// Getters -----------------------------------------------------------------------------------

public HtmlPanelGroup getActionDataTableGroup() {
// This will be called once in the first RESTORE VIEW phase.
if (actionDataTableGroup == null) {
populateActionDataTable(); // Populate action datatable.
}
return actionDataTableGroup;
}

public HtmlDataTable getActionDataTable() {
return actionDataTable;
}

// Setters -----------------------------------------------------------------------------------

public void setActionDataTableGroup(HtmlPanelGroup actionDataTableGroup) {
this.actionDataTableGroup = actionDataTableGroup;
}

public void setActionDataTable(HtmlDataTable actionDataTable) {
this.actionDataTable = actionDataTable;
}

// Helpers -----------------------------------------------------------------------------------

private ValueExpression createValueExpression(String valueExpression, Class valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createValueExpression(
facesContext.getELContext(), valueExpression, valueType);
}

private MethodExpression createActionExpression(String actionExpression, Class returnType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createMethodExpression(
facesContext.getELContext(), actionExpression, returnType, new Class[0]);
}

}


This creates exactly the same datatable as shown in Add backing bean action to every row.


Back to top


Populate editable datatable


You can also programmatically populate an editable datatable in the backing bean. This may be useful if you don't know the expected state of the datatable in the JSF page, but only in the backing bean.


Here is the basic JSF code:





binding="#{myBean.editableDataTableGroup}" />



Fairly simple, isn't it? We're using a h:panelGroup as placeholder, because when you want to bind the datatable itself, then you have to specify the var attribute in the JSF page. Using a h:panelGroup without any style doesn't output anything to the HTML, so this won't harm that much. This approach is not needed anymore if you're using JSF 1.2_10 or newer, since this bug is been fixed.


And here is the relevant Java code of the backing bean, it might look like a lot of code, but it is actually rather simple:



package mypackage;

import java.util.List;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlPanelGroup;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private HtmlPanelGroup editableDataTableGroup; // Placeholder.

// Actions -----------------------------------------------------------------------------------

private void populateEditableDataTable() {

// Create .
HtmlDataTable editableDataTable = new HtmlDataTable();
editableDataTable.setValueExpression("value",
createValueExpression("#{myBean.dataList}", List.class));
editableDataTable.setVar("dataItem");

// Create for 'ID' column.
HtmlColumn idColumn = new HtmlColumn();
editableDataTable.getChildren().add(idColumn);

// Create for of 'ID' column.
HtmlOutputText idHeader = new HtmlOutputText();
idHeader.setValue("ID");
idColumn.setHeader(idHeader);

// Create for the body of 'ID' column.
HtmlOutputText idOutput = new HtmlOutputText();
idOutput.setValueExpression("value",
createValueExpression("#{dataItem.id}", Long.class));
idColumn.getChildren().add(idOutput);

// Create for 'Name' column.
HtmlColumn nameColumn = new HtmlColumn();
editableDataTable.getChildren().add(nameColumn);

// Create for of 'Name' column.
HtmlOutputText nameHeader = new HtmlOutputText();
nameHeader.setValue("Name");
nameColumn.setHeader(nameHeader);

// Create for the body of 'Name' column.
HtmlInputText nameInput = new HtmlInputText();
nameInput.setId("name"); // Custom ID is required in dynamic UIInput and UICommand.
nameInput.setValueExpression("value",
createValueExpression("#{dataItem.name}", String.class));
nameColumn.getChildren().add(nameInput);

// Create for 'Value' column.
HtmlColumn valueColumn = new HtmlColumn();
editableDataTable.getChildren().add(valueColumn);

// Create for of 'Value' column.
HtmlOutputText valueHeader = new HtmlOutputText();
valueHeader.setValue("Value");
valueColumn.setHeader(valueHeader);

// Create for the body of 'Value' column.
HtmlInputText valueInput = new HtmlInputText();
valueInput.setId("value"); // Custom ID is required in dynamic UIInput and UICommand.
valueInput.setValueExpression("value",
createValueExpression("#{dataItem.value}", String.class));
valueColumn.getChildren().add(valueInput);

// Add the datatable to .
editableDataTableGroup = new HtmlPanelGroup();
editableDataTableGroup.getChildren().add(editableDataTable);

// Create and add to
// binding="#{myBean.editableDataTableGroup}">.
HtmlOutputText lineBreak = new HtmlOutputText();
lineBreak.setValue("
"
);
lineBreak.setEscape(false); // Don't escape HTML.
editableDataTableGroup.getChildren().add(lineBreak);

// Create
// and add to .
HtmlCommandButton saveButton = new HtmlCommandButton();
saveButton.setId("save"); // Custom ID is required in dynamic UIInput and UICommand.
saveButton.setValue("Save");
saveButton.setActionExpression(
createActionExpression("#{myBean.saveDataList}", String.class));
editableDataTableGroup.getChildren().add(saveButton);
}

// Getters -----------------------------------------------------------------------------------

public HtmlPanelGroup getEditableDataTableGroup() {
// This will be called once in the first RESTORE VIEW phase.
if (editableDataTableGroup == null) {
populateEditableDataTable(); // Populate editable datatable.
}
return editableDataTableGroup;
}

// Setters -----------------------------------------------------------------------------------

public void setEditableDataTableGroup(HtmlPanelGroup editableDataTableGroup) {
this.editableDataTableGroup = editableDataTableGroup;
}

// Helpers -----------------------------------------------------------------------------------

private ValueExpression createValueExpression(String valueExpression, Class valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createValueExpression(
facesContext.getELContext(), valueExpression, valueType);
}

private MethodExpression createActionExpression(String actionExpression, Class returnType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createMethodExpression(
facesContext.getELContext(), actionExpression, returnType, new Class[0]);
}

}


This creates exactly the same datatable as shown in Editable datatable.


Back to top


Populate dynamic datatable


You also can dynamically populate a datatable in the backing bean. This may be very useful if you are using dynamic generated two-dimensional lists (for example, uploaded CSV files, local text files, DB fields, etc) and you don't know the amount of columns before.


The basic JSF code:





binding="#{myBean.dynamicDataTableGroup}" />



The relevant Java code of the backing bean:



package mypackage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.el.ValueExpression;
import javax.faces.component.HtmlColumn;
import javax.faces.component.HtmlOutputText;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.context.FacesContext;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private static List> dynamicList; // Simulate fake DB.
private static String[] dynamicHeaders; // Optional.
private HtmlPanelGroup dynamicDataTableGroup; // Placeholder.

// Actions -----------------------------------------------------------------------------------

private void loadDynamicList() {

// Set headers (optional).
dynamicHeaders = new String[] {"ID", "Name", "Value"};

// Set rows. This is a stub example, just do your dynamic thing.
dynamicList = new ArrayList>();
dynamicList.add(Arrays.asList(new String[] { "ID1", "Name1", "Value1" }));
dynamicList.add(Arrays.asList(new String[] { "ID2", "Name2", "Value2" }));
dynamicList.add(Arrays.asList(new String[] { "ID3", "Name3", "Value3" }));
dynamicList.add(Arrays.asList(new String[] { "ID4", "Name4", "Value4" }));
dynamicList.add(Arrays.asList(new String[] { "ID5", "Name5", "Value5" }));
}

private void populateDynamicDataTable() {

// Create .
HtmlDataTable dynamicDataTable = new HtmlDataTable();
dynamicDataTable.setValueExpression("value",
createValueExpression("#{myBean.dynamicList}", List.class));
dynamicDataTable.setVar("dynamicItem");

// Iterate over columns.
for (int i = 0; i < dynamicList.get(0).size(); i++) {

// Create .
HtmlColumn column = new HtmlColumn();
dynamicDataTable.getChildren().add(column);

// Create for of column.
HtmlOutputText header = new HtmlOutputText();
header.setValue(dynamicHeaders[i]);
column.setHeader(header);

// Create for the body of column.
HtmlOutputText output = new HtmlOutputText();
output.setValueExpression("value",
createValueExpression("#{dynamicItem[" + i + "]}", String.class));
column.getChildren().add(output);
}

// Add the datatable to .
dynamicDataTableGroup = new HtmlPanelGroup();
dynamicDataTableGroup.getChildren().add(dynamicDataTable);
}

// Getters -----------------------------------------------------------------------------------

public HtmlPanelGroup getDynamicDataTableGroup() {
// This will be called once in the first RESTORE VIEW phase.
if (dynamicDataTableGroup == null) {
loadDynamicList(); // Preload dynamic list.
populateDynamicDataTable(); // Populate editable datatable.
}

return dynamicDataTableGroup;
}

public List> getDynamicList() {
return dynamicList;
}

// Setters -----------------------------------------------------------------------------------

public void setDynamicDataTableGroup(HtmlPanelGroup dynamicDataTableGroup) {
this.dynamicDataTableGroup = dynamicDataTableGroup;
}

}


Back to top


Add row numbers


This is fairly simple to implement using getRowIndex() method of the HtmlDataTable class, which is bound by h:dataTable binding.



 binding="#{myBean.dataTable}">

name="header">
value="Row #" />

value="#{myBean.dataTable.rowIndex + 1}" />


...


We are using + 1 in the value binding expression, because the row index usually starts with 0.


Back to top


Alternating rows


You can alternate the rows using the dataTable attribute rowClasses and some piece of CSS. Put in there at least two style class names and the rows will apply those styles concurrently and repeating. Here is a basic example:



 rowClasses="row1, row2">
...


Here is the CSS defining the row1 and row2 styles. This is just a basic example, you can define your own colors for example.



.row1 {
background-color: #ddd;
}
.row2 {
background-color: #bbb;
}


Back to top


Highlight rows on click


You can use Javascript and DOM to alter the rows of the dataTable by adding an onclick function to it which triggers a Javascript function. Here is a basic Javascript example:



function addOnclickToDatatableRows() {
var trs = document.getElementById('dataTable').getElementsByTagName('tbody')[0]
.getElementsByTagName('tr');
for (var i = 0; i < trs.length; i++) {
trs[i].onclick = new Function("highlightRow(this)");
}
}

function highlightRow(tr) {
tr.bgColor = (tr.bgColor != '#ff0000') ? '#ff0000' : '#ffffff';
}


Call addOnclickToDatatableRows() during onload of the window. Take note that the element ID in the Javascript have to be exactly the same as the rendered element ID of the table.








id="dataTable">
...



Back to top


Highlight and select row on click


You can also highlight single row and select the row for submit using an onclick function on the table row. Basically just pass its row index to a plain vanilla HTML hidden input. Here is a basic Javascript example:



function addOnclickToDatatableRows() {
var trs = document.getElementById('form:dataTable').getElementsByTagName('tbody')[0]
.getElementsByTagName('tr');
for (var i = 0; i < trs.length; i++) {
trs[i].onclick = new Function("highlightAndSelectRow(this)");
}
}

function highlightAndSelectRow(tr) {
var trs = document.getElementById('form:dataTable').getElementsByTagName('tbody')[0]
.getElementsByTagName('tr');
for (var i = 0; i < trs.length; i++) {
if (trs[i] == tr) {
trs[i].bgColor = '#ff0000';
document.form.rowIndex.value = trs[i].rowIndex;
} else {
trs[i].bgColor = '#ffffff';
}
}
}


Call addOnclickToDatatableRows() during onload of the window. Take note that the element ID in the Javascript have to be exactly the same as the generated element ID of the form and the table.








id="form">
id="dataTable">
...

type="hidden" name="rowIndex" />
value="Edit selected row" action="#{myBean.editDataItem}" />



Here is the relevant part of the backing bean code. This achieves exactly the same actions as shown in Add backing bean action to every row and Get selected datatable row:



package mypackage;

import java.util.List;

import javax.faces.context.FacesContext;

public class MyBean {

// Init --------------------------------------------------------------------------------------

private List dataList;
private MyData dataItem = new MyData();

// Actions -----------------------------------------------------------------------------------

public String editDataItem() {

// Obtain the row index from the hidden input element.
String rowIndex = FacesContext.getCurrentInstance().getExternalContext()
.getRequestParameterMap().get("rowIndex");
if (rowIndex != null && rowIndex.trim().length() != 0) {
dataItem = dataList.get(Integer.parseInt(rowIndex));
} else {
// Handle unexpected state, e.g. show message "Please select row" or so.
}

return "edit"; // Navigation case.
}

}


Back to top


Highlight rows on hover


You can use Javascript and DOM to alter the rows of the dataTable by adding an onmouseover and onmouseout function to it which triggers a Javascript function. Here is a basic Javascript example:



function addHoverToDatatableRows() {
var trs = document.getElementById('dataTable').getElementsByTagName('tbody')[0]
.getElementsByTagName('tr');
for (var i = 0; i < trs.length; i++) {
trs[i].onmouseover = new Function("this.bgColor='#ff0000'");
trs[i].onmouseout = new Function("this.bgColor='#ffffff'");
}
}


Call addHoverToDatatableRows() during onload of the window. Take note that the element ID in the Javascript have to be exactly the same as the generated element ID of the table.








id="dataTable">
...



Back to top


Customized tables


If you're using at least JSF 1.2 and JSTL 1.2 in a JSP 2.1 container (available in Java EE 5 server, e.g. Tomcat 6.0 or Glassfish), then you can access JSF managed beans in the JSTL c:forEach tag, thanks to the unified EL. This will give you more freedom to create exotic tables based on a collection of DTO's from a managed bean, e.g. introducing colspans and so on.


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>

...





colspan="3">MASTER TABLE








items="#{myBean.dataList}" var="dataItem" varStatus="status">
rendered="#{dataItem.id % 2 != 0}">
style="background-color: red;">
colspan="3">This is just an example of an alternately inserted row.


style="background-color: ${status.index % 2 == 0 ? 'lightgray' : 'gray'};">






ID Name Value

value="#{dataItem.id}" action="#{myBean.editDataItem}">

target="#{myBean.dataItem}" value="#{dataItem}" />

value="#{dataItem.name}" /> value="#{dataItem.value}" />



This creates a table with exactly the same behaviour as the table as shown in Add backing bean action to every row with the difference that you can introduce colspans to your taste. The example also shows how to use f:verbatim whether to render plain vanilla HTML or not. The example also shows how to alternate the row colors using the c:forEach varStatus. The f:setPropertyActionListener (which was introduced in JSF 1.2 and only works inside UICommand components) will set the current dataItem object in the appropriate property of the backing bean. It is the same dataItem object as you should obtain by HtmlDataTable#getRowData() when using a regular h:dataTable.



Back to top


Download WAR


Here is a WAR containing all of the above examples in Request as well as Session scope. This WAR also contains "The ultimate CRUD example" which is a show-off of the capacities of datatables (and JSF itself).


UsingDatatables.war (1.49 MB, updated at 2008-12-08), based on JSF 1.2_10 in a Java EE 5.0 environment.


This WAR is developed and tested in Eclipse 3.4 using Tomcat Application Server 6.0.18.


  


Back to top


Copyright - There is no copyright on the code. You can steal, change and distribute it freely. Just mentioning this site should be fair.


(C) June 2006, BalusC



Source:http://balusc.blogspot.com/2006/06/using-datatables.html

Tidak ada komentar:

Posting Komentar