Vaadin

Join Vaadin Log In

The Table component is intended for presenting tabular data organized in rows and columns. The 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 Table is managed using the Data Model of Vaadin (see Chapter 9, Binding Components to Data), through the Container interface of the 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 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.)

/* Create the table with a caption. */
Table table = new Table("This is my Table");
/* Define the names and data types of columns.
 * The "default value" parameter is meaningless here. */
table.addContainerProperty("First Name", String.class,  null);
table.addContainerProperty("Last Name",  String.class,  null);
table.addContainerProperty("Year",       Integer.class, null);
/* Add a few items in the table. */
table.addItem(new Object[] {
    "Nicolaus","Copernicus",new Integer(1473)}, new Integer(1));
table.addItem(new Object[] {
    "Tycho",   "Brahe",     new Integer(1546)}, new Integer(2));
table.addItem(new Object[] {
    "Giordano","Bruno",     new Integer(1548)}, new Integer(3));
table.addItem(new Object[] {
    "Galileo", "Galilei",   new Integer(1564)}, new Integer(4));
table.addItem(new Object[] {
    "Johannes","Kepler",    new Integer(1571)}, new Integer(5));
table.addItem(new Object[] {
    "Isaac",   "Newton",    new Integer(1643)}, new Integer(6));

In this example, we used an increasing Integer object as the Item Identifier, given as the second parameter to 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 addContainerProperty() calls.


Scalability of the Table is largely dictated by the container. The default 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.

The 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 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 Table and how to handle ValueChangeEvent events that are caused by changes in selection. You need to handle the event with the valueChange() method of the Property.ValueChangeListener interface.

// 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.addListener(new Property.ValueChangeListener() {
    public void valueChange(ValueChangeEvent event) {
        current.setValue("Selected: " + table.getValue());
    }
});

If the user clicks on an already selected item, the selection will deselected and the table property will have null value. You can disable this behaviour by setting setNullSelectionAllowed(false) for the table.

A table can also be in multiselect mode, where a user can select and unselect any item by clicking on it. The mode is enabled with the setMultiSelect() method of the Select interface of Table. Selecting an item triggers a ValueChangeEvent, which will have as its parameter an array of item identifiers.

Styling the overall style of a Table can be done with the following CSS rules.

.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.

The Table.CellStyleGenerator interface allows you to set the CSS style for each individual cell in a table. You need to implement the 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 "v-table-cell-content-".

Alternatively, you can use a Table.ColumnGenerator (see Section 5.12.4, “Generated Table Columns”) to generate the actual UI components of the cells and add style names to them. A cell style generator is not used for the cells in generated columns.

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) {
        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:

/* 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 5.34, “Cell Style Generator for a Table”.


The cells of a 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 Table, such as for the 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 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 Label in XHTML formatting mode, a multiline TextField, a CheckBox, and a Button that shows as a link.

// 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)}),
                               Label.CONTENT_XHTML);
    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.addListener(new Button.ClickListener() {
        public void buttonClick(ClickEvent event) {
            // Get the item identifier from the user-defined data.
            Integer itemId = (Integer)event.getButton().getData();
            getWindow().showNotification("Link "+
                                   itemId.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);

The row height has to be set higher than the default with a style rule such as the following:

/* Table rows contain three-row TextField components. */
.v-table-components-inside .v-table-cell-content {
    height: 54px;
}

The table will look as shown in Figure 5.35, “Components in a Table”.


Normally, a 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 above, or you can simply call setEditable(true) and 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 Date, Boolean, and a String.

// Create a table. It is by default not editable.
final 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);
// Add a few items in the table.
for (int i=0; i<100; i++) {
    Calendar calendar = new GregorianCalendar(2008,0,1);
    calendar.add(Calendar.DAY_OF_YEAR, i);
    
    // Create the table row.
    table.addItem(new Object[] {calendar.getTime(),
                                new Boolean(false),
                                ""},
                  new Integer(i)); // Item identifier
}
table.setPageLength(8);
layout.addComponent(table);

You could put the table in editable mode right away if you need to. We'll continue the example by adding a mechanism to switch the Table from and to the editable mode.

final CheckBox switchEditable = new CheckBox("Editable");
switchEditable.addListener(new Property.ValueChangeListener() {
    public void valueChange(ValueChangeEvent event) {
        table.setEditable(((Boolean)event.getProperty()
                             .getValue()).booleanValue());
    }
});
switchEditable.setImmediate(true);
layout.addComponent(switchEditable);

Now, when you check to checkbox, the components in the table turn into editable fields, as shown in Figure 5.36, “A Table in Normal and Editable Mode”.


The field components that allow editing the values of particular types in a table are defined in a field factory that implements the TableFieldFactory interface. The default implementation is DefaultFieldFactory, which offers the following crude mappings:


Field factories are covered with more detail in Section 5.17.2, “Binding Form to Data”. You could just implement the TableFieldFactory interface, but we recommend that you extend the DefaultFieldFactory according to your needs. In the default implementation, the mappings are defined in the createFieldByPropertyType() method (you might want to look at the source code) both for tables and forms.

You might want to have a column that has values calculated from other columns. Or you might want to format table columns in some way, for example if you have columns that display currencies. The ColumnGenerator interface allows defining custom generators for such columns.

You add new generated columns to a Table with addGeneratedColumn(). It takes the column identifier as its parameters. Usually you want to have a more user-friendly and possibly internationalized column header. You can set the header and a possible icon by calling addContainerProperty() before adding the generated column.

// Define table columns. 
table.addContainerProperty(
    "date",     Date.class,   null, "Date",         null, null);
table.addContainerProperty(
    "quantity", Double.class, null, "Quantity (l)", null, null);
table.addContainerProperty(
    "price",    Double.class, null, "Price (e/l)",  null, null);
table.addContainerProperty(
    "total",    Double.class, null, "Total (e)",    null, null);
// Define the generated columns and their generators.
table.addGeneratedColumn("date",
                         new DateColumnGenerator());
table.addGeneratedColumn("quantity",
                         new ValueColumnGenerator("%.2f l"));
table.addGeneratedColumn("price",
                         new PriceColumnGenerator());
table.addGeneratedColumn("total",
                         new ValueColumnGenerator("%.2f e"));

Notice that the 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 setVisibleColumns().

table.setVisibleColumns(new Object[] {"date", "quantity", "price", "total"});

The generators are objects that implement the Table.ColumnGenerator interface and its generateCell() method. The method gets the identity of the item and column as its parameters, in addition to the table object. It has to return a component object.

The following example defines a generator for formatting Double valued fields according to a format string (as in java.util.Formatter).

/** 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 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. The columns in the visible (cached) rows are also generated always when an item has a value change. It is therefore usually safe to calculate the value of generated cells from the values of different rows (items).

When you set a table as editable, regular fields will change to editing fields. When the user changes the values in the fields, the generated columns will be updated automatically. Putting a table with generated columns in editable mode has a few quirks. The editable mode of Table 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 generator in the editable mode. The example below uses the latter approach.

// 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.addListener(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(new Object[] {"date", "quantity",
                 "price", "total", "consumption", "dailycost"});
    }
});

You will also have to set the editing fields in immediate mode to have the update occur immediately when an edit field loses the focus. You can set the fields in immediate mode with the a custom TableFieldFactory, such as the one given below, that just extends the default implementation to set the mode:

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.setFieldFactory(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 5.37, “Table with Generated Columns in Normal and Editable Mode” shows a table with columns calculated (blue) and simply formatted (black) with column generators.


You can find the complete generated columns example in the Feature Browser demo application in the installation package, in com.vaadin.demo.featurebrowser.GeneratedColumnExample.java.

Table of Contents

Preface
1. Introduction
1.1. Overview
1.2. Example Application Walkthrough
1.3. Support for the Eclipse IDE
1.4. Goals and Philosophy
1.5. Background
2. Getting Started with Vaadin
2.1. Installing Vaadin
2.2. Setting up the Development Environment
2.3. QuickStart with Eclipse
2.4. Your First Project with Vaadin
3. Architecture
3.1. Overview
3.2. Technological Background
3.3. Applications as Java Servlet Sessions
3.4. Client-Side Engine
3.5. Events and Listeners
4. Writing a Web Application
4.1. Overview
4.2. Managing the Main Window
4.3. Child Windows
4.4. Handling Events with Listeners
4.5. Referencing Resources
4.6. Shutting Down an Application
4.7. Handling Errors
4.8. Setting Up the Application Environment
5. User Interface Components
5.1. Overview
5.2. Interfaces and Abstractions
5.3. Common Component Features
5.4. Label
5.5. Link
5.6. TextField
5.7. RichTextArea
5.8. Date and Time Input
5.9. Button
5.10. CheckBox
5.11. Selecting Items
5.12. Table
5.13. Tree
5.14. MenuBar
5.15. Embedded
5.16. Upload
5.17. Form
5.18. ProgressIndicator
5.19. Slider
5.20. Component Composition with CustomComponent
6. Managing Layout
6.1. Overview
6.2. Window and Panel Root Layout
6.3. VerticalLayout and HorizontalLayout
6.4. GridLayout
6.5. FormLayout
6.6. Panel
6.7. SplitPanel
6.8. TabSheet
6.9. Accordion
6.10. Layout Formatting
6.11. Custom Layouts
7. Visual User Interface Design with Eclipse (experimental)
7.1. Overview
7.2. Creating a New CustomComponent
7.3. Using The Visual Editor
7.4. Structure of a Visually Editable Component
8. Themes
8.1. Overview
8.2. Introduction to Cascading Style Sheets
8.3. Creating and Using Themes
8.4. Creating a Theme in Eclipse
9. Binding Components to Data
9.1. Overview
9.2. Properties
9.3. Holding properties in Items
9.4. Collecting items in Containers
10. Developing Custom Components
10.1. Overview
10.2. Doing It the Simple Way in Eclipse
10.3. Google Web Toolkit Widgets
10.4. Integrating a GWT Widget
10.5. Defining a Widget Set
10.6. Server-Side Components
10.7. Using a Custom Component
10.8. GWT Widget Development
11. Advanced Web Application Topics
11.1. Special Characteristics of AJAX Applications
11.2. Application-Level Windows
11.3. Embedding Applications in Web Pages
11.4. Debug and Production Mode
11.5. Resources
11.6. Shortcut Keys
11.7. Printing
11.8. Portal Integration
11.9. Google App Engine Integration
11.10. Common Security Issues
11.11. URI Fragment and History Management with UriFragmentUtility
11.12. Capturing HTTP Requests
A. User Interface Definition Language (UIDL)
A.1. API for Painting Components
A.2. JSON Rendering
B. Songs of Vaadin
Index