Skip to content

Commit 4c74bb5

Browse files
committed
WIP Master-Detail
1 parent cee1055 commit 4c74bb5

File tree

4 files changed

+285
-0
lines changed

4 files changed

+285
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Add a Grid
3+
page-title: How to add a data grid to a Vaadin application
4+
description: Learn how to add a data grid to a Vaadin application.
5+
meta-description: Learn how to add a data grid to a Vaadin application.
6+
order: 10
7+
---
8+
9+
= Add a Grid
10+
11+
TODO Write me
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: Add a Master-Detail View
3+
page-title: How to add a master-detail view to a Vaadin application
4+
description: TODO
5+
meta-description: TODO
6+
order: 25
7+
---
8+
9+
10+
= Add a Master-Detail View
11+
:toclevels: 2
12+
13+
This guide teaches you how to build a rudimentary master-detail view in Vaadin.
14+
15+
.Copy-Paste into Your Project
16+
[TIP]
17+
====
18+
If you want to quickly try out a master-detail view, you can copy-paste the following class into your Vaadin project:
19+
20+
[source,java]
21+
----
22+
include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=snippet,indent=0]
23+
----
24+
25+
For more detailed instructions on how to build a master-detail view from scratch, continue reading below.
26+
====
27+
28+
== What is a Master-Detail View?
29+
30+
In a master-detail view, the user typically selects an item from a list (the master), and the details of the selected item are shown in another area (the detail). When no item is selected, the detail area is either hidden or shows a placeholder message.
31+
32+
// TODO add image
33+
34+
35+
== Creating a Master-Detail View
36+
37+
Building a master-detail view in Vaadin involves three things: creating the master component, the detail component, and handling selection state.
38+
39+
Since you're building a web application, you should use a URL parameter to represent the selection state. This way, users can bookmark or share links to specific details.
40+
41+
42+
=== Scaffolding the View
43+
44+
When creating a master-detail view, it helps to start with the general structure. The following example uses:
45+
46+
* a `SplitLayout` to arrange the master and detail components side by side,
47+
* an inner class for the master component (e.g., a grid),
48+
* an inner class for the detail component (e.g., a form),
49+
* a factory method for creating a placeholder component when no detail is selected, and
50+
* an optional <<pass-data/route-parameters#,route parameter>> to represent the selected item's ID.
51+
52+
[source,java]
53+
----
54+
@Route("customers")
55+
public class CustomerView extends SplitLayout implements HasUrlParameter<Long> {
56+
57+
private final CustomerService customerService;
58+
private final CustomerListComponent master;
59+
60+
CustomerView(CustomerService customerService) {
61+
this.customerService = customerService;
62+
this.master = new CustomerListComponent();
63+
addToPrimary(master);
64+
setSizeFull();
65+
}
66+
67+
private class CustomerListComponent extends VerticalLayout {
68+
// TODO Implement the master component (e.g., a grid of customers)
69+
70+
void select(Customer customer) {
71+
// Select the given customer in the grid
72+
}
73+
void deselectAll() {
74+
// Clear any selection in the grid
75+
}
76+
}
77+
78+
private class CustomerDetailComponent extends VerticalLayout {
79+
// TODO Implement the detail component (e.g., a form showing customer details)
80+
81+
CustomerDetailComponent(Customer customer) {
82+
// Populate the form with the given customer's details
83+
}
84+
}
85+
86+
private Component createNoDetailSelected() {
87+
// TODO Create and return a placeholder component when no detail is selected
88+
}
89+
90+
@Override
91+
public void setParameter(BeforeEvent event, @OptionalParameter Long customerId) {
92+
// TODO Handle the URL parameter to show the appropriate detail
93+
}
94+
}
95+
----
96+
97+
98+
=== Creating the Components
99+
100+
The next step is to implement the master and detail components. The master component is typically a grid, while the detail component is often a form. See the <<../forms-data/add-grid#,Add a Grid>> and <<../forms-data/add-form#,Add a Form>> guides for more information on creating these components.
101+
102+
103+
=== Handling Selection State
104+
105+
The final step is to implement selection handling. You should cover the following scenarios:
106+
107+
* No detail is selected (i.e., no URL parameter is present)
108+
* A detail is selected (i.e., a URL parameter is present)
109+
* A detail is selected, but it doesn't exist (i.e., the URL parameter is invalid)
110+
111+
To keep things simple, you should handle all these scenarios in the `setParameter` method. Here's an example implementation:
112+
113+
[source,java]
114+
----
115+
@Override
116+
public void setParameter(BeforeEvent event, @OptionalParameter Long id) {
117+
if (getSecondaryComponent() != null) {
118+
getSecondaryComponent().removeFromParent(); // <1>
119+
}
120+
Optional.ofNullable(id)
121+
.flatMap(customerService::findById) // <2>
122+
.ifPresentOrElse(
123+
customer -> { // <3>
124+
addToSecondary(new CustomerDetailComponent(customer));
125+
master.select(customer);
126+
},
127+
() -> { // <4>
128+
addToSecondary(createNoDetailSelected());
129+
master.deselectAll();
130+
}
131+
);
132+
}
133+
----
134+
<1> Remove any existing detail or placeholder before adding a new one.
135+
<2> Use the service to find the customer by ID.
136+
<3> If the customer exists, show the detail component and select the customer in the master.
137+
<4> If no customer is found, or the ID is not present, show the placeholder and clear the selection in the master.
138+
139+
However, this is only the first half of selection handling. You also need to update the URL when the user selects an item in the master component. Start by creating <<navigate#your-own-api,navigation utility methods>> for setting and clearing the selection:
140+
141+
[source,java]
142+
----
143+
public static void showCustomerDetails(long customerId) {
144+
UI.getCurrent().navigate(CustomerView.class, customerId);
145+
}
146+
147+
public static void showCustomerList() {
148+
UI.getCurrent().navigate(CustomerView.class);
149+
}
150+
----
151+
152+
Then, call these methods from the master component when the user selects or deselects an item. For example, if you're using a `Grid`, you can add a selection listener like this:
153+
154+
[source,java]
155+
----
156+
customerGrid.addSelectionListener(event -> event.getFirstSelectedItem()
157+
.ifPresentOrElse(
158+
customer -> showCustomerDetails(customer.getId()),
159+
() -> showCustomerList()
160+
)
161+
);
162+
----
163+
164+
You don't have to worry about an infinite loop caused by the selection listener navigating, and `setParameter()` updating the selection. Vaadin's router is smart enough to avoid re-navigating to the same URL.
165+
166+
167+
== The Master-Detail Layout
168+
169+
TODO Explain the component and link to its documentation.

articles/building-apps/views/add-router-layout.adoc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ Declaring a layout explicitly also disables the automatic layout for the view.
136136
[annotationname]`@RouteAlias` also has the `layout` attribute. You can render the same view in different layouts depending on the route used to access it.
137137

138138

139+
== Nested Layouts
140+
141+
Layouts can be nested within other layouts, for example when creating <<add-master-detail#,master-detail user interfaces>>. The following screenshot demonstrates a view inside a router layout, which itself is inside another router layout:
142+
143+
image::images/nested-layout.png[Example of a nested router layout]
144+
145+
To *render a router layout inside another router layout*, use the [annotationname]`@ParentLayout` annotation:
146+
147+
[source,java]
148+
----
149+
// tag::snippet[]
150+
@ParentLayout(MainLayout.class)
151+
// end::snippet[]
152+
public class NestedLayout extends Div implements RouterLayout {
153+
...
154+
}
155+
----
156+
157+
Parent layouts are always explicit. That is, automatic layouts never apply to other layouts.
158+
139159

140160
== Route Prefixes
141161

@@ -180,5 +200,30 @@ public class MyLayout extends Div implements RouterLayout {
180200
}
181201
----
182202

203+
Nested router layouts can also opt out from route prefixes.
204+
205+
In the following example, the path of `MyView` is in fact `nested/path`, as opposed to `some/nested/path`:
206+
207+
[source,java]
208+
----
209+
@Route(value = "path", layout = MyNestedLayout.class)
210+
public class MyView extends Main {
211+
...
212+
}
213+
214+
// tag::snippet[]
215+
@RoutePrefix(value = "nested", absolute = true)
216+
// end::snippet[]
217+
@ParentLayout(MyLayout.class)
218+
public class MyNestedLayout extends Div implements RouterLayout {
219+
...
220+
}
221+
222+
@RoutePrefix("some")
223+
public class MyLayout extends Div implements RouterLayout {
224+
...
225+
}
226+
----
227+
183228
[NOTE]
184229
[annotationname]`@RouteAlias` also has the `absolute` attribute.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.vaadin.demo.buildingapps.masterdetail;
2+
3+
import com.vaadin.flow.component.Component;
4+
import com.vaadin.flow.component.UI;
5+
import com.vaadin.flow.component.button.Button;
6+
import com.vaadin.flow.component.html.Paragraph;
7+
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
8+
import com.vaadin.flow.component.splitlayout.SplitLayout;
9+
import com.vaadin.flow.router.BeforeEvent;
10+
import com.vaadin.flow.router.HasUrlParameter;
11+
import com.vaadin.flow.router.OptionalParameter;
12+
import com.vaadin.flow.router.Route;
13+
14+
// tag::snippet[]
15+
@Route("building-apps/master-detail")
16+
public class MasterDetailView extends SplitLayout
17+
implements HasUrlParameter<Long> {
18+
19+
MasterDetailView() {
20+
addToPrimary(createMaster());
21+
setSizeFull();
22+
}
23+
24+
@Override
25+
public void setParameter(BeforeEvent event, @OptionalParameter Long id) {
26+
if (getSecondaryComponent() != null) {
27+
getSecondaryComponent().removeFromParent();
28+
}
29+
if (id == null) {
30+
addToSecondary(createNoDetailSelected());
31+
} else {
32+
addToSecondary(createDetail(id));
33+
}
34+
}
35+
36+
private Component createMaster() {
37+
return new VerticalLayout(
38+
new Button("Detail 1", e -> showDetail(1)),
39+
new Button("Detail 2", e -> showDetail(2)),
40+
new Button("Detail 3", e -> showDetail(3)),
41+
new Button("Master only", e -> showMaster()));
42+
}
43+
44+
private Component createDetail(long id) {
45+
return new VerticalLayout(new Paragraph("Detail: " + id));
46+
}
47+
48+
private Component createNoDetailSelected() {
49+
return new VerticalLayout(new Paragraph("No detail selected"));
50+
}
51+
52+
public static void showMaster() {
53+
UI.getCurrent().navigate(MasterDetailView.class);
54+
}
55+
56+
public static void showDetail(long id) {
57+
UI.getCurrent().navigate(MasterDetailView.class, id);
58+
}
59+
}
60+
// end::snippet[]

0 commit comments

Comments
 (0)