5.2. Layout Components

5.2.1. VerticalLayout and HorizontalLayout

VerticalLayout and HorizontalLayout components are containers for laying out components either vertically or horizontally, respectively. Some components, such as Window, have a VerticalLayout as the root layout, which you can set with setLayout().

Typical use of the layouts goes as follows:

VerticalLayout vertical = new VerticalLayout ();
vertical.addComponent(new TextField("Name"));
vertical.addComponent(new TextField("Street address"));
vertical.addComponent(new TextField("Postal code"));
main.addComponent(vertical);

The text fields have a label attached, which will by default be placed above the field. The layout will look on screen as follows:

Using HorizontalLayout gives the following layout:

The layouts can have spacing between the horizontal or vertical cells, defined with setSpacing(), as described in Section 5.3.3, “Layout Cell Spacing”. The contained components can be aligned within their cells with setComponentAlignment(), as described in Section 5.3.2, “Layout Cell Alignment”.

You can use setWidth() and setHeight() to specify width and height of a component in either fixed units or relatively with a percentage.

Sizing Contained Components

The components contained within an ordered layout can be laid out in a number of different ways depending on how you specify their height or width in the primary direction of the layout component.

Figure 5.1. Component Widths in HorizontalLayout

Component Widths in HorizontalLayout

Figure 5.1, “Component Widths in HorizontalLayout above gives a summary of the sizing options for a HorizontalLayout. Let us break down the figure as follows.

If a VerticalLayout has undefined height or HorizontalLayout undefined width, the layout will shrink to fit the contained components so that there is no extra space between them.

HorizontalLayout fittingLayout = new HorizontalLayout();
fittingLayout.setWidth(Sizeable.SIZE_UNDEFINED, 0);
fittingLayout.addComponent(new Button("Small"));
fittingLayout.addComponent(new Button("Medium-sized"));
fittingLayout.addComponent(new Button("Quite a big component"));
parentLayout.addComponent(fittingLayout);

If such a vertical layout continues below the bottom of a window (a Window object), the window will pop up a vertical scroll bar on the right side of the window area. This way, you get a "web page".

If you set a HorizontalLayout to a defined size horizontally or a VerticalLayout vertically, and there is space left over from the contained components, the extra space is distributed equally between the component cells. The components are aligned within these cells according to their alignment setting, top left by default, as in the example below.

fixedLayout.setWidth("400px");

Using percentual sizes for contained components requires answering the question, "Percentage of what?" There is no sensible default answer for this question in the current implementation of the layouts, so in practice, you may not define "100%" size alone.

Often, you want to have one component that takes all the available space left over from other components. You need to set its size as 100% and set it as expanding with setExpandRatio(). The second parameter for the method is an expansion ratio, which is relevant if there are more than one expanding component, but its value is irrelevant for a single expanding component.

HorizontalLayout layout = new HorizontalLayout();
layout.setWidth("400px");

// These buttons take the minimum size.
layout.addComponent(new Button("Component"));
layout.addComponent(new Button("Component"));

// This button will expand.
Button expandButton = new Button("Component");

// Use 100% of the expansion cell's width.
expandButton.setWidth("100%");

// The component must be added to layout before setting the ratio.
layout.addComponent(expandButton);

// Set the component's cell to expand.
layout.setExpandRatio(expandButton, 1.0f);

parentLayout.addComponent(layout);

Notice that you must call setExpandRatio() after addComponent(), because the layout can not operate on an component that it doesn't (yet) include.

Warning: A layout that contains components with percentual size must have a defined size! If a layout has undefined size and component has, say, 100% size, the component would fill the space given by the layout, while the layout would shrink to fit the space taken by the component, which is a paradox. This requirement holds for height and width separately. The debug mode allows detecting such invalid cases; see Section 9.1.1, “Debug Mode”.

If you specify an expand ratio for multiple components, they will all try to use the available space according to the ratio.

HorizontalLayout layout = new HorizontalLayout();
layout.setWidth("400px");

// Create three equally expanding components.
String[] captions = { "Small", "Medium-sized", "Quite a big component" };
for (int i = 1; i <= 3; i++) {
    Button button = new Button(captions[i-1]);
    button.setWidth("100%");
    layout.addComponent(button);

    // Have uniform 1:1:1 expand ratio.
    layout.setExpandRatio(button, 1.0f);
}

As we used the same ratio for each components, the ones with more content may be have the content cut. Below, we use differing ratios:

// Expand ratios for the components are 1:2:3.
layout.setExpandRatio(button, i * 1.0f);

If the size of the expanding components is defined as a percentage (typically "100%"), the ratio is calculated from the overall space available for the relatively sized components. For example, if you have a 100 pixels wide layout with two cells with 1.0 and 4.0 respective expansion ratios, and both the components in the layout are set as setWidth("100%"), the cells will have respective widths of 20 and 80 pixels, regardless of the minimum size of the components.

However, if the size of the contained components is undefined or fixed, the expansion ratio is of the excess available space. In this case, it is the excess space that expands, not the components.

        for (int i = 1; i <= 3; i++) {
    // Button with undefined size.
    Button button = new Button(captions[i - 1]);
    
    layout4.addComponent(button);

    // Expand ratios are 1:2:3.
    layout4.setExpandRatio(button, i * 1.0f);
}

It is not meaningful to combine expanding components with percentually defined size and components with fixed or undefined size. Such combination can lead to a very expected size for the percentually sized components.

A percentual size of a component defines the size of the component within it's cell. Usually, you use "100%", but a smaller percentage or a fixed size (smaller than the cell size) will leave an empty space in the cell and align the component within the cell according to its alignment setting, top left by default.

HorizontalLayout layout50 = new HorizontalLayout();
layout50.setWidth("400px");

String[] captions1 = { "Small 50%", "Medium 50%", "Quite a big 50%" };
for (int i = 1; i <= 3; i++) {
    Button button = new Button(captions1[i-1]);
    button.setWidth("50%");
    layout50.addComponent(button);

    // Expand ratios for the components are 1:2:3.
    layout50.setExpandRatio(button, i * 1.0f);
}
parentLayout.addComponent(layout50);

5.2.2. GridLayout

GridLayout container lays components out on a grid of defined width and height. The columns and rows of the grid serve as coordinates that are used for laying out components on the grid. Each component can use multiple cells from the grid, defined as an area (x1,y1,x2,y2), although they typically take up only a single grid cell.

The grid layout maintains a cursor for adding components in left-to-right, top-to-bottom order. If the cursor goes past the bottom-right corner, it will automatically extend the grid downwards.

The following example demonstrates the use of GridLayout. The addComponent takes a component and optional coordinates. The coordinates can be given for a single cell or for an area in x,y (column,row) order. The coordinate values have a base value of 0. If coordinates are not given, the cursor will be used.

/* Create a 4 by 4 grid layout. */
GridLayout grid = new GridLayout(4, 4);
grid.addStyleName("example-gridlayout");

/* Fill out the first row using the cursor. */
grid.addComponent(new Button("R/C 1"));
for (int i = 0; i < 3; i++) {
    grid.addComponent(new Button("Col " + (grid.getCursorX() + 1)));
}

/* Fill out the first column using coordinates. */
for (int i = 1; i < 4; i++) {
    grid.addComponent(new Button("Row " + i), 0, i);
}

/* Add some components of various shapes. */
grid.addComponent(new Button("3x1 button"), 1, 1, 3, 1);
grid.addComponent(new Label("1x2 cell"), 1, 2, 1, 3);
InlineDateField date = new InlineDateField("A 2x2 date field");
date.setResolution(DateField.RESOLUTION_DAY);
grid.addComponent(date, 2, 2, 3, 3);

The resulting layout will look as follows. The borders have been made visible to illustrate the layout cells.

Figure 5.2. The Grid Layout Component

The Grid Layout Component

For a more complete example of grid layout, please see Section 1.2.2, “Calculator”.

A component to be placed on the grid must not overlap with existing components. A conflict causes throwing a GridLayout.OverlapsException.

Sizing Grid Cells

You can define the size of both a grid layout and its components in either fixed or percentual units, or leave the size undefined altogether, as described in Section 4.18.1, “Sizing Components through Sizeable interface”. Section 5.3.1, “Layout Size” gives an introduction to sizing of layouts.

The size of the GridLayout component is undefined by default, so it will shrink to fit the size of the components placed inside it. In most cases, especially if you set a defined size for the layout but do not set the contained components to full size, there will be some unused space. The position of the non-full components within the grid cells will be determined by their alignment. See Section 5.3.2, “Layout Cell Alignment” for details on how to align the components inside the cells.

The components contained within a GridLayout layout can be laid out in a number of different ways depending on how you specify their height or width. The layout options are similar to HorizontalLayout and VerticalLayout, as described in Section 5.2.1, “VerticalLayout and HorizontalLayout.

Warning: A layout that contains components with percentual size must have a defined size! If a layout has undefined size and component has, say, 100% size, the component would fill the space given by the layout, while the layout would shrink to fit the space taken by the component, which is a paradox. This requirement holds for height and width separately. The debug mode allows detecting such invalid cases; see Section 9.1.1, “Debug Mode”.

Often, you want to have one or more rows or columns that take all the available space left over from non-expanding rows or columns. You need to set the rows or columns as expanding with setRowExpandRatio() and setColumnExpandRatio(). The first parameter for these methods is the index of the row or column to set as expanding. The second parameter for the methods is an expansion ratio, which is relevant if there are more than one expanding row or column, but its value is irrelevant if there is only one. With multiple expanding rows or columns, the ratio parameter sets the relative portion how much a specific row/column will take in relation with the other expanding rows/columns.

GridLayout grid = new GridLayout(3,2);
grid.addStyleName("gridexpandratio");

// Layout containing relatively sized components must have a defined size.
grid.setWidth("600px");
grid.setHeight("200px");

// Add content
grid.addComponent(new Label("Shrinking column<br/>Shrinking row", Label.CONTENT_XHTML));
grid.addComponent(new Label("Expanding column (1:)<br/>Shrinking row", Label.CONTENT_XHTML));
grid.addComponent(new Label("Expanding column (5:)<br/>Shrinking row", Label.CONTENT_XHTML));

grid.addComponent(new Label("Shrinking column<br/>Expanding row", Label.CONTENT_XHTML));
grid.addComponent(new Label("Expanding column (1:)<br/>Expanding row", Label.CONTENT_XHTML));
grid.addComponent(new Label("Expanding column (5:)<br/>Expanding row", Label.CONTENT_XHTML));

// Set different expansion ratios for the two columns
grid.setColumnExpandRatio(1, 1);
grid.setColumnExpandRatio(2, 5);

// Set the bottom row to expand
grid.setRowExpandRatio(1, 1);

// Align and size the labels.
for (int col=0; col<grid.getColumns(); col++) {
    for (int row=0; row<grid.getRows(); row++) {
        Component c = grid.getComponent(col, row);
        grid.setComponentAlignment(c, Alignment.TOP_CENTER);
        
        // Make the labels high to illustrate the empty horizontal space.
        if (col != 0 || row != 0) {
            c.setHeight("100%");
        }
    }
}

Figure 5.3. Expanding Rows and Columns in GridLayout

Expanding Rows and Columns in GridLayout

If the size of the contained components is undefined or fixed, the expansion ratio is of the excess space, as in Figure 5.3, “Expanding Rows and Columns in GridLayout (excess horizontal space shown in white). However, if the size of the all the contained components in the expanding rows or columns is defined as a percentage, the ratio is calculated from the overall space available for the percentually sized components. For example, if we had a 100 pixels wide grid layout with two columns with 1.0 and 4.0 respective expansion ratios, and all the components in the grid were set as setWidth("100%"), the columns would have respective widths of 20 and 80 pixels, regardless of the minimum size of their contained components.

CSS Style Rules

.i-gridlayout {}
.i-gridlayout-margin {}

The i-gridlayout is the root element of the GridLayout component. The i-gridlayout-margin is a simple element inside it that allows setting a padding between the outer element and the cells.

For styling the individual grid cells, you should style the components inserted in the cells. The implementation structure of the grid can change, so depending on it, as is done in the example below, is not generally recommended. Normally, if you want to have, for example, a different color for a certain cell, just make set the component inside it setSizeFull(), and add a style name for it. Sometimes you may need to use a layout component between a cell and its actual component just for styling.

The following example shows how to make the grid borders visible, as in Figure 5.3, “Expanding Rows and Columns in GridLayout.

.i-gridlayout-gridexpandratio {
    background: blue; /* Creates a "border" around the layout grid. */
    margin:     10px; /* Empty space around the layout. */
}

/* Add padding through which the background color of the grid shows. */
.i-gridlayout-gridexpandratio .i-gridlayout-margin {
    padding: 2px;     
}

/* Add cell borders and make the cell backgrounds white.
 * Warning: This depends heavily on the HTML structure. */
.i-gridlayout-gridexpandratio > div > div > div {
    padding:    2px;   /* Layout's background will show through. */
    background: white; /* The cells will be colored white. */
}

/* Components inside the layout. This is a safe way to style the cells. */
.i-gridlayout-gridexpandratio .i-label {
    text-align: left;
    background: #ffffc0; /* Pale yellow */
}

You should beware of margin, padding, and border settings in CSS as they can mess up the layout. The dimensions of layouts are calculated in the Client-Side Engine of IT Mill Toolkit and some settings can interfere with these calculations.

5.2.3. Panel

Panel is a simple container with a frame and an optional caption. The content are has an inner layout component for laying out the contained components.

The caption can have an icon in addition to the text.

// Create a panel with a caption.
final Panel panel = new Panel("Contact Information");
panel.addStyleName("panelexample");

// The width of a Panel is 100% by default, make it
// shrink to fit the contents.
panel.setWidth(Sizeable.SIZE_UNDEFINED, 0);

// Create a layout inside the panel, and have some margin around it.
final FormLayout form = new FormLayout();
form.setMargin(true);

// Add some components
form.addComponent(new TextField("Name"));
form.addComponent(new TextField("Email"));

// Set the layout as the root layout of the panel
panel.setLayout(form);

The resulting layout will look as follows.

Figure 5.4. A Panel Layout

A Panel Layout

CSS Style Rules

.i-panel {}
.i-panel-caption {}
.i-panel-nocaption {}
.i-panel-content {}
.i-panel-deco {}

The entire panel has i-panel style. A panel consists of three parts: the caption, content, and bottom decorations (shadow). These can be styled with i-panel-caption, i-panel-content, and i-panel-deco, respectively. If the panel has no caption, the caption element will have the style i-panel-nocaption.

The light style for the Panel is a predefined style that has now borders or border decorations for the panel. You enable it simply by adding the light style name for the panel, as is done in the example below.

The light style is typical when using a Panel as the root layout of a window or some similar layout, as in the example below.

// Have a window with a SplitPanel.
final Window window = new Window("Window with a Light Panel");
window.setWidth("400px");
window.setHeight("200px");
final SplitPanel splitter = new SplitPanel(SplitPanel.ORIENTATION_HORIZONTAL);
window.setLayout(splitter);

// Create a panel with a caption.
final Panel light = new Panel("Light Panel");
light.setSizeFull();

// The "light" style is a predefined style without borders.
light.addStyleName("light");

light.addComponent(new Label("The light Panel has no borders."));
light.getLayout().setMargin(true);

// The Panel will act as a "caption" of the left panel in SplitPanel.
splitter.addComponent(light);
splitter.setSplitPosition(250, Sizeable.UNITS_PIXELS);

main.addWindow(window);

Figure 5.5. A Panel with Light Style

A Panel with Light Style

5.2.4. TabSheet

The TabSheet is a multicomponent container that allows switching between the components with "tabs". The tabs are organized as a tab bar at the top of the tab sheet. Clicking on a tab opens its contained component in the main display area of the layout.

New tabs can be added simply with the addComponent() method, but doing so leaves them without a caption. You can set the caption with setTabCaption() or simply use the addTab() method to create tabs and give them a caption. In addition to a caption, tabs can contain an icon, which you can define either in the addtab() call or set later with setTabIcon().

The following example demonstrates the creation of a simple tab sheet, where each the tabs shows a different Label component. The tabs have an icon, which are loaded as Java class loader resources from the WAR package of the application.

final TabSheet tabsheet = new TabSheet();
tabsheet.addStyleName("tabsheetexample");

// Make the tabsheet to shrink to fit the contents.
tabsheet.setSizeUndefined();

tabsheet.addTab(new Label("Contents of the first tab"),
                "First Tab",
                new ClassResource("images/Mercury_small.png", this));
tabsheet.addTab(new Label("Contents of the second tab"),
                "Second Tab",
                new ClassResource("images/Venus_small.png", this));
tabsheet.addTab(new Label("Contents of the third tab"),
                "Third tab",
                new ClassResource("images/Earth_small.png", this));

Figure 5.6. A Simple TabSheet Layout

A Simple TabSheet Layout

The hideTabs() method allows hiding the tab bar entirely. This can be useful in tabbed document interfaces (TDI) when there is only one tab. An individual tab can be made invisible by making its component invisible with setVisible(false). A tab can be disabled by disabling its component with setEnabled(false). A tab can be selected programmatically with setSelectedTab().

Clicking on a tab selects it. This fires a TabSheet.SelectedTabChangeEvent, which can be handled with the TabSheet.SelectedTabChangeListener. The source component of the event, which you can retrieve with getSource() method of the event, will be the TabSheet component. You can find out the currently selected component with getSelectedTab().

The example below demonstrates handling TabSheet related events and enabling and disabling tabs. The sort of logic used in the example is useful in sequential user interfaces, often called wizards, where the user goes through the tabs one by one, but can return back if needed.

import com.itmill.toolkit.ui.*;
import com.itmill.toolkit.ui.Button.ClickEvent;
import com.itmill.toolkit.ui.TabSheet.SelectedTabChangeEvent;

public class TabSheetExample extends CustomComponent
        implements Button.ClickListener, TabSheet.SelectedTabChangeListener {
    TabSheet tabsheet = new TabSheet();
    Button tab1 = new Button("Push this button");
    Label  tab2 = new Label("Contents of Second Tab");
    Label  tab3 = new Label("Contents of Third Tab");
    
    TabSheetExample () {
        setCompositionRoot (tabsheet);

        /* Listen for changes in tab selection. */
        tabsheet.addListener(this);

        /* First tab contains a button, for which we listen button click events. */
        tab1.addListener(this);
        tabsheet.addTab(tab1, "First Tab", null);

        /* A tab that is initially invisible. */
        tab2.setVisible(false);
        tabsheet.addTab(tab2, "Second Tab", null);
        
        /* A tab that is initially disabled. */
        tab3.setEnabled(false);
        tabsheet.addTab(tab3, "Third tab", null);
    }

    public void buttonClick(ClickEvent event) {
        /* Enable the invisible and disabled tabs. */
        tab2.setVisible(true);
        tab3.setEnabled(true);
        
        /* Change selection automatically to second tab. */
        tabsheet.setSelectedTab(tab2);
    }

    public void selectedTabChange(SelectedTabChangeEvent event) {
        /* Cast to a TabSheet. This isn't really necessary in this example,
         * as we have only one TabSheet component, but would be useful if
         * there were multiple TabSheets. */
        TabSheet source = (TabSheet) event.getSource();
        if (source == tabsheet) {
            /* If the first tab was selected. */
            if (source.getSelectedTab() == tab1) {
                tab2.setVisible(false);
                tab3.setEnabled(false);
            }
        }
    }
}

Figure 5.7. A TabSheet with Hidden and Disabled Tabs

A TabSheet with Hidden and Disabled Tabs