Table of Contents


Since 3.0

The ace:dataTable component renders an HTML table element. Rows are created from a List or DataModel object bound to the value property. The header and footer are rendered by ace:column component children, via their header/footer facets, or by their headerText / footerText properties. Optionally a child ace:columnGroup component definition can override these properties of the ace:column children to allow for column and row spanning configurations.

The full list of features provided by the table and dependant components:

  • Cell Selection, Multiple and Single
  • Row Selection, Multiple and Single
  • Row Editing
  • Row Panel Expansion
  • Row Sub-row Expansion
  • Per-Row Feature Configuration
  • Filtering, Per-Column and Global
  • Sorting, Multiple and Single Column
  • Column Reordering
  • Column Resizing
  • Column Visibility
  • Column Stacking
  • Column Pinning
  • Column Configuration Panel
  • Pagination
  • Scrolling (incl. Lazy Mode)
  • Lazy Loading Integration
  • Select, Edit and Filter Event Listeners
  • Column, Row Spanning Headers & Footers
  • Optional Application-bound State
    • Rows (selection, expansion, etc.)
    • Filtering
    • Sorting
    • Column Reordering
    • Pagination
See the ICEfaces Showcase Live Demo of this component, complete with source code.

Getting Started

A relatively basic usage of ace:dataTable:

<ace:dataTable var="single"
    <ace:ajax event="select" execute="details @this" render="details @this"
              onSuccess="ice.ace.jq('#details').effect('highlight');" />

    <ace:column headerText="UPC">
        <h:outputText value="#{single.upc}"/>

    <ace:column headerText="Artist">
        <h:outputText value="#{single.artist}" />

    <ace:column headerText="Album">
        <h:outputText value="#{single.album}"/>

    <ace:column headerText="Name">
        <h:outputText value="#{}"/>


TagLib Documentation
This section covers attributes involved in the typical use-cases for this component. For reference, the complete taglib documentation for this component is available here. Also of interest is the related components section.

The var attribute specifies a request-scope attribute under which the model data for the current row will be exposed.

The value attribute specifies model data for this table. Expected type is a List or DataModel. Lazy loading requires an instance of the LazyDataModel object. Sub-row expansion requires a model of type List<Map.Entry<Object, List>> where the Entry object make up the nodes of a tree; with the key being the value, and the List as possible child nodes.

The stateMap attribute specifies a RowStateMap object. Instantiated by the user or the component, allows application layer manipulation of row properties like selection, visibility and expansion. Maps from table model objects to RowState objects that keep state for component features. See the RowStateMap API for full details.

The paginator attribute specifies a boolean value indicating the use of pagination on this table. Default is false.

The rows attribute specifies the number of rows to display per page. Default is 0, to show all rows.

Component Documentation
This section covers complementary components used in the row selection / pagination sample above. For further reference on the component interaction features listed in the overview, see the related components section.

ace:ajax - Configures the component select event to use ajax communication, executing and rendering itself and another area ('details') of the page. Should this communication succeed, the provided javascript snippet is executed. Without this tag, the table would by default cache selections until a submit required by another feature or the tableis submitted as part of a larger request, rather then submitting selections immediately. For more info see Selection Modes, Passive vs. Active.

ace:column - Renders a td element (unless stacked) that represents a single column of data within a parent DataTable container.

Event Listeners

rowSelectListener Used to define a server side FacesListener which will be notified each time a row is added to selection. The listener receives a single SelectEvent argument. The server event is fired the same lifecycle as 'select' client event.
rowUnselectListener Used to define a server side FacesListener which will be notified each a row is removed from selection. The listener receives a single UnselectEvent argument. The server event is fired the same lifecycle as 'deselect' client event.
filterListener Used to define a server side FacesListener which will be notified each lifecycle that filtering is about to take place. The listener is notified before the filtering actually takes place. The listener receives no argument. The server event is fired the same lifecycle as 'filter' client event.

Client Behaviours

select Fired during the communication of selection of any row or cell, the component default if an ace:ajax tag doesn't declare an event.
page Fired during the change to another page, by default, execute @this, render @all.
deselect Fired during the deselection of any row or cell, by default, execute @this, render @all.
sort Fired during the communication of sorting of any column, by default, execute @this, render @all.
filter Fired during the communication of filtering of any column, by default, execute @this, render @all.
reorder Fired during the communication of column reordering, by default, execute @this, render @all.
editStart Fired during the display of row edit input fields, by default, execute @none, render @none.
editSubmit Fired during the execute and hiding of row edit input fields, by default, execute @none, render @none. The execute and render of this event are controlled by the component to correctly submit the edit controls of this row without executing anything else.
editCancel Fired during the hiding of row edit input fields, by default, execute @none, render @none. The execute and render of this event are controlled by the component to correctly reset the edit controls of this row without executing anything else.
expand Fired by expansion of either row sub-panel or row sub-rows, by default, execute @this, render @all.
contract Fired by contraction of either row sub-panel or row sub-rows, by default, execute @this, render @all.
rowsPerPage Fired when the rows per page is changed on the DataTable via the paginator control.
cellClick Fired when a cell is clicked on the DataTable. (as of ICEfaces-4.0.0, ICEfaces-3.3.0_P02)
cellDblClick Fired when a cell is double clicked on the DataTable. (as of ICEfaces-4.0.0, ICEfaces-3.3.0_P02)
pin Fired when a column is added to the pinning region of the table.
unpin Fired when a column is removed to the pinning region of the table.
find Fired for a find request (via table.ajaxFind() on the client).
It is not recommended to use @all or @form as the 'execute' value in edit* events, especially in editSubmit, since these events work in a special way, where their 'execute' attributes are calculated by the component code to target only those input components in the current row. Modifying these default values could lead to unexpected results.

Javascript API

ICEfaces 3.x

The client side component object is exposed through the global variable name specified in the widgetVar attribute.

ICEfaces 4+

The "widgetVar" attribute on the ACE components has been removed in ICEfaces 4 and in its place a new "ice.ace.instance()" client JavaScript object lookup API has been introduced. The reason for this change is to enable lazy-initialization of the ACE component JavaScript objects to improve runtime performance and reduce browser memory use.

var widget = ice.ace.instance('frm:componentId);
The ice.ace.instance function requires the full client id of the component to be specified, such as "j_idt77:componentId" instead of just "componentId". To reduce the complexity of working with complete IDs with this function it may be preferable in some cases to use prependId="false" in the enclosing form (e.g. <h:form prependId="false">).
This component doesn't have a client-side API made specifically to be used by application developers. However, the component's internal methods and variables can be accessed in this way, including the underlying jQuery object and objects from underlying Javascript libraries (if applicable), and can be used for whatever purpose a developer might have in mind.
The utility functions ice.ace.DataTable.removeAllInputNames(parentId) and ice.ace.DataTable.restoreInputNames(jqParent) can be used by developers to prevent unwanted submits when doing row/cell editing. The function ice.ace.DataTable.removeAllInputNames(parentId) removes all name attributes from all inputs inside cell editors in editing mode for an entire table. This way, if the form is submitted by another means, the values in the input fields in rows in editing mode will not be submitted. They will only be submitted when they are explicitly committed by the user. This is already handled by the ace:dataTable component, but these functions can be used to have more control in atypical use cases or in more complex scenarios.
Another set of utility functions that can be useful on tables that have selection enabled are table.selectAllRows(), table.deselectAllRows(), table.selectAllCells(), and table.deselectAllCells(). They are self-explanatory, and they only apply to visible rows/cells on the page (The aliases table.selectAllRowsOnPage(), table.deselectAllRowsOnPage(), table.selectAllCellsOnPage(), and table.deselectAllCellsOnPage() were introduced to make this clearer). The functions table.selectAllRowsInTable() and table.deselectAllRowsInTable() select all the rows in the table, whether they are currently visible or not on the page (e.g. because of pagination). These last two functions are client side functions that trigger requests to the server to mark all rows as selected in the RowStateMap object. Note that if you're using lazy loading, these last two functions will only work on the rows that have been already loaded into memory. In all these cases, the variable table is obtained via the ice.ace.instance() function described above.

Data Model Lifecycle

The following is a description of the creation of the DataModel from the object bound to the value property.

During table execution, getDataModel() is called. If setDataModel(null) hasn't been called or this is the first time getDataModel() has been run (this request), getValue() is called to return an object to back our DataModel.

The value returned from getValue() is optionally the value property of the table, as returned by a superclass implementation of getValue(), else it returns a filtered List of values. In all cases, the superclass return value has its hashCode calculated.

If the superclass hashCode has changed since getValue() has last been called, we're in a situation where we may need to handle the creation of a TreeDataModel (so we flag a later check in getDataModel()) and if we've applied any filters to the previous DataModel created from the old value, we flag that we ought to refilter the model (to create a new set of filtered values) prior to rendering.

If the superclass hashCode hasn't changed since getValue() has last been called, we return the filtered data set if one exists (avoiding the need to refilter) or return the superclass value. When returning to getDataModel() we skip the check in to handle TreeDataModel creation, and allow the getDataModel() superclass implementation to synthesize our model.

Custom hashCode() and equals() Methods

Because of the way the ace:dataTable data model works, it is necessary that all data objects (represented by rows) implement custom hashCode() and equals methods. It is not necessary to do so in all cases, but it's recommended and even required in some cases, especially for advanced features. It is imperative to do this when manipulating the table's RowStateMap and when working in lazy loading mode. The state of each row is stored in a RowState object, which contains information about the state of the row in regards to selection, expansion, edit mode, and visibility. RowStateMap implements Map<Object, RowState>, so it behaves like a regular map, where the key is the data object itself and the value is the state of the row. Thus, it is necessary for the ace:dataTable component to be able to identify each row uniquely across different requests. It is not reliable to depend on the default hashCode() method, because the returned value is prone to change at every lifecycle, since new object instances are created at every request, instead of preserving these same objects throughout the lifetime of the application or session. So, it is important to implement a hashCode() method that will always return the same value for an object, based on it's data (especially its id or whatever makes it unique) across requests. Because of the same reasons, it is necessary to add a custom equals() method that will compare the object's data to determine if two objects are equal or not. The default equals method simply checks for object references, but this is not enough in our case, since object references can change at any moment. This is especially important, in order to keep consistent row states, when working in the lazy loading mode, since row objects aren't kept in memory, but retrieved dynamically, so by correctly implementing equals() and hashCode() methods we can preserve the state of a row every time it is fetched from the external data source to be displayed, otherwise the row state map won't return the same RowState object when using the data object as key, since it'd have a different hash code at a different lifecyle.

Lazy Loading

The lazy loading mode is a convenient way to allow a table to have a large number of rows, without having to load all the data into memory, as is normally done. This mode requires some additional work from the application developer. First of all, the 'lazy' attribute must be set to true (paginator="true" will be necessary as well in most cases). Second, the value attribute must be bound to an instance of LazyDataModel (org.icefaces.ace.model.table.LazyDataModel), which the developer must implement. Actually, the only method to implement of this abstract class is the 'load' method. This method is invoked at every lifecycle. This method gets passed all the necessary information to determine which rows are to be rendered, and it is the job of the application developer to use this information and retrieve the rows to be displayed from the external data source. This information includes the index of the first row, the page size, the sorting criteria per column, and the filters used in each column. Thus, filtering and sorting are left to the application developer to implement, since the ace:dataTable component would require the entire data set to be in memory in order to apply its sorting and filtering logic. Note that when using multiple filters in a column, they will be passed as a JSON array string. For example, by looking at the index of the first row of the table (as when paginating) and by looking at the page size, the application developer can determine where to start looking in the external data source and how many rows to retrieve. A page size of zero (0) means that all rows are to be retrieved. Also, when implementing LazyDataModel it is necessary to set the total row count explicitly, according to how many rows are available in the external data source. Finally, the class used for the data objects must implement custom hashCode() and equals() methods, in order to keep the state of each row across lifecycles, as discussed above. This way, rows lazily loaded can still use all of the advanced features that ace:dataTable offers such as selection, panel expansion, row expansion, visibility, editing mode, etc.

Selection Modes, Passive vs. Active

The table supports submitting selections and deselections passively, that is, storing them until submitted as part of an ajax request initiated by another feature of the table, or as part of communication initiated by another component in the view.

The alternative is active selection, which performs an ajax communication for every (de)selection. This was typically configured in 2.1 and prior with the 'update', 'rowSelectUpdate' and 'rowDeselectUpdate' properties which defined targets on the page to render during selection, and implied use of active selection. Our new handling, which checks to see if there are any non-disabled ace:ajax tags applied to the component for the "select" event, and if so, enables 'active' selection, using the configuration of the ajax tag, or "select" event defaults.

Custom Styling

The ace:dataTable component has some structural CSS that makes the table work well and be usable, and it also follows the current theme for colors, fonts, backgrounds and general look and feel. If custom styling is required, it is advised to define more specific CSS declarations for styling any node that composes the table. For example, to modify the header cells, a declaration can start with the root container's CSS class followed by a class used in header cells like .ui-datatable .ui-widget-header {/* CSS rules */}. This way only elements using the ui-wdiget-header class that are inside a data table will be affected, and not all the other nodes on the page that use the same class that are not inside a data table.

Custom styling is more complex in the ace:dataTable than in other components. The style and styleClass are rendered on the root div node. So, it might not be possible to affect the whole table by simply specifying inline styling or a class name on the root container, as this is usually easily overridden by so many other necessary styling rules used by the table.
In order to accomplish your custom styling goals for the ace:dataTable, follow the following recommendations:
1) Do not to use the style attribute, since, most likely it won't have any effect at all.
2) Use only the styleClass attribute as a class namespace that you include in any CSS rule you define for your table.
3) Be as specific as possible when defining CSS rules for the various nodes that compose the ace:dataTable. More specificity always takes precedence when the browser decides what styling to apply to any given node.
4) Examine the rendered HTML structure of the ace:dataTable that you want to customize to make sure you select the right nodes in your CSS rules.

The following table contains all the CSS class names used by all the nodes that compose the ace:dataTable.

Root container ui-datatable ui-widget
Column header ui-widget-header
Column header container ui-header-column
Column footer ui-widget-header
Column footer container ui-footer-column
Data ui-datatable-data ui-widget-content
Empty data ui-datatable-data-empty
Header ui-datatable-header ui-widget-header
Right header ui-header-right
Left header ui-header-left
Footer ui-datatable-footer ui-widget-header
Header text ui-header-text
Pin column control ui-pin-control
Sortable column ui-sortable-column
Sortable column control ui-sortable-control
Sortable column icon container ui-sortable-column-icon
Sortable column icon up ui-icon ui-icon-triangle-1-n
Sortable column icon down ui-icon ui-icon-triangle-1-s
Sortable column order ui-sortable-column-order
Column filter ui-column-filter
Unselectable row ui-unselectable
Reorderable column ui-reorderable-col
Expanded row ui-expanded-row
Expanded row content ui-expanded-row-content
Row panel toggler ui-row-panel-toggler
Row toggler ui-row-toggler
Row toggler span ui-row-toggler-span
Row toggle container ui-row-toggler-parent
Editable column ui-editable-column
Cell editor ui-cell-editor
Cell editor input ui-cell-editor-input
Cell editor output ui-cell-editor-output
Row editor ui-row-editor
Even row ui-datatable-even
Odd row ui-datatable-odd
Scrollable X ui-datatable-scroll-x
Scrollable container ui-datatable-scrollable
Scrollable header ui-datatable-scrollable-header
Scrollable body ui-datatable-scrollable-body
Scrollable footer ui-datatable-scrollable-footer
Column resizer ui-column-resizer
Conditional row dt-cond-row

Keyboard Support

Keyboard navigable sub-controls include Sorting, Filtering, Row Expansions and Row Editing.

Keypress Result
Enter/Space Key Begin new sort in selected direction.
Submit filter. (when configured)
Expand/Contract selected expandable rows/panel.
Execute selected Edit/Submit/Cancel row button.
Meta Key + Enter/Space Key Add column to current sort in selected direction.

Known Issues

When working in lazy mode and defining the columns using c:forEach, the LazyDataModel.load() method might receive an incorrect property name (the keys in the filters map and SortCriteria.getPropertyName()). This is because the EL expression is more complex and not possible to be resolved at render time, and it's also not possible to partially evaluate the EL expression to obtain the name of the property corresponding to a column. In order to work around this, use the lazyColumnKey attribute. In this attribute you can specify an arbitrary string to help you identify the column that is being sorted and/or filtered, in the LazyDataModel.load() method. Typically, one would use the same property name used in the sortBy and filterBy attributes.

It has been known that when using the binding attribute in conjunction with filtering, some things don't work as expected. Specifically, the text fields in the column headers, where the filter values are entered, are cleared at each request, resulting in the filter action not working. This can be avoided in two ways. The first way to avoid it is to simply put the DataTable binding property in a request-scoped backing bean. The other way to avoid it is to simply bind the filterValue attributes in the columns to properties in a backing bean.

For nested tables, the execute value in the 'expand' and 'contract' ajax events should be @form or @all for the expansion/contraction to work.

Because of the complexity of the column pinning feature and the styling requirements of this and other features, it's no possible to support certain features at the same time as column pinning. This is summary of the features that work and don't work in conjunction with column pinning.


  • selection
  • sorting
  • filtering (the whole table is slightly shifted to the right after filtering a pinned column, no more shifting on subsequent filtering actions)
  • panel expansion
  • row expansion
  • row listener

Not Working

  • paginator (misalignments after paginating when at least one column is pinned, it seems like applying resizeScrolling() afterwards fixes them)
  • column reordering (misalignments when reordering a column when at least one column is pinned)
  • column resizing (column resizing functionality not possible at all on scrollable tables, not directly related to column pinning)
  • grouping, column group (as expected)
  • live scrolling (misalignments occur after the first live scrollling dynamic update)
  • cell editor (pinned columns are moved to the left edge of the page when activating the cell editor)
  • table config panel (changes to table structure or table order won't work, but sorting and name changes will work)

Other Resources

Other Tips

The request delay for filter events is about half a second and is hard-coded in the javascript. However, it's possible to increase this delay by using ace:ajax and providing an onStart script that looks like this:

<ace:ajax event="filter" onStart="cfg.onstart = null; setTimeout(function() {ice.ace.ab(cfg);}, 500); return false;" />

This will increment the delay by the number of milliseconds specified in the setTimeout() call. The cfg object is always passed to the onStart callback (More information here:, and it contains all the settings for the ajax request. So, what this piece of code is doing first is removing the onStart function from the ajax settings, in order to avoid an infinite loop, and then sending the request with an additional delay. Returning false makes sure to cancel the initial request attempt.

The column pinning functionality only works in conjunction with the scrollable table functionality.

The 'find' ajax event works differently than other ajax events. The table.ajaxFind() function must be invoked on the table instance on the client side to send a request to the server. This could be done via an external button or link, for example. Then, you have to register an ajax listener in your bean that takes an object of type DataTableFindEvent and then you do the find operation yourself, which should involve calling the findRow() method on the DataTable component on the server. An example of this can be found on the ace:dataTable > Find demo on the showcase.

Related Components

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

© Copyright 2021 ICEsoft Technologies Canada Corp.