Container
is the highest-level of the data model
interfaces supported by Vaadin. It provides a very flexible way of managing a
set of items that share common properties. Contained items are identified by
an item identifier or IID.
Items can be added to a container with the addItem()
method. Notice that the actual item is not passed as a parameter to the
method, only the item ID, as the interface assumes that the container
implementation knows how to create the item. The parameterless version of the
method uses an automatically generated item ID. Implementations can provide
methods to add externally created items, or they can assume that the item ID
is also the item itself.
Properties can be requested from container by first requesting an item with
getItem()
and then getting the properties from the
item with getItemProperty()
. You can also get a
property directly by the item and property ids with
getContainerProperty()
.
The Container
interface was designed with flexibility
and efficiency in mind. It contains inner interfaces that containers can
optionally implement for ordering the items sequentially, indexing the items,
and accessing them hierarchically. Such ordering models provide the basis for
the Table
, Tree
, and
Select
components. As with other data model interfaces,
the Container
supports events for notifying about
changes made to their contents.
As containers can be unordered, ordered, indexed, or hierarchical, they can
interface practically any kind of data representation. Vaadin includes data
connectors for some common data sources, such as the simple tabular data, with
IndexedContainer
, and the filesystem, with
FilesystemContainer
.
In addition to generic container implementations, also many user interface
components are containers as themselves, in addition to being properties. This
is especially true for selection components, that is, those that implement
Select
, because they are containers that contain
selectable items. Their property is the currently selected item. This is
useful as it allows binding components to view and updating each others' data
directly, and makes it easy to reuse already constructed data models, for
example, a form could edit a row (item) of a table directly, and the table
could use a database container as its underlying container. The fields of the
form would correspond to the properties of the item, that is, the cells of the
table row.
The library contains a set of utilities for converting between different
container implementations by adding external ordering or hierarchy into
existing containers. In-memory containers implementing indexed and
hierarchical models provide easy-to-use tools for setting up in-memory data
storages. Such default container implementations include
IndexedContainer
, which can be thought of as a
generalization of a two-dimensional data table, and
BeanItemContainer
, which maps standard Java objects
(beans) to items of an indexed container. In addition, the built-in containers
include a hierarchical container for direct file system browsing.
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); }
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.
If you have a nested bean with a 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 a top-level bean in a bean container, also a nested bean must have public visibility or otherwise an access exception is thrown. Intermediary getters returning a nested bean must always return a non-null value.
For example, 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 */ ... constructor and setters and getters for the properties ... } /** Bean containing a nested bean */ public class Star implements Serializable { String name; EqCoord equatorial; /* Nested bean */ ... constructor and setters and getters for the properties ... }
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 final 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)));
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(new Object[]{"name", "equatorial.rightAscension", "equatorial.declination"});
The resulting table is shown in Figure 9.4, “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.
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
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 Section 9.4.1, “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 Section 9.4.1, “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);
It is not possible to add additional properties to a
BeanItemContainer
, except properties in a nested
bean, as described in Section 9.4.1, “BeanContainer
”.
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 Section 5.14, “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.
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);
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.
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
and 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));
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.
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); }
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); } }
You can use such a custom filter just like any other:
c.addContainerFilter( new MyCustomFilter("Name", (String) tf.getValue()));