Introduction
The ADF Table component is used to display a list of structured data.
For example, if we have a data structure called Person that has two
properties - firstname and lastname, we could use a Table with two
columns - one for firstname, and the other for lastname - to display
a list of Person objects.
The Table component is similar to the standard
UIData
component in JSF, but include
s a number of extra features, including
support for identifying rows by key (instead of by index),
built-in support for paging through large models,
sorting the model, selecting single or multiple items in
the model, and toggling the display of details for particular
items in the model.
The Table Model
The ADF Table component uses a model to access the data in the underlying list.
The specific model class is
oracle.adf.view.faces.model.CollectionModel
. You may
also use other model instances, e.g.,
java.util.List
,
array, and
javax.faces.model.DataModel
. The Table will
automatically convert the instance into a CollectionModel.
To access a particular row in the list, first make that row current,
and then call the
getRowData()
method on the Table. To
make a row current, call the
setRowIndex(...)
method (on
the Table) with the appropriate index into the list. Alternatively,
call the
setRowKey(...)
method with the appropriate rowKey.
To obtain the total number of rows in the list, call the
getRowCount()
method on the Table. In the case where the
model does not yet know the total number of rows that are
available,
getRowCount()
will return -1.
The Table has an
isRowAvailable()
method that returns
true
if the current row ( see
setRowIndex(...)
)
is available. This method is especially useful when the total number
of rows is unknown.
Columns
The immediate children of a Table component must all be
<
af:column
>
components. Each visible ADF Column
component creates a separate column in the Table.
Headers
Use the "header" facet on a Column to create the column header.
The following example creates a two-column table with the column headers -
"Firstname" and "Lastname":
<af:table>
<af:column>
<f:facet name="header">
<af:outputText value="Firstname"/>
</f:facet>
...
</af:column>
<af:column>
<f:facet name="header">
<h:outputText value="Lastname"/>
</f:facet>
...
</af:column>
</af:table>
Note that both ADF Faces and JSF HTML tags can be used inside of
the ADF table.
Data
The child components of each Column display the data for each row in
that column. The Column does not create child components per row;
instead, each child is repeatedly rendered (stamped) once per
row. Because of this stamping behavior, some components many
not work inside the table. Anything that is just pure output,
with no behavior, will work without problems, as will components that
don't "mutate" even as they deliver events (for example,
command components are fine). Components that mutate
their state are affected, but any that implement
EditableValueHolder are supported (this includes all
form input controls from the JSF specification as well
as ADF Faces input controls), as are UIXShowDetail components.
As each row is stamped, the data for the current row ( see
getRowData()
) is copied into an EL reachable property.
The name of this property is defined by the
var
property on the Table. Once the Table has completed rendering, this
property is removed (or r
everted back to its previous value). In
the following example, the data for each row is placed under the EL
property "row". Each Column displays the data for each row by getting
further properties from the "row" property:
<af:table var="row" value="#{myBean.allEmployees}">
<af:column>
<af:outputText value="#{row.firstname}"/>
</af:column>
<af:column>
af:outputText value="#{row.lastname}"/>
</af:column>
</af:table>
Formatting
The Column component supports the following attributes related to
formatting:
-
formatType
-
The type of formatting to use for this column. The legal
values are "text", "number" and "icon",
which describe columns that display text, numbers and buttons,
respectively. Text columns are left-justified, number columns
are right-justified, and button columns are center-justified.
-
gridVisible
-
Controls the display of a grid line on the left of the column.
-
width
-
The width of this column, e.g., "30%", "100px".
-
bandingShade
-
Determines what background color to use for this column. The
legal values are "light" and "dark".
-
noWrap
-
Controls whether long lines of text in the column data should
be wrapped.
-
headerNoWrap
-
Controls whether long lines of text in the column header should
be wrapped.
-
separateRows
-
Controls whether each child of this
column should be rendered in separate cells, or inside the same
cell.
Column Groups
<
af:column
>
tags can be nested to produce
groups of columns. The header of a column group spans across
all the columns it contains. The following example creates
a column group that has the header "Name" and contains
two sub columns with headers "First" and "Last":
<af:table var="row" value="#{myBean.employees}">
<af:column>
<f:facet name="header">
<af:outputText value="Name"/>
</f:facet>
<af:column>
<f:facet name="header">
<af:outputText value="First"/>
</f:facet>
<af:outputText value="#{row.firstname}"/>
</af:column>
<af:column>
<f:facet name="header">
<af:outputText value="Last"/>
</f:facet>
af:outputText value="#{row.lastname}"/>
</af:column>
</af:column>
</af:table>
Row Headers
Columns can be rendered as row headers by setting the
"rowHeader" attribute on
<
column
>
to be true.
Row header columns must be the first columns in a table.
Range Navigation
When the list being displayed by a Table is huge, you can enable the Table
to break up the list into ranges and display a single range at a time.
Range controls are provided on the Table to let the user scroll to the next range,
or to go back to the previous range. If the total size of the list is known, a
control to let the user jump directly to a particular part of the list is also
provided on the Table. Use the Table attributes "rows" and "first" to control the
range navigation feature.
The maximum number of rows to display in a single range is controlled
by the "rows" attribute, which defaults to a reasonable size (e.g.,
25). To disable range navigation (and display all the rows on one
page) set this attribute to 0. In the following example, the maximum
number of rows to display per range is set to 10:
The current range to display is controlled by the "first"
attribute. This attribute is an index (based at zero) of a row in the
list. Each range starts with the row identified by "first", and only
contains as many rows as indicated by the "rows" attribute. Initially,
the "first" attribute defaults to zero, which displays the range
with the first row at the top.
RangeChangeEvent
When the user changes the range (being displayed in the Table), the
Table generates a
RangeChangeEvent
. This event includes the
index (based at zero) of the row that should now be at the top of the
range. The Table responds to this event by automatically changing the
value of the "first" attribute to this index.
In some cases it is necessary to know when the user changes the range
on the Table (e.g., when the user navigates forward it might be necessary
to release any cached data created for a previous range). To receive
notifications of when the user changes the range, a
RangeChangeListener
instance should be registered with the
Table. This is done by calling the
addRangeChangeListener
method, or by using the
rangeChangeListener
MethodBinding on the Table.
These listeners are called after the Table has changed the
"first" attribute in response to the event.
Displaying Details
You can configure the Table to display or hide additional details of a
particular row in response to a user gesture. When the details feature
is enabled, a new column containing a toggle (per row) will render in
the Table. When a toggle is activated, the details for that row are
displayed. When a toggle is deactivated, the details for the row are
hidden. The user can also display or hide the details for all rows at
the same time (the controls for this feature are enabled by setting
the "allDetailsEnabled" property to true.)
To enable the details feature set the "detailStamp" facet on the
Table. Place the components that are used to show the details (of a
row), inside this facet. In the following example, the Person's age is
displayed in the details section:
<af:table var="row" value="#{myBean.allEmployees}">
<f:facet name="detailStamp">
<af:outputText value="#{row.age}"/>
</f:facet>
</af:table>
Usually, the default behavior of the Table (with respect to displaying
and hiding details) is sufficient. In some cases, however, it might be
desirable to programmatically display or hide the details for a
particular row. This can be done via the
RowKeySet
object
obtained from the Table by calling the
getDisclosureState()
method. First, make the relevant row
current by calling
setRowIndex(...)
or
setRowKey(...)
on the Table, and then call
add()
or
remove()
on the
RowKeySet
object. Adding the row to the set displays
the details of the row. Removing the row hides them.
DisclosureEvent
When the user hides or shows the details of a row, the Table
generates a
DisclosureEvent
. This event has an
isExpanded
method that returns whether the user wants
to show or hide the details of a particular row. That particular row
is made current on the Table before the event is delivered. The
Table responds to this by expanding or collapsing the details of
that row.
You can register custom
DisclosureListener
instances (that can
do post processing) on the Table component.
DisclosureAllEvent
When the user hides or shows the details of all rows, the Table
generates a
DisclosureAllEvent
. This event has an
isExpandAll
method that returns whether the user wants
to show or hide the details of all rows. The
Table responds to this by expanding or collapsing all the detail
rows.
Selection
The selection feature of a Table lets the user select one
or more rows in the list. The user can then perform some operation on
the selected rows by activating an appropriate ActionSource component (e.g.,
by clicking on an ADF CommandButton). Use the "selection" facet on the Table
to enable the selection feature (see the examples below).
There are two types of selection - single and multiple. The type of
selection is determined by the component defined as the "selection" facet.
Use the
<
af:tableSelectOne
>
component to enable
single selection, and
<
af:tableSelectMany
>
for
multiple selection.
The current selection of rows on the Table can be
programmatically
inspected and modified via the
RowKeySet
object obtained
by calling
getSelectionState()
on the Table. The rowKeys
of the currently selected rows can be accessed via the
getRowKeyIterator()
method on the
RowKeySet
instance. To programmatically change a selection, the appropriate row
must first be made current by calling
setRowIndex(...)
or
setRowKey(...)
on the Table. Then the selection of the
row may be changed by calling
add()
or
remove()
on the
RowKeySet
.
Single Selection
You enable single selection by setting the
<
af:tableSelectOne
>
component as the "selection"
facet on the Table. This component supports the "text" attribute,
which is used to display instructions to the user. The children of
this component are usually ActionSource components; these provide the
actions available to the user after selecting a row.
<af:table ...>
<f:facet name="selection">
<af:tableSelectOne text="Select an item and click on a button">
<af:commandButton text="Edit" action="..."/>
<af:commandButton text="Delete" .../>
<af:commandButton text="Copy" .../>
<af:commandButton text="Cut" .../>
</af:tableSelectOne>
</f:facet>
</af:table>
Once the user makes a selection and clicks a button, the
TableSelectOne component updates the
RowKeySet
obtained
by calling
getSelectionState()
on the Table. The
currently selected row is made current prior to calling the
ActionListener associated with the activated button. This means
that an action method on the selected row can easily be called,
as
shown in the following example:
<af:table var="row">
<f:facet name="selection">
<af:tableSelectOne>
<af:commandButton text="Delete" action="#{row.markForDeletion}"/>
</af:tableSelectOne>
</f:facet>
</af:table>
In the example above, a method (on the selected row instance) with
the following signature will be called:
public String markForDeletion()
Multiple Selection
Multiple selection is enabled by setting the
<
af:tableSelectMany
>
component as the "selection"
facet on the Table. Like
<
af:tableSelectOne
>
this
component supports the "text" attribute, which is used to display
instructions to the user. The children of this component are usually
ActionSource components; these provide the actions available to
the user after selecting the rows.
<af:table ...>
<f:facet name="selection">
<af:tableSelectMany text="Make a selection and click on a button">
<af:commandButton text="Delete"/>
<af:commandButton text="Copy"/>
<af:commandButton text="Cut"/>
</af:tableSelectMany>
</f:facet>
</af:table>
Once the user makes a selection and clicks a button, the
TableSelectMany component updates the
RowKeySet
obtained by calling
getSelectionState()
on the Table
prior to calling the ActionListener associated with the button. The
ActionListener gets the selected rowKeys by calling
getSelectionState().getRowKeyIterator()
on the Table.
In the following example, the
performDelete
method is
called when the "Delete" button is clicked.
<af:table binding="#{mybean.table}" ...>
<f:facet name="selection">
<af:tableSelectMany text="Make a selection and click on a button">
<af:commandButton text="Delete" actionListener="#{mybean.performDelete}"/>
</af:tableSelectMany>
</f:facet>
</af:table>
The
performDelete
method iterates through all the
selected rows and calls the
markForDeletion()
method on
each one:
public void performDelete(ActionEvent action)
{
UIXTable table = getTable();
Iterator selection = table.getSelectionState().getRowKeyIterator();
String oldKey = table.getRowKey();
while(selection.hasNext())
{
String rowKey = (String) selection.next();
table.setRowKey(rowKey);
MyRowImpl row = (MyRowImpl) table.getRowData();
row.markForDeletion();
}
// restore the old key:
table.setRowKey(oldKey);
}
// Binding methods for access to the table.
public void setTable(UIXTable table) { _table = table; }
public UIXTable getTable() { return _table; }
private UIXTable _table;
The above code sample will actually work with both
<
af:tableSelectOne
>
and
<
af:tableSelectMany
>
, though with
<
af:tableSelectOne
>
, at most
a single row would ever be returned from the Iterator.
SelectionEvent
Both the
<
af:tableSelectOne
>
and the
<
af:tableSelectMany
>
components trigger SelectionEvents when
the selection state of the table is changed. The SelectionEvent
reports which rows were just deselected and which rows were just
selected. Listeners for this event may be registered on the table
using the "selectionListener" attribute or by adding the listener to
the table using the "addSele
ctionListener" method.
Sorting
The Table component supports sorting columns in ascending or
descending order. A special UI indicator on a column header lets the
user know that the column is sortable. When the user clicks on a
column header to sort a previously unsorted column, the Table sorts
the column data in ascending order. Subsequent clicks on the same
header sort the data in the reverse order.
There are three requirements to enable sorting: the underlying table
model must support sorting, the "sortProperty" and "sortable"
attributes must be set on the column to enable the sort capability for
that column.
To support sorting, the
CollectionModel
instance must implement the following methods:
public boolean isSortable(String propertyName)
public List getSortCriteria()
public void setSortCriteria(List criteria)
If the underlying model is not a
CollectionModel
, the
Table automatically examines the actual data to determine which
properties are sortable. Any column that has data that implements
java.lang.Comparable
are sortable. This automatic
support cannot be nearly as efficient as coding sorting directly into
a
CollectionModel
(for instance, by translating the sort
into an "ORDER BY" SQL clause), but is sufficient for small data sets.
To associate a column with a particular property-name to be used for
sorting purposes, use the "sortProperty" attribute on the column.
To enable the UI for sorting a particular column, set the
"sortable" property to
true
.
In the following example,
both columns are sortable. S
orting the first column sorts by the
"firstname" property; sorting the second column sorts by the "lastname"
property.
<af:table ...>
<af:column sortProperty="firstname" sortable="true">
<f:facet name="header">
<af:outputText value="Firstname" />
</f:facet>
...
</af:column>
<af:column>
<f:facet name="header" sortProperty="lastname" sortable="true">
<af:outputText value="Lastname" />
</f:facet>
...
</af:column>
</af:table>
SortEvent
When the user clicks a sortable column header, the
<
af:table
>
component generates a
SortEvent
. This event has a
getSortCriteria()
property that returns the criteria
that the table must be sorted by.
The Table responds to this event by calling the
setSortCriteria
method on the underlying
CollectionModel
. Any registered
SortListener
instances will also be called.
Banding
Banding is a technique where groups of rows (or columns)
are displayed with alternating background colors. This helps to
differentiate between adjacent rows (or columns).
The "banding" attribute on the Table controls the type of banding to
use. The legal values are "row", "column" and "none". When "banding"
is "row", adjacent rows have alternating background colors. When
"banding" is "column", adjacent columns have alternating background
colors. When "banding" is "none", banding is disabled. The default is
"none".
When banding is enabled, the "bandingInterval" attribute controls
the number of consecutive rows (or
columns) that are colored the same.
It defaults to "1".
Note that the above banding attributes on the Table are ignored when
the "bandingShade" attribute is used on the Column (see
Formatting
).