diff options
-rw-r--r-- | documentation/components/chapter-components.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-customcomponent.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-customfield.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-embedded.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-menubar.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-popupview.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-progressbar.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-slider.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/components-treegrid.asciidoc | 95 | ||||
-rw-r--r-- | documentation/components/components-upload.asciidoc | 2 | ||||
-rw-r--r-- | documentation/components/img/tree-grid-basic.png | bin | 0 -> 113924 bytes | |||
-rw-r--r-- | documentation/datamodel/chapter-datamodel.asciidoc | 2 | ||||
-rw-r--r-- | documentation/datamodel/datamodel-hierarchical.asciidoc | 130 | ||||
-rw-r--r-- | documentation/datamodel/datamodel-overview.asciidoc | 3 | ||||
-rw-r--r-- | documentation/datamodel/datamodel-providers.asciidoc | 1 |
15 files changed, 240 insertions, 9 deletions
diff --git a/documentation/components/chapter-components.asciidoc b/documentation/components/chapter-components.asciidoc index f92a132e98..e1093ba03e 100644 --- a/documentation/components/chapter-components.asciidoc +++ b/documentation/components/chapter-components.asciidoc @@ -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] diff --git a/documentation/components/components-customcomponent.asciidoc b/documentation/components/components-customcomponent.asciidoc index dd5a86b823..5529bf21be 100644 --- a/documentation/components/components-customcomponent.asciidoc +++ b/documentation/components/components-customcomponent.asciidoc @@ -1,6 +1,6 @@ --- title: Composition with CustomComponent -order: 31 +order: 32 layout: page --- diff --git a/documentation/components/components-customfield.asciidoc b/documentation/components/components-customfield.asciidoc index 38ac780e8f..a369f390a2 100644 --- a/documentation/components/components-customfield.asciidoc +++ b/documentation/components/components-customfield.asciidoc @@ -1,6 +1,6 @@ --- title: Composite Fields with CustomField -order: 32 +order: 33 layout: page --- diff --git a/documentation/components/components-embedded.asciidoc b/documentation/components/components-embedded.asciidoc index bf4f9eb47b..84a7971500 100644 --- a/documentation/components/components-embedded.asciidoc +++ b/documentation/components/components-embedded.asciidoc @@ -1,6 +1,6 @@ --- title: Embedded Resources -order: 33 +order: 34 layout: page --- diff --git a/documentation/components/components-menubar.asciidoc b/documentation/components/components-menubar.asciidoc index be3548012f..0c54196b87 100644 --- a/documentation/components/components-menubar.asciidoc +++ b/documentation/components/components-menubar.asciidoc @@ -1,6 +1,6 @@ --- title: MenuBar -order: 25 +order: 26 layout: page --- diff --git a/documentation/components/components-popupview.asciidoc b/documentation/components/components-popupview.asciidoc index a1296ad14d..bc58fe6b19 100644 --- a/documentation/components/components-popupview.asciidoc +++ b/documentation/components/components-popupview.asciidoc @@ -1,6 +1,6 @@ --- title: PopupView -order: 29 +order: 30 layout: page --- diff --git a/documentation/components/components-progressbar.asciidoc b/documentation/components/components-progressbar.asciidoc index db7e8990de..b6337ff3ad 100644 --- a/documentation/components/components-progressbar.asciidoc +++ b/documentation/components/components-progressbar.asciidoc @@ -1,6 +1,6 @@ --- title: ProgressBar -order: 27 +order: 28 layout: page --- diff --git a/documentation/components/components-slider.asciidoc b/documentation/components/components-slider.asciidoc index 9b447f27fe..7ae972ae77 100644 --- a/documentation/components/components-slider.asciidoc +++ b/documentation/components/components-slider.asciidoc @@ -1,6 +1,6 @@ --- title: Slider -order: 28 +order: 29 layout: page --- diff --git a/documentation/components/components-treegrid.asciidoc b/documentation/components/components-treegrid.asciidoc new file mode 100644 index 0000000000..b0e28fee5f --- /dev/null +++ b/documentation/components/components-treegrid.asciidoc @@ -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"); +---- + diff --git a/documentation/components/components-upload.asciidoc b/documentation/components/components-upload.asciidoc index 8288bd2bc1..cde631d148 100644 --- a/documentation/components/components-upload.asciidoc +++ b/documentation/components/components-upload.asciidoc @@ -1,6 +1,6 @@ --- title: Upload -order: 26 +order: 27 layout: page --- diff --git a/documentation/components/img/tree-grid-basic.png b/documentation/components/img/tree-grid-basic.png Binary files differnew file mode 100644 index 0000000000..fa826a2525 --- /dev/null +++ b/documentation/components/img/tree-grid-basic.png diff --git a/documentation/datamodel/chapter-datamodel.asciidoc b/documentation/datamodel/chapter-datamodel.asciidoc index 6e9cf214d5..d3b6c5bac2 100644 --- a/documentation/datamodel/chapter-datamodel.asciidoc +++ b/documentation/datamodel/chapter-datamodel.asciidoc @@ -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"))) diff --git a/documentation/datamodel/datamodel-hierarchical.asciidoc b/documentation/datamodel/datamodel-hierarchical.asciidoc new file mode 100644 index 0000000000..a67a698291 --- /dev/null +++ b/documentation/datamodel/datamodel-hierarchical.asciidoc @@ -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. diff --git a/documentation/datamodel/datamodel-overview.asciidoc b/documentation/datamodel/datamodel-overview.asciidoc index ea11d63f2f..49b936c77e 100644 --- a/documentation/datamodel/datamodel-overview.asciidoc +++ b/documentation/datamodel/datamodel-overview.asciidoc @@ -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">> diff --git a/documentation/datamodel/datamodel-providers.asciidoc b/documentation/datamodel/datamodel-providers.asciidoc index 80b9fb9cb4..c082b7a02b 100644 --- a/documentation/datamodel/datamodel-providers.asciidoc +++ b/documentation/datamodel/datamodel-providers.asciidoc @@ -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. |