diff options
Diffstat (limited to 'documentation/components/components-table.asciidoc')
-rw-r--r-- | documentation/components/components-table.asciidoc | 1166 |
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"))) |