From 99d6de546c74f0eed230ea8253dda6b85109d2e7 Mon Sep 17 00:00:00 2001 From: Markus Koivisto Date: Fri, 22 Jan 2016 14:55:18 +0200 Subject: Add documentation to master branch Change-Id: I2504bb10f1ae73ec0cbc08b7ba5a88925caa1674 --- documentation/datamodel/chapter-datamodel.asciidoc | 20 + .../datamodel/datamodel-container.asciidoc | 834 ++++++++++++ .../datamodel/datamodel-itembinding.asciidoc | 377 ++++++ documentation/datamodel/datamodel-items.asciidoc | 194 +++ .../datamodel/datamodel-overview.asciidoc | 72 ++ .../datamodel/datamodel-properties.asciidoc | 399 ++++++ .../datamodel/img/beanitem-nested-beans.png | Bin 0 -> 3142 bytes .../img/beanitemcontainer-nested-beans.png | Bin 0 -> 18032 bytes .../datamodel/img/datamodel-interfaces-hi.png | Bin 0 -> 170869 bytes .../datamodel/img/datamodel-interfaces-lo.png | Bin 0 -> 41890 bytes documentation/datamodel/img/datamodel-whitebg.png | Bin 0 -> 287119 bytes .../original-drawings/beanitem-doublebinding.svg | 1026 +++++++++++++++ .../original-drawings/datamodel-interfaces.svg | 1321 ++++++++++++++++++++ 13 files changed, 4243 insertions(+) create mode 100644 documentation/datamodel/chapter-datamodel.asciidoc create mode 100644 documentation/datamodel/datamodel-container.asciidoc create mode 100644 documentation/datamodel/datamodel-itembinding.asciidoc create mode 100644 documentation/datamodel/datamodel-items.asciidoc create mode 100644 documentation/datamodel/datamodel-overview.asciidoc create mode 100644 documentation/datamodel/datamodel-properties.asciidoc create mode 100644 documentation/datamodel/img/beanitem-nested-beans.png create mode 100644 documentation/datamodel/img/beanitemcontainer-nested-beans.png create mode 100644 documentation/datamodel/img/datamodel-interfaces-hi.png create mode 100644 documentation/datamodel/img/datamodel-interfaces-lo.png create mode 100644 documentation/datamodel/img/datamodel-whitebg.png create mode 100644 documentation/datamodel/original-drawings/beanitem-doublebinding.svg create mode 100644 documentation/datamodel/original-drawings/datamodel-interfaces.svg (limited to 'documentation/datamodel') diff --git a/documentation/datamodel/chapter-datamodel.asciidoc b/documentation/datamodel/chapter-datamodel.asciidoc new file mode 100644 index 0000000000..c28e5b716b --- /dev/null +++ b/documentation/datamodel/chapter-datamodel.asciidoc @@ -0,0 +1,20 @@ +[[datamodel]] +== Binding Components to Data + +((("Vaadin Data Model", id="term.datamodel", range="startofrange"))) + + +This chapter describes the Vaadin Data Model and shows how you can use it to +bind components directly to data sources, such as database queries. + + +include::datamodel-overview.asciidoc[leveloffset=+2] + +include::datamodel-properties.asciidoc[leveloffset=+2] + +include::datamodel-items.asciidoc[leveloffset=+2] + +include::datamodel-itembinding.asciidoc[leveloffset=+2] + +include::datamodel-container.asciidoc[leveloffset=+2] +(((range="endofrange", startref="term.datamodel"))) diff --git a/documentation/datamodel/datamodel-container.asciidoc b/documentation/datamodel/datamodel-container.asciidoc new file mode 100644 index 0000000000..8f88e7c8bf --- /dev/null +++ b/documentation/datamodel/datamodel-container.asciidoc @@ -0,0 +1,834 @@ +--- +title: Collecting Items in Containers +order: 5 +layout: page +--- + +[[datamodel.container]] += Collecting Items in Containers + +((("[classname]#Container#", id="term.datamodel.container", range="startofrange"))) + + +The [classname]#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 [classname]#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__. + +[[datamodel.container.intro]] +== 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. + +[[datamodel.container.intro.default]] +=== 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, [classname]#Table# is bound to a +[classname]#IndexedContainer#, [classname]#Tree# to a +[classname]#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"); +---- + + +[[datamodel.container.intro.creating]] +=== 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 [methodname]#setContainerDataSource()#. +Creation of the container depends on its type. For some containers, such as the +[classname]#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 [methodname]#addContainerProperty()# requires a +unique property ID, type, and a default value. You can also give +[parameter]#null#. + +Vaadin has a several built-in in-memory container implementations, such as +[classname]#IndexedContainer# and [classname]#BeanItemContainer#, which are easy +to use for setting up nonpersistent data storages. For persistent data, either +the built-in [classname]#SQLContainer# or the [classname]#JPAContainer# add-on +container can be used. + + +[[datamodel.container.intro.adding]] +=== Adding Items and Accessing Properties + +Items can be added to a container with the [methodname]#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 +[methodname]#getItem()# and then getting the properties from the item with +[methodname]#getItemProperty()#. + + +---- +// Get the item object +Item item = container.getItem(itemId); + +// Access a property in the item +Property 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 +[methodname]#getContainerProperty()#. + + +---- +container.getContainerProperty(itemId, "volume").setValue(5.0); +---- + + +[[datamodel.container.intro.givenid]] +=== Adding Items by Given ID + +Some containers, such as [classname]#IndexedContainer# and +[classname]#HierarchicalContainer#, allow adding items by a given ID, which can +be any [classname]#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. + + + +[[datamodel.container.inner]] +== Container Subinterfaces + +The [classname]#Container# interface contains inner interfaces that container +implementations can implement to fulfill different features required by +components that present container data. + +[interfacename]#Container.Filterable#:: Filterable containers allow filtering the contained items by filters, as +described in <>. + +[interfacename]#Container.Hierarchical#:: Hierarchical containers allow representing hierarchical relationships between +items and are required by the [classname]#Tree# and [classname]#TreeTable# +components. The [classname]#HierarchicalContainer# is a built-in in-memory +container for hierarchical data, and is used as the default container for the +tree components. The [classname]#FilesystemContainer# provides access to +browsing the content of a file system. Also [classname]#JPAContainer# is +hierarchical, as described in +<>. + +[interfacename]#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 +[classname]#Table#, which needs to provide lazy access to large containers. The +[classname]#IndexedContainer# is a basic in-memory implementation, as described +in <>. + +[interfacename]#Container.Ordered#:: An ordered container allows traversing the items in successive order in either +direction. Most built-in containers are ordered. + +[interfacename]#Container.SimpleFilterable#:: This interface enables filtering a container by string matching with +[methodname]#addContainerFilter()#. The filtering is done by either searching +the given string anywhere in a property value, or as its prefix. + +[interfacename]#Container.Sortable#:: A sortable container is required by some components that allow sorting the +content, such as [classname]#Table#, where the user can click a column header to +sort the table by the column. Some other components, such as +[classname]#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 [methodname]#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. + + +[[datamodel.container.indexedcontainer]] +== [classname]#IndexedContainer# + +The [classname]#IndexedContainer# is an in-memory container that implements the +[interfacename]#Indexed# interface to allow referencing the items by an index. +[classname]#IndexedContainer# is used as the default container in most selection +components in Vaadin. + +The properties need to be defined with [methodname]#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 [methodname]#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 [classname]#Table# component, which has +[classname]#IndexedContainer# as its default container, has a conveniency +[methodname]#addItem()# method that allows adding items as object vectors +containing the property values. + +The container implements the [interfacename]#Container.Indexed# feature to allow +accessing the item IDs by their index number, with [methodname]#getIdByIndex()#, +etc. The feature is required mainly for internal purposes of some components, +such as [classname]#Table#, which uses it to enable lazy transmission of table +data to the client-side. + + +[[datamodel.container.beancontainer]] +== [classname]#BeanContainer# + +The [classname]#BeanContainer# is an in-memory container for JavaBean objects. +Each contained bean is wrapped inside a [classname]#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 [classname]#BeanItemContainer#, which uses the bean object +itself as the item identifier, making the use usually simpler. Managing the item +IDs makes [classname]#BeanContainer# more complex to use, but it is necessary in +some cases where the [methodname]#equals()# or [methodname]#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 beans = + new BeanContainer(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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.beancontainer.basic[on-line example, window="_blank"]. + +To use explicit item IDs, use the methods [methodname]#addItem(Object, Object)#, +[methodname]#addItemAfter(Object, Object, Object)#, and +[methodname]#addItemAt(int, Object, Object)#. + +It is not possible to add additional properties to the container, except +properties in a nested bean. + +[[datamodel.container.beancontainer.nestedproperties]] +=== Nested Properties + +((("nested bean properties", id="term.datamodel.container.beancontainer.nestedproperties", range="startofrange"))) + + +If you have a nested bean with an 1:1 relationship inside a bean type contained +in a [classname]#BeanContainer# or [classname]#BeanItemContainer#, you can add +its properties to the container by specifying them with +[methodname]#addNestedContainerProperty()#. The feature is defined at the level +of [classname]#AbstractBeanContainer#. +((("[methodname]#addNestedContainerProperty()#"))) + +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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.beanitemcontainer.nestedbean[on-line example, window="_blank"]. + +After creating the container, you can declare the nested properties by +specifying their property identifiers with the +[methodname]#addNestedContainerProperty()# in dot notation. + + +---- +// Create a container for beans +BeanItemContainer stars = + new BeanItemContainer(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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.beanitemcontainer.nestedbean[on-line example, window="_blank"]. + +If you bind such a container to a [classname]#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 +[methodname]#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 [methodname]#setVisibleColumns()#. +((("[methodname]#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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.beanitemcontainer.nestedbean[on-line example, window="_blank"]. + +The resulting table is shown in +<>. + +[[figure.datamodel.container.beancontainer.nestedproperties]] +.[classname]#Table# Bound to a [classname]#BeanContainer# with Nested Properties +image::img/beanitemcontainer-nested-beans.png[] + +The bean binding in [classname]#AbstractBeanContainer# normally uses the +[classname]#MethodProperty# implementation of the [classname]#Property# +interface to access the bean properties using the setter and getter methods. For +nested properties, the [classname]#NestedMethodProperty# implementation is used. +((("[classname]#MethodProperty#"))) +((("[classname]#NestedMethodProperty#"))) + +(((range="endofrange", startref="term.datamodel.container.beancontainer.nestedproperties"))) + +ifdef::web[] +[[datamodel.container.beancontainer.idresolver]] +=== Defining a Bean ID Resolver + +If a bean ID resolver is set using [methodname]#setBeanIdResolver()# or +[methodname]#setBeanIdProperty()#, the methods [methodname]#addBean()#, +[methodname]#addBeanAfter()#, [methodname]#addBeanAt()# and +[methodname]#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 [parameter]#null#). + +Note that explicit item identifiers can also be used when a resolver has been +set by calling the [methodname]#addItem*()# methods - the resolver is only used +when adding beans using the [methodname]#addBean*()# or +[methodname]#addAll(Collection)# methods. + +endif::web[] + + +[[datamodel.container.beanitemcontainer]] +== [classname]#BeanItemContainer# + +[classname]#BeanItemContainer# is a container for JavaBean objects where each +bean is wrapped inside a [classname]#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. + +[classname]#BeanItemContainer# is a specialized version of the +[classname]#BeanContainer# described in <>. +It uses the bean itself as the item identifier, which makes it a bit easier to +use than [classname]#BeanContainer# in many cases. The latter is, however, +needed if the bean has reimplemented the [methodname]#equals()# or +[methodname]#hashCode()# methods. + +Let us revisit the example given in <> using +the [classname]#BeanItemContainer#. + + +---- +// Create a container for the beans +BeanItemContainer beans = + new BeanItemContainer(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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.beanitemcontainer.basic[on-line example, window="_blank"]. + +It is not possible to add additional properties to a +[classname]#BeanItemContainer#, except properties in a nested bean, as described +in <>. ((("nested bean +properties"))) + + +ifdef::web[] +[[datamodel.container.iterating]] +== Iterating Over a Container + +As the items in a [classname]#Container# are not necessarily indexed, iterating +over the items has to be done using an [classname]#Iterator#. The +[methodname]#getItemIds()# method of [classname]#Container# returns a +[classname]#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 [classname]#Table# component. The context of the +example is the example used in +<>. + + +---- +// 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 [methodname]#getItemIds()# returns an __unmodifiable +collection__, so the [classname]#Container# may not be modified during +iteration. You can not, for example, remove items from the +[classname]#Container# during iteration. The modification includes modification +in another thread. If the [classname]#Container# is modified during iteration, a +[classname]#ConcurrentModificationException# is thrown and the iterator may be +left in an undefined state. + +endif::web[] + +[[datamodel.container.gpc]] +== [classname]#GeneratedPropertyContainer# + +[classname]#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 [classname]#Grid#, which does not +support generated columns or hiding columns like [classname]#Table# does. + +[[datamodel.container.gpc.wrapping]] +=== Wrapping a Container + +A container to be wrapped must be a [interfacename]#Container.Indexed#. It can +optionally also implement [interfacename]#Container.Sortable# or +[interfacename]#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); +---- + + +[[datamodel.container.gpc.properties]] +=== Generated Properties + +Now, you can add generated properties in the container with +[methodname]#addGeneratedProperty()# by specifying a property ID and a +[interfacename]#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 [methodname]#getType()#, which must return the class +object of the value type of the property, and [methodname]#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() { + @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 getType() { + return Integer.class; + } +}); +---- + +You can access other items in the container, also their generated properties, +although you should beware of accidental recursion. + + +[[datamodel.container.gpc.using]] +=== Using [classname]#GeneratedPropertyContainer# + +Finally, you need to bind the [classname]#GeneratedPropertyContainer# to the +component instead of the wrapped container. + + +---- +Grid grid = new Grid(gpcontainer); +---- + +When using [classname]#GeneratedPropertyContainer# in [classname]#Grid#, notice +that generated columns are read-only, so you can not add grid rows with +[methodname]#addRow()#. In editable mode, editor fields are not generated for +generated columns. + + +[[datamodel.container.gpc.sorting]] +=== Sorting + +Even though the [classname]#GeneratedPropertyContainer# implements +[interfacename]#Container.Sortable#, the wrapped container must also support it +or otherwise sorting is disabled. Also, the generated properties are not +normally sortable, but require special handling to enable sorting. + + + +[[datamodel.container.filtered]] +== [classname]#Filterable# Containers + +((("Container", "Filterable", id="term.datamodel.container.filtered.filterable", range="startofrange"))) + + +((("[classname]#Filter# (in [classname]#Container#)", id="term.datamodel.container.filtered.filters", range="startofrange"))) + + +Containers that implement the [classname]#Container.Filterable# interface can be +filtered. For example, the built-in [classname]#IndexedContainer# and the bean +item containers implement it. Filtering is typically used for filtering the +content of a [classname]#Table#. +((("[classname]#IndexedContainer#"))) +((("[classname]#Table#"))) + +Filters implement the [classname]#Filter# interface and you add them to a +filterable container with the [methodname]#addContainerFilter()# method. +Container items that pass the filter condition are kept and shown in the +filterable component. +((("[methodname]#addContainerFilter()#"))) + + +---- +Filter filter = new SimpleStringFilter("name", + "Douglas", true, false); +table.addContainerFilter(filter); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.filter.basic[on-line example, window="_blank"]. + +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. + +[[datamodel.container.filtered.composite]] +=== Atomic and Composite Filters + +Filters can be classified as __atomic__ and __composite__. Atomic filters, such +as [classname]#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 +[literal]#++name++# property contains the name "Douglas" somewhere __or__ where +the [literal]#++age++# property has value less than 42. The properties must have +[classname]#String# and [classname]#Integer# types, respectively. + + +---- + +filter = new Or(new SimpleStringFilter("name", + "Douglas", true, false), + new Compare.Less("age", 42)); +---- + + +[[datamodel.container.filtered.builtin]] +=== Built-In Filter Types + +The built-in filter types are the following: + +[classname]#SimpleStringFilter#:: ((("[classname]#SimpleStringFilter#"))) ++ +Passes items where the specified property, that must be of [classname]#String# +type, contains the given [parameter]#filterString# as a substring. If +[parameter]#ignoreCase# is [parameter]#true#, the search is case insensitive. If +the [parameter]#onlyMatchPrefix# is [parameter]#true#, the substring may only be +in the beginning of the string, otherwise it may be elsewhere as well. + +[classname]#IsNull#:: ((("[classname]#IsNull# (filter)"))) ++ +Passes items where the specified property has null value. For in-memory +filtering, a simple [literal]#++==++# check is performed. For other containers, +the comparison implementation is container dependent, but should correspond to +the in-memory null check. + +[classname]#Equal#,[classname]#Greater#, + [classname]#Less#, + [classname]#GreaterOrEqual#, and[classname]#LessOrEqual#:: ((("[classname]#Equal# (filter)"))) +((("[classname]#Greater# (filter)"))) +((("[classname]#Less# (filter)"))) +((("[classname]#GreaterOrEqual# (filter)"))) +((("[classname]#LessOrEqual# (filter)"))) ++ +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 [classname]#Compare# class. + ++ +For the [classname]#Equal# filter, the [methodname]#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 +[classname]#Comparable# interface to work with the built-in in-memory +containers. Again for the other types of containers, the comparison is container +dependent. + +[classname]#And#and[classname]#Or#:: ((("[classname]#And# (filter)"))) +((("[classname]#Or# (filter)"))) ++ +These logical operator filters are composite filters that combine multiple other +filters. + +[classname]#Not#:: ((("[classname]#Not# (filter)"))) ++ +The logical unary operator filter negates which items are passed by the filter +given as the parameter. + + + + +[[datamodel.container.filtered.custom]] +=== Implementing Custom Filters + +A custom filter needs to implement the [classname]#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 +[methodname]#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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.filter.custom[on-line example, window="_blank"]. + +The actual filtering logic is done in the [methodname]#passesFilter()# method, +which simply returns [literal]#++true++# if the item should pass the filter and +[literal]#++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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.filter.custom[on-line example, window="_blank"]. + +You can use such a custom filter just like any other: + + +---- +c.addContainerFilter( + new MyCustomFilter("Name", (String) tf.getValue())); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.container.filter.custom[on-line example, window="_blank"]. + + +(((range="endofrange", startref="term.datamodel.container.filtered.filters"))) +(((range="endofrange", startref="term.datamodel.container.filtered.filterable"))) + +(((range="endofrange", startref="term.datamodel.container"))) + + diff --git a/documentation/datamodel/datamodel-itembinding.asciidoc b/documentation/datamodel/datamodel-itembinding.asciidoc new file mode 100644 index 0000000000..fd21b72267 --- /dev/null +++ b/documentation/datamodel/datamodel-itembinding.asciidoc @@ -0,0 +1,377 @@ +--- +title: Creating Forms by Binding Fields to Items +order: 4 +layout: page +--- + +[[datamodel.itembinding]] += Creating Forms by Binding Fields to Items + +Most applications in existence have forms of some sort. Forms contain fields, +which you want to bind to a data source, an item in the Vaadin data model. +[classname]#FieldGroup# provides an easy way to bind fields to the properties of +an item. You can use it by first creating a layout with some fields, and then +call it to bind the fields to the data source. You can also let the +[classname]#FieldGroup# create the fields using a field factory. It can also +handle commits. Notice that [classname]#FieldGroup# is not a user interface +component, so you can not add it to a layout. + +[[datamodel.itembinding.simple]] +== Simple Binding + +Let us start with a data model that has an item with a couple of properties. The +item could be any item type, as described earlier. + + +---- +// Have an item +PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty("Zaphod")); +item.addItemProperty("age", new ObjectProperty(42)); +---- + +Next, you would design a form for editing the data. The [classname]#FormLayout# +( +<> +is ideal for forms, but you could use any other layout as well. + + +---- +// Have some layout and create the fields +FormLayout form = new FormLayout(); + +TextField nameField = new TextField("Name"); +form.addComponent(nameField); + +TextField ageField = new TextField("Age"); +form.addComponent(ageField); +---- + +Then, we can bind the fields to the data as follows: + + +---- +// Now create the binder and bind the fields +FieldGroup binder = new FieldGroup(item); +binder.bind(nameField, "name"); +binder.bind(ageField, "age"); +---- + +The above way of binding is not different from simply calling +[methodname]#setPropertyDataSource()# for the fields. It does, however, register +the fields in the field group, which for example enables buffering or validation +of the fields using the field group, as described in +<>. + +Next, we consider more practical uses for a [classname]#FieldGroup#. + + +[[datamodel.itembinding.fieldfactory]] +== Using a [interfacename]#FieldFactory# to Build and Bind Fields + +Using the [methodname]#buildAndBind()# methods, [classname]#FieldGroup# can +create fields for you using a [interfacename]#FieldGroupFieldFactory#, but you +still have to add them to the correct position in your layout. + + +---- +// Have some layout +FormLayout form = new FormLayout(); + +// Now create a binder that can also create the fields +// using the default field factory +FieldGroup binder = new FieldGroup(item); +form.addComponent(binder.buildAndBind("Name", "name")); +form.addComponent(binder.buildAndBind("Age", "age")); +---- + + +[[datamodel.itembinding.formclass]] +== Binding Member Fields + +The [methodname]#bindMemberFields()# method in [classname]#FieldGroup# uses +reflection to bind the properties of an item to field components that are member +variables of a class. Hence, if you implement a form as a class with the fields +stored as member variables, you can use this method to bind them super-easy. + +The item properties are mapped to the members by the property ID and the name of +the member variable. If you want to map a property with a different ID to a +member, you can use the [literal]#++@PropertyId++# annotation for the member, +with the property ID as the parameter. + +For example: + + +---- +// Have an item +PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty("Zaphod")); +item.addItemProperty("age", new ObjectProperty(42)); + +// Define a form as a class that extends some layout +class MyForm extends FormLayout { + // Member that will bind to the "name" property + TextField name = new TextField("Name"); + + // Member that will bind to the "age" property + @PropertyId("age") + TextField ageField = new TextField("Age"); + + public MyForm() { + // Customize the layout a bit + setSpacing(true); + + // Add the fields + addComponent(name); + addComponent(ageField); + } +} + +// Create one +MyForm form = new MyForm(); + +// Now create a binder that can also creates the fields +// using the default field factory +FieldGroup binder = new FieldGroup(item); +binder.bindMemberFields(form); + +// And the form can be used in an higher-level layout +layout.addComponent(form); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.extended[on-line example, window="_blank"]. + +[[datamodel.itembinding.formclass.customcomponent]] +=== Encapsulating in [classname]#CustomComponent# + +Using a [classname]#CustomComponent# can be better for hiding the implementation +details than extending a layout. Also, the use of the [classname]#FieldGroup# +can be encapsulated in the form class. + +Consider the following as an alternative for the form implementation presented +earlier: + + +---- +// A form component that allows editing an item +class MyForm extends CustomComponent { + // Member that will bind to the "name" property + TextField name = new TextField("Name"); + + // Member that will bind to the "age" property + @PropertyId("age") + TextField ageField = new TextField("Age"); + + public MyForm(Item item) { + FormLayout layout = new FormLayout(); + layout.addComponent(name); + layout.addComponent(ageField); + + // Now use a binder to bind the members + FieldGroup binder = new FieldGroup(item); + binder.bindMemberFields(this); + + setCompositionRoot(layout); + } +} + +// And the form can be used as a component +layout.addComponent(new MyForm(item)); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.customcomponent[on-line example, window="_blank"]. + + + +[[datamodel.itembinding.buffering]] +== Buffering Forms + +Just like for individual fields, as described in +<>, a [classname]#FieldGroup# can handle buffering the form content so +that it is written to the item data source only when [methodname]#commit()# is +called for the group. It runs validation for all fields in the group and writes +their values to the item data source only if all fields pass the validation. +Edits can be discarded, so that the field values are reloaded from the data +source, by calling [methodname]#discard()#. Buffering is enabled by default, but +can be disabled by calling [methodname]#setBuffered(false)# for the +[classname]#FieldGroup#. + + +---- +// Have an item of some sort +final PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty("Q")); +item.addItemProperty("age", new ObjectProperty(42)); + +// Have some layout and create the fields +Panel form = new Panel("Buffered Form"); +form.setContent(new FormLayout()); + +// Build and bind the fields using the default field factory +final FieldGroup binder = new FieldGroup(item); +form.addComponent(binder.buildAndBind("Name", "name")); +form.addComponent(binder.buildAndBind("Age", "age")); + +// Enable buffering (actually enabled by default) +binder.setBuffered(true); + +// A button to commit the buffer +form.addComponent(new Button("OK", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + binder.commit(); + Notification.show("Thanks!"); + } catch (CommitException e) { + Notification.show("You fail!"); + } + } +})); + +// A button to discard the buffer +form.addComponent(new Button("Discard", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + binder.discard(); + Notification.show("Discarded!"); + } +})); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.customcomponent[on-line example, window="_blank"]. + + +[[datamodel.itembinding.beans]] +== Binding Fields to a Bean + +The [classname]#BeanFieldGroup# makes it easier to bind fields to a bean. It +also handles binding to nested beans properties. The build a field bound to a +nested bean property, identify the property with dot notation. For example, if a +[classname]#Person# bean has a [literal]#++address++# property with an +[classname]#Address# type, which in turn has a [literal]#++street++# property, +you could build a field bound to the property with +[methodname]#buildAndBind("Street", "address.street")#. + +The input to fields bound to a bean can be validated using the Java Bean +Validation API, as described in <>. The +[classname]#BeanFieldGroup# automatically adds a [classname]#BeanValidator# to +every field if a bean validation implementation is included in the classpath. + + +[[datamodel.itembinding.beanvalidation]] +== Bean Validation + +Vaadin allows using the Java Bean Validation API 1.0 (JSR-303) for validating +input from fields bound to bean properties before the values are committed to +the bean. The validation is done based on annotations on the bean properties, +which are used for creating the actual validators automatically. See +<> for general information about validation. + +Using bean validation requires an implementation of the Bean Validation API, +such as Hibernate Validator ( [filename]#hibernate-validator-4.2.0.Final.jar# or +later) or Apache Bean Validation. The implementation JAR must be included in the +project classpath when using the bean validation, or otherwise an internal error +is thrown. + +Bean validation is especially useful when persisting entity beans with the +Vaadin JPAContainer, described in +<>. + +[[datamodel.itembinding.beanvalidation.annotations]] +=== Annotations + +The validation constraints are defined as annotations. For example, consider the +following bean: + + +---- +// Here is a bean +public class Person implements Serializable { + @NotNull + @javax.validation.constraints.Size(min=2, max=10) + String name; + + @Min(1) + @Max(130) + int age; + + // ... setters and getters ... +} +---- + +For a complete list of allowed constraints for different data types, please see +the link:http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html[Bean Validation +API documentation]. + + +[[datamodel.itembinding.beanvalidation.validating]] +=== Validating the Beans + +Validating a bean is done with a [classname]#BeanValidator#, which you +initialize with the name of the bean property it should validate and add it the +the editor field. + +In the following example, we validate a single unbuffered field: + + +---- +Person bean = new Person("Mung bean", 100); +BeanItem item = new BeanItem (bean); + +// Create an editor bound to a bean field +TextField firstName = new TextField("First Name", + item.getItemProperty("name")); + +// Add the bean validator +firstName.addValidator(new BeanValidator(Person.class, "name")); + +firstName.setImmediate(true); +layout.addComponent(firstName); +---- + +In this case, the validation is done immediately after focus leaves the field. +You could do the same for the other field as well. + +Bean validators are automatically created when using a +[classname]#BeanFieldGroup#. + + +---- +// Have a bean +Person bean = new Person("Mung bean", 100); + +// Form for editing the bean +final BeanFieldGroup binder = + new BeanFieldGroup(Person.class); +binder.setItemDataSource(bean); +layout.addComponent(binder.buildAndBind("Name", "name")); +layout.addComponent(binder.buildAndBind("Age", "age")); + +// Buffer the form content +binder.setBuffered(true); +layout.addComponent(new Button("OK", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + binder.commit(); + } catch (CommitException e) { + } + } +})); +---- + + +[[datamodel.itembinding.beanvalidation.locale]] +=== Locale Setting for Bean Validation + +The validation error messages are defined in the bean validation implementation, +in a [filename]#ValidationMessages.properties# file. The message is shown in the +language specified with the locale setting for the form. The default language is +English, but for example Hibernate Validator contains translations of the +messages for a number of languages. If other languages are needed, you need to +provide a translation of the properties file. + + + + + diff --git a/documentation/datamodel/datamodel-items.asciidoc b/documentation/datamodel/datamodel-items.asciidoc new file mode 100644 index 0000000000..75a7c1ecf8 --- /dev/null +++ b/documentation/datamodel/datamodel-items.asciidoc @@ -0,0 +1,194 @@ +--- +title: Holding properties in Items +order: 3 +layout: page +--- + +[[datamodel.items]] += Holding properties in Items + +The [classname]#Item# interface provides access to a set of named properties. +Each property is identified by a __property identifier__ (PID) and a reference +to such a property can be queried from an [classname]#Item# with +[methodname]#getItemProperty()# using the identifier. + +Examples on the use of items include rows in a [classname]#Table#, with the +properties corresponding to table columns, nodes in a [classname]#Tree#, and the +the data bound to a [classname]#Form#, with item's properties bound to +individual form fields. + +Items are generally equivalent to objects in the object-oriented model, but with +the exception that they are configurable and provide an event handling +mechanism. The simplest way to utilize [classname]#Item# interface is to use +existing implementations. Provided utility classes include a configurable +property set ( [classname]#PropertysetItem#) and a bean-to-item adapter ( +[classname]#BeanItem#). Also, a [classname]#Form# implements the interface and +can therefore be used directly as an item. + +In addition to being used indirectly by many user interface components, items +provide the basic data model underlying the [classname]#Form# component. In +simple cases, forms can even be generated automatically from items. The +properties of the item correspond to the fields of the form. + +The [classname]#Item# interface defines inner interfaces for maintaining the +item property set and listening changes made to it. +[classname]#PropertySetChangeEvent# events can be emitted by a class +implementing the [classname]#PropertySetChangeNotifier# interface. They can be +received through the [classname]#PropertySetChangeListener# interface. + +ifdef::web[] +[[datamodel.items.propertysetitem]] +== The [classname]#PropertysetItem# Implementation + +The [classname]#PropertysetItem# is a generic implementation of the +[classname]#Item# interface that allows storing properties. The properties are +added with [methodname]#addItemProperty()#, which takes a name and the property +as parameters. + +The following example demonstrates a typical case of collecting +[classname]#ObjectProperty# properties in an item: + + +---- +PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty("Zaphod")); +item.addItemProperty("age", new ObjectProperty(42)); + +// Bind it to a component +Form form = new Form(); +form.setItemDataSource(item); +---- + +endif::web[] + +[[datamodel.items.beanitem]] +== Wrapping a Bean in a [classname]#BeanItem# + +The [classname]#BeanItem# implementation of the [classname]#Item# interface is a +wrapper for Java Bean objects. In fact, only the setters and getters are +required while serialization and other bean features are not, so you can wrap +almost any POJOs with minimal requirements. + + +---- +// Here is a bean (or more exactly a POJO) +class Person { + String name; + int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age.intValue(); + } +} + +// Create an instance of the bean +Person bean = new Person(); + +// Wrap it in a BeanItem +BeanItem item = new BeanItem(bean); + +// Bind it to a component +Form form = new Form(); +form.setItemDataSource(item); +---- + +You can use the [methodname]#getBean()# method to get a reference to the +underlying bean. + +[[datamodel.items.beanitem.nested]] +=== Nested Beans + +You may often have composite classes where one class "has a" another class. For +example, consider the following [classname]#Planet# class which "has a" +discoverer: + + +---- +// Here is a bean with two nested beans +public class Planet implements Serializable { + String name; + Person discoverer; + + public Planet(String name, Person discoverer) { + this.name = name; + this.discoverer = discoverer; + } + + ... getters and setters ... +} + +... +// Create an instance of the bean +Planet planet = new Planet("Uranus", + new Person("William Herschel", 1738)); +---- + +When shown in a [classname]#Form#, for example, you would want to list the +properties of the nested bean along the properties of the composite bean. You +can do that by binding the properties of the nested bean individually with a +[classname]#MethodProperty# or [classname]#NestedMethodProperty#. You should +usually hide the nested bean from binding as a property by listing only the +bound properties in the constructor. + + +---- +// Wrap it in a BeanItem and hide the nested bean property +BeanItem item = new BeanItem(planet, + new String[]{"name"}); + +// Bind the nested properties. +// Use NestedMethodProperty to bind using dot notation. +item.addItemProperty("discoverername", + new NestedMethodProperty(planet, "discoverer.name")); + +// The other way is to use regular MethodProperty. +item.addItemProperty("discovererborn", + new MethodProperty(planet.getDiscoverer(), + "born")); +---- + +The difference is that [classname]#NestedMethodProperty# does not access the +nested bean immediately but only when accessing the property values, while when +using [classname]#MethodProperty# the nested bean is accessed when creating the +method property. The difference is only significant if the nested bean can be +null or be changed later. + +You can use such a bean item for example in a [classname]#Form# as follows: + + +---- +// Bind it to a component +Form form = new Form(); +form.setItemDataSource(item); + +// Nicer captions +form.getField("discoverername").setCaption("Discoverer"); +form.getField("discovererborn").setCaption("Born"); +---- + +[[figure.datamodel.items.beanitem.nested]] +.A [classname]#Form# with Nested Bean Properties +image::img/beanitem-nested-beans.png[] + +The [classname]#BeanContainer# and [classname]#BeanItemContainer# allow easy +definition of nested bean properties with +[methodname]#addNestedContainerProperty()#, as described in +<>. + + + + + diff --git a/documentation/datamodel/datamodel-overview.asciidoc b/documentation/datamodel/datamodel-overview.asciidoc new file mode 100644 index 0000000000..95209e0b76 --- /dev/null +++ b/documentation/datamodel/datamodel-overview.asciidoc @@ -0,0 +1,72 @@ +--- +title: Overview +order: 1 +layout: page +--- + +[[datamodel.overview]] += Overview + +The Vaadin Data Model is one of the core concepts of the library. To allow the +view (user interface components) to access the data model of an application +directly, we have introduced a standard data interface. + +The model allows binding user interface components directly to the data that +they display and possibly allow to edit. There are three nested levels of +hierarchy in the data model: __property__, __item__, and __container__. Using a +spreadsheet application as an analogy, these would correspond to a cell, a row, +and a table, respectively. + +.Vaadin Data Model +image::img/datamodel-whitebg.png[] + +The Data Model is realized as a set of interfaces in the +[classname]#com.vaadin.data# package. The package contains the +[classname]#Property#, [classname]#Item#, and [classname]#Container# interfaces, +along with a number of more specialized interfaces and classes. + +Notice that the Data Model does not define data representation, but only +interfaces. This leaves the representation fully to the implementation of the +containers. The representation can be almost anything, such as a plain old Java +object (POJO) structure, a filesystem, or a database query. + +The Data Model is used heavily in the core user interface components of Vaadin, +especially the field components, that is, components that implement the +[classname]#Field# interface or more typically extend +[classname]#AbstractField#, which defines many common features. A key feature of +all the built-in field components is that they can either maintain their data by +themselves or be bound to an external data source. The value of a field is +always available through the [classname]#Property# interface. As more than one +component can be bound to the same data source, it is easy to implement various +viewer-editor patterns. + +The relationships of the various interfaces are shown in +<>; the value change event and listener +interfaces are shown only for the [classname]#Property# interface, while the +notifier interfaces are omitted altogether. + +[[figure.datamodel.overview.relationships]] +.Interface Relationships in Vaadin Data Model +image::img/datamodel-interfaces-hi.png[] + +The Data Model has many important and useful features, such as support for +change notification. Especially containers have many helper interfaces, +including ones that allow indexing, ordering, sorting, and filtering the data. +Also [classname]#Field# components provide a number of features involving the +data model, such as buffering, validation, and lazy loading. + +Vaadin provides a number of built-in implementations of the data model +interfaces. The built-in implementations are used as the default data models in +many field components. + +In addition to the built-in implementations, many data model implementations, +such as containers, are available as add-ons, either from the Vaadin Directory +or from independent sources. Both commercial and free implementations exist. The +JPAContainer, described in +<>, is the most often used conmmercial container add-on. The +installation of add-ons is described in +<>. Notice that unlike with most regular add-on components, you +do not need to compile a widget set for add-ons that include just data model +implementations. diff --git a/documentation/datamodel/datamodel-properties.asciidoc b/documentation/datamodel/datamodel-properties.asciidoc new file mode 100644 index 0000000000..d5d0d3e39d --- /dev/null +++ b/documentation/datamodel/datamodel-properties.asciidoc @@ -0,0 +1,399 @@ +--- +title: Properties +order: 2 +layout: page +--- + +[[datamodel.properties]] += Properties + +The [interfacename]#Property# interface is the base of the Vaadin Data Model. It +provides a standardized API for a single data value object that can be read +(get) and written (set). A property is always typed, but can optionally support +data type conversions. The type of a property can be any Java class. Optionally, +properties can provide value change events for following their changes. + +You can set the value of a property with [methodname]#setValue()# and read with +[methodname]#getValue()#. + +In the following, we set and read the property value from a +[classname]#TextField# component, which implements the [interfacename]#Property# +interface to allow accessing the field value. + + +---- +final TextField tf = new TextField("Name"); + +// Set the value +tf.setValue("The text field value"); + +// When the field value is edited by the user +tf.addValueChangeListener( + new Property.ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + // Do something with the new value + layout.addComponent(new Label(tf.getValue())); + } +}); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.properties.basic[on-line example, window="_blank"]. + +Changes in the property value usually fire a [classname]#ValueChangeEvent#, +which can be handled with a [classname]#ValueChangeListener#. The event object +provides reference to the property with [methodname]#getProperty()#. Note that +its [methodname]#getValue()# method returns the value with [classname]#Object# +type, so you need to cast it to the proper type. + +Properties are in themselves unnamed. They are collected in __items__, which +associate the properties with names: the __Property Identifiers__ or __PID__s. +Items can be further contained in containers and are identified with __Item +Identifiers__ or __IID__s. In the spreadsheet analogy, __Property Identifiers__ +would correspond to column names and __Item Identifiers__ to row names. The +identifiers can be arbitrary objects, but must implement the +[methodname]#equals(Object)# and [methodname]#hashCode()# methods so that they +can be used in any standard Java [classname]#Collection#. + +The [classname]#Property# interface can be utilized either by implementing the +interface or by using some of the built-in property implementations. Vaadin +includes a [classname]#Property# interface implementation for arbitrary function +pairs and bean properties, with the [classname]#MethodProperty# class, and for +simple object properties, with the [classname]#ObjectProperty# class, as +described later. + +In addition to the simple components, selection components provide their current +selection as the property value. In single selection mode, the property is a +single item identifier, while in multiple selection mode it is a set of item +identifiers. See the documentation of the selection components for further +details. + +Components that can be bound to a property have an internal default data source +object, typically a [classname]#ObjectProperty#, which is described later. As +all such components are viewers or editors, also described later, so you can +rebind a component to any data source with +[methodname]#setPropertyDataSource()#. + +[[datamodel.properties.viewers]] +== Property Viewers and Editors + +The most important function of the [classname]#Property# as well as of the other +data model interfaces is to connect classes implementing the interface directly +to editor and viewer classes. This means connecting a data source (model) to a +user interface component (views) to allow editing or viewing the data model. + +A property can be bound to a component implementing the [classname]#Viewer# +interface with [methodname]#setPropertyDataSource()#. + + +---- +// Have a data model +ObjectProperty property = + new ObjectProperty("Hello", String.class); + +// Have a component that implements Viewer +Label viewer = new Label(); + +// Bind it to the data +viewer.setPropertyDataSource(property); +---- + +You can use the same method in the [classname]#Editor# interface to bind a +component that allows editing a particular property type to a property. + + +---- +// Have a data model +ObjectProperty property = + new ObjectProperty("Hello", String.class); + +// Have a component that implements Viewer +TextField editor = new TextField("Edit Greeting"); + +// Bind it to the data +editor.setPropertyDataSource(property); + +---- + +As all field components implement the [classname]#Property# interface, you can +bind any component implementing the [classname]#Viewer# interface to any field, +assuming that the viewer is able the view the object type of the field. +Continuing from the above example, we can bind a [classname]#Label# to the +[classname]#TextField# value: + + +---- +Label viewer = new Label(); +viewer.setPropertyDataSource(editor); + +// The value shown in the viewer is updated immediately +// after editing the value in the editor (once it +// loses the focus) +editor.setImmediate(true); +---- + +If a field has validators, as described in +<>, the validators are executed before writing the value to the +property data source, or by calling the [methodname]#validate()# or +[methodname]#commit()# for the field. + + +[[datamodel.properties.objectproperty]] +== [classname]#ObjectProperty# Implementation + +The [classname]#ObjectProperty# class is a simple implementation of the +[classname]#Property# interface that allows storing an arbitrary Java object. + + +---- +// Have a component that implements Viewer interface +final TextField tf = new TextField("Name"); + +// Have a data model with some data +String myObject = "Hello"; + +// Wrap it in an ObjectProperty +ObjectProperty property = + new ObjectProperty(myObject, String.class); + +// Bind the property to the component +tf.setPropertyDataSource(property); +---- + + +[[datamodel.properties.converter]] +== Converting Between Property Type and Representation + +Fields allow editing a certain type, such as a [classname]#String# or +[classname]#Date#. The bound property, on the other hand, could have some +entirely different type. Conversion between a representation edited by the field +and the model defined in the property is handler with a converter that +implements the [interfacename]#Converter# interface. + +Most common type conversions, such as between string and integer, are handled by +the default converters. They are created in a converter factory global in the +application. + +[[datamodel.properties.converter.basic]] +=== Basic Use of Converters + +The [methodname]#setConverter([interfacename]#Converter#)# method sets the +converter for a field. The method is defined in [classname]#AbstractField#. + + +---- +// Have an integer property +final ObjectProperty property = + new ObjectProperty(42); + +// Create a TextField, which edits Strings +final TextField tf = new TextField("Name"); + +// Use a converter between String and Integer +tf.setConverter(new StringToIntegerConverter()); + +// And bind the field +tf.setPropertyDataSource(property); +---- + +The built-in converters are the following: + +[[datamodel.properties.converter.basic.built-in]] +.Built-in Converters +[options="header"] +|=============== +|Converter|Representation|Model +|[classname]#StringToIntegerConverter#|[classname]#String#|[classname]#Integer# +|[classname]#StringToDoubleConverter#|[classname]#String#|[classname]#Double# +|[classname]#StringToNumberConverter#|[classname]#String#|[classname]#Number# +|[classname]#StringToBooleanConverter#|[classname]#String#|[classname]#Boolean# +|[classname]#StringToDateConverter#|[classname]#String#|[classname]#Date# +|[classname]#DateToLongConverter#|[classname]#Date#|[classname]#Long# + +|=============== + + + +In addition, there is a [classname]#ReverseConverter# that takes a converter as +a parameter and reverses the conversion direction. + +If a converter already exists for a type, the +[methodname]#setConverter([interfacename]#Class#)# retrieves the converter for +the given type from the converter factory, and then sets it for the field. This +method is used implicitly when binding field to a property data source. + + +[[datamodel.properties.converter.custom]] +=== Implementing a Converter + +A conversion always occurs between a __representation type__, edited by the +field component, and a __model type__, that is, the type of the property data +source. Converters implement the [interfacename]#Converter# interface defined in +the [package]#com.vaadin.data.util.converter# package. + +For example, let us assume that we have a simple [classname]#Complex# type for +storing complex values. + + +---- +public class ComplexConverter + implements Converter { + @Override + public Complex convertToModel(String value, Locale locale) + throws ConversionException { + String parts[] = + value.replaceAll("[\\(\\)]", "").split(","); + if (parts.length != 2) + throw new ConversionException( + "Unable to parse String to Complex"); + return new Complex(Double.parseDouble(parts[0]), + Double.parseDouble(parts[1])); + } + + @Override + public String convertToPresentation(Complex value, + Locale locale) + throws ConversionException { + return "("+value.getReal()+","+value.getImag()+")"; + } + + @Override + public Class getModelType() { + return Complex.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } +} +---- + +The conversion methods get the locale for the conversion as a parameter. + + +[[datamodel.properties.converter.converterfactory]] +=== Converter Factory + +If a field does not directly allow editing a property type, a default converter +is attempted to create using an application-global converter factory. If you +define your own converters that you wish to include in the converter factory, +you need to implement one yourself. While you could implement the +[interfacename]#ConverterFactory# interface, it is usually easier to just extend +[classname]#DefaultConverterFactory#. + + +---- +class MyConverterFactory extends DefaultConverterFactory { + @Override + public Converter + createConverter(Class presentationType, + Class modelType) { + // Handle one particular type conversion + if (String.class == presentationType && + Complex.class == modelType) + return (Converter) + new ComplexConverter(); + + // Default to the supertype + return super.createConverter(presentationType, + modelType); + } +} + +// Use the factory globally in the application +Application.getCurrentApplication().setConverterFactory( + new MyConverterFactory()); +---- + + + +ifdef::web[] +[[datamodel.properties.implementing]] +== Implementing the [classname]#Property# Interface + +Implementation of the [classname]#Property# interface requires defining setters +and getters for the value and the __read-only__ mode. Only a getter is needed +for the property type, as the type is often fixed in property implementations. + +The following example shows a simple implementation of the [classname]#Property# +interface: + + +---- +class MyProperty implements Property { + Integer data = 0; + boolean readOnly = false; + + // Return the data type of the model + public Class getType() { + return Integer.class; + } + + public Object getValue() { + return data; + } + + // Override the default implementation in Object + @Override + public String toString() { + return Integer.toHexString(data); + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean newStatus) { + readOnly = newStatus; + } + + public void setValue(Object newValue) + throws ReadOnlyException, ConversionException { + if (readOnly) + throw new ReadOnlyException(); + + // Already the same type as the internal representation + if (newValue instanceof Integer) + data = (Integer) newValue; + + // Conversion from a string is required + else if (newValue instanceof String) + try { + data = Integer.parseInt((String) newValue, 16); + } catch (NumberFormatException e) { + throw new ConversionException(); + } + else + // Don't know how to convert any other types + throw new ConversionException(); + + // Reverse decode the hexadecimal value + } +} + +// Instantiate the property and set its data +MyProperty property = new MyProperty(); +property.setValue(42); + +// Bind it to a component +final TextField tf = new TextField("Name", property); +---- + +The components get the displayed value by the [methodname]#toString()# method, +so it is necessary to override it. To allow editing the value, value returned in +the [methodname]#toString()# must be in a format that is accepted by the +[methodname]#setValue()# method, unless the property is read-only. The +[methodname]#toString()# can perform any type conversion necessary to make the +internal type a string, and the [methodname]#setValue()# must be able to make a +reverse conversion. + +The implementation example does not notify about changes in the property value +or in the read-only mode. You should normally also implement at least the +[classname]#Property.ValueChangeNotifier# and +[classname]#Property.ReadOnlyStatusChangeNotifier#. See the +[classname]#ObjectProperty# class for an example of the implementation. + +endif::web[] + + + diff --git a/documentation/datamodel/img/beanitem-nested-beans.png b/documentation/datamodel/img/beanitem-nested-beans.png new file mode 100644 index 0000000000..ea8c51ccd4 Binary files /dev/null and b/documentation/datamodel/img/beanitem-nested-beans.png differ diff --git a/documentation/datamodel/img/beanitemcontainer-nested-beans.png b/documentation/datamodel/img/beanitemcontainer-nested-beans.png new file mode 100644 index 0000000000..fbd5fa155a Binary files /dev/null and b/documentation/datamodel/img/beanitemcontainer-nested-beans.png differ diff --git a/documentation/datamodel/img/datamodel-interfaces-hi.png b/documentation/datamodel/img/datamodel-interfaces-hi.png new file mode 100644 index 0000000000..9bebdb0ac0 Binary files /dev/null and b/documentation/datamodel/img/datamodel-interfaces-hi.png differ diff --git a/documentation/datamodel/img/datamodel-interfaces-lo.png b/documentation/datamodel/img/datamodel-interfaces-lo.png new file mode 100644 index 0000000000..2139cf9f08 Binary files /dev/null and b/documentation/datamodel/img/datamodel-interfaces-lo.png differ diff --git a/documentation/datamodel/img/datamodel-whitebg.png b/documentation/datamodel/img/datamodel-whitebg.png new file mode 100644 index 0000000000..803dd70c08 Binary files /dev/null and b/documentation/datamodel/img/datamodel-whitebg.png differ diff --git a/documentation/datamodel/original-drawings/beanitem-doublebinding.svg b/documentation/datamodel/original-drawings/beanitem-doublebinding.svg new file mode 100644 index 0000000000..509013ab07 --- /dev/null +++ b/documentation/datamodel/original-drawings/beanitem-doublebinding.svg @@ -0,0 +1,1026 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + BeanItem<DaBean> + + + + DaBean + + + Form + + + + + Form + + + + + + BeanItem<DaBean> + + + + DaBean + + + Form + + + + + Form + + + + + + BeanItem<DaBean> + + + + diff --git a/documentation/datamodel/original-drawings/datamodel-interfaces.svg b/documentation/datamodel/original-drawings/datamodel-interfaces.svg new file mode 100644 index 0000000000..051f60c48d --- /dev/null +++ b/documentation/datamodel/original-drawings/datamodel-interfaces.svg @@ -0,0 +1,1321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Property + + + Item + + + ValueChangeListener + + + + + + + Field + + + + ValueChangeEvent + + + AbstractField + + + Container + + + Editor + + + Viewer + Data Model + + + + Ordered + + + Hierarchical + + + + Sortable + + + Indexed + + + Filterable + + + + + + + IndexedContainer + + + + + HierarchicalContainer + + + + + + + + + + n n + setValue()getValue() + addItem() + addItemProperty() + valueChange() + + + Editor + + + Viewer + + + Editor + + + Viewer + + -- cgit v1.2.3