aboutsummaryrefslogtreecommitdiffstats
path: root/documentation/components/components-table.asciidoc
diff options
context:
space:
mode:
Diffstat (limited to 'documentation/components/components-table.asciidoc')
-rw-r--r--documentation/components/components-table.asciidoc1166
1 files changed, 1166 insertions, 0 deletions
diff --git a/documentation/components/components-table.asciidoc b/documentation/components/components-table.asciidoc
new file mode 100644
index 0000000000..43d5653868
--- /dev/null
+++ b/documentation/components/components-table.asciidoc
@@ -0,0 +1,1166 @@
+---
+title: Table
+order: 21
+layout: page
+---
+
+[[components.table]]
+= [classname]#Table#
+
+ifdef::web[]
+[.sampler]
+image:{live-demo-image}[alt="Live Demo", link="http://demo.vaadin.com/sampler/#ui/grids-and-trees/table]
+endif::web[]
+
+((("[classname]#Table#", id="term.components.table", range="startofrange")))
+
+
+The [classname]#Table# component is intended for presenting tabular data
+organized in rows and columns. The [classname]#Table# is one of the most
+versatile components in Vaadin. Table cells can include text or arbitrary UI
+components. You can easily implement editing of the table data, for example
+clicking on a cell could change it to a text field for editing.
+
+The data contained in a [classname]#Table# is managed using the Data Model of
+Vaadin (see
+<<dummy/../../../framework/datamodel/datamodel-overview.asciidoc#datamodel.overview,"Binding
+Components to Data">>), through the [classname]#Container# interface of the
+[classname]#Table#. This makes it possible to bind a table directly to a data
+source, such as a database query. Only the visible part of the table is loaded
+into the browser and moving the visible window with the scrollbar loads content
+from the server. While the data is being loaded, a tooltip will be displayed
+that shows the current range and total number of items in the table. The rows of
+the table are __items__ in the container and the columns are __properties__.
+Each table row (item) is identified with an __item identifier__ (IID), and each
+column (property) with a __property identifier__ (PID).
+
+When creating a table, you first need to define columns with
+[methodname]#addContainerProperty()#. This method comes in two flavors. The
+simpler one takes the property ID of the column and uses it also as the caption
+of the column. The more complex one allows differing PID and header for the
+column. This may make, for example, internationalization of table headers
+easier, because if a PID is internationalized, the internationalization has to
+be used everywhere where the PID is used. The complex form of the method also
+allows defining an icon for the column from a resource. The "default value"
+parameter is used when new properties (columns) are added to the table, to fill
+in the missing values. (This default has no meaning in the usual case, such as
+below, where we add items after defining the properties.)
+
+
+[source, java]
+----
+Table table = new Table("The Brightest Stars");
+
+// Define two columns for the built-in container
+table.addContainerProperty("Name", String.class, null);
+table.addContainerProperty("Mag", Float.class, null);
+
+// Add a row the hard way
+Object newItemId = table.addItem();
+Item row1 = table.getItem(newItemId);
+row1.getItemProperty("Name").setValue("Sirius");
+row1.getItemProperty("Mag").setValue(-1.46f);
+
+// Add a few other rows using shorthand addItem()
+table.addItem(new Object[]{"Canopus", -0.72f}, 2);
+table.addItem(new Object[]{"Arcturus", -0.04f}, 3);
+table.addItem(new Object[]{"Alpha Centauri", -0.01f}, 4);
+
+// Show exactly the currently contained rows (items)
+table.setPageLength(table.size());
+----
+
+In this example, we used an increasing [classname]#Integer# object as the Item
+Identifier, given as the second parameter to [methodname]#addItem()#. The actual
+rows are given simply as object arrays, in the same order in which the
+properties were added. The objects must be of the correct class, as defined in
+the [methodname]#addContainerProperty()# calls.
+
+.Basic Table Example
+image::img/table-example1.png[]
+
+Scalability of the [classname]#Table# is largely dictated by the container. The
+default [classname]#IndexedContainer# is relatively heavy and can cause
+scalability problems, for example, when updating the values. Use of an optimized
+application-specific container is recommended. Table does not have a limit for
+the number of items and is just as fast with hundreds of thousands of items as
+with just a few. With the current implementation of scrolling, there is a limit
+of around 500 000 rows, depending on the browser and the pixel height of rows.
+
+Common selection component features are described in
+<<dummy/../../../framework/components/components-selection#components.selection,"Selection
+Components">>.
+
+[[components.table.selecting]]
+== Selecting Items in a Table
+
+The [classname]#Table# allows selecting one or more items by clicking them with
+the mouse. When the user selects an item, the IID of the item will be set as the
+property of the table and a [classname]#ValueChangeEvent# is triggered. To
+enable selection, you need to set the table __selectable__. You will also need
+to set it as __immediate__ in most cases, as we do below, because without it,
+the change in the property will not be communicated immediately to the server.
+
+The following example shows how to enable the selection of items in a
+[classname]#Table# and how to handle [classname]#ValueChangeEvent# events that
+are caused by changes in selection. You need to handle the event with the
+[methodname]#valueChange()# method of the
+[classname]#Property.ValueChangeListener# interface.
+
+
+[source, java]
+----
+// Allow selecting items from the table.
+table.setSelectable(true);
+
+// Send changes in selection immediately to server.
+table.setImmediate(true);
+
+// Shows feedback from selection.
+final Label current = new Label("Selected: -");
+
+// Handle selection change.
+table.addValueChangeListener(new Property.ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ current.setValue("Selected: " + table.getValue());
+ }
+});
+----
+
+.Table Selection Example
+image::img/table-example2.png[]
+
+If the user clicks on an already selected item, the selection will deselected
+and the table property will have [parameter]#null# value. You can disable this
+behaviour by setting [methodname]#setNullSelectionAllowed(false)# for the table.
+
+The selection is the value of the table's property, so you can get it with
+[methodname]#getValue()#. You can get it also from a reference to the table
+itself. In single selection mode, the value is the item identifier of the
+selected item or [parameter]#null# if no item is selected. In multiple selection
+mode (see below), the value is a [classname]#Set# of item identifiers. Notice
+that the set is unmodifiable, so you can not simply change it to change the
+selection.
+
+=== Multiple Selection Mode
+
+A table can also be in __multiselect__ mode, where a user can select multiple
+items by clicking them with left mouse button while holding the kbd:[Ctrl] key (or kbd:[Meta] key) pressed. If kbd:[Ctrl] is not held, clicking an item will select it and
+other selected items are deselected. The user can select a range by selecting an
+item, holding the kbd:[Shift] key pressed, and clicking another item, in which case
+all the items between the two are also selected. Multiple ranges can be selected
+by first selecting a range, then selecting an item while holding kbd:[Ctrl], and then
+selecting another item with both kbd:[Ctrl] and kbd:[Shift] pressed.
+
+The multiselect mode is enabled with the [methodname]#setMultiSelect()# method
+of the [classname]#AbstractSelect# superclass of [classname]#Table#. Setting
+table in multiselect mode does not implicitly set it as __selectable__, so it
+must be set separately.
+
+The [methodname]#setMultiSelectMode()# property affects the control of multiple
+selection: [parameter]#MultiSelectMode.DEFAULT# is the default behaviour, which
+requires holding the kbd:[Ctrl] (or kbd:[Meta]) key pressed while selecting items, while in
+[parameter]#MultiSelectMode.SIMPLE# holding the kbd:[Ctrl] key is not needed. In the
+simple mode, items can only be deselected by clicking them.
+
+
+
+[[components.table.features]]
+== Table Features
+
+=== Page Length and Scrollbar
+
+The default style for [classname]#Table# provides a table with a scrollbar. The
+scrollbar is located at the right side of the table and becomes visible when the
+number of items in the table exceeds the page length, that is, the number of
+visible items. You can set the page length with [methodname]#setPageLength()#.
+
+Setting the page length to zero makes all the rows in a table visible, no matter
+how many rows there are. Notice that this also effectively disables buffering,
+as all the entire table is loaded to the browser at once. Using such tables to
+generate reports does not scale up very well, as there is some inevitable
+overhead in rendering a table with Ajax. For very large reports, generating HTML
+directly is a more scalable solution.
+
+
+[[components.table.features.resizing]]
+=== Resizing Columns
+
+You can set the width of a column programmatically from the server-side with
+[methodname]#setColumnWidth()#. The column is identified by the property ID and
+the width is given in pixels.
+
+The user can resize table columns by dragging the resize handle between two
+columns. Resizing a table column causes a [classname]#ColumnResizeEvent#, which
+you can handle with a [classname]#Table.ColumnResizeListener#. The table must be
+set in immediate mode if you want to receive the resize events immediately,
+which is typical.
+
+
+[source, java]
+----
+table.addColumnResizeListener(new Table.ColumnResizeListener(){
+ public void columnResize(ColumnResizeEvent event) {
+ // Get the new width of the resized column
+ int width = event.getCurrentWidth();
+
+ // Get the property ID of the resized column
+ String column = (String) event.getPropertyId();
+
+ // Do something with the information
+ table.setColumnFooter(column, String.valueOf(width) + "px");
+ }
+});
+
+// Must be immediate to send the resize events immediately
+table.setImmediate(true);
+----
+
+See <<figure.component.table.columnresize>> for a result after the columns of a
+table has been resized.
+
+[[figure.component.table.columnresize]]
+.Resizing Columns
+image::img/table-column-resize.png[]
+
+
+[[components.table.features.reordering]]
+=== Reordering Columns
+
+If [methodname]#setColumnReorderingAllowed(true)# is set, the user can reorder
+table columns by dragging them with the mouse from the column header,
+
+
+[[components.table.features.collapsing]]
+=== Collapsing Columns
+
+When [methodname]#setColumnCollapsingAllowed(true)# is set, the right side of
+the table header shows a drop-down list that allows selecting which columns are
+shown. Collapsing columns is different than hiding columns with
+[methodname]#setVisibleColumns()#, which hides the columns completely so that
+they can not be made visible (uncollapsed) from the user interface.
+
+You can collapse columns programmatically with
+[methodname]#setColumnCollapsed()#. Collapsing must be enabled before collapsing
+columns with the method or it will throw an [classname]#IllegalAccessException#.
+
+
+[source, java]
+----
+// Allow the user to collapse and uncollapse columns
+table.setColumnCollapsingAllowed(true);
+
+// Collapse this column programmatically
+try {
+ table.setColumnCollapsed("born", true);
+} catch (IllegalAccessException e) {
+ // Can't occur - collapsing was allowed above
+ System.err.println("Something horrible occurred");
+}
+
+// Give enough width for the table to accommodate the
+// initially collapsed column later
+table.setWidth("250px");
+----
+
+See <<figure.component.table.columncollapsing>>.
+
+[[figure.component.table.columncollapsing]]
+.Collapsing Columns
+image::img/table-column-collapsing.png[]
+
+If the table has undefined width, it minimizes its width to fit the width of the
+visible columns. If some columns are initially collapsed, the width of the table
+may not be enough to accomodate them later, which will result in an ugly
+horizontal scrollbar. You should consider giving the table enough width to
+accomodate columns uncollapsed by the user.
+
+
+[[components.table.features.components]]
+=== Components Inside a Table
+
+The cells of a [classname]#Table# can contain any user interface components, not
+just strings. If the rows are higher than the row height defined in the default
+theme, you have to define the proper row height in a custom theme.
+
+When handling events for components inside a [classname]#Table#, such as for the
+[classname]#Button# in the example below, you usually need to know the item the
+component belongs to. Components do not themselves know about the table or the
+specific item in which a component is contained. Therefore, the handling method
+must use some other means for finding out the Item ID of the item. There are a
+few possibilities. Usually the easiest way is to use the [methodname]#setData()#
+method to attach an arbitrary object to a component. You can subclass the
+component and include the identity information there. You can also simply search
+the entire table for the item with the component, although that solution may not
+be so scalable.
+
+The example below includes table rows with a [classname]#Label# in HTML content
+mode, a multiline [classname]#TextField#, a [classname]#CheckBox#, and a
+[classname]#Button# that shows as a link.
+
+
+[source, java]
+----
+// Create a table and add a style to allow setting the row height in theme.
+final Table table = new Table();
+table.addStyleName("components-inside");
+
+/* Define the names and data types of columns.
+ * The "default value" parameter is meaningless here. */
+table.addContainerProperty("Sum", Label.class, null);
+table.addContainerProperty("Is Transferred", CheckBox.class, null);
+table.addContainerProperty("Comments", TextField.class, null);
+table.addContainerProperty("Details", Button.class, null);
+
+/* Add a few items in the table. */
+for (int i=0; i<100; i++) {
+ // Create the fields for the current table row
+ Label sumField = new Label(String.format(
+ "Sum is <b>$%04.2f</b><br/><i>(VAT incl.)</i>",
+ new Object[] {new Double(Math.random()*1000)}),
+ ContentMode.HTML);
+ CheckBox transferredField = new CheckBox("is transferred");
+
+ // Multiline text field. This required modifying the
+ // height of the table row.
+ TextField commentsField = new TextField();
+ commentsField.setRows(3);
+
+ // The Table item identifier for the row.
+ Integer itemId = new Integer(i);
+
+ // Create a button and handle its click. A Button does not
+ // know the item it is contained in, so we have to store the
+ // item ID as user-defined data.
+ Button detailsField = new Button("show details");
+ detailsField.setData(itemId);
+ detailsField.addClickListener(new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ // Get the item identifier from the user-defined data.
+ Integer iid = (Integer)event.getButton().getData();
+ Notification.show("Link " +
+ iid.intValue() + " clicked.");
+ }
+ });
+ detailsField.addStyleName("link");
+
+ // Create the table row.
+ table.addItem(new Object[] {sumField, transferredField,
+ commentsField, detailsField},
+ itemId);
+}
+
+// Show just three rows because they are so high.
+table.setPageLength(3);
+----
+See the http://demo.vaadin.com/book-examples-vaadin7/book#component.table.components.components2[on-line example, window="_blank"].
+
+The row height has to be set higher than the default with a style rule such as
+the following:
+
+
+[source, css]
+----
+/* Table rows contain three-row TextField components. */
+.v-table-components-inside .v-table-cell-content {
+ height: 54px;
+}
+----
+See the http://demo.vaadin.com/book-examples-vaadin7/book#component.table.components.components2[on-line example, window="_blank"].
+
+The table will look as shown in <<figure.components.table.components-inside>>.
+
+[[figure.components.table.components-inside]]
+.Components in a Table
+image::img/table-components.png[]
+
+
+[[components.table.features.iterating]]
+=== Iterating Over a Table
+
+As the items in a [classname]#Table# are not indexed, iterating over the items
+has to be done using an iterator. The [methodname]#getItemIds()# method of the
+[classname]#Container# interface of [classname]#Table# returns a
+[classname]#Collection# of item identifiers over which you can iterate using an
+[classname]#Iterator#. For an example about iterating over a [classname]#Table#,
+please see
+<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container,"Collecting
+Items in Containers">>. Notice that you may not modify the [classname]#Table#
+during iteration, that is, add or remove items. Changing the data is allowed.
+
+
+[[components.table.features.filtering]]
+=== Filtering Table Contents
+
+A table can be filtered if its container data source implements the
+[classname]#Filterable# interface, as the default [classname]#IndexedContainer#
+does. See
+<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.filtered,"Filterable
+Containers">>. ((("Container",
+"Filterable")))
+
+
+
+[[components.table.editing]]
+== Editing the Values in a Table
+
+Normally, a [classname]#Table# simply displays the items and their fields as
+text. If you want to allow the user to edit the values, you can either put them
+inside components as we did earlier or simply call
+[methodname]#setEditable(true)#, in which case the cells are automatically
+turned into editable fields.
+
+Let us begin with a regular table with a some columns with usual Java types,
+namely a [classname]#Date#, [classname]#Boolean#, and a [classname]#String#.
+
+
+[source, java]
+----
+// Create a table. It is by default not editable.
+Table table = new Table();
+
+// Define the names and data types of columns.
+table.addContainerProperty("Date", Date.class, null);
+table.addContainerProperty("Work", Boolean.class, null);
+table.addContainerProperty("Comments", String.class, null);
+
+...
+----
+
+You could put the table in editable mode right away. We continue the example by
+adding a check box to switch the table between normal and editable modes:
+
+
+[source, java]
+----
+CheckBox editable = new CheckBox("Editable", true);
+editable.addValueChangeListener(valueChange -> // Java 8
+ table.setEditable((Boolean) editable.getValue()));
+----
+
+Now, when you check to checkbox, the components in the table turn into editable
+fields, as shown in <<figure.component.table.editable>>.
+
+[[figure.component.table.editable]]
+.A Table in Normal and Editable Mode
+image::img/table-editable3.png[]
+
+[[components.table.editing.fieldfactories]]
+=== Field Factories
+
+The field components that allow editing the values of particular types in a
+table are defined in a field factory that implements the
+[classname]#TableFieldFactory# interface. The default implementation is
+[classname]#DefaultFieldFactory#, which offers the following crude mappings:
+
+.Type to Field Mappings in [classname]#DefaultFieldFactory#
+[options="header"]
+|===============
+|Property Type|Mapped to Field Class
+|[classname]#Date#|A[classname]#DateField#.
+|[classname]#Boolean#|A[classname]#CheckBox#.
+|[classname]#Item#|A[classname]#Form#(deprecated in Vaadin 7). The fields of the form are automatically created from the item's properties using a[classname]#FormFieldFactory#. The normal use for this property type is inside a[classname]#Form#and is less useful inside a[classname]#Table#.
+|__other__|A[classname]#TextField#. The text field manages conversions from the basic types, if possible.
+
+|===============
+
+
+
+Field factories are covered with more detail in
+<<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding,"Creating
+Forms by Binding Fields to Items">>. You could just implement the
+[classname]#TableFieldFactory# interface, but we recommend that you extend the
+[classname]#DefaultFieldFactory# according to your needs. In the default
+implementation, the mappings are defined in the
+[methodname]#createFieldByPropertyType()# method (you might want to look at the
+source code) both for tables and forms.
+
+
+ifdef::web[]
+[[components.table.editing.navigation]]
+=== Navigation in Editable Mode
+
+In the editable mode, the editor fields can have focus. Pressing Tab moves the
+focus to next column or, at the last column, to the first column of the next
+item. Respectively, pressing kbd:[Shift+Tab] moves the focus backward. If the focus is
+in the last column of the last visible item, the pressing kbd:[Tab] moves the focus
+outside the table. Moving backward from the first column of the first item moves
+the focus to the table itself. Some updates to the table, such as changing the
+headers or footers or regenerating a column, can move the focus from an editor
+component to the table itself.
+
+The default behaviour may be undesirable in many cases. For example, the focus
+also goes through any read-only editor fields and can move out of the table
+inappropriately. You can provide better navigation is to use event handler for
+shortcut keys such as kbd:[Tab], kbd:[Arrow Up], kbd:[Arrow Down], and kbd:[Enter].
+
+
+[source, java]
+----
+// Keyboard navigation
+class KbdHandler implements Handler {
+ Action tab_next = new ShortcutAction("Tab",
+ ShortcutAction.KeyCode.TAB, null);
+ Action tab_prev = new ShortcutAction("Shift+Tab",
+ ShortcutAction.KeyCode.TAB,
+ new int[] {ShortcutAction.ModifierKey.SHIFT});
+ Action cur_down = new ShortcutAction("Down",
+ ShortcutAction.KeyCode.ARROW_DOWN, null);
+ Action cur_up = new ShortcutAction("Up",
+ ShortcutAction.KeyCode.ARROW_UP, null);
+ Action enter = new ShortcutAction("Enter",
+ ShortcutAction.KeyCode.ENTER, null);
+ public Action[] getActions(Object target, Object sender) {
+ return new Action[] {tab_next, tab_prev, cur_down,
+ cur_up, enter};
+ }
+
+ public void handleAction(Action action, Object sender,
+ Object target) {
+ if (target instanceof TextField) {
+ // Move according to keypress
+ int itemid = (Integer) ((TextField) target).getData();
+ if (action == tab_next || action == cur_down)
+ itemid++;
+ else if (action == tab_prev || action == cur_up)
+ itemid--;
+ // On enter, just stay where you were. If we did
+ // not catch the enter action, the focus would be
+ // moved to wrong place.
+
+ if (itemid >= 0 && itemid < table.size()) {
+ TextField newTF = valueFields.get(itemid);
+ if (newTF != null)
+ newTF.focus();
+ }
+ }
+ }
+}
+
+// Panel that handles keyboard navigation
+Panel navigator = new Panel();
+navigator.addStyleName(Reindeer.PANEL_LIGHT);
+navigator.addComponent(table);
+navigator.addActionHandler(new KbdHandler());
+----
+
+The main issue in implementing keyboard navigation in an editable table is that
+the editor fields do not know the table they are in. To find the parent table,
+you can either look up in the component container hierarchy or simply store a
+reference to the table with [methodname]#setData()# in the field component. The
+other issue is that you can not acquire a reference to an editor field from the
+[classname]#Table# component. One solution is to use some external collection,
+such as a [classname]#HashMap#, to map item IDs to the editor fields.
+
+
+[source, java]
+----
+// Can't access the editable components from the table so
+// must store the information
+final HashMap<Integer,TextField> valueFields =
+ new HashMap<Integer,TextField>();
+----
+
+The map has to be filled in a [classname]#TableFieldFactory#, such as in the
+following. You also need to set the reference to the table there and you can
+also set the initial focus there.
+
+
+[source, java]
+----
+table.setTableFieldFactory(new TableFieldFactory () {
+ public Field createField(Container container, Object itemId,
+ Object propertyId, Component uiContext) {
+ TextField field = new TextField((String) propertyId);
+
+ // User can only edit the numeric column
+ if ("Source of Fear".equals(propertyId))
+ field.setReadOnly(true);
+ else { // The numeric column
+ // The field needs to know the item it is in
+ field.setData(itemId);
+
+ // Remember the field
+ valueFields.put((Integer) itemId, field);
+
+ // Focus the first editable value
+ if (((Integer)itemId) == 0)
+ field.focus();
+ }
+ return field;
+ }
+});
+----
+
+The issues are complicated by the fact that the editor fields are not generated
+for the entire table, but only for a cache window that includes the visible
+items and some items above and below it. For example, if the beginning of a big
+scrollable table is visible, the editor component for the last item does not
+exist. This issue is relevant mostly if you want to have wrap-around navigation
+that jumps from the last to first item and vice versa.
+
+endif::web[]
+
+
+[[components.table.headersfooters]]
+== Column Headers and Footers
+
+[classname]#Table# supports both column headers and footers; the headers are
+enabled by default.
+
+[[components.table.headersfooters.headers]]
+=== Headers
+
+The table header displays the column headers at the top of the table. You can
+use the column headers to reorder or resize the columns, as described earlier.
+By default, the header of a column is the property ID of the column, unless
+given explicitly with [methodname]#setColumnHeader()#.
+
+
+[source, java]
+----
+// Define the properties
+table.addContainerProperty("lastname", String.class, null);
+table.addContainerProperty("born", Integer.class, null);
+table.addContainerProperty("died", Integer.class, null);
+
+// Set nicer header names
+table.setColumnHeader("lastname", "Name");
+table.setColumnHeader("born", "Born");
+table.setColumnHeader("died", "Died");
+----
+
+The text of the column headers and the visibility of the header depends on the
+__column header mode__. The header is visible by default, but you can disable it
+with [methodname]#setColumnHeaderMode(Table.COLUMN_HEADER_MODE_HIDDEN)#.
+
+
+[[components.table.headersfooters.footers]]
+=== Footers
+
+The table footer can be useful for displaying sums or averages of values in a
+column, and so on. The footer is not visible by default; you can enable it with
+[methodname]#setFooterVisible(true)#. Unlike in the header, the column headers
+are empty by default. You can set their value with
+[methodname]#setColumnFooter()#. The columns are identified by their property
+ID.
+
+The following example shows how to calculate average of the values in a column:
+
+
+[source, java]
+----
+// Have a table with a numeric column
+Table table = new Table("Custom Table Footer");
+table.addContainerProperty("Name", String.class, null);
+table.addContainerProperty("Died At Age", Integer.class, null);
+
+// Insert some data
+Object people[][] = { {"Galileo", 77},
+ {"Monnier", 83},
+ {"Vaisala", 79},
+ {"Oterma", 86}};
+for (int i=0; i<people.length; i++)
+ table.addItem(people[i], new Integer(i));
+
+// Calculate the average of the numeric column
+double avgAge = 0;
+for (int i=0; i<people.length; i++)
+ avgAge += (Integer) people[i][1];
+avgAge /= people.length;
+
+// Set the footers
+table.setFooterVisible(true);
+table.setColumnFooter("Name", "Average");
+table.setColumnFooter("Died At Age", String.valueOf(avgAge));
+
+// Adjust the table height a bit
+table.setPageLength(table.size());
+----
+
+The resulting table is shown in
+<<figure.components.table.headersfooters.footer>>.
+
+[[figure.components.table.headersfooters.footer]]
+.A Table with a Footer
+image::img/table-footer.png[]
+
+
+[[components.table.headersfooters.clicks]]
+=== Handling Mouse Clicks on Headers and Footers
+
+Normally, when the user clicks a column header, the table will be sorted by the
+column, assuming that the data source is [classname]#Sortable# and sorting is
+not disabled. In some cases, you might want some other functionality when the
+user clicks the column header, such as selecting the column in some way.
+
+Clicks in the header cause a [classname]#HeaderClickEvent#, which you can handle
+with a [classname]#Table.HeaderClickListener#. Click events on the table header
+(and footer) are, like button clicks, sent immediately to server, so there is no
+need to set [methodname]#setImmediate()#.
+
+
+[source, java]
+----
+// Handle the header clicks
+table.addHeaderClickListener(new Table.HeaderClickListener() {
+ public void headerClick(HeaderClickEvent event) {
+ String column = (String) event.getPropertyId();
+ Notification.show("Clicked " + column +
+ "with " + event.getButtonName());
+ }
+});
+
+// Disable the default sorting behavior
+table.setSortDisabled(true);
+----
+
+Setting a click handler does not automatically disable the sorting behavior of
+the header; you need to disable it explicitly with
+[methodname]#setSortDisabled(true)#. Header click events are not sent when the
+user clicks the column resize handlers to drag them.
+
+The [classname]#HeaderClickEvent# object provides the identity of the clicked
+column with [methodname]#getPropertyId()#. The [methodname]#getButton()# reports
+the mouse button with which the click was made: [parameter]#BUTTON_LEFT#,
+[parameter]#BUTTON_RIGHT#, or [parameter]#BUTTON_MIDDLE#. The
+[methodname]#getButtonName()# a human-readable button name in English: "
+[parameter]#left#", " [parameter]#right#", or " [parameter]#middle#". The
+[methodname]#isShiftKey()#, [methodname]#isCtrlKey()#, etc., methods indicate if
+the kbd:[Shift], kbd:[Ctrl], kbd:[Alt] or other modifier keys were pressed during the click.
+
+Clicks in the footer cause a [classname]#FooterClickEvent#, which you can handle
+with a [classname]#Table.FooterClickListener#. Footers do not have any default
+click behavior, like the sorting in the header. Otherwise, handling clicks in
+the footer is equivalent to handling clicks in the header.
+
+
+
+[[components.table.columngenerator]]
+== Generated Table Columns
+
+A table can have generated columns which values can be calculated based on the
+values in other columns. The columns are generated with a class implementing the
+[interfacename]#Table.ColumnGenerator# interface.
+
+The [classname]#GeneratedPropertyContainer# described in
+<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.gpc,"GeneratedPropertyContainer">>
+is another way to accomplish the same task at container level. In addition to
+generating values, you can also use the feature for formatting or styling
+columns.
+
+ifdef::web[]
+[[components.table.columngenerator.generator]]
+=== Defining a Column Generator
+
+Column generators are objects that implement the
+[classname]#Table.ColumnGenerator# interface and its
+[methodname]#generateCell()# method. The method gets the identity of the item
+and column as its parameters, in addition to the table object, and has to return
+a component. The interface is functional, so you can also define it by a lambda
+expression or a method reference in Java 8.
+
+The following example defines a generator for formatting [classname]#Double#
+valued fields according to a format string (as in
+[classname]#java.util.Formatter#).
+
+
+[source, java]
+----
+/** Formats the value in a column containing Double objects. */
+class ValueColumnGenerator implements Table.ColumnGenerator {
+ String format; /* Format string for the Double values. */
+
+ /**
+ * Creates double value column formatter with the given
+ * format string.
+ */
+ public ValueColumnGenerator(String format) {
+ this.format = format;
+ }
+
+ /**
+ * Generates the cell containing the Double value.
+ * The column is irrelevant in this use case.
+ */
+ public Component generateCell(Table source, Object itemId,
+ Object columnId) {
+ // Get the object stored in the cell as a property
+ Property prop =
+ source.getItem(itemId).getItemProperty(columnId);
+ if (prop.getType().equals(Double.class)) {
+ Label label = new Label(String.format(format,
+ new Object[] { (Double) prop.getValue() }));
+
+ // Set styles for the column: one indicating that it's
+ // a value and a more specific one with the column
+ // name in it. This assumes that the column name
+ // is proper for CSS.
+ label.addStyleName("column-type-value");
+ label.addStyleName("column-" + (String) columnId);
+ return label;
+ }
+ return null;
+ }
+}
+----
+
+The column generator is called for all the visible (or more accurately cached)
+items in a table. If the user scrolls the table to another position in the
+table, the columns of the new visible rows are generated dynamically.
+
+Generated column cells are automatically updated when a property value in the
+table row changes. Note that a generated cell, even if it is a field, does not
+normally have a property value bound to the table's container, so changes in
+generated columns do not trigger updates in other generated columns. It should
+also be noted that if a generated column cell depends on values in other rows,
+changes in the other rows do not trigger automatic update. You can get notified
+of such value changes by listening for them with a
+[interfacename]#ValueChangeListener# in the generated components. If you do so,
+you must remove such listeners when the generated components are detached from
+the UI or otherwise the listeners will accumulate in the container when the
+table is scrolled back and forth, causing possibly severe memory leak.
+
+endif::web[]
+
+ifdef::web[]
+[[components.table.columngenerator.adding]]
+=== Adding Generated Columns
+
+You add new generated columns to a [classname]#Table# with
+[methodname]#addGeneratedColumn()#. It takes a property ID of the generated
+column as the first parameter and the generator as the second.
+
+
+[source, java]
+----
+// Define the generated columns and their generators
+table.addGeneratedColumn("date", // Java 8:
+ this::generateNonEditableCell);
+table.addGeneratedColumn("price",
+ new PriceColumnGenerator());
+table.addGeneratedColumn("consumption",
+ new ConsumptionColumnGenerator());
+table.addGeneratedColumn("dailycost",
+ new DailyCostColumnGenerator());
+----
+
+Notice that the [methodname]#addGeneratedColumn()# always places the generated
+columns as the last column, even if you defined some other order previously. You
+will have to set the proper order with [methodname]#setVisibleColumns()#.
+
+
+[source, java]
+----
+table.setVisibleColumns("date", "quantity", "price", "total");
+----
+
+endif::web[]
+
+ifdef::web[]
+[[components.table.columngenerator.editable]]
+=== Generators in Editable Table
+
+When you set a table as [parameter]#editable#, table cells change to editable
+fields. When the user changes the values in the fields, the generated cells in
+the same row are updated automatically. However, putting a table with generated
+columns in editable mode has a few quirks. One is that the editable mode does
+not affect generated columns. You have two alternatives: either you generate the
+editing fields in the generator or, in case of formatter generators, remove the
+generators in the editable mode to allow editing the values. The following
+example uses the latter approach.
+
+
+[source, java]
+----
+// Have a check box that allows the user
+// to make the quantity and total columns editable.
+final CheckBox editable = new CheckBox(
+ "Edit the input values - calculated columns are regenerated");
+
+editable.setImmediate(true);
+editable.addClickListener(new ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ table.setEditable(editable.booleanValue());
+
+ // The columns may not be generated when we want to
+ // have them editable.
+ if (editable.booleanValue()) {
+ table.removeGeneratedColumn("quantity");
+ table.removeGeneratedColumn("total");
+ } else { // Not editable
+ // Show the formatted values.
+ table.addGeneratedColumn("quantity",
+ new ValueColumnGenerator("%.2f l"));
+ table.addGeneratedColumn("total",
+ new ValueColumnGenerator("%.2f e"));
+ }
+ // The visible columns are affected by removal
+ // and addition of generated columns so we have
+ // to redefine them.
+ table.setVisibleColumns("date", "quantity",
+ "price", "total", "consumption", "dailycost");
+ }
+});
+----
+
+You will also have to set the editing fields in [parameter]#immediate# mode to
+have the update occur immediately when an edit field loses the focus. You can
+set the fields in [parameter]#immediate# mode with the a custom
+[classname]#TableFieldFactory#, such as the one given below, that just extends
+the default implementation to set the mode:
+
+
+[source, java]
+----
+public class ImmediateFieldFactory extends DefaultFieldFactory {
+ public Field createField(Container container,
+ Object itemId,
+ Object propertyId,
+ Component uiContext) {
+ // Let the DefaultFieldFactory create the fields...
+ Field field = super.createField(container, itemId,
+ propertyId, uiContext);
+
+ // ...and just set them as immediate.
+ ((AbstractField)field).setImmediate(true);
+
+ return field;
+ }
+}
+...
+table.setTableFieldFactory(new ImmediateFieldFactory());
+----
+
+If you generate the editing fields with the column generator, you avoid having
+to use such a field factory, but of course have to generate the fields for both
+normal and editable modes.
+
+<<figure.ui.table.generated>> shows a table with columns calculated (blue) and
+simply formatted (black) with column generators.
+
+[[figure.ui.table.generated]]
+.Table with Generated Columns in Normal and Editable Mode
+image::img/table-generatedcolumns1.png[]
+
+endif::web[]
+
+
+[[components.table.columnformatting]]
+== Formatting Table Columns
+
+The displayed values of properties shown in a table are normally formatted using
+the [methodname]#toString()# method of each property. Customizing the format in
+a table column can be done in several ways:
+
+* Using [classname]#ColumnGenerator# to generate a second column that is formatted. The original column needs to be set invisible. See <<components.table.columngenerator>>.
+* Using a [classname]#Converter# to convert between the property data model and its representation in the table.
+* Using a [classname]#GeneratedPropertyContainer# as a wrapper around the actual container to provide formatting.
+* Overriding the default [methodname]#formatPropertyValue()# in [classname]#Table#.
+
+As using a [classname]#PropertyFormatter# is generally much more awkward than
+overriding the [methodname]#formatPropertyValue()#, its use is not described
+here.
+
+You can override [methodname]#formatPropertyValue()# as is done in the following
+example:
+
+
+[source, java]
+----
+// Create a table that overrides the default
+// property (column) format
+final Table table = new Table("Formatted Table") {
+ @Override
+ protected String formatPropertyValue(Object rowId,
+ Object colId, Property property) {
+ // Format by property type
+ if (property.getType() == Date.class) {
+ SimpleDateFormat df =
+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+ return df.format((Date)property.getValue());
+ }
+
+ return super.formatPropertyValue(rowId, colId, property);
+ }
+};
+
+// The table has some columns
+table.addContainerProperty("Time", Date.class, null);
+
+... Fill the table with data ...
+----
+
+You can also distinguish between columns by the [parameter]#colId# parameter,
+which is the property ID of the column. [classname]#DecimalFormat# is useful for
+formatting decimal values.
+
+
+[source, java]
+----
+... in formatPropertyValue() ...
+} else if ("Value".equals(pid)) {
+ // Format a decimal value for a specific locale
+ DecimalFormat df = new DecimalFormat("#.00",
+ new DecimalFormatSymbols(locale));
+ return df.format((Double) property.getValue());
+}
+...
+table.addContainerProperty("Value", Double.class, null);
+----
+
+A table with the formatted date and decimal value columns is shown in
+<<figure.components.table.columnformatting>>.
+
+[[figure.components.table.columnformatting]]
+.Formatted Table Columns
+image::img/table-columnformatting.png[]
+
+You can use CSS for further styling of table rows, columns, and individual cells
+by using a [classname]#CellStyleGenerator#. It is described in
+<<components.table.css>>.
+
+
+[[components.table.css]]
+== CSS Style Rules
+
+Styling the overall style of a [classname]#Table# can be done with the following
+CSS rules.
+
+
+[source, css]
+----
+.v-table {}
+ .v-table-header-wrap {}
+ .v-table-header {}
+ .v-table-header-cell {}
+ .v-table-resizer {} /* Column resizer handle. */
+ .v-table-caption-container {}
+ .v-table-body {}
+ .v-table-row-spacer {}
+ .v-table-table {}
+ .v-table-row {}
+ .v-table-cell-content {}
+----
+
+Notice that some of the widths and heights in a table are calculated dynamically
+and can not be set in CSS.
+
+ifdef::web[]
+[[components.table.css.cellstylegenerator]]
+=== Generating Cell Styles With [interfacename]#CellStyleGenerator#
+
+The [classname]#Table.CellStyleGenerator# interface allows you to set the CSS
+style for each individual cell in a table. You need to implement the
+[methodname]#getStyle()#, which gets the row (item) and column (property)
+identifiers as parameters and can return a style name for the cell. The returned
+style name will be concatenated to prefix "
+[literal]#++v-table-cell-content-++#".
+
+The [methodname]#getStyle()# is called also for each row, so that the
+[parameter]#propertyId# parameter is [literal]#++null++#. This allows setting a
+row style.
+
+Alternatively, you can use a [classname]#Table.ColumnGenerator# (see
+<<components.table.columngenerator>>) to generate the actual UI components of
+the cells and add style names to them.
+
+
+[source, java]
+----
+Table table = new Table("Table with Cell Styles");
+table.addStyleName("checkerboard");
+
+// Add some columns in the table. In this example, the property
+// IDs of the container are integers so we can determine the
+// column number easily.
+table.addContainerProperty("0", String.class, null, "", null, null);
+for (int i=0; i<8; i++)
+ table.addContainerProperty(""+(i+1), String.class, null,
+ String.valueOf((char) (65+i)), null, null);
+
+// Add some items in the table.
+table.addItem(new Object[]{
+ "1", "R", "N", "B", "Q", "K", "B", "N", "R"}, new Integer(0));
+table.addItem(new Object[]{
+ "2", "P", "P", "P", "P", "P", "P", "P", "P"}, new Integer(1));
+for (int i=2; i<6; i++)
+ table.addItem(new Object[]{String.valueOf(i+1),
+ "", "", "", "", "", "", "", ""}, new Integer(i));
+table.addItem(new Object[]{
+ "7", "P", "P", "P", "P", "P", "P", "P", "P"}, new Integer(6));
+table.addItem(new Object[]{
+ "8", "R", "N", "B", "Q", "K", "B", "N", "R"}, new Integer(7));
+table.setPageLength(8);
+
+// Set cell style generator
+table.setCellStyleGenerator(new Table.CellStyleGenerator() {
+ public String getStyle(Object itemId, Object propertyId) {
+ // Row style setting, not relevant in this example.
+ if (propertyId == null)
+ return "green"; // Will not actually be visible
+
+ int row = ((Integer)itemId).intValue();
+ int col = Integer.parseInt((String)propertyId);
+
+ // The first column.
+ if (col == 0)
+ return "rowheader";
+
+ // Other cells.
+ if ((row+col)%2 == 0)
+ return "black";
+ else
+ return "white";
+ }
+});
+----
+
+You can then style the cells, for example, as follows:
+
+
+[source, css]
+----
+/* Center the text in header. */
+.v-table-header-cell {
+ text-align: center;
+}
+
+/* Basic style for all cells. */
+.v-table-checkerboard .v-table-cell-content {
+ text-align: center;
+ vertical-align: middle;
+ padding-top: 12px;
+ width: 20px;
+ height: 28px;
+}
+
+/* Style specifically for the row header cells. */
+.v-table-cell-content-rowheader {
+ background: #E7EDF3
+ url(../default/table/img/header-bg.png) repeat-x scroll 0 0;
+}
+
+/* Style specifically for the "white" cells. */
+.v-table-cell-content-white {
+ background: white;
+ color: black;
+}
+
+/* Style specifically for the "black" cells. */
+.v-table-cell-content-black {
+ background: black;
+ color: white;
+}
+----
+
+The table will look as shown in <<figure.components.table.cell-style>>.
+
+[[figure.components.table.cell-style]]
+.Cell Style Generator for a Table
+image::img/table-cellstylegenerator1.png[]
+
+endif::web[]
+
+
+(((range="endofrange", startref="term.components.table")))