* Initial Documentation for TreeGrid and Hierarchical Data Fixes #8615, part of #8616tags/8.1.0.alpha2
@@ -46,6 +46,8 @@ include::components-twincolselect.asciidoc[leveloffset=+2] | |||
include::components-grid.asciidoc[leveloffset=+2] | |||
include::components-treegrid.asciidoc[leveloffset=+2] | |||
include::components-menubar.asciidoc[leveloffset=+2] | |||
include::components-upload.asciidoc[leveloffset=+2] |
@@ -1,6 +1,6 @@ | |||
--- | |||
title: Composition with CustomComponent | |||
order: 31 | |||
order: 32 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: Composite Fields with CustomField | |||
order: 32 | |||
order: 33 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: Embedded Resources | |||
order: 33 | |||
order: 34 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: MenuBar | |||
order: 25 | |||
order: 26 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: PopupView | |||
order: 29 | |||
order: 30 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: ProgressBar | |||
order: 27 | |||
order: 28 | |||
layout: page | |||
--- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: Slider | |||
order: 28 | |||
order: 29 | |||
layout: page | |||
--- | |||
@@ -0,0 +1,95 @@ | |||
--- | |||
title: TreeGrid | |||
order: 25 | |||
layout: page | |||
--- | |||
[[components.treegrid]] | |||
= TreeGrid | |||
ifdef::web[] | |||
[.sampler] | |||
image:{live-demo-image}[alt="Live Demo", link="http://demo.vaadin.com/sampler/#ui/grids-and-trees/treegrid"] | |||
endif::web[] | |||
IMPORTANT: The [classname]#TreeGrid# component is currently being developed and only available in the Framework 8.1 prerelease versions, starting from 8.1.0.alpha1. | |||
[[components.treegrid.overview]] | |||
== Overview | |||
[classname]#TreeGrid# is for displaying hierarchical tabular data laid out in rows and columns. | |||
It is otherwise identical to the [classname]#Grid# component, but it adds the possibility to show | |||
hierarchical data, allowing the user to expand and collapse nodes to show or hide data. | |||
See the documentation for <<dummy/../../../framework/components/components-grid.asciidoc#components.grid,"Grid">> for all the shared features between [classname]#Grid# and [classname]#TreeGrid#. | |||
[[figure.components.treegrid.basic]] | |||
.A [classname]#TreeGrid# | |||
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 | |||
<<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">>. | |||
Populating a [classname]#TreeGrid# with in-memory data can be done as follows | |||
[source, java] | |||
---- | |||
Project rootProject = getRootRroject(); | |||
HierarchyData<Project> data = new HierarchyData<>(); | |||
// add a root level item with null parent | |||
data.addItem(null, rootProject); | |||
// Add all children for root item | |||
rootProject.flattened().forEach( | |||
project -> data.addItems(project, project.getSubProjects())); | |||
TreeGrid<Project> treeGrid = new TreeGrid<>(); | |||
treeGrid.setDataProvider(new InMemoryHierarchicalDataProvider<>(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::getdLastModified).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 | |||
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 | |||
[source, java] | |||
---- | |||
InMemoryHierarchicalDataProvider<Project> dataProvider = (InMemoryHierarchicalDataProvider<Project>) treeGrid.getDataProvider(); | |||
HierarchyData<Project> data = dataProvider.getData(); | |||
// add new items | |||
data.addItem(null, newProject); | |||
data.addItems(newProject, newProject.getChildren()); | |||
// after adding / removing data, data provider needs to be refreshed | |||
dataProvider.refreshAll(); | |||
---- | |||
Note that for adding or removing nodes, you always need to call the [methodname]#refreshAll# method in the data provider you are using. The [methodname]#refreshItem# method can only be used when just the data for that item is updated, but not for updates that add or remove items. | |||
== Changing the Hierarchy Column | |||
By default, the [classname]#TreeGrid# shows the hierarchy indicator by default in the first column of the grid. | |||
You can change it with with the [methodname]#setHierarchyColumn#, method, that takes as a parameter the column's ID specified with the [methodname]#setId# method in [classname]#Column#. | |||
[source, java] | |||
---- | |||
// the first column gets the hierarchy indicator by default | |||
treeGrid.addColumn(Project::getLastModified).setCaption("Last Modified"); | |||
treeGrid.addColumn(Project::getName).setCaption("Project Name").setId("name"); | |||
treeGrid.addColumn(Project::getHoursDone).setCaption("Hours Done"); | |||
treeGrid.setHierarchyColumn("name"); | |||
---- | |||
@@ -1,6 +1,6 @@ | |||
--- | |||
title: Upload | |||
order: 26 | |||
order: 27 | |||
layout: page | |||
--- | |||
@@ -17,4 +17,6 @@ include::datamodel-forms.asciidoc[leveloffset=+2] | |||
include::datamodel-providers.asciidoc[leveloffset=+2] | |||
include::datamodel-selection.asciidoc[leveloffset=+2] | |||
include::datamodel-hierarchical.asciidoc[leveloffset=+2] | |||
(((range="endofrange", startref="term.datamodel"))) |
@@ -0,0 +1,130 @@ | |||
--- | |||
title: Hierarchical Data | |||
order: 6 | |||
layout: page | |||
--- | |||
[[datamodel.hierarchical]] | |||
= Hierarchical Data | |||
IMPORTANT: The [interfacename]#HierarchicalDataProvider# is currently being developed and only available in the Framework 8.1 prerelease versions, starting from 8.1.0.alpha1. | |||
The [classname]#TreeGrid# component allows 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. For using them with the [classname]#TreeGrid# component you should see <<dummy/../../../framework/components/components-grid.asciidoc#components.treegrid,"its 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. | |||
The following example populates a [classname]#HierarchyData# with two levels of data: | |||
[source, java] | |||
---- | |||
Collection<Project> projects = service.getProjects(); | |||
HierarchyData<Project> data = new HierarchyData<>(); | |||
// 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); | |||
---- | |||
=== 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. | |||
[source, java] | |||
---- | |||
HierarchyData<Project> data = dataProvider.getHierarchyData(); | |||
data.addItem(null, newProject); | |||
data.addItems(newProject, newProject.getChildren()); | |||
// removes the item and all of its children | |||
data.removeItem(oldProject); | |||
// refresh data provider and the components it is bound to | |||
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. | |||
[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); | |||
---- | |||
== Lazy Loading Hierarchical Data from a Back End | |||
The lazy loading hierarchical data, same concepts apply as with the non-hierarchical data, so you should take a look at <<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.dataproviders.lazy,"Lazy Loading Data to a Listing">> if you have not already. | |||
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)` | |||
** 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)` | |||
** This method returns the number of children for a certain tree level, but only for that level, excluding all subtrees | |||
** The parent node is available in the [classname]#HierarchicalQuery# via the [methodname]#getParent# method, which returns `null` for the root level. | |||
** This method is only called when a node has been expanded | |||
* `Stream<T> fetchChildren(HierarchicalQuery<T, F> query)` | |||
** This method returns a subset of the children for a certain tree level | |||
** The subset starts from the index retuned by the [methodname]#getOffset# method, thus for fetching the first item for a subtree it is always 0 | |||
** The amount of nodes to fetch is returned by the [methodname]#getLimit# method, thus the amount of nodes returned should always (!) be the same as the _limit_. | |||
** 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. | |||
The following code snippet shows a simple example on how to building a lazy hierarchical data provider based on file system structure: | |||
[source, java] | |||
---- | |||
class FileSystemDataProvider | |||
extends AbstractHierarchicalDataProvider<File, FilenameFilter> { | |||
private final File root; | |||
public FileSystemDataProvider(File root) { | |||
this.root = root; | |||
} | |||
@Override | |||
public int getChildCount( | |||
HierarchicalQuery<File, FilenameFilter> query) { | |||
return (int) fetchChildren(query).count(); | |||
} | |||
@Override | |||
public Stream<File> fetchChildren( | |||
HierarchicalQuery<File, FilenameFilter> query) { | |||
final File parent = query.getParentOptional().orElse(root); | |||
return query.getFilter() | |||
.map(filter -> Stream.of(parent.listFiles(filter))) | |||
.orElse(Stream.of(parent.listFiles())) | |||
.skip(query.getOffset()).limit(query.getLimit()); | |||
} | |||
@Override | |||
public boolean hasChildren(File item) { | |||
return item.list() != null && item.list().length > 0; | |||
} | |||
@Override | |||
public boolean isInMemory() { | |||
return false; | |||
} | |||
} | |||
---- | |||
If there are any updates on the hierarchical data, such as adding or removing rows, you should call the [methodname]#refreshAll# method that is inherited by extending [classname]#AbstractHierarchicalDataProvider#. This will reset the data. If only the data for a specific item has been updated, you can call the [methodname]#refreshItem# method to only update that item. |
@@ -22,7 +22,7 @@ You have full control over how you configure and lay out the individual input fi | |||
There are UI components in the framework that lists multiple similar objects and lets the user view, select and in some cases even edit those objects. | |||
A listing component can get its data from an in-memory collection or lazily fetch it from some backend. | |||
In either case, there are options available for defining how the data is sorted and filtered before being displayed to the user. | |||
Read more about how to provide lists of data to these components in <<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.providers,"Showing Many Items in a Listing">>. | |||
Read more about how to provide lists of data to these components in <<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.providers,"Showing Many Items in a Listing">>. For using hierarchical data, see <<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">>. | |||
Using a listing component as an input field to select one or many of the listed items is described in <<dummy/../../../framework/datamodel/datamodel-selection.asciidoc#datamodel.selection,"Selecting items">>. | |||
Vaadin Data Model topic references:: | |||
@@ -30,3 +30,4 @@ Vaadin Data Model topic references:: | |||
* <<dummy/../../../framework/datamodel/datamodel-forms.asciidoc#datamodel.forms,"Binding Data to Forms">> | |||
* <<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.providers,"Showing Many Items in a Listing">> | |||
* <<dummy/../../../framework/datamodel/datamodel-selection.asciidoc#datamodel.selection,"Selecting items">> | |||
* <<dummy/../../../framework/datamodel/datamodel-hierarchical.asciidoc#datamodel.hierarchical,"Hierarchical Data">> |
@@ -185,6 +185,7 @@ Button modifyPersonButton = new Button("Modify person", | |||
}); | |||
---- | |||
[[datamodel.dataproviders.lazy]] | |||
== Lazy Loading Data to a Listing | |||
All the previous examples have shown cases with a limited amount of data that can be loaded as item instances in memory. |