diff --git a/articles/flow/advanced/downloads.adoc b/articles/flow/advanced/downloads.adoc index 4cfddf483b..1d587365a7 100644 --- a/articles/flow/advanced/downloads.adoc +++ b/articles/flow/advanced/downloads.adoc @@ -66,6 +66,21 @@ This method is particularly useful for: * Generating dynamic content * Streaming large files +==== Overriding the File Name [since:com.vaadin:vaadin@V25.2] + +By default, the file name of the download comes from the [classname]`DownloadResponse` returned by the callback, and the download URL is a random-looking string. The [methodname]`fromInputStream` overload that takes a `fileNameOverride` parameter lets you define the file name already when the handler is created: + +[source,java] +---- +Anchor downloadLink = new Anchor(DownloadHandler.fromInputStream(event -> { + byte[] report = reportService.generateReport(); + return new DownloadResponse(new ByteArrayInputStream(report), + event.getFileName(), "application/pdf", report.length); +}, "monthly-report.pdf"), "Download Monthly Report"); +---- + +The given file name is appended to the download URL as a postfix (see <<#url-postfix,URL Postfix>>), and [methodname]`getFileName()` of the [classname]`DownloadEvent` returns it inside the callback, as shown above. This is useful when serving generated content under a stable, user-friendly file name, without writing a custom download handler. An overload that additionally accepts a [classname]`TransferProgressListener` is also available. + === Render Or Download Static Resource The [methodname]`forClassResource` and [methodname]`forServletResource` methods allows you to serve resources from the classpath or servlet context. @@ -394,6 +409,7 @@ The available modes are: [classname]`DownloadHandler` allows to override this mode by overriding the [methodname]`getDisabledUpdateMode()` method. +[#url-postfix] === URL Postfix The [methodname]`getUrlPostfix()` method allows you to specify an optional URL postfix that appends application-controlled string, e.g. the logical name of the target file, to the end of the otherwise random-looking download URL. diff --git a/articles/flow/binding-data/data-provider.adoc b/articles/flow/binding-data/data-provider.adoc index 0d57d65e9b..9da48b8220 100644 --- a/articles/flow/binding-data/data-provider.adoc +++ b/articles/flow/binding-data/data-provider.adoc @@ -563,6 +563,72 @@ gridDataView.removeFilters(); ---- +== Hierarchical Data [since:com.vaadin:vaadin@V25.2] + +Components such as Tree Grid display hierarchical data, where each item can have child items. The [interfacename]`HierarchicalData` interface in the `com.vaadin.flow.data.provider.hierarchy` package represents such data. It defines three methods: + +* [methodname]`getChildren(T item)` returns the immediate child items of the given item, or the root items when the given item is `null`. +* [methodname]`getParent(T item)` returns the parent of the given item, or `null` for root items. +* [methodname]`contains(T item)` tells whether the given item is part of the hierarchy. + +The built-in [classname]`TreeData` class implements [interfacename]`HierarchicalData` and is a good choice when you need a ready-made, modifiable in-memory tree structure. See <<{articles}/components/tree-grid/data-binding#,Binding Data to Tree Grid>> for details. + +When your domain model is already hierarchical, you can implement [interfacename]`HierarchicalData` directly instead of copying the items into a [classname]`TreeData` instance. For example, consider a `Department` type whose items reference their parent department: + +[source,java] +---- +public class DepartmentData implements HierarchicalData { + + private final List departments; + + public DepartmentData(List departments) { + this.departments = departments; + } + + @Override + public List getChildren(Department item) { + // A null item means the root of the hierarchy + return departments.stream() + .filter(department -> Objects + .equals(department.getParent(), item)) + .toList(); + } + + @Override + public Department getParent(Department item) { + return item.getParent(); + } + + @Override + public boolean contains(Department item) { + // Must return true for null, which represents the root + return item == null || departments.contains(item); + } +} +---- + +The [classname]`InMemoryHierarchicalDataProvider` class turns any [interfacename]`HierarchicalData` implementation into a hierarchical data provider with support for in-memory sorting and filtering, so there is no need to implement a data provider from scratch: + +[source,java] +---- +TreeGrid treeGrid = new TreeGrid<>(); +treeGrid.addHierarchyColumn(Department::getName).setHeader("Department"); + +InMemoryHierarchicalDataProvider dataProvider = + new InMemoryHierarchicalDataProvider<>( + new DepartmentData(departmentService.findAll())); +treeGrid.setDataProvider(dataProvider); + +// Show only active departments +dataProvider.setFilter(Department::isActive); + +// Sort sibling items by name +dataProvider.setSortOrder(Department::getName, SortDirection.ASCENDING); +---- + +The data provider doesn't track changes in the backing data structure: call [methodname]`refreshAll()` after modifying the underlying [interfacename]`HierarchicalData` instance. The built-in [classname]`TreeDataProvider` is an [classname]`InMemoryHierarchicalDataProvider` backed by a [classname]`TreeData` instance. + + == Recycling Data Binding Logic In large applications, you typically have multiple places where you display the same data type in a listing component. You can use various approaches to share the lazy data binding logic.