-
Notifications
You must be signed in to change notification settings - Fork 17
WTable
WTable is used to create tables. Tables must only be used to layout tabular data. It is generally an error to use a table to lay out a navigation system (see WMenu or ListLayout) or form controls (see WFieldLayout).
help wanted This enormous document was written to help UI designers working with WComponents, has subsequently grown in weird ways and needs a rewrite. IT also misses some important aspects of WTable, does not include anything abut the rather comlicated process of creating a WTable and misses many code samples.
- Why use WTable
- Creating a WTable
- Accessibility
- Empty tables
- Appearance
- Row selection
- Row expansion
- Pagination
- Sorting
- Table actions
- HTML output
- Hiding
- Error states
- Related Components
- Further information
WTable is for arranging the layout of tabular data. Tabular data consists of a matrix repeated (or notionally repeatable) data which is able to be rationally segmented into rows and columns. It is recommended that the data should be identified by at least one of column heading and/or row headings. WTable expects that the data will be able to be divided into columns and each data column will have a palpable and meaningful column heading.
Do not use WTable for general layout or to lay out a set of label:control pairs. For this latter case use WFieldLayout.
As a rule of thumb a WTable should be directly added to any of the following only:
- the WApplication;
- a WPanel or a layout component of WPanel;
- a WColumn or column of a ColumnLayout
- the content of a WCollapsible; or
- another WTable (though this is discouraged).
WTable must not be placed in any container WComponent which may only contain phrasing content.
to be written
Also see WTableColumn and the table examples in the wcomponents-examples
module.
The primary use case of a table is to tabulate data with column and (optionally) row headers. Any deviation from this should be accompanied by an adequate description of the table. The only appropriate meachanism for describing a table is its caption. For more information see the specification.
The table caption is a string property which represents the title of the table. It is output as the caption element. This is currently under review as the caption should be a component container rather than being limited to simple text.
From the specification:
The caption element represents the title of the table that is its parent
The WTable caption should add context and clarity to the table. It represents the title of the table but can also be used to add information explaining the table. The caption, unlike the (deleted in HTML 5) HTML summary
attribute the caption is able to be presented to all users and should be used as a matter of course.
// given WTable table
table.setCaption("Uptake of mobile broadband per capita by country.");
The summary attribute of the HTML4 (and earlier) table element is not supported by WTable as it is not part of the HTML5 specification. The summary attribute has always been controversial and is not an accessibility feature. It is opaque to almost all users.
Column headers are required for a WTable. The content of the column heading should appropriately label the column. See WTableColumn for more information.
Row headings are optional. If row headers are used then the first data column is deemed to be the row heading. This is set on the table as a whole and is not configurable per row.
// with WTable `table`
// use row headers
table.setRowHeaders(true);
Column headings may be hidden. This is done by using WTable's setShowColumnHeaders(boolean)
. If set false
the column headings will be moved out of the viewport but will still be available to assistive technologies. This is strongly discouraged since your table should always provide all users with information as to the data presented. This property is under review and may be removed in future releases.
Column headings must not be hidden if the table implements sorting (as the column heading is the sort control).
// hide all column headers for a WTable table:
table.setShowColumnHeaders(false);
If a WTable has a temporary lack of data (for example if a search returns no results or if a filter is applied which hides all data) then a noData
message may be shown. The default is "no data" but this may be overridden using setNoDataMessage(String)
;
// With WTable table
table.setNoDataMessage("Yes, we have no bananas.");
The following screenshot is of a simple WTable:
The following screenshot is of a WTable with all available options enabled; this is not necessarily a typical WTable:
It is possible to set alternating row or column background effects to produce a striped table which may provide usability improvements, especially in large tables. This is done using the stripingType
property from an option in the WTable.StripingType
enum. Available settings for this property are:
-
NONE
which provides no striping. This is the default.// With Wtable table table.setStripingType(WTable.StripingType.NONE);
-
ROWS
in which the striping is horizontal. This is the recommended setting for most use cases.// With Wtable table table.setStripingType(WTable.StripingType.ROWS);
-
COLUMNS
in which the striping is vertical. This can cause issues if the WTable is also sortable and the sorted column has a background effect.// With Wtable table table.setStripingType(WTable.StripingType.COLUMNS);
Setting row and/or column separators will place borders between rows and/or columns to provide visible separation of the set table structure. This is done using the separatorType
property from the WTable.SeparatorType
enum. The available settings for this property are:
-
NONE
which is the default and provides no visible separation.// With Wtable table table.setSeparatorType(WTable.SeparatorType.NONE);
-
HORIZONTAL
in which rows are separated by a single pixel combined border.// With Wtable table table.setSeparatorType(WTable.SeparatorType.HORIZONTAL);
-
VERTICAL
in which columns are separated by a single pixel combined border.// With Wtable table table.setSeparatorType(WTable.SeparatorType.VERTICAL);
-
BOTH
in which both separators are shown.// With Wtable table table.setSeparatorType(WTable.SeparatorType.BOTH);
When adding a separator the default for WTable is to have have a single pixel border which is shared between the affected cells such that 0.5 pixels is assigned to each cell on each side of the border. This will prevent some user agents from displaying styled borders such as dotted or dashed due to limitations in those user agent's border calculation mechanism.
WTable rows may 'contain' other rows using a sub-row paradigm. This produces a treegrid structure. Due to a historical error it is possible to create these treegrids without the appearance of a row hierarchy. This is under review but at v1.4 and earlier the type
property, selected from the WTable.Type
enum, determines the model for sub row display. By default all table rows are vertically aligned to the column headers. When this property has the value HIERARCHIC
then each sub row is indented relative to its parent.
// With Wtable table
table.setType(WTable.Type.HIERARCHIC);
See WTableColumn.
WTable is a marginable component.
WTable offers some simple in-built responsive design which is opt-in on a table-by-table basis. See Responsive design for more information.
Any WTable may be configured so that the user may select 0-1 (single row) or 0...n (multiple row) of n rows using .setSelectMode(WTable.SelectMode)
. The row itself is the selected item so clicking on any part of a row which is not itself an interactive element will select the row. When row selection is enabled any given row may be marked as unselectable
.
Row selection is used in combination with table actions to provide a mechanism to undertake specific activities based on the number of rows selected.
An Action may be triggered when row selection has changed. This is attached to the WTable using setSelectionChangeAction(Action)
:
// with WTable table
table.setSelectionChangeAction(new Action() {
@Override
public void execute(final ActionEvent event) {
// do stuff here...
}
});
When single selection is specified selecting a row will automatically deselect the previously selected row (if any). The rows are analogous to a set of radio buttons and may be navigated using the arrow keys. The row selection indicator is a decoration which appears similar to a radio button and the selected state of the row is indicated in a manner equivalent to the appearance of a selected radio button.
// with WTable table
table.setSelectMode(WTable.SelectMode.SINGLE)
When multiple selection is specified selecting a row will not automatically deselect the previously selected row(s). The rows are analogous to a set of check boxes. The row selection indicator is a decoration which appears similar to a check box and the selected state of the row is indicated in a manner equivalent to the appearance of a selected check box. Unlike a set of check boxes though selectable rows may be navigated by the keyboard using the arrow keys.
// with WTable table
table.setSelectMode(WTable.SelectMode.MULTIPLE);
If MULTIPLE
selection is enabled and the current table view (for example if pagination is enabled) contains at least one selectable row then the table may be given a set of controls to allow the user to select or deselect all visible rows. This control is analogous to WSelectToggle and is able to appear in the same forms:
-
TEXT
which consists of two controls, one to select all and one to deselect all rows. This control is placed above the column headers and aligned to the left of the table.// with WTable table table.setSelectAllMode(WTable.SelectAllType.TEXT);
-
CONTROL
which consists of a single tri-state check box analog which toggles through the selected states and the state of which indicates whether all, some or none of the rows are selected. This control is placed in the table header along-side the column headers in the column header for the selected state reflection indicator.// with WTable table table.setSelectAllMode(WTable.SelectAllType.CONTROL);
The select all/none control(s) will only act on selectable rows. If the table contains WCheckBox components their state is unaffected by these controls. A WSelectToggle can adjust the state of WCheckBox components within a WTable and any multi-selectable table rows within its target container.
Only perceptible rows are able to have their state changed by the select all/none controls.
If the table has collapsed sub rows or pagination then using select all (for example) will only select those rows visible to the user and the control may be in the "mixed" state. Whilst there are means to overcome this limitation we do not generally allow a user to interact with controls they are not able to perceive as this is somewhat akin to magic.
We do allow a table 'select none' control (or deselection of the control if the property value is CONTROL
) to deselect "hidden" rows. This is to prevent unexpected behaviour and inconsistency when a table has expandable rows.
When table also has row expansion as well as multiple selection and select all/none controls:
- in client mode all sub rows are present so could be "selectable" by a select toggle;
- in lazy or dynamic mode only the descendants of opened rows are available.
When "select all" is invoked if we allowed hidden rows to be selected:
- in client rows the newly visible rows would be selected;
- in lazy/dynamic mode the newly visible rows would not be selected.
This leads to an inconsistent user experience so we do not usually allow interaction with controls which are not visible.
This can, however, lead to further inconsistency under the following conditions:
- a row is expanded; then
- "select all" is invoked so the (now visible) sub row(s) will be selected;
- the row is then collapsed; then
- "deselect all" is invoked so the (now hidden) sub row(s) will not be deselected as we do not allow interaction with hidden controls; then
- the view (or any part of the view containing the table) is refreshed.
If the expand mode is LAZY
or DYNAMIC
the closed row does not have children:
- the parent row is then expanded again;
- the table state does not include "selected" for the child rows as they are not present;
- therefore the newly visible rows will not be selected.
If the expand mode is CLIENT
all rows are always present:
- the child rows retain their selected state as they were not deselected by the "deselect all" command since they were hidden (as described above);
- the parent row is expanded again;
- the table does not send its state to the server and the child rows are not changed;
- therefore the newly visible rows will be selected which is inconsistent with the above.
To work around this inconsistency we must allow table row selection to deselect hidden rows but not select them.
If a WTable has both
-
MULTIPLE
row selection and - row expansion
then there is an option to output a local select all/none control
in any row which has selectable nested rows.
This is done using the method .setToggleSubRowSelection(boolean)
. When this functionality is enabled a controller is placed into the same cell as the row's selected state reflector (note: this is a secondary indicator of the row's selected state, the primary indicator is the row's aria-selected
state). This control allows the user to select/deselect the row and its descendant rows. As with the table level select all/none controls
select all
will only work on perceptible rows whereas select none
will work on all available descendant rows.
// given WTable table with a tree model as sub-row selection depends
// upon the existence of sub-rows...
// the table must have multiple selection to support select all/none.
table.setSelectMode(WTable.SelectMode.MULTIPLE);
// the table must have row expansion and nested rows to have sub-row selection
table.setExpandMode(WTable.ExpandMode.CLIENT); // any mode except NONE will work.
// then to have a selector in each parent row to control the selection if
// that rows descendants.
table.setToggleSubRowSelection(true);
Any row within a WTable with row selection may be marked as not being able to be selected. This is done on the individual row using accessors for selectable
from the TableModel
interface.
A row may be marked as selected on load. If the WTable has row selection enabled and a row is not unselectable then this property will indicate that the row is selected on page load. This is usually determined from the data which is used to populate the table.
WTable supports multiple levels of sub row.
The appearance of the sub row content relative to its parent row is determined by the WTable's type
property where the HIERARCHIC
type will cause a sub row to be indented.
A table row with a sub row is expanded or collapsed by interacting with the sub row collapse control button.
To enable row expansion the WTable must implement a model which supports nested data. Examples of these may be found in the wcomponents-examples project. Once this model is employed row expansion is determined by the WTable.ExpandMode
.
Table row expansion supports the following modes:
-
CLIENT
which is the default and in which the sub row exists in the original page payload and expansion merely shows it;// With Wtable table table.setExpandMode(WTable.ExpandMode.CLIENT);
-
LAZY
in which the sub row content is fetched only when the row is expanded, if the row is expanded on page load this is the same asCLIENT
, this mode will replace the whole table and should only be used if the rows are very unlikely to be expanded;// With Wtable table table.setExpandMode(WTable.ExpandMode.LAZY);
-
DYNAMIC
in which the sub row content is fetched each time the row is expanded.// With Wtable table table.setExpandMode(WTable.ExpandMode.DYNAMIC);
A table with row expansion enabled and with at least one expandable row in the current view may have a set of expand all/collapse all
controls which are analogous to WCollapsibleToggle. This control is placed above the column headers and aligned to the right of the table. This feature is controlled using setExpandAll(boolean)
.
// With Wtable table
table.setExpandMode(WTable.ExpandMode.DYNAMIC);
table.setExpandAll(true);
The expand/collapse all controls will only act on rows which are perceptible to prevent inconsistent behaviour between ExpandMode.CLIENT
and the ajax modes.
If the table has more than one level of expandable row and a row which is closed has at least one sub-row which is also expandable then the following could occur when expand all
is invoked if we allowed the row expansion on hidden rows:
- if the expand mode is
CLIENT
-- all rows are present in the UI,
- expanding hidden rows would expand all descendants of the top level row and all rows would be expanded;
- if the expand mode is
LAZY
orDYNAMIC
-- only the sub row(s) of expanded rows are present in the UI,
- expanding all would expand all rows available in the UI,
- expandable rows which are descendants of previously closed rows were not in the UI at the time the state of the table was reported to the server and therefore could not be marked as expanded,
- therefore the newly added expandable rows are collapsed which is inconsistent with the above.
Further issues would arise if the table also had pagination with ExpandMode.CLIENT
. If hidden rows could be expanded then top-level rows on "pages" other than the current page would be expanded showing their descendant rows whilst they themselves remained hidden.
Pagination allows a large table to be split into sections for easier consumption and improved client performance. It is most appropriate for large data sets such as tabular data returned from search results. When specifying table pagination you must specify the appropriate number of rows to return per page. All other aspects of pagination are determined by the results set. The WTable model must support pagination.
If a table has pagination enabled then the table will show pagination controls. The user may change page by interacting with either of the drop down page list or the pagination buttons. If pagination is enabled then the WTable should at least have the number of rows per page and a PaginationMode set.
// With Wtable table - set max of 20 rows per page.
table.setRowsPerPage(20);
If the table allows user selectable rows per page then the number of rows per page set in setRowsPerPage(int)
must be one of the options in the rowsPerPageOptions List.
Table pagination supports the following modes:
-
CLIENT
in which the all pages exist in the original page payload and pagination merely changes which page is shown;// With Wtable table table.setPaginationMode(WTable.PaginationMode.CLIENT);
-
DYNAMIC
in which the page is changed using AJAX.// With Wtable table table.setPaginationMode(WTable.PaginationMode.DYNAMIC);
If a WTable has pagination enabled then it can be configured to allow the user to opt to show a different number of rows per page than the default number. This is done via a dropdown in the pagination controls. The values the user can select are determined by the individual application and the value "0" equates to "all rows in the available data set" which may or may not be something an application wants to support.
This is set using setRowsPerPageOptions(List<Integer>)
.
// a list of options from which the user can select a number of rows
// to show on each page
table.setRowsPerPageOptions(Arrays.asList(0, 5, 10, 15));
// setRowsPerPage must only use an item from the List
table.setRowsPerPage(15);
At the moment (v1.4 and earlier) selectable rows per page is not compatible with PaginationMode.CLIENT
. This was a conscious decision based on analysis of use scenarios and may be changed if there is any demand.
The pagination controls can be placed above the table, below the table or both. This is controlled by using WTable.setPaginationLocation(WTable.PaginationLocation)
and defaults to AUTO
. The options for WTable.PaginationLocation
are:
-
AUTO
- the position is determined solely by the theme which defaults to below the table; -
TOP
- the pagination controls are placed above the table; -
BOTTOM
- the pagination controls are placed below the table; -
BOTH
- the pagination controls are output both above the table and below the table. This is only really useful for tables with a relatively large number of rows.
// given WTable table ...
// this is the default so would only be used to reset.
table.setPaginationLocation(WTable.PaginationLocation.AUTO);
// put the pagination controls above the table only.
table.setPaginationLocation(WTable.PaginationLocation.TOP);
// put the pagination controls below the table only.
table.setPaginationLocation(WTable.PaginationLocation.BOTTOM);
// put the pagination controls above and below the table.
table.setPaginationLocation(WTable.PaginationLocation.BOTH);
The data in a table may be sorted by any specified data-bound column. In addition the table may be provided with a mechanism for the user to determine on which column, from a set of predetermined sortable columns, the data should be sorted.
Each sortable column is provided with controls to sort the data on that column. When the column is sorted a control is provided to reverse the sort order on that column. To be technically sortable the column data must conform to a mechanism which allows the data to be sorted, to be sensibly sortable the column should contain text content which a reasonable user could expect to be sortable and for which the order would be reasonably apparent.
It would be reasonable to sort text content alphabetically, numeric content or date content. Sorting icon content, on the other hand, may be technically feasible if each icon has a data-bound value but may be nonsensical to a reasonable user or may not be easily understood by the user.
If sortable columns are required the following should be specified:
- the sort criterion (even if self evident);
- if the data is not obviously sortable (such as image data) the data relationship and values on which the data should be sorted; and
- which, if any, column should be sorted initially and in which direction.
Even if a WTable does not have client enabled column sort a UI specification should still specify the data point on which to sort the data and the direction of sort. If the sorted column is not the first data column and the sort is not A-Z alphabetic, 0+ numeric, or most recent first date order one should specify the sort criteria (if any) in the table caption.
Table sorting supports only mode DYNAMIC
.
Each column may be set to be sortable if sorting is enabled. This is done on the table column.
// Make WTableColumn `sortableColumn` sortable
sortableColumn.setSortable(true);
// make column `notSortableColumn` not sortable
//(which is a bit superfluous as this is the default state of columns)
notSortableColumn.setSortable(false);
In the illustration below the first two columns are sortable, the third is not.
A table action is a submit button and performs a complete form submission. Unlike a normal WButton, however, a table action may be tied to the state of selected rows in a particular WTable. The action can also include a custom message as either a warning (which allows the user to continue the action after confirmation) or an error (which prevents the action) based on a minimum and/or maximum number of rows selected.
Note a table action is a WButton with optional constraints, it should not be confused with a WTable's actionOnChangeSelection
Action.
A particular action may include a warning under some conditions and an error under others. A table action need not be tied to any row selection condition(s) and table actions are available even if the table does not support row selection. If the table does not support row selection then no conditions should be specified for an action since they will not be able to be met.
Let us take the case of an action to delete rows from a table with multiple row selection. You may want to prevent the action if no rows are selected, since that would be a waste of the lengthy page reload; allow the action after confirmation if exactly one row is selected; and prevent the action with an error message if more than a given number of rows are selected (let us assume we will allow a user to delete three rows at a time, but one would be more likely).
Each of these conditions:
- maximum 0 rows with error "please select a row";
- minimum 1 row, maximum 3 rows with warning "are you sure you want to delete these rows";
- minimum four rows with error "you may only delete up to three records at a time".
This combination of action conditions allows for numerous common use-cases such as record comparison (minimum 2), record editing (minimum and maximum both 1), record movement (minimum 1), and record deletion (usually a maximum of 1 but always a minimum of 1).
WTable outputs a div element containing a table element. The div is used as a wrapper to encapsulate some table controls, to provide cross-user-agent support for horizontal scrolling of wide tables and for placing error messages.
A WTable must not be placed in the content of WComponents which must only contain phrasing content.
The column headers are placed in th elements within a tr element in a thead element. The column header may contain any other WComponents appropriate to a table column header. A column header must not, therefore, contain WHeading; WSkipLinks; or WPanel of types HEADER
, FOOTER
, CHROME
or ACTION
.
The table content is placed in a tbody element consisting of tr element(s) which contain 0-1 th element and the td elements for the data. The table data may consist of any other WComponent appropriate to displaying or interacting with tabular data.
The content of a table column or row heading is inserted into a th element as described above. The HTML th element specification was changed from HTML4.01 to HTML5 and now allows flow content but no header
, footer
, HEADING content or SECTIONING content elements. This change will result in an false-negative if a WComponents application's transformed output is tested against a validator which uses a HTML4.01 DTD or XHTML1.0 Schema so don't do this!
In addition a row heading does not have to be the first child of a tr element. The specification and algorithm for processing rows are both very clear and straight-forward on this point. They are also a lot of fun to read so I recommend you read the whole table processing model.
A WTable may be hidden on page load. When hidden the WTable is not available to any compliant user agent. It is present in the source and is not obscured in any way. This property is determined by a WSubordinateControl. When a WTable is hidden all of its content and internal controls are hidden.
A WTable is merely a container for other content. It cannot, in itself, be in an error state. It is sometimes required that a table indicate some kind of error message to users. In this case the table should have a validation scenario and be immediately followed by a WFieldErrorIndicator which may be used to output error messages resulting from this validation. This level of container-based validation is not currently catered for by client-side validation.
- WTableColumn
- WDefinitionList for laying out name:value pairs
- WFieldLayout for laying out input controls
- WPanel for generic layout
- WSection for generic layout of major UI segments