1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156 |
- ---
- 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 Vaadin data model (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[width=35%, scaledwidth=50%]
-
- 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[width=35%, scaledwidth=80%]
-
- 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[width=50%, scaledwidth=80%]
-
-
- [[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[width=40%, scaledwidth=80%]
-
- 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.
- 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[width=70%, scaledwidth=100%]
-
-
- [[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[width=100%, scaledwidth=100%]
-
- [[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",cols="2,5"]
- |===============
- |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[width=25%, scaledwidth=40%]
-
-
- [[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
- image::img/table-generatedcolumns1.png[width=90%, scaledwidth=100%]
-
- 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[width=40%, scaledwidth=50%]
-
- You can use CSS for further styling of table rows, columns, and individual cells by using a [classname]#CellStyleGenerator#.
- ifdef::web[It is described in <<components.table.css>>.]
-
- [[components.table.css]]
- == CSS Style 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[width=50%, scaledwidth=80%]
-
- endif::web[]
-
-
- (((range="endofrange", startref="term.components.table")))
|