Collecting Items in Containers
- Basic Use of Containers
- Container Subinterfaces
- IndexedContainer
- BeanContainer
- BeanItemContainer
- Iterating Over a Container
- GeneratedPropertyContainer
- Filterable Containers
- Sortable Containers
The Container interface is the highest containment level of the Vaadin data model, for containing items (rows) which in turn contain properties (columns). Containers can therefore represent tabular data, which can be viewed in a Table or some other selection component, as well as hierarchical data.
The items contained in a container are identified by an item identifier or IID, and the properties by a property identifier or PID.
Basic Use of Containers
The basic use of containers involves creating one, adding items to it, and binding it as a container data source of a component.
Default Containers and Delegation
Before saying anything about creation of containers, it should be noted that all components that can be bound to a container data source are by default bound to a default container. For example, Table is bound to a IndexedContainer, Tree to a HierarchicalContainer, and so forth.
All of the user interface components using containers also implement the relevant container interfaces themselves, so that the access to the underlying data source is delegated through the component.
// Create a table with one column
Table table = new Table("My Table");
table.addContainerProperty("col1", String.class, null);
// Access items and properties through the component
table.addItem("row1"); // Create item by explicit ID
Item item1 = table.getItem("row1");
Property property1 = item1.getItemProperty("col1");
property1.setValue("some given value");
// Equivalent access through the container
Container container = table.getContainerDataSource();
container.addItem("row2");
Item item2 = container.getItem("row2");
Property property2 = item2.getItemProperty("col1");
property2.setValue("another given value");
Creating and Binding a Container
A container is created and bound to a component as follows:
// Create a container of some type
Container container = new IndexedContainer();
// Initialize the container as required by the container type
container.addContainerProperty("name", String.class, "none");
container.addContainerProperty("volume", Double.class, 0.0);
... add items ...
// Bind it to a component
Table table = new Table("My Table");
table.setContainerDataSource(container);
Most components that can be bound to a container allow passing it also in the constructor, in addition to using setContainerDataSource(). Creation of the container depends on its type. For some containers, such as the IndexedContainer, you need to define the contained properties (columns) as was done above, while some others determine them otherwise. The definition of a property with addContainerProperty() requires a unique property ID, type, and a default value. You can also give null. If the container of a component is replaced and the new container contains a different set of columns, such as a property with the same ID but a different data type, the component should be reinitialized. For a table or grid, it means redefining their columns.
Vaadin has a several built-in in-memory container implementations, such as IndexedContainer and BeanItemContainer, which are easy to use for setting up nonpersistent data storages. For persistent data, either the built-in SQLContainer or the JPAContainer add-on container can be used.
Adding Items and Accessing Properties
Items can be added to a container with the addItem() method. The parameterless version of the method automatically generates the item ID.
// Create an item
Object itemId = container.addItem();
Properties can be requested from container by first requesting an item with getItem() and then getting the properties from the item with getItemProperty().
// Get the item object
Item item = container.getItem(itemId);
// Access a property in the item
Property<String> nameProperty =
item.getItemProperty("name");
// Do something with the property
nameProperty.setValue("box");
You can also get a property directly by the item and property ids with getContainerProperty().
container.getContainerProperty(itemId, "volume").setValue(5.0);
Adding Items by Given ID
Some containers, such as IndexedContainer and HierarchicalContainer, allow adding items by a given ID, which can be any Object.
Item item = container.addItem("agivenid");
item.getItemProperty("name").setValue("barrel");
Item.getItemProperty("volume").setValue(119.2);
Notice that the actual item is not given as a parameter to the method, only its ID, as the interface assumes that the container itself creates all the items it contains. Some container implementations can provide methods to add externally created items, and they can even assume that the item ID object is also the item itself. Lazy containers might not create the item immediately, but lazily when it is accessed by its ID.
Container Subinterfaces
The Container interface contains inner interfaces that container implementations can implement to fulfill different features required by components that present container data.
- Container.Filterable
-
Filterable containers allow filtering the contained items by filters, as described in Filterable Containers.
- Container.Hierarchical
-
Hierarchical containers allow representing hierarchical relationships between items and are required by the Tree and TreeTable components. The HierarchicalContainer is a built-in in-memory container for hierarchical data, and is used as the default container for the tree components. The FilesystemContainer provides access to browsing the content of a file system. Also JPAContainer is hierarchical, as described in "Hierarchical Container".
- Container.Indexed
-
An indexed container allows accessing items by an index number, not just their item ID. This feature is required by some components, especially Table, which needs to provide lazy access to large containers. The IndexedContainer is a basic in-memory implementation, as described in IndexedContainer.
- Container.Ordered
-
An ordered container allows traversing the items in successive order in either direction. Most built-in containers are ordered.
- Container.SimpleFilterable
-
This interface enables filtering a container by string matching with addContainerFilter(). The filtering is done by either searching the given string anywhere in a property value, or as its prefix.
- Container.Sortable
-
A sortable container is required by some components that allow sorting the content, such as Table, where the user can click a column header to sort the table by the column. Some other components, such as Calendar, may require that the content is sorted to be able to display it properly. Depending on the implementation, sorting can be done only when the sort() method is called, or the container is automatically kept in order according to the last call of the method.
See the API documentation for a detailed description of the interfaces.
IndexedContainer
The IndexedContainer is an in-memory container that implements the Indexed interface to allow referencing the items by an index. IndexedContainer is used as the default container in most selection components in Vaadin.
The properties need to be defined with addContainerProperty(), which takes the property ID, type, and a default value. This must be done before any items are added to the container.
// Create the container
IndexedContainer container = new IndexedContainer();
// Define the properties (columns)
container.addContainerProperty("name", String.class, "noname");
container.addContainerProperty("volume", Double.class, -1.0d);
// Add some items
Object content[][] = { {"jar", 2.0}, {"bottle", 0.75},
{"can", 1.5}};
for (Object[] row: content) {
Item newItem = container.getItem(container.addItem());
newItem.getItemProperty("name").setValue(row[0]);
newItem.getItemProperty("volume").setValue(row[1]);
}
New items are added with addItem(), which returns the item ID of the new item, or by giving the item ID as a parameter as was described earlier. Note that the Table component, which has IndexedContainer as its default container, has a conveniency addItem() method that allows adding items as object vectors containing the property values.
The container implements the Container.Indexed feature to allow accessing the item IDs by their index number, with getIdByIndex(), etc. The feature is required mainly for internal purposes of some components, such as Table, which uses it to enable lazy transmission of table data to the client-side.
BeanContainer
The BeanContainer is an in-memory container for JavaBean objects. Each contained bean is wrapped inside a BeanItem wrapper. The item properties are determined automatically by inspecting the getter and setter methods of the class. This requires that the bean class has public visibility, local classes for example are not allowed. Only beans of the same type can be added to the container.
The generic has two parameters: a bean type and an item identifier type. The item identifiers can be obtained by defining a custom resolver, using a specific item property for the IDs, or by giving item IDs explicitly. As such, it is more general than the BeanItemContainer, which uses the bean object itself as the item identifier, making the use usually simpler. Managing the item IDs makes BeanContainer more complex to use, but it is necessary in some cases where the equals() or hashCode() methods have been reimplemented in the bean.
// Here is a JavaBean
public class Bean implements Serializable {
String name;
double energy; // Energy content in kJ/100g
public Bean(String name, double energy) {
this.name = name;
this.energy = energy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getEnergy() {
return energy;
}
public void setEnergy(double energy) {
this.energy = energy;
}
}
void basic(VerticalLayout layout) {
// Create a container for such beans with
// strings as item IDs.
BeanContainer<String, Bean> beans =
new BeanContainer<String, Bean>(Bean.class);
// Use the name property as the item ID of the bean
beans.setBeanIdProperty("name");
// Add some beans to it
beans.addBean(new Bean("Mung bean", 1452.0));
beans.addBean(new Bean("Chickpea", 686.0));
beans.addBean(new Bean("Lentil", 1477.0));
beans.addBean(new Bean("Common bean", 129.0));
beans.addBean(new Bean("Soybean", 1866.0));
// Bind a table to it
Table table = new Table("Beans of All Sorts", beans);
layout.addComponent(table);
}
See the on-line example.
To use explicit item IDs, use the methods addItem(Object, Object), addItemAfter(Object, Object, Object), and addItemAt(int, Object, Object).
It is not possible to add additional properties to the container, except properties in a nested bean.
Nested Properties
If you have a nested bean with an 1:1 relationship inside a bean type contained in a BeanContainer or BeanItemContainer, you can add its properties to the container by specifying them with addNestedContainerProperty(). The feature is defined at the level of AbstractBeanContainer.
As with the bean in a bean container, also a nested bean must have public visibility or otherwise an access exception is thrown. An intermediate reference from a bean in the bean container to a nested bean may have a null value.
For example, let us assume that we have the following two beans with the first one nested inside the second one.
/** Bean to be nested */
public class EqCoord implements Serializable {
double rightAscension; /* In angle hours */
double declination; /* In degrees */
... setters and getters for the properties ...
}
/** Bean referencing a nested bean */
public class Star implements Serializable {
String name;
EqCoord equatorial; /* Nested bean */
... setters and getters for the properties ...
}
See the on-line example.
After creating the container, you can declare the nested properties by specifying their property identifiers with the addNestedContainerProperty() in dot notation.
// Create a container for beans
BeanItemContainer<Star> stars =
new BeanItemContainer<Star>(Star.class);
// Declare the nested properties to be used in the container
stars.addNestedContainerProperty("equatorial.rightAscension");
stars.addNestedContainerProperty("equatorial.declination");
// Add some items
stars.addBean(new Star("Sirius", new EqCoord(6.75, 16.71611)));
stars.addBean(new Star("Polaris", new EqCoord(2.52, 89.26417)));
// Here the nested bean reference is null
stars.addBean(new Star("Vega", null));
See the on-line example.
If you bind such a container to a Table, you probably also need to set the column headers. Notice that the entire nested bean itself is still a property in the container and would be displayed in its own column. The toString() method is used for obtaining the displayed value, which is by default an object reference. You normally do not want this, so you can hide the column with setVisibleColumns().
// Put them in a table
Table table = new Table("Stars", stars);
table.setColumnHeader("equatorial.rightAscension", "RA");
table.setColumnHeader("equatorial.declination", "Decl");
table.setPageLength(table.size());
// Have to set explicitly to hide the "equatorial" property
table.setVisibleColumns("name",
"equatorial.rightAscension", "equatorial.declination");
See the on-line example.
The resulting table is shown in Table Bound to a BeanContainer with Nested Properties.
The bean binding in AbstractBeanContainer normally uses the MethodProperty implementation of the Property interface to access the bean properties using the setter and getter methods. For nested properties, the NestedMethodProperty implementation is used.
Defining a Bean ID Resolver
If a bean ID resolver is set using setBeanIdResolver() or setBeanIdProperty(), the methods addBean(), addBeanAfter(), addBeanAt() and addAll() can be used to add items to the container. If one of these methods is called, the resolver is used to generate an identifier for the item (must not return null).
Note that explicit item identifiers can also be used when a resolver has been set by calling the addItem*() methods - the resolver is only used when adding beans using the addBean*() or addAll(Collection) methods.
BeanItemContainer
BeanItemContainer is a container for JavaBean objects where each bean is wrapped inside a BeanItem wrapper. The item properties are determined automatically by inspecting the getter and setter methods of the class. This requires that the bean class has public visibility, local classes for example are not allowed. Only beans of the same type can be added to the container.
BeanItemContainer is a specialized version of the BeanContainer described in BeanContainer. It uses the bean itself as the item identifier, which makes it a bit easier to use than BeanContainer in many cases. The latter is, however, needed if the bean has reimplemented the equals() or hashCode() methods.
Let us revisit the example given in BeanContainer using the BeanItemContainer.
// Create a container for the beans
BeanItemContainer<Bean> beans =
new BeanItemContainer<Bean>(Bean.class);
// Add some beans to it
beans.addBean(new Bean("Mung bean", 1452.0));
beans.addBean(new Bean("Chickpea", 686.0));
beans.addBean(new Bean("Lentil", 1477.0));
beans.addBean(new Bean("Common bean", 129.0));
beans.addBean(new Bean("Soybean", 1866.0));
// Bind a table to it
Table table = new Table("Beans of All Sorts", beans);
See the on-line example.
It is not possible to add additional properties to a BeanItemContainer, except properties in a nested bean, as described in BeanContainer.
Iterating Over a Container
As the items in a Container are not necessarily indexed, iterating over the items has to be done using an Iterator. The getItemIds() method of Container returns a Collection of item identifiers over which you can iterate. The following example demonstrates a typical case where you iterate over the values of check boxes in a column of a Table component. The context of the example is the example used in "Table".
// Collect the results of the iteration into this string.
String items = "";
// Iterate over the item identifiers of the table.
for (Iterator i = table.getItemIds().iterator(); i.hasNext();) {
// Get the current item identifier, which is an integer.
int iid = (Integer) i.next();
// Now get the actual item from the table.
Item item = table.getItem(iid);
// And now we can get to the actual checkbox object.
Button button = (Button)
(item.getItemProperty("ismember").getValue());
// If the checkbox is selected.
if ((Boolean)button.getValue() == true) {
// Do something with the selected item; collect the
// first names in a string.
items += item.getItemProperty("First Name")
.getValue() + " ";
}
}
// Do something with the results; display the selected items.
layout.addComponent (new Label("Selected items: " + items));
Notice that the getItemIds() returns an unmodifiable collection, so the Container may not be modified during iteration. You can not, for example, remove items from the Container during iteration. The modification includes modification in another thread. If the Container is modified during iteration, a ConcurrentModificationException is thrown and the iterator may be left in an undefined state.
GeneratedPropertyContainer
GeneratedPropertyContainer is a container wrapper that allows defining generated values for properties (columns). The generated properties can shadow properties with the same IDs in the wrapped container. Removing a property from the wrapper hides it.
The container is especially useful with Grid, which does not support generated columns or hiding columns like Table does.
Wrapping a Container
A container to be wrapped must be a Container.Indexed. It can optionally also implement Container.Sortable or Container.Filterable to enable sorting and filtering the container, respectively.
For example, let us consider the following container with some regular columns:
IndexedContainer container = new IndexedContainer();
container.addContainerProperty("firstname", String.class, null);
container.addContainerProperty("lastname", String.class, null);
container.addContainerProperty("born", Integer.class, null);
container.addContainerProperty("died", Integer.class, null);
// Wrap it
GeneratedPropertyContainer gpcontainer =
new GeneratedPropertyContainer(container);
Generated Properties
Now, you can add generated properties in the container with addGeneratedProperty() by specifying a property ID and a PropertyValueGenerator. The method takes the ID of the generated property as first parameter; you can use a same ID as in the wrapped container to shadow its properties.
You need to implement getType(), which must return the class object of the value type of the property, and getValue(), which returns the property value for the given item. The item ID and the property ID of the generated property are also given in case they are needed. You can access other properties of the item to compute the property value.
gpcontainer.addGeneratedProperty("lived",
new PropertyValueGenerator<Integer>() {
@Override
public Integer getValue(Item item, Object itemId,
Object propertyId) {
int born = (Integer)
item.getItemProperty("born").getValue();
int died = (Integer)
item.getItemProperty("died").getValue();
return Integer.valueOf(died - born);
}
@Override
public Class<Integer> getType() {
return Integer.class;
}
});
You can access other items in the container, also their generated properties, although you should beware of accidental recursion.
Using GeneratedPropertyContainer
Finally, you need to bind the GeneratedPropertyContainer to the component instead of the wrapped container.
Grid grid = new Grid(gpcontainer);
When using GeneratedPropertyContainer in Grid, notice that generated columns are read-only, so you can not add grid rows with addRow(). In editable mode, editor fields are not generated for generated columns.
Filterable Containers
Containers that implement the Container.Filterable interface can be filtered. For example, the built-in IndexedContainer and the bean item containers implement it. Filtering is typically used for filtering the content of a Table.
Filters implement the Filter interface and you add them to a filterable container with the addContainerFilter() method. Container items that pass the filter condition are kept and shown in the filterable component.
Filter filter = new SimpleStringFilter("name",
"Douglas", true, false);
table.addContainerFilter(filter);
See the on-line example.
If multiple filters are added to a container, they are evaluated using the logical AND operator so that only items that are passed by all the filters are kept.
Atomic and Composite Filters
Filters can be classified as atomic and composite. Atomic filters, such as SimpleStringFilter, define a single condition, usually for a specific container property. Composite filters make filtering decisions based on the result of one or more other filters. The built-in composite filters implement the logical operators AND, OR, or NOT.
For example, the following composite filter would filter out items where the name property contains the name "Douglas" somewhere or where the age property has value less than 42. The properties must have String and Integer types, respectively.
filter = new Or(new SimpleStringFilter("name",
"Douglas", true, false),
new Compare.Less("age", 42));
Built-In Filter Types
The built-in filter types are the following:
- SimpleStringFilter
-
Passes items where the specified property, that must be of String type, contains the given filterString as a substring. If ignoreCase is true, the search is case insensitive. If the onlyMatchPrefix is true, the substring may only be in the beginning of the string, otherwise it may be elsewhere as well.
- IsNull
-
Passes items where the specified property has null value. For in-memory filtering, a simple == check is performed. For other containers, the comparison implementation is container dependent, but should correspond to the in-memory null check.
- Equal, Greater, Less, GreaterOrEqual, and LessOrEqual
-
The comparison filter implementations compare the specified property value to the given constant and pass items for which the comparison result is true. The comparison operators are included in the abstract Compare class.
For the Equal filter, the equals() method for the property is used in built-in in-memory containers. In other types of containers, the comparison is container dependent and may use, for example, database comparison operations.
For the other filters, the property value type must implement the Comparable interface to work with the built-in in-memory containers. Again for the other types of containers, the comparison is container dependent.
- And and Or
-
These logical operator filters are composite filters that combine multiple other filters.
- Not
-
The logical unary operator filter negates which items are passed by the filter given as the parameter.
Implementing Custom Filters
A custom filter needs to implement the Container.Filter interface.
A filter can use a single or multiple properties for the filtering logic. The properties used by the filter must be returned with the appliesToProperty() method. If the filter applies to a user-defined property or properties, it is customary to give the properties as the first argument for the constructor of the filter.
class MyCustomFilter implements Container.Filter {
protected String propertyId;
protected String regex;
public MyCustomFilter(String propertyId, String regex) {
this.propertyId = propertyId;
this.regex = regex;
}
/** Tells if this filter works on the given property. */
@Override
public boolean appliesToProperty(Object propertyId) {
return propertyId != null &&
propertyId.equals(this.propertyId);
}
See the on-line example.
The actual filtering logic is done in the passesFilter() method, which simply returns true if the item should pass the filter and false if it should be filtered out.
/** Apply the filter on an item to check if it passes. */
@Override
public boolean passesFilter(Object itemId, Item item)
throws UnsupportedOperationException {
// Acquire the relevant property from the item object
Property p = item.getItemProperty(propertyId);
// Should always check validity
if (p == null || !p.getType().equals(String.class))
return false;
String value = (String) p.getValue();
// The actual filter logic
return value.matches(regex);
}
}
See the on-line example.
You can use such a custom filter just like any other:
c.addContainerFilter(
new MyCustomFilter("Name", (String) tf.getValue()));
See the on-line example.
Sortable Containers
Containers that implement the Container.Sortable interface can be sorted. Many built-in containers, such as IndexedContainer and the bean item containers, implement this interface out-of-the-box. Both Table and Grid have helper methods for programmatic sorting and provide the option for changing the sort order via clicking the header cells, and forward those events to the underlying container that performs the actual sorting.
The usual way to sort a container directly is to call the sort(Object[], boolean[]) method. By default, if any of the given property ids do not match the properties that the container considers sortable, the values from those indexes are ignored.
// lastName ascending, age descending
container.sort(new Object[] { "lastName", "age" },
new boolean[] { true, false });
Whether a container property can be sorted or not is controlled by the getSortableContainerPropertyIds() method. In the default implementations, any container property with a type that implements the Comparable interface is considered sortable. This can be changed by overriding the getSortableContainerPropertyIds() method, but note that you should not include properties that are not natively comparable without also adding a custom sorting implementation.
IndexedContainer container = new IndexedContainer() {
@Override
public Collection<?> getSortableContainerPropertyIds() {
// make all properties non-sortable
return Collections.emptyList();
}
};
Custom Sorting
You must have an in-memory container if you wish to add a custom sorting implementation using the helpers provided by the framework. These helpers are included in all containers that extend AbstractInMemoryContainer (including the built-in IndexedContainer and the bean item containers). If the container data is loaded into the container by a request from elsewhere, e.g. from a database (like in SQLContainer), the database handles the sorting. There is no default implementation for programmatically sorting only the currently loaded subset of the data.
Custom sorting implementation is required if
-
you want to use non-default comparing logic for one or more of the container properties
-
you want to be able to sort a container property that has a type that is not natively comparable
In the latter case you should also override the getSortableContainerPropertyIds() method to include that property id.
IndexedContainer container = new IndexedContainer() {
@Override
public Collection<?> getSortableContainerPropertyIds() {
// make all properties sortable
return getContainerPropertyIds();
}
};
Sorting in any container that extends AbstractInMemoryContainer can be customized by giving it a new ItemSorter. The easiest way to do this is to use a DefaultItemSorter with a custom Comparator.
container.setItemSorter(new DefaultItemSorter(new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
// custom handling, ignores the possibility of null values
if (o1 instanceof CheckBox && o2 instanceof CheckBox) {
Boolean b1 = ((CheckBox) o1).getValue();
return b1.compareTo(((CheckBox) o2).getValue());
}
if (o1 instanceof Button && o2 instanceof Button) {
String caption1 = ((Button) o1).getCaption().toLowerCase();
String caption2 = ((Button) o2).getCaption().toLowerCase();
return caption1.compareTo(caption2);
}
if (o1 instanceof String && o2 instanceof String) {
return ((String) o1).toLowerCase()
.compareTo(((String) o2).toLowerCase());
}
return 0;
}
}));
Note that the given ItemSorter will be used for all the properties listed by the getSortableContainerPropertyIds() method, not only those that have types that do not implement Comparable. For an easy access to the default comparator logic (including null value handling) you might want to use the DefaultPropertyValueComparator as a starting point. Remember to add custom handling for any and all data types you have that do not implement Comparable. Such types can only be passed on to the default comparator handling when the other compared value is null.
container.setItemSorter(
new DefaultItemSorter(new DefaultPropertyValueComparator() {
@Override
public int compare(Object o1, Object o2) {
// custom handling
...
return super.compare(o1, o1);
}
}));
SQLContainer
SQLContainer is not an in-memory data container, so the sorting options are limited to using the database’s own sorting. SQLContainer uses the OrderBy class for forwarding the sorting information to the database. The property id given to an OrderBy instance must match a previously added property id within the container.
Calling the sort(Object[], boolean[]) method clears out any previously set OrderBy instances and creates new ones based on the given arrays.
If you wish to only change the sorting for a single property without affecting the previously set sorting otherwise, the addOrderBy(OrderBy) adds the given OrderBy to the list and refreshes the container contents with the new sorting rules. Note that this does not remove any previously set OrderBy instance for the same property. There is no corresponding method for clearing the sorting rules from a single property, or for querying the currently set list of rules.
// ascending
container.addOrderBy(new OrderBy("lastName", true));
// descending
container.addOrderBy(new OrderBy("age", false));