This content app framework is based on the deprecated Vaadin 7, which
has been replaced with Vaadin 8 since Magnolia 6.0. It is part of the
Magnolia 5 UI framework.
A Container represents a data source in a Vaadin context. The Container
provides methods to get, add and remove items and properties in the data
source. The Container can hold 0 to n items. An Item can hold 0 to n
Properties. The Presenter uses the data of a Container to render a
Content view.
Container wraps the data of the data source. It provides methods to
get items by their ID, to get all Items, to get all itemIds and so on.
Every Container must implement at least the
Container
interface.
ItemId is the key to get an item. Vaadin doesn’t type the ItemId. It
is just an object. A simple ItemId could be a string. In some cases, an
ItemId is a POJO
with its own properties (for example, JcrItemId).
ItemIds are sent by events.
Item is comparable to a row in a database table; it must implement
the Vaadin Item
interface. An item contains a set of properties and offers methods to
add, modify and remove the properties.
Every property is identified by a property ID. The property ID is not
typed either, it is just an object, usually a string. Use the property
ID to configure columns in the content view.
Property is the
Vaadin interface for a item property. A property has a value and a type.
You must configure the Container before adding items: The Container must
know the properties (IDs and types) which it will display. You can add
an item only, if all of its properties are known to the Container. It is
valid to add an item which has not all properties as the configured
Container.
Guidelines for creating Containers
Write methods of the
Container
interface are not required when running in the content app.
If you want to manipulate data - add, edit, delete - do not manipulate
Container items. Instead, create actions that interact directly with the
data source. After an action is executed, the view and its underlying
Container are refreshed automatically if the Container implements
Refreshable.
Create a fully customized Container or extend a concrete
implementation provided by Vaadin or Magnolia.
When creating a fully custom Container, implement empty write
methods (not required).
@Override
public Object addItem() throws UnsupportedOperationException {
throw new UnsupportedOperationException("addItem is not supported!");
}
Copy
Mandatory and optional Container interfaces
In addition to the base interface
Container there
are a number of subinterfaces. Which of them should a custom Container
implement?
The Container interface is mandatory.
A Container of a tree view additionally requires at least
Container.Hierarchical.
Implementing
Collapsible
is not mandatory but helpful in a custom tree Container.
To prepare a concrete Vaadin Container within a content subapp, create
the data structure using the Container’s write methods to add the data
which goes into memory.
Concrete Vaadin Containers are difficult to use for lazy loading. If you
find a nice lazy loading solution in a subclass of
HierarchicalContainer, please leave a comment!
Completely custom Containers
For a completely custom Container, implement the required interfaces.
Depending on the data source and your requirements concerning speed in a
production environment your Container probably should have these
abilities:
Lazy loading
Caching data
Guidelines for ItemIds
The Vaadin API leaves it open how you want to implement ItemIds. In the
Vaadin API an ItemId is just an Object. Think carefully what ItemId to
use.
An ItemId must be unique within a Container.
The itemId must carry enough information to fetch data from the data
source to instantiate its corresponding Item. This is required in the
context of ContentConnector. For example, JcrItemId
is a plain Java object that carries two properties: uuid and
workspace. These two properties are sufficient to get data from a JCR
repository given that there is access to
Session.
It must be possible to get ItemId of a given item in the context of
ContentConnector.
What is a good ItemId? String or POJO?
A String is a very simple and easy implementation for an ItemId. If your
data source provides a unique String which is sufficient to build the
corresponding item by querying the data source using only the String,
then a String as ItemId is fine. However, if you must concatenate a
String to build a unique itemId you should rather implement a plain Java
object (POJO) for
the ItemId.
When using a POJO you will require a method which creates a String
representation of your ItemId. Furthermore it must be possible to create
an ItemId (of your type, the POJO) by the String representation.
Creating Items
When using a concrete Container from Vaadin, you won’t need a specific
Item implementation,
instead you have to add items by calling:
Item item = addItem(Object itemId);Copy
which returns an Item on which you have to set properties.
Tip: When using a Container which extends
IndexedContainer,
Items returned by #addItem already have set properties with default
values. You cannot add the properties again. Instead, you have to set
the values on the already existing properties.
Object itemId = createItemId(/* some arguments here */)
Item item = indexedContainer.addItem(itemId);
// Adding a property will fail. (Property has already been set by container with default value.)// item.addItemProperty("property-id", new ObjectProperty("value"));// Instead, change the value of the properties that are already set.
item.getItemProperty("property-id").setValue("new value");Copy
When you implement a fully customized Container you most probably
require a custom Item. Extend a Vaadin implementation.
BeanItem
and
PropertysetItem
are both adequate for many use cases.
Content view Presenter knows columns and provides a Container
A Magnolia content app contains at least one subapp, displayed to users
as a tab. A browser subapp is a special type of subapp that contains a
workbench for operating on content items.
The workbench must have at least one content view. Magnolia provides
three view types out of the box: tree, list and thumbnail. You can also
define other customized views.
A content view is rendered by the content presenter which must be
configured separately for each view. The data rendered on the view is
provided by the Container. The presenter instantiates a suitable
Container.
A tree view requires a Container which implements the
Container.Hierarchical
interface. For list and thumbnail views it is sufficient to have a
Container which implements only the base interface Container.
This means that you will probably need different Containers for
different views.
The subapp has exactly one ContentConnector which must work for all used
Containers within the sub-app.
Content view configuration
A content view configuration requires the following parts:
ContentPresenter for every content view in a content app.
ContentPresenterDefinition to set the implementation class of the custom content presenter.
ContentPresenter that extends AbstractContentPresenterBase
or one of its subclasses. Connected to a Container. Must implement the
AbstractContentPresenterBase#initializeContainer method.
Container that is bound to the content presenter.
Column configuration
Every column in the view must be configured. Example:
ContentConnector knows the relation between item, itemId and URL fragment
ContentConnector
is a core piece for a content app. It knows which item belongs to an
itemId and vice versa. The connector can create a URL fragment from an
itemId and vice versa.
URL fragments and events
When the user selects an item in a content app, SelectionChangedEvent is fired.
The event carries the ID of the selected item. The event leads to a change in the BrowserLocation
object of the subapp. That’s why the URL fragment methods are required.
ContentConnector methods
If you look at the
Container
interface, its methods are similar to methods in the ContentConnector:
A ContentConnector must implement methods that are very similar to
methods in the Container. That said, it can be handy to have an instance
of the Container in your ContentConnector. However, as soon as you have
more than one Container this pattern no longer works.
The presenter responsible for a view must initialize the Container. If
you can use one Container for all presenters, it is fine to couple
the ContentConnector with the Container. For example, if a subapp uses a
list view and a thumbnail view it should be possible to use the same
Container.
If you need more than one Container within a sub app, do not use
Container in the ContentConnector implementation but get data directly
from your data source the same way you retrieve data when preparing data
for the Container.
You may implement a utility class which provides access to the data
source and creates ItemIds and Items to use both in Container and
contentConnector.
Best practice
Make the Container instance available in your ContentConnector. This
makes it easier to implement item and itemId related methods. This works
only if you have only one Container per subapp. If you need more
Containers you must decouple the Container and the ContentConnector.
Configure and register a ContentConnector
Every subapp must provide and configure and register its own
ContentConnector.
Once registered, you can get the ContentConnector via injection.
Implement ContentConnector.
Create an interface that extends ContentConnectorDefinition
(for example, FlickrBrowserContentConnectorDefinition).
Create a class that implements your interface and extends ConfiguredContentConnectorDefinition.
Set the implementation class.
ContentConnectorProvider creates one instance of the type of connector you set in
ConfiguredContentConnectorDefinition. You can inject your
ContentConnector into different classes wherever it is required. You
will always get the same instance.