Showing posts with label DataTable. Show all posts
Showing posts with label DataTable. Show all posts

Sunday, June 6, 2010

The benefits and pitfalls of @ViewScoped

Introduction

To prepare for a new set of JSF 2.0 targeted articles (have patience, I'd like to wait for Eclipse Helios and Tomcat 7 to be finished), I've played intensively with JSF 2.0 and Facelets the last weeks (to be precise, with Mojarra 2.0.2). The new JSF 2.0 annotations and implicit navigation (the outcome will implicitly go to /outcomevalue.xhtml page) are great and very useful. No hassling with faces-config.xml anymore. It's awesome.

JSF 2.0 also introduces an important new scope and offers the possibility to define your own custom scopes. The new scope is the view scope. It lies between the existing and well-known request and session scopes in. You can put the managed bean in the view scope using the @ViewScoped annotation.

package com.example;

import java.io.Serializable;

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

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    // ...
    
}

Note that the bean needs to implement Serializable as it will be stored in the view map which is in turn stored in the session. Some servletcontainer configurations will namely store sessions on disk instead of in memory. This is also mandatory when you have configured JSF view state saving method to client instead of (default) server.

You'll probably recognize yourself in abusing the session scope for data which you would like to retain in the subsequent requests to avoid the expensive task of reloading it from the database on every request again and again, such as the datamodel for the <h:dataTable> in order to be able to retrieve the selected row. A major caveat is that the changes are reflected in every opened window/tab in the same session which leads to unintuitive webapplication behaviour and thus bad user experience. Tomahawk was early to introduce a solution for this in flavor of <t:saveState> and <t:dataTable preserveDataModel="true">. The first will store the given model value temporarily in the viewroot and set it back in the model during the restore view phase of the subsequent request. The second does that for the datatable's value. Hereafter JBoss Seam came with the Conversation Scope and Apache MyFaces Orchestra followed shortly. Both saves the bean state in the session among requests, identified by an extra request parameter.

You'll probably also recognize the issue of the <h:commandButton> or <h:commandLink> action not being fired because the rendered attribute of the component or one of its parents returns false during the form submit, while it was true during the initial request. You would need to fall back to the session scope or grab Tomahawk's <t:saveState> to fix it. How annoying!

The new view scope should solve exactly those issues. A @ViewScoped bean will live as long as you're submitting the form to the same view again and again. In other words, as long as when the action method(s) returns null or even void, the bean will be there in the next request. Once you navigate to a different view, then the bean will be trashed.

Back to top

Really simple CRUD

Here's a quick'n'dirty example in flavor of a really simple CRUD on a single page how you could take benefit of it in combination with a datatable.

package com.example;

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

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    private List<Item> list;
    private transient DataModel<Item> model;
    private Item item = new Item();
    private boolean edit;

    @PostConstruct
    public void init() {
        // list = dao.list();
        // Actually, you should retrieve the list from DAO. This is just for demo.
        list = new ArrayList<Item>();
        list.add(new Item(1L, "item1"));
        list.add(new Item(2L, "item2"));
        list.add(new Item(3L, "item3"));
    }
    
    public void add() {
        // dao.create(item);
        // Actually, the DAO should already have set the ID from DB. This is just for demo.
        item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
        list.add(item);
        item = new Item(); // Reset placeholder.
    }

    public void edit() {
        item = model.getRowData();
        edit = true;
    }

    public void save() {
        // dao.update(item);
        item = new Item(); // Reset placeholder.
        edit = false;
    }

    public void delete() {
        // dao.delete(item);
        list.remove(model.getRowData());
    }

    public List<Item> getList() {
        return list;
    }

    public DataModel<Item> getModel() {
        if (model == null) {
            model = new ListDataModel<Item>(list);
        }

        return model;
    }

    public Item getItem() {
        return item;
    }

    public boolean isEdit() {
        return edit;
    }

    // Other getters/setters are actually unnecessary. Feel free to add them though.

}

Note: the outcommented dao.somemethod() lines are what you actually should do as well in real code. Also note that the DataModel is lazily instantiated in the getter, because it doesn't implement Serializable and it would otherwise be null after deserialization.

The Item class is just a simple model object, its code should be straightforward enough. A Serializable Javabean with two properties Long id and String value, a default constructor and a constructor filling both properties, a bunch of appropriate getters/setters, equals() and hashCode() overriden.

And now the view, it's Facelets, save it as crud.xhtml:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Really simple CRUD</title>
    </h:head>
    <h:body>
        <h3>List items</h3>
        <h:form rendered="#{not empty bean.list}">
            <h:dataTable value="#{bean.model}" var="item">
                <h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
                <h:column><f:facet name="header">Value</f:facet>#{item.value}</h:column>
                <h:column><h:commandButton value="edit" action="#{bean.edit}" /></h:column>
                <h:column><h:commandButton value="delete" action="#{bean.delete}" /></h:column>
            </h:dataTable>
        </h:form>
        <h:panelGroup rendered="#{empty bean.list}">
            <p>Table is empty! Please add new items.</p>
        </h:panelGroup>
        <h:panelGroup rendered="#{!bean.edit}">
            <h3>Add item</h3>
            <h:form>
                <p>Value: <h:inputText value="#{bean.item.value}" /></p>
                <p><h:commandButton value="add" action="#{bean.add}" /></p>
            </h:form>
        </h:panelGroup>
        <h:panelGroup rendered="#{bean.edit}">
            <h3>Edit item #{bean.item.id}</h3>
            <h:form>
                <p>Value: <h:inputText value="#{bean.item.value}" /></p>
                <p><h:commandButton value="save" action="#{bean.save}" /></p>
            </h:form>
        </h:panelGroup>
    </h:body>
</html>

Amazingly simple, isn't it? If you know or have read the well known Using Datatables article (it has been almost exactly 4 year ago when I wrote it for first! according to Google Analytics, that page alone has already been viewed almost 150,000 times since 1 September 2007), you'll realize how hacky and verbose it could/would be when doing it in the request scope alone with all of those bound <h:inputHidden> components and reloading the data in the action method or getter.

If you've studied the managed bean code closely, you'll also see that the javax.faces.model.DataModel is finally parameterized in JSF 2.0. No need for nasty casts on getRowData() anymore.

Really simple CRUD, now without DataModel!

If you're targeting a Servlet 3.0 / EL 2.2 capable container such as Tomcat 7, Glassfish 3, JBoss AS 6, etc, then it can be done even more simple! You can pass method arguments in EL! This allows you for passing the current row just straight into bean's action method. Here's a minor rewrite of the above quick'n'dirty example which utilizes EL 2.2 powers.

package com.example;

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

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class Bean implements Serializable {

    private List<Item> list;
    private Item item = new Item();
    private boolean edit;

    @PostConstruct
    public void init() {
        // list = dao.list();
        // Actually, you should retrieve the list from DAO. This is just for demo.
        list = new ArrayList<Item>();
        list.add(new Item(1L, "item1"));
        list.add(new Item(2L, "item2"));
        list.add(new Item(3L, "item3"));
    }
    
    public void add() {
        // dao.create(item);
        // Actually, the DAO should already have set the ID from DB. This is just for demo.
        item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
        list.add(item);
        item = new Item(); // Reset placeholder.
    }

    public void edit(Item item) {
        this.item = item;
        edit = true;
    }

    public void save() {
        // dao.update(item);
        item = new Item(); // Reset placeholder.
        edit = false;
    }

    public void delete(Item item) {
        // dao.delete(item);
        list.remove(item);
    }

    public List<Item> getList() {
        return list;
    }

    public Item getItem() {
        return item;
    }

    public boolean isEdit() {
        return edit;
    }

    // Other getters/setters are actually unnecessary. Feel free to add them though.

}

And now the view:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Really simple CRUD</title>
    </h:head>
    <h:body>
        <h3>List items</h3>
        <h:form rendered="#{not empty bean.list}">
            <h:dataTable value="#{bean.list}" var="item">
                <h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
                <h:column><f:facet name="header">Value</f:facet>#{item.value}</h:column>
                <h:column><h:commandButton value="edit" action="#{bean.edit(item)}" /></h:column>
                <h:column><h:commandButton value="delete" action="#{bean.delete(item)}" /></h:column>
            </h:dataTable>
        </h:form>
        <h:panelGroup rendered="#{empty bean.list}">
            <p>Table is empty! Please add new items.</p>
        </h:panelGroup>
        <h:panelGroup rendered="#{!bean.edit}">
            <h3>Add item</h3>
            <h:form>
                <p>Value: <h:inputText value="#{bean.item.value}" /></p>
                <p><h:commandButton value="add" action="#{bean.add}" /></p>
            </h:form>
        </h:panelGroup>
        <h:panelGroup rendered="#{bean.edit}">
            <h3>Edit item #{bean.item.id}</h3>
            <h:form>
                <p>Value: <h:inputText value="#{bean.item.value}" /></p>
                <p><h:commandButton value="save" action="#{bean.save}" /></p>
            </h:form>
        </h:panelGroup>
    </h:body>
</html>

Note the action="#{bean.edit(item)}" and action="#{bean.delete(item)}". The current item is simply been passed as method argument! This allows us to get rid from DataModel altogether.

Back to top

Hey, there's "pitfalls" in the title?

Yes, well spotted. Check the following two questions on Stackoverflow.com:

In a nutshell: the @ViewScoped breaks when any UIComponent is bound to the bean using binding attribute or when using JSTL <c:forEach> or <c:if> tags in the view. In both cases the bean will behave like a request scoped one.

This is related to JSF 2.0 issue 1492. Here's an extract of relevance:

This is a chicken/egg issue with partial state saving. The view is executed to populate the view *before* delta state is applied, so we see the behavior you've described.

At this point, I don't see a clear way to resolve this use case.

The workaround, if you must use view-scoped bindings would be setting javax.faces.PARTIAL_STATE_SAVING to false.

Update: this issue is fixed in Mojarra 2.1.18. See also @ViewScoped calls @PostConstruct on every postback request.

Back to top

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

(C) June 2010, BalusC

Friday, October 10, 2008

Effective datatable paging and sorting

WARNING - OUTDATED CONTENT!

This article is targeted on JSF 1.2. For JSF 2.0, many component libraries offer builtin "lazy loading" capability for effective paging and sorting, such as PrimeFaces <p:dataTable>.

Effective datatable paging and sorting at DAO level

In the 2 year old article Using datatables I wrote how to sort and page a JSF h:dataTable. Those are useful and nice if the dataset is small. But it is in fact less efficient as it first loads the whole data list from the database and uses Java to do the sorting and paging. It is much more efficient if you let the database do all the task. A self-respected database can sort the results much faster than Java can do. Querying a sublist from the database consumes much less memory in Java than when you query the complete list from the database. This all will make it much faster when you have a database with at least hundreds of rows.

Sorting using SQL can easily be done with the standardized ORDER BY clause. The way to obtain a subset of results differs per database. This article is targeted on MySQL. In MySQL you can obtain a subset of results with the LIMIT x, y clause. PostgreSQL uses LIMIT x OFFSET y. In Oracle you need to execute a ORDER BY subquery first and then use the ROWNUM clause on its results (SELECT * FROM (SELECT * FROM table ORDER BY column) WHERE ROWNUM BETWEEN x AND y). For MSSQL and DB2 you'll need to write a (w)hacky SQL query or to create a stored procedure. Consult Google or database specific documentation for details.

Back to top

Preparations

Next to a standard JSF implementation, we need the Tomahawk component library as it offers us the t:dataList and t:saveState components. The t:dataList is needed to display a collection of links with page numbers. It is preferred above JSTL's c:forEach, because it does its work more efficient. The t:saveState is needed to cache the displayed list and some important paging and sorting variables for the next request. It is preferred above h:inputHidden, because it does its work more efficient and it doesn't require a converter for non-standard object types. You can even cache a complete bean for the subsequent request, with which you can simulate a "conversation scope".

Integrating Tomahawk isn't that hard, you can even do that on a Sun Mojarra environment. You just need to add at least the following JAR's to the classpath, e.g. /WEB-INF/lib. The version numbers doesn't matter that much, as long as you get the newest.

The Tomahawk JAR is the Tomahawk component library itself which under each contains the t:dataList and t:saveState components. The commons JAR's are required by other components and/or the core of the Tomahawk component library.

Back to top

Backing Bean

Here is how the basic backing bean code look like. It is request scoped. If you're interested, an example of the DAOFactory can be found here: DAO tutorial - the data layer.

package mypackage;

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

import javax.faces.component.UICommand;
import javax.faces.event.ActionEvent;

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

/**
 * The example backing bean for effective datatable paging and sorting.
 *
 * @author BalusC
 * @link http://balusc.blogspot.com/2008/10/effective-datatable-paging-and-sorting.html
 */
public class MyBean implements Serializable {

    // Properties ---------------------------------------------------------------------------------

    // DAO.
    private static MyDataDAO dao = DAOFactory.getInstance("javabase").getMyDataDAO();

    // Data.
    private List<MyData> dataList;
    private int totalRows;

    // Paging.
    private int firstRow;
    private int rowsPerPage;
    private int totalPages;
    private int pageRange;
    private Integer[] pages;
    private int currentPage;

    // Sorting.
    private String sortField;
    private boolean sortAscending;

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

    public MyBean() {
        // Set default values somehow (properties files?).
        rowsPerPage = 10; // Default rows per page (max amount of rows to be displayed at once).
        pageRange = 10; // Default page range (max amount of page links to be displayed at once).
        sortField = "id"; // Default sort field.
        sortAscending = true; // Default sort direction.
    }

    // Paging actions -----------------------------------------------------------------------------

    public void pageFirst() {
        page(0);
    }

    public void pageNext() {
        page(firstRow + rowsPerPage);
    }

    public void pagePrevious() {
        page(firstRow - rowsPerPage);
    }

    public void pageLast() {
        page(totalRows - ((totalRows % rowsPerPage != 0) ? totalRows % rowsPerPage : rowsPerPage));
    }

    public void page(ActionEvent event) {
        page(((Integer) ((UICommand) event.getComponent()).getValue() - 1) * rowsPerPage);
    }

    private void page(int firstRow) {
        this.firstRow = firstRow;
        loadDataList(); // Load requested page.
    }

    // Sorting actions ----------------------------------------------------------------------------

    public void sort(ActionEvent event) {
        String sortFieldAttribute = (String) event.getComponent().getAttributes().get("sortField");

        // If the same field is sorted, then reverse order, else sort the new field ascending.
        if (sortField.equals(sortFieldAttribute)) {
            sortAscending = !sortAscending;
        } else {
            sortField = sortFieldAttribute;
            sortAscending = true;
        }

        pageFirst(); // Go to first page and load requested page.
    }

    // Loaders ------------------------------------------------------------------------------------

    private void loadDataList() {

        // Load list and totalCount.
        try {
            dataList = dao.list(firstRow, rowsPerPage, sortField, sortAscending);
            totalRows = dao.count();
        } catch (DAOException e) {
            throw new RuntimeException(e); // Handle it yourself.
        }

        // Set currentPage, totalPages and pages.
        currentPage = (totalRows / rowsPerPage) - ((totalRows - firstRow) / rowsPerPage) + 1;
        totalPages = (totalRows / rowsPerPage) + ((totalRows % rowsPerPage != 0) ? 1 : 0);
        int pagesLength = Math.min(pageRange, totalPages);
        pages = new Integer[pagesLength];

        // firstPage must be greater than 0 and lesser than totalPages-pageLength.
        int firstPage = Math.min(Math.max(0, currentPage - (pageRange / 2)), totalPages - pagesLength);

        // Create pages (page numbers for page links).
        for (int i = 0; i < pagesLength; i++) {
            pages[i] = ++firstPage;
        }
    }

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

    public List<MyData> getDataList() {
        if (dataList == null) {
            loadDataList(); // Preload page for the 1st view.
        }
        return dataList;
    }

    public int getTotalRows() {
        return totalRows;
    }

    public int getFirstRow() {
        return firstRow;
    }

    public int getRowsPerPage() {
        return rowsPerPage;
    }

    public Integer[] getPages() {
        return pages;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public int getTotalPages() {
        return totalPages;
    }

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

    public void setRowsPerPage(int rowsPerPage) {
        this.rowsPerPage = rowsPerPage;
    }

}

Define it as usual in the faces-config.xml:


    <managed-bean>
        <managed-bean-name>myBean</managed-bean-name>
        <managed-bean-class>mypackage.MyBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

Back to top

Example DTO

Here is the basic DTO example. It's nothing special. It's just a dummy DTO with three fields: ID, Name and Value.

package mymodel;

import java.io.Serializable;

/**
 * MyData. The example DTO (Data Transfer Object).
 *
 * @author BalusC
 * @link http://balusc.blogspot.com/2008/10/effective-datatable-paging-and-sorting.html
 */
public class MyData implements Serializable {

    // Properties ---------------------------------------------------------------------------------

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

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

    public MyData() {
        // Keep default constructor alive.
    }

    public MyData(Long id, String name, Integer value) {
        this.id = id;
        this.name = name;
        this.value = value;
    }

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

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Integer getValue() {
        return value;
    }

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

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

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

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

}
Back to top

Example DAO

The basic DAO example. Note that you cannot set the ORDER BY field and direction as PreparedStatement value. That's why it uses the String#format() for it. Keep SQL injection risks in mind. As long as the client can't control the values, you don't need to be afraid.

For more information and examples of the DAO layer and the DAOUtil class, you may find this article useful: DAO tutorial - the data layer.

package mydao;

import static mydao.DAOUtil.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import mymodel.MyData;

/**
 * This class represents a SQL Database Access Object for the {@link MyData} DTO.
 *
 * @author BalusC
 * @link http://balusc.blogspot.com/2008/10/effective-datatable-paging-and-sorting.html
 */
public final class MyDataDAO {

    // Constants ----------------------------------------------------------------------------------

    private static final String SQL_LIST_BY_ORDER_AND_LIMIT =
        "SELECT id, name, value FROM mydata ORDER BY %s %s LIMIT ?, ?";
    private static final String SQL_COUNT =
        "SELECT count(*) FROM mydata";

    // Properties ---------------------------------------------------------------------------------

    private DAOFactory daoFactory;

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

    /**
     * Construct MyData DAO for the given DAOFactory. Package private so that it can be constructed
     * inside the DAO package only.
     * @param daoFactory The DAOFactory to construct this MyData DAO for.
     */
    MyDataDAO(DAOFactory daoFactory) {
        this.daoFactory = daoFactory;
    }

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

    /**
     * Returns list of MyData items starting at the given first index with the given row count,
     * sorted by the given sort field and sort order.
     * @param firstRow First index of rows to be returned.
     * @param rowCount Amount of rows to be returned.
     * @param sortField Field to sort the data on.
     * @param sortAscending Whether to sort data ascending or not.
     * @return list of MyData items starting at the given first index with the given row count,
     * sorted by the given sort field and sort order.
     * @throws DAOException If something fails at DAO level.
     */
    public List<MyData> list(int firstRow, int rowCount, String sortField, boolean sortAscending)
        throws DAOException
    {
        Object[] values = { firstRow, rowCount };

        String sortDirection = sortAscending ? "ASC" : "DESC";
        String sql = String.format(SQL_LIST_BY_ORDER_AND_LIMIT, sortField, sortDirection);
        List<MyData> dataList = new ArrayList<>();

        try (
            Connection connection = daoFactory.getConnection();
            PreparedStatement statement = prepareStatement(connection, sql, false, values);
            ResultSet resultSet = statement.executeQuery();
        ) {
            while (resultSet.next()) {
                dataList.add(mapMyData(resultSet));
            }
        } catch (SQLException e) {
            throw new DAOException(e);
        }

        return dataList;
    }

    /**
     * Returns total amount of rows in table.
     * @return Total amount of rows in table.
     * @throws DAOException If something fails at DAO level.
     */
    public int count() throws DAOException {
        int count = 0;

        try (
            Connection connection = daoFactory.getConnection();
            PreparedStatement statement = connection.prepareStatement(SQL_COUNT);
            ResultSet resultSet = statement.executeQuery();
        ) {
            if (resultSet.next()) {
                count = resultSet.getInt(1);
            }
        } catch (SQLException e) {
            throw new DAOException(e);
        }

        return count;
    }

    /**
     * Map the current row of the given ResultSet to MyData.
     * @param resultSet The ResultSet of which the current row is to be mapped to MyData.
     * @return The mapped MyData from the current row of the given ResultSet.
     * @throws SQLException If something fails at database level.
     */
    private static MyData mapMyData(ResultSet resultSet) throws SQLException {
        return new MyData(
            resultSet.getLong("id"),
            resultSet.getString("name"),
            resultSet.getObject("value") != null ? resultSet.getInt("value") : null
        );
    }

}
Back to top

JSF file

And now the JSF file, it has a sortable datatable, a bunch of paging buttons (first, previous, next and last), the status of current page and total pages, a bunch of links pointing to a specific page and finally a input field where you can specify the amount of rows to be displayed at once.

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<f:view>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>Effective datatable paging and sorting at DAO level</title>
        </head>
        <body>
            <h:form id="form">

                <%-- The sortable datatable --%>
                <h:dataTable value="#{myBean.dataList}" var="item">
                    <h:column>
                        <f:facet name="header">
                            <h:commandLink value="ID" actionListener="#{myBean.sort}">
                                <f:attribute name="sortField" value="id" />
                            </h:commandLink>
                        </f:facet>
                        <h:outputText value="#{item.id}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:commandLink value="Name" actionListener="#{myBean.sort}">
                                <f:attribute name="sortField" value="name" />
                            </h:commandLink>
                        </f:facet>
                        <h:outputText value="#{item.name}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:commandLink value="Value" actionListener="#{myBean.sort}">
                                <f:attribute name="sortField" value="value" />
                            </h:commandLink>
                        </f:facet>
                        <h:outputText value="#{item.value}" />
                    </h:column>
                </h:dataTable>

                <%-- The paging buttons --%>
                <h:commandButton value="first" action="#{myBean.pageFirst}"
                    disabled="#{myBean.firstRow == 0}" />
                <h:commandButton value="prev" action="#{myBean.pagePrevious}"
                    disabled="#{myBean.firstRow == 0}" />
                <h:commandButton value="next" action="#{myBean.pageNext}"
                    disabled="#{myBean.firstRow + myBean.rowsPerPage >= myBean.totalRows}" />
                <h:commandButton value="last" action="#{myBean.pageLast}"
                    disabled="#{myBean.firstRow + myBean.rowsPerPage >= myBean.totalRows}" />
                <h:outputText value="Page #{myBean.currentPage} / #{myBean.totalPages}" />
                <br />

                <%-- The paging links --%>
                <t:dataList value="#{myBean.pages}" var="page">
                    <h:commandLink value="#{page}" actionListener="#{myBean.page}"
                        rendered="#{page != myBean.currentPage}" />
                    <h:outputText value="<b>#{page}</b>" escape="false"
                        rendered="#{page == myBean.currentPage}" />
                </t:dataList>
                <br />

                <%-- Set rows per page --%>
                <h:outputLabel for="rowsPerPage" value="Rows per page" />
                <h:inputText id="rowsPerPage" value="#{myBean.rowsPerPage}" size="3" maxlength="3" />
                <h:commandButton value="Set" action="#{myBean.pageFirst}" />
                <h:message for="rowsPerPage" errorStyle="color: red;" />

                <%-- Cache bean with data list, paging and sorting variables for next request --%>
                <t:saveState value="#{myBean}" />
            </h:form>
        </body>
    </html>
</f:view>

Save it as paging.jsp or so and invoke it by http://localhost:8080/playground/paging.jsf, assuming that your development server runs at port 8080 and the playground environment's context root is called 'playground'.

That's all!

Back to top

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

(C) October 2008, BalusC

Wednesday, June 7, 2006

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<MyData> 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<MyData> 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<MyData> 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<MyData> 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.

<managed-bean>
    <managed-bean-name>myBean</managed-bean-name>
    <managed-bean-class>mypackage.MyBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

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:

<h:dataTable value="#{myBean.dataList}" var="dataItem">
    <h:column>
        <f:facet name="header">
            <h:outputText value="ID" />
        </f:facet>
        <h:outputText value="#{dataItem.id}" />
    </h:column>

    <h:column>
        <f:facet name="header">
            <h:outputText value="Name" />
        </f:facet>
        <h:outputText value="#{dataItem.name}" />
    </h:column>

    <h:column>
        <f:facet name="header">
            <h:outputText value="Value" />
        </f:facet>
        <h:outputText value="#{dataItem.value}" />
    </h:column>
</h:dataTable>

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 <th> 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:

<h:form>
    <h:dataTable binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="ID" />
            </f:facet>
            <h:commandLink value="#{dataItem.id}" action="#{myBean.editDataItem}" />
        </h:column>

        ...
    </h:dataTable>
</h:form>

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

<h:form>
    <h:dataTable value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="ID" />
            </f:facet>
            <h:commandLink value="#{dataItem.id}" action="#{myBean.editDataItem}">
                <f:setPropertyActionListener target="#{myBean.dataItem}" value="#{dataItem}" />
            </h:commandLink>
        </h:column>

        ...
    </h:dataTable>
</h:form>
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:

<h:form>
    <h:panelGrid columns="2">
        <h:outputText value="ID" />
        <h:outputText value="#{myBean.dataItem.id}" />

        <h:outputText value="Name" />
        <h:inputText value="#{myBean.dataItem.name}" />

        <h:outputText value="Value" />
        <h:inputText value="#{myBean.dataItem.value}" />
    </h:panelGrid>

    <h:inputHidden binding="#{myBean.dataItemId}" />
    <h:commandButton value="Save" action="#{myBean.saveDataItem}" />
</h:form>

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:

<h:form>
    <h:dataTable value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="ID" />
            </f:facet>
            <h:outputText value="#{dataItem.id}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:outputText value="Name" />
            </f:facet>
            <h:inputText value="#{dataItem.name}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:outputText value="Value" />
            </f:facet>
            <h:inputText value="#{dataItem.value}" />
        </h:column>
    </h:dataTable>
    <br/>
    <h:commandButton value="Save" action="#{myBean.saveDataList}" />
</h:form>

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:

<h:form>
    <h:dataTable value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="ID" />
            </f:facet>
            <h:outputText value="#{dataItem.id}" rendered="#{dataItem.id != null}" />
            <h:outputText value="new" rendered="#{dataItem.id == null}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:outputText value="Name" />
            </f:facet>
            <h:outputText value="#{dataItem.name}" rendered="#{dataItem.id != null}" />
            <h:inputText value="#{dataItem.name}" rendered="#{dataItem.id == null}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:outputText value="Value" />
            </f:facet>
            <h:outputText value="#{dataItem.value}" rendered="#{dataItem.id != null}" />
            <h:inputText value="#{dataItem.value}" rendered="#{dataItem.id == null}" />
        </h:column>
    </h:dataTable>
    <br/>
    <h:inputHidden binding="#{myBean.addCount}" converter="javax.faces.Integer" />
    <h:commandButton value="Add" action="#{myBean.addNewDataItem}" />
    <h:commandButton value="Save" action="#{myBean.saveNewDataItems}" />
</h:form>

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<MyData> 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<SomeIDType, Boolean> 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:

<h:form>
    <h:dataTable value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="Select" />
            </f:facet>
            <h:selectBooleanCheckbox value="#{dataItem.selected}" />
        </h:column>

        ...
    </h:dataTable>

    <h:commandButton value="Get selected items" action="#{myBean.getSelectedItems}" />
</h:form>

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<MyData> selectedDataList;

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

    public String getSelectedItems() {

        // Get selected items.
        selectedDataList = new ArrayList<MyData>();
        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<MyData> 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<SomeIDType, Boolean> where the SomeIDType must represent the same type as the unique ID of the DAO, in this case Long:

<h:form>
    <h:dataTable value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="Select" />
            </f:facet>
            <h:selectBooleanCheckbox value="#{myBean.selectedIds[dataItem.id]}" />
        </h:column>

        ...
    </h:dataTable>

    <h:commandButton value="Get selected items" action="#{myBean.getSelectedItems}" />
</h:form>

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<Long, Boolean> selectedIds = new HashMap<Long, Boolean>();
    private List<MyData> selectedDataList;

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

    public String getSelectedItems() {

        // Get selected items.
        selectedDataList = new ArrayList<MyData>();
        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<Long, Boolean> getSelectedIds() {
        return selectedIds;
    }

    public List<MyData> 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:

<h:form>
    <h:dataTable binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:outputText value="Select" />
            </f:facet>
            <h:selectOneRadio valueChangeListener="#{myBean.setSelectedItem}"
                onclick="dataTableSelectOneRadio(this);">
                <f:selectItem itemValue="null" />
            </h:selectOneRadio>
        </h:column>

        ...
    </h:dataTable>

    <h:commandButton value="Get selected item" action="#{myBean.getSelectedItem}" />
</h:form>

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<MyData>();
        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:

<h:form>
    <h:dataTable binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem">
        <h:column>
            <f:facet name="header">
                <h:commandLink actionListener="#{myBean.sortDataList}">
                    <f:attribute name="sortField" value="getId" />
                    <h:outputText value="ID" />
                </h:commandLink>
            </f:facet>
            <h:outputText value="#{dataItem.id}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:commandLink actionListener="#{myBean.sortDataList}">
                    <f:attribute name="sortField" value="getName" />
                    <h:outputText value="Name" />
                </h:commandLink>
            </f:facet>
            <h:outputText value="#{dataItem.name}" />
        </h:column>

        <h:column>
            <f:facet name="header">
                <h:commandLink actionListener="#{myBean.sortDataList}">
                    <f:attribute name="sortField" value="getValue" />
                    <h:outputText value="Value" />
                </h:commandLink>
            </f:facet>
            <h:outputText value="#{dataItem.value}" />
        </h:column>
    </h:dataTable>

    <h:inputHidden value="#{myBean.sortField}" />
    <h:inputHidden value="#{myBean.sortAscending}" />
</h:form>

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:
 * <pre>
 * Collections.sort(dataList, new DTOComparator("getName", true));
 * </pre>
 * The following call is also supported. The "get" will be prefixed automatically if not present:
 * <pre>
 * Collections.sort(dataList, new DTOComparator("name", true));
 * </pre>
 * 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:
 * <pre>
 * Collections.sort(myData1List, new DTOComparator("myData2.name", true));
 * </pre>
 * 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.
 * <p>
 * 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<Object> {

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

    private List<String> 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<String>();
        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<String> 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<Object>) o1).compareTo(o2));
        } else {
            return (o2 == null) ? -1 : ((o1 == null) ? 1 : ((Comparable<Object>) 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.

<h:form>
    <h:dataTable binding="#{myBean.dataTable}" value="#{myBean.dataList}" var="dataItem" rows="3">
        <h:column>
            ...
        </h:column>

        ...

        <f:facet name="footer">
            <h:panelGroup>
                <h:commandButton value="first" action="#{myBean.pageFirst}"
                    disabled="#{myBean.dataTable.first == 0}" />
                <h:commandButton value="prev" action="#{myBean.pagePrevious}"
                    disabled="#{myBean.dataTable.first == 0}" />
                <h:commandButton value="next" action="#{myBean.pageNext}"
                    disabled="#{myBean.dataTable.first + myBean.dataTable.rows
                        >= myBean.dataTable.rowCount}" />
                <h:commandButton value="last" action="#{myBean.pageLast}"
                    disabled="#{myBean.dataTable.first + myBean.dataTable.rows
                        >= myBean.dataTable.rowCount}" />
            </h:panelGroup>
        </f:facet>
    </h:dataTable>
</h:form>

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:

<h:dataTable value="#{myBean.dataList}" var="dataItem">
    <h:column>
        <h:outputText value="#{dataItem.name}" />
    </h:column>

    <h:column>
        <h:outputText value="#{dataItem.value}" />
    </h:column>

    <h:column>
        <h:dataTable value="#{dataItem.innerList}" var="innerItem">
            <h:column>
                <h:outputText value="#{innerItem.name}" />
            </h:column>

            <h:column>
                <h:outputText value="#{innerItem.value}" />
            </h:column>
        </h:dataTable>
    </h:column>
</h:dataTable>

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

package mypackage;

public class MyData {

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

    private List<MyData> innerList;

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

    public List<MyData> getInnerList() {
        return innerList;
    }

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

    public void setInnerList(List<MyData> 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:


<h:panelGroup 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 <h:dataTable value="#{myBean.dataList}" var="dataItem">.
        HtmlDataTable dataTable = new HtmlDataTable();
        dataTable.setValueExpression("value",
            createValueExpression("#{myBean.dataList}", List.class));
        dataTable.setVar("dataItem");

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

        // Create <h:outputText value="ID"> for <f:facet name="header"> of 'ID' column.
        HtmlOutputText idHeader = new HtmlOutputText();
        idHeader.setValue("ID");
        idColumn.setHeader(idHeader);

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

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

        // Create <h:outputText value="Name"> for <f:facet name="header"> of 'Name' column.
        HtmlOutputText nameHeader = new HtmlOutputText();
        nameHeader.setValue("Name");
        nameColumn.setHeader(nameHeader);

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

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

        // Create <h:outputText value="Value"> for <f:facet name="header"> of 'Value' column.
        HtmlOutputText valueHeader = new HtmlOutputText();
        valueHeader.setValue("Value");
        valueColumn.setHeader(valueHeader);

        // Create <h:outputText value="#{dataItem.value}"> 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 <h:panelGroup binding="#{myBean.dataTableGroup}">.
        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:


<h:panelGroup 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 <h:dataTable binding="#{myBean.dataTable}"
        // 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 <h:column> for 'ID' column.
        HtmlColumn idColumn = new HtmlColumn();
        actionDataTable.getChildren().add(idColumn);

        // Create <h:outputText value="ID"> for <f:facet name="header"> of 'ID' column.
        HtmlOutputText idHeader = new HtmlOutputText();
        idHeader.setValue("ID");
        idColumn.setHeader(idHeader);

        // Create <h:commandLink value="#{dataItem.id}" action="#{myBean.editPopulatedDataItem}" />
        // 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 <h:column> for 'Name' column.
        HtmlColumn nameColumn = new HtmlColumn();
        actionDataTable.getChildren().add(nameColumn);

        // Create <h:outputText value="Name"> for <f:facet name="header"> of 'Name' column.
        HtmlOutputText nameHeader = new HtmlOutputText();
        nameHeader.setValue("Name");
        nameColumn.setHeader(nameHeader);

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

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

        // Create <h:outputText value="Value"> for <f:facet name="header"> of 'Value' column.
        HtmlOutputText valueHeader = new HtmlOutputText();
        valueHeader.setValue("Value");
        valueColumn.setHeader(valueHeader);

        // Create <h:outputText value="#{dataItem.value}"> 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 <h:panelGroup binding="#{myBean.actionDataTableGroup}">.
        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:


<h:panelGroup 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 <h:dataTable value="#{myBean.dataList}" var="dataItem">.
        HtmlDataTable editableDataTable = new HtmlDataTable();
        editableDataTable.setValueExpression("value",
            createValueExpression("#{myBean.dataList}", List.class));
        editableDataTable.setVar("dataItem");

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

        // Create <h:outputText value="ID"> for <f:facet name="header"> of 'ID' column.
        HtmlOutputText idHeader = new HtmlOutputText();
        idHeader.setValue("ID");
        idColumn.setHeader(idHeader);

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

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

        // Create <h:outputText value="Name"> for <f:facet name="header"> of 'Name' column.
        HtmlOutputText nameHeader = new HtmlOutputText();
        nameHeader.setValue("Name");
        nameColumn.setHeader(nameHeader);

        // Create <h:inputText id="name" value="#{dataItem.name}"> 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 <h:column> for 'Value' column.
        HtmlColumn valueColumn = new HtmlColumn();
        editableDataTable.getChildren().add(valueColumn);

        // Create <h:outputText value="Value"> for <f:facet name="header"> of 'Value' column.
        HtmlOutputText valueHeader = new HtmlOutputText();
        valueHeader.setValue("Value");
        valueColumn.setHeader(valueHeader);

        // Create <h:inputText id="value" value="#{dataItem.value}"> 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 <h:panelGroup binding="#{myBean.editableDataTableGroup}">.
        editableDataTableGroup = new HtmlPanelGroup();
        editableDataTableGroup.getChildren().add(editableDataTable);

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

        // Create <h:commandButton id="save" value="Save" action="#{myBean.saveDataList}">
        // and add to <h:panelGroup binding="#{myBean.editableDataTableGroup}">.
        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:


<h:panelGroup 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<List<String>> 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<List<String>>();
        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 <h:dataTable value="#{myBean.dynamicList}" var="dynamicItem">.
        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 <h:column>.
            HtmlColumn column = new HtmlColumn();
            dynamicDataTable.getChildren().add(column);

            // Create <h:outputText value="dynamicHeaders[i]"> for <f:facet name="header"> of column.
            HtmlOutputText header = new HtmlOutputText();
            header.setValue(dynamicHeaders[i]);
            column.setHeader(header);

            // Create <h:outputText value="#{dynamicItem[" + i + "]}"> 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 <h:panelGroup binding="#{myBean.dynamicDataTableGroup}">.
        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<List<String>> 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.

<h:dataTable binding="#{myBean.dataTable}">
    <h:column>
        <f:facet name="header">
            <h:outputText value="Row #" />
        </f:facet>
        <h:outputText value="#{myBean.dataTable.rowIndex + 1}" />
    </h:column>

    ...
</h:dataTable>

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:

<h:dataTable rowClasses="row1, row2">
    ...
</h:dataTable

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.

<head>
    <script>
        window.onload = addOnclickToDatatableRows;
    </script>
</head>

<body>
    <h:dataTable id="dataTable">
        ...
    </h:dataTable>
</body>
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.

<head>
    <script>
        window.onload = addOnclickToDatatableRows;
    </script>
</head>

<body>
    <h:form id="form">
        <h:dataTable id="dataTable">
            ...
        </h:dataTable>
        <input type="hidden" name="rowIndex" />
        <h:commandButton value="Edit selected row" action="#{myBean.editDataItem}" />
    </h:form>
</body>

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<MyData> 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.

<head>
    <script>
        window.onload = addHoverToDatatableRows;
    </script>
</head>

<body>
    <h:dataTable id="dataTable">
        ...
    </h:dataTable>
</body>
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"%>

...

<h:form>
    <table>
        <thead>
            <tr>
                <th colspan="3">MASTER TABLE</th>
            </tr>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
            <c:forEach items="#{myBean.dataList}" var="dataItem" varStatus="status">
                <f:verbatim rendered="#{dataItem.id % 2 != 0}">
                    <tr style="background-color: red;">
                        <td colspan="3">This is just an example of an alternately inserted row.</td>
                    </tr>
                </f:verbatim>
                <tr style="background-color: ${status.index % 2 == 0 ? 'lightgray' : 'gray'};">
                    <td>
                        <h:commandLink value="#{dataItem.id}" action="#{myBean.editDataItem}">
                            <f:setPropertyActionListener
                               target="#{myBean.dataItem}" value="#{dataItem}" />
                        </h:commandLink>
                    </td>
                    <td><h:outputText value="#{dataItem.name}" /></td>
                    <td><h:outputText value="#{dataItem.value}" /></td>
                </tr>
            </c:forEach>
        </tbody>
    </table>
</h:form>

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