Skip to content

Commit

Permalink
Rename HierarchyData and InMemoryHierarchicalDataProvider, introduce …
Browse files Browse the repository at this point in the history
…HasHierarchicalDataProvider

Additionally adds a helper method for recursive constructing
TreeData with a child item provider.
  • Loading branch information
ahie committed May 15, 2017
1 parent 80b2412 commit a604370
Show file tree
Hide file tree
Showing 23 changed files with 338 additions and 249 deletions.
2 changes: 1 addition & 1 deletion all/src/main/templates/release-notes.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ <h2 id="incompatible">Incompatible or Behavior-altering Changes in @version@</h2

<li>The <tt>TreeGrid</tt> component replaces <tt>TreeTable</tt> component</li>
<li>The <tt>Tree</tt> component replaces old <tt>Tree</tt> component</li>
<li>The <tt>HierarchicalDataProvider</tt> interface replaces <tt>Container.Hierarchical</tt> and <<tt>InMemoryHierarchicalDataProvider</tt> replaces <tt>HierarchicalContainer</tt></li>
<li>The <tt>HierarchicalDataProvider</tt> interface replaces <tt>Container.Hierarchical</tt> and <<tt>TreeDataProvider</tt> replaces <tt>HierarchicalContainer</tt></li>
<li>The <tt>DragSourceExtension</tt> and <tt>DropTargetExtension</tt> extensions replace the old DnD features</li>
<li>OSGi bundle manifests of Vaadin Framework JARs no longer export <tt>/VAADIN</tt>, and there are new mechanisms for publishing static resources for OSGi</li>

Expand Down
28 changes: 14 additions & 14 deletions documentation/components/components-tree.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,32 @@ image::img/tree-basic.png[width=70%, scaledwidth=100%]
[[components.tree.data]]
== Binding to Data

[classname]#Tree# is used by binding it to a hierarchical data provider. The data provider can be based on in-memory or back end data. For in-memory data, the [classname]#InMemoryHierarchicalDataProvider# can be used, and for loading data from a back end, you need to implement three methods from the [interfacename]#HierarchicalDataProvider# interface. Usage of both data providers is described in
[classname]#Tree# is used by binding it to a hierarchical data provider. The data provider can be based on in-memory or back end data. For in-memory data, the [classname]#TreeDataProvider# can be used, and for loading data from a back end, you need to implement three methods from the [interfacename]#HierarchicalDataProvider# interface. Usage of both data providers is described in
<<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">>.


The [classname]#HierarchyData# class can be used to build the hierarchical data structure,
and it can then be passed on to [classname]#InMemoryHierarchicalDataProvider#. It is simply a hierarchical
The [classname]#TreeData# class can be used to build the hierarchical data structure,
and it can then be passed on to [classname]#TreeDataProvider#. It is simply a hierarchical
collection, that the data provider uses to populate the [classname]#Tree#.

The [methodname]#setItems# method in [classname]#Tree# can be used to set the root level items. Internally
an [classname]#InMemoryHierarchicalDataProvider# with [classname]#HierarchyData# is used.
an [classname]#TreeDataProvider# with [classname]#TreeData# is used.

[source, java]
----
// An initial planet tree
Tree<String> tree = new Tree<>();
HierarchyData<String> hierarchyData = new HierarchyData<>();
TreeData<String> treeData = new TreeData<>();
// Couple of childless root items
hierarchyData.addItem(null,"Mercury");
hierarchyData.addItem(null,"Venus");
treeData.addItem(null,"Mercury");
treeData.addItem(null,"Venus");
// Items with hierarchy
hierarchyData.addItem(null,"Earth");
hierarchyData.addItem("Earth","The Moon");
treeData.addItem(null,"Earth");
treeData.addItem("Earth","The Moon");
inMemoryDataProvider = new InMemoryHierarchicalDataProvider<>(hierarchyData);
inMemoryDataProvider = new TreeDataProvider<>(treeData);
tree.setDataProvider(inMemoryDataProvider);
tree.expand("Earth"); // Expand programmatically
----
Expand All @@ -67,9 +67,9 @@ the in-memory data in the tree, you may do it as follows:
[source, java]
----
// Add Mars with satellites
hierarchyData.addItem(null, "Mars");
hierarchyData.addItem("Mars", "Phobos");
hierarchyData.addItem("Mars", "Deimos");
treeData.addItem(null, "Mars");
treeData.addItem("Mars", "Phobos");
treeData.addItem("Mars", "Deimos");
inMemoryDataProvider.refreshAll();
----
Expand Down Expand Up @@ -208,4 +208,4 @@ You could thereby define the item styling as follows:
font-style: italic;
}
----
////
////
16 changes: 8 additions & 8 deletions documentation/components/components-treegrid.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ image::img/tree-grid-basic.png[width=70%, scaledwidth=100%]
[[components.treegrid.data]]
== Binding to Data

[classname]#TreeGrid# is used by binding it to a hierarchical data provider. The data provider can be based on in-memory or back end data. For in-memory data, the [classname]#InMemoryHierarchicalDataProvider# can be used, and for loading data from a back end, you need to implement three methods from the [interfacename]#HierarchicalDataProvider# interface. Usage of both data providers is described in
[classname]#TreeGrid# is used by binding it to a hierarchical data provider. The data provider can be based on in-memory or back end data. For in-memory data, the [classname]#TreeDataProvider# can be used, and for loading data from a back end, you need to implement three methods from the [interfacename]#HierarchicalDataProvider# interface. Usage of both data providers is described in
<<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">>.

Populating a [classname]#TreeGrid# with in-memory data can be done as follows
Expand All @@ -39,7 +39,7 @@ Populating a [classname]#TreeGrid# with in-memory data can be done as follows
----
Project rootProject = getRootRroject();
HierarchyData<Project> data = new HierarchyData<>();
TreeData<Project> data = new TreeData<>();
// add a root level item with null parent
data.addItem(null, rootProject);
Expand All @@ -48,26 +48,26 @@ rootProject.flattened().forEach(
project -> data.addItems(project, project.getSubProjects()));
TreeGrid<Project> treeGrid = new TreeGrid<>();
treeGrid.setDataProvider(new InMemoryHierarchicalDataProvider<>(data));
treeGrid.setDataProvider(new TreeDataProvider<>(data));
// the first column gets the hierarchy indicator by default
treeGrid.addColumn(Project::getName).setCaption("Project Name");
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done");
treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified");
----

The [classname]#HierarchyData# class can be used to build the hierarchical data structure,
and it can then be passed on to [classname]#InMemoryHierarchicalDataProvider#. It is simply a hierarchical
The [classname]#TreeData# class can be used to build the hierarchical data structure,
and it can then be passed on to [classname]#TreeDataProvider#. It is simply a hierarchical
collection, that the data provider uses to populate the [classname]#TreeGrid#.

The [methodname]#setItems# method in [classname]#TreeGrid# can be used to set the root level items. Internally
an [classname]#InMemoryHierarchicalDataProvider# with [classname]#HierarchyData# is used. If at any time you want to modify the in-memory data in the grid, you may do it as follows
an [classname]#TreeDataProvider# with [classname]#TreeData# is used. If at any time you want to modify the in-memory data in the grid, you may do it as follows

[source, java]
----
InMemoryHierarchicalDataProvider<Project> dataProvider = (InMemoryHierarchicalDataProvider<Project>) treeGrid.getDataProvider();
TreeDataProvider<Project> dataProvider = (TreeDataProvider<Project>) treeGrid.getDataProvider();
HierarchyData<Project> data = dataProvider.getData();
TreeData<Project> data = dataProvider.getData();
// add new items
data.addItem(null, newProject);
data.addItems(newProject, newProject.getChildren());
Expand Down
22 changes: 11 additions & 11 deletions documentation/datamodel/datamodel-hierarchical.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,38 @@ IMPORTANT: The [interfacename]#HierarchicalDataProvider# is currently being deve

The [classname]#Tree# and the [classname]#TreeGrid# components allow you to show data with hierarchical relationships between items.
That data can be populated by on-demand from a back end by implementing the [interfacename]#HierarchicalDataProvider# interface. If you have the data available in-memory on the server,
you use the collection style API of [classname]#HierarchyData# and then pass it to a [classname]#InMemoryHierarchicalDataProvider#. This chapter introduces the hierarchical data providers and how they work.
you use the collection style API of [classname]#TreeData# and then pass it to a [classname]#TreeDataProvider#. This chapter introduces the hierarchical data providers and how they work.
For using them with the components you should see <<dummy/../../../framework/components/components-tree.asciidoc#components.tree,"Tree">>
and <<dummy/../../../framework/components/components-treegrid.asciidoc#components.treegrid,"TreeGrid">> documentation.

== In-memory Hierarchical Data

When the hierarchical data is available in the server side memory, you can use it to populate the [classname]#HierarchyData# that is the source of data for an [classname]#InMemoryHierarchicalDataProvider#. It contains collection style API to construct the hierarchical structure of your data, and also verifies that the hierarchy structure is valid.
When the hierarchical data is available in the server side memory, you can use it to populate the [classname]#TreeData# that is the source of data for an [classname]#TreeDataProvider#. It contains collection style API to construct the hierarchical structure of your data, and also verifies that the hierarchy structure is valid.

The following example populates a [classname]#HierarchyData# with two levels of data:
The following example populates a [classname]#TreeData# with two levels of data:

[source, java]
----
Collection<Project> projects = service.getProjects();
HierarchyData<Project> data = new HierarchyData<>();
TreeData<Project> data = new TreeData<>();
// add root level items
data.addItems(null, projects);
// add children for the root level items
projects.forEach(project -> data.addItems(project, project.getChildren()));
// construct the data provider for the hierarchical data we've built
InMemoryHierarchicalDataProvider<Project> dataProvider = new InMemoryHierarchicalDataProvider<>(data);
TreeDataProvider<Project> dataProvider = new TreeDataProvider<>(data);
----

=== Updating data

When adding or removing items from the [classname]#HierarchyData#, you need to always notify the data provider that it should refresh its data. This can be done with the [methodname]#refreshAll# method in the data provider.
When adding or removing items from the [classname]#TreeData#, you need to always notify the data provider that it should refresh its data. This can be done with the [methodname]#refreshAll# method in the data provider.

[source, java]
----
HierarchyData<Project> data = dataProvider.getHierarchyData();
TreeData<Project> data = dataProvider.getData();
data.addItem(null, newProject);
data.addItems(newProject, newProject.getChildren());
Expand All @@ -55,14 +55,14 @@ dataProvider.refreshAll();

=== Sorting and Filtering

For [classname]#InMemoryHierarchicalDataProvider#, both the sorting and filtering API is the same as in [classname]#ListDataProvider#. Sorting and filtering are applied separately for each hierarchy level, meaning e.g. that for a node that has not passed the filter there are no children shown.
For [classname]#TreeDataProvider#, both the sorting and filtering API is the same as in [classname]#ListDataProvider#. Sorting and filtering are applied separately for each hierarchy level, meaning e.g. that for a node that has not passed the filter there are no children shown.

[source, java]
----
// setting sorting or filtering automatically refreshes the data
dataProvider.setSortComparator((projectA, projectB) ->
projectA.getHours().compareTo(projectB.getHours()));
dataProvider.setFilter(project -> project.getHours() > 100);
----

Expand All @@ -72,7 +72,7 @@ The lazy loading hierarchical data, same concepts apply as with the non-hierarch

To load hierarchical data on-demand from your back end, you should extend the [classname]#AbstractHierarchicalDataProvider# class. Then you just have to implement the following three methods:

* `boolean hasChildren(T item)`
* `boolean hasChildren(T item)`
** This tells the data provider whether the given item has children and should be expandable. Note that this method is called frequently and should not do any costly operations.

* `int getChildCount(HierarchicalQuery<T, F> query)`
Expand All @@ -87,7 +87,7 @@ To load hierarchical data on-demand from your back end, you should extend the [c
** The parent node is available in the [classname]#HierarchicalQuery# via the [methodname]#getParent# method, which returns `null` for the root level.
** This method is called whenever the data should be displayed in the UI

Note that the [classname]#HierarchicalQuery# query object contains the relevant information regarding the sorting and filtering.
Note that the [classname]#HierarchicalQuery# query object contains the relevant information regarding the sorting and filtering.

The following code snippet shows a simple example on how to building a lazy hierarchical data provider based on file system structure:

Expand Down
153 changes: 153 additions & 0 deletions server/src/main/java/com/vaadin/data/HasHierarchicalDataProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.vaadin.data;

import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.HierarchicalDataProvider;
import com.vaadin.data.provider.TreeDataProvider;

public interface HasHierarchicalDataProvider<T> extends HasDataProvider<T> {

/**
* TODO
*
* @return TODO
* @throws IllegalStateException
* if TODO
*/
@SuppressWarnings("unchecked")
public default TreeData<T> getTreeData() {
if (getDataProvider() instanceof TreeDataProvider) {
return ((TreeDataProvider<T>) getDataProvider()).getTreeData();
} else {
throw new IllegalStateException("");
}
}

/**
* TODO
*
* @param rootItems
* TODO
* @param childItemProvider
* TODO
*/
public default void setItems(Collection<T> rootItems,
ValueProvider<T, Collection<T>> childItemProvider) {
Objects.requireNonNull(rootItems, "Given root items may not be null");
Objects.requireNonNull(childItemProvider,
"Given child item provider may not be null");
setDataProvider(new TreeDataProvider<>(
new TreeData<T>().addItems(rootItems, childItemProvider)));
}

/**
* Sets the data items of this component provided as a collection.
* <p>
* The provided items are wrapped into a {@link TreeDataProvider} backed by
* a flat {@link TreeData} structure. The data provider instance is used as
* a parameter for the {@link #setDataProvider(DataProvider)} method. It
* means that the items collection can be accessed later on via
* {@link #getTreeData()}:
*
* <pre>
* <code>
* HasHierarchicalDataProvider<String> treeGrid = new TreeGrid<>();
* treeGrid.setItems(Arrays.asList("a","b"));
* ...
*
* TreeData<String> data = treeGrid.getTreeData();
* </code>
* </pre>
* <p>
* The returned HierarchyData instance may be used as-is to add, remove or
* modify items in the hierarchy. These modifications to the object are not
* automatically reflected back to the TreeGrid. Items modified should be
* refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
* when adding or removing items
* {@link HierarchicalDataProvider#refreshAll()} should be called.
*
* @param items
* the data items to display, not {@code null}
*/
@Override
public default void setItems(Collection<T> items) {
Objects.requireNonNull(items, "Given collection may not be null");
setDataProvider(new TreeDataProvider<>(
new TreeData<T>().addItems(null, items)));
}

/**
* Sets the data items of this component provided as a stream.
* <p>
* The provided items are wrapped into a {@link TreeDataProvider} backed by
* a flat {@link TreeData} structure. The data provider instance is used as
* a parameter for the {@link #setDataProvider(DataProvider)} method. It
* means that the items collection can be accessed later on via
* {@link #getTreeData()}:
*
* <pre>
* <code>
* HasHierarchicalDataProvider<String> treeGrid = new TreeGrid<>();
* treeGrid.setItems(Stream.of("a","b"));
* ...
*
* TreeData<String> data = treeGrid.getTreeData();
* </code>
* </pre>
* <p>
* The returned HierarchyData instance may be used as-is to add, remove or
* modify items in the hierarchy. These modifications to the object are not
* automatically reflected back to the TreeGrid. Items modified should be
* refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
* when adding or removing items
* {@link HierarchicalDataProvider#refreshAll()} should be called.
*
* @param items
* the data items to display, not {@code null}
*/
@Override
public default void setItems(Stream<T> items) {
Objects.requireNonNull(items, "Given stream may not be null");
setItems(items.collect(Collectors.toList()));
}

/**
* Sets the data items of this listing.
* <p>
* The provided items are wrapped into a {@link TreeDataProvider} backed by
* a flat {@link TreeData} structure. The data provider instance is used as
* a parameter for the {@link #setDataProvider(DataProvider)} method. It
* means that the items collection can be accessed later on via
* {@link #getTreeData()}:
*
* <pre>
* <code>
* TreeGrid<String> treeGrid = new TreeGrid<>();
* treeGrid.setItems("a","b");
* ...
*
* TreeData<String> data = treeGrid.getTreeData();
* </code>
* </pre>
* <p>
* The returned HierarchyData instance may be used as-is to add, remove or
* modify items in the hierarchy. These modifications to the object are not
* automatically reflected back to the TreeGrid. Items modified should be
* refreshed with {@link HierarchicalDataProvider#refreshItem(Object)} and
* when adding or removing items
* {@link HierarchicalDataProvider#refreshAll()} should be called.
*
* @param items
* the data items to display, not {@code null}
*/
@Override
public default void setItems(@SuppressWarnings("unchecked") T... items) {
Objects.requireNonNull(items, "Given items may not be null");
setItems(Arrays.asList(items));
}
}
Loading

0 comments on commit a604370

Please sign in to comment.