Skip to content

Commit 72f2555

Browse files
paodbjavier-godoy
authored andcommitted
feat: make tabs bookmarkable by binding routes to them
Close #5
1 parent 2bfd4c7 commit 72f2555

File tree

5 files changed

+122
-56
lines changed

5 files changed

+122
-56
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.flowingcode.vaadin.addons.demo;
2+
3+
import java.util.LinkedHashMap;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import com.vaadin.flow.component.tabs.Tab;
7+
import com.vaadin.flow.component.tabs.Tabs;
8+
import com.vaadin.flow.router.BeforeEnterEvent;
9+
import com.vaadin.flow.router.BeforeEnterObserver;
10+
import com.vaadin.flow.router.HighlightConditions;
11+
import com.vaadin.flow.router.RouterLink;
12+
13+
/**
14+
* Extension of Tabs in order to allow to bind tabs with Routes.
15+
*
16+
* @see https://cookbook.vaadin.com/tabs-with-routes/a
17+
*/
18+
public class RouteTabs extends Tabs implements BeforeEnterObserver {
19+
20+
private final Map<RouterLink, Tab> routerLinkTabMap = new LinkedHashMap<>();
21+
22+
public void add(RouterLink routerLink) {
23+
routerLink.setHighlightCondition(HighlightConditions.sameLocation());
24+
routerLink.setHighlightAction((link, shouldHighlight) -> {
25+
if (shouldHighlight)
26+
setSelectedTab(routerLinkTabMap.get(routerLink));
27+
});
28+
routerLinkTabMap.put(routerLink, new Tab(routerLink));
29+
add(routerLinkTabMap.get(routerLink));
30+
}
31+
32+
@Override
33+
public void beforeEnter(BeforeEnterEvent event) {
34+
// In case no tabs will match
35+
setSelectedTab(null);
36+
}
37+
38+
public Map<RouterLink, Tab> getRouterLinkTabMap() {
39+
return routerLinkTabMap;
40+
}
41+
42+
public RouterLink getFirstRoute() {
43+
Optional<RouterLink> first =
44+
routerLinkTabMap.entrySet().stream().map(Map.Entry::getKey).findFirst();
45+
return first.isPresent() ? first.get() : null;
46+
}
47+
}

src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,35 @@
1919
*/
2020
package com.flowingcode.vaadin.addons.demo;
2121

22+
import java.util.Optional;
2223
import com.flowingcode.vaadin.addons.GithubLink;
2324
import com.vaadin.flow.component.Component;
25+
import com.vaadin.flow.component.HasElement;
2426
import com.vaadin.flow.component.checkbox.Checkbox;
2527
import com.vaadin.flow.component.dependency.StyleSheet;
28+
import com.vaadin.flow.component.html.Div;
2629
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
2730
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
2831
import com.vaadin.flow.component.splitlayout.SplitLayout.Orientation;
29-
import com.vaadin.flow.component.tabs.Tab;
30-
import com.vaadin.flow.component.tabs.Tabs;
32+
import com.vaadin.flow.router.BeforeEnterEvent;
33+
import com.vaadin.flow.router.BeforeEnterObserver;
3134
import com.vaadin.flow.router.PageTitle;
32-
import java.util.HashMap;
33-
import java.util.Map;
34-
import java.util.Optional;
35+
import com.vaadin.flow.router.Route;
36+
import com.vaadin.flow.router.RouterLayout;
37+
import com.vaadin.flow.router.RouterLink;
3538

3639
@StyleSheet("context://frontend/styles/commons-demo/shared-styles.css")
3740
@SuppressWarnings("serial")
38-
public class TabbedDemo extends VerticalLayout {
41+
public class TabbedDemo extends VerticalLayout implements RouterLayout, BeforeEnterObserver {
3942

40-
private Tabs tabs;
43+
private RouteTabs tabs;
4144
private HorizontalLayout footer;
4245
private SplitLayoutDemo currentLayout;
43-
private Map<Tab, Component> demos;
4446
private Checkbox orientationCB;
4547
private Checkbox codeCB;
4648

4749
public TabbedDemo() {
48-
tabs = new Tabs();
49-
demos = new HashMap<>();
50+
tabs = new RouteTabs();
5051
tabs.setWidthFull();
5152

5253
// Footer
@@ -68,22 +69,10 @@ public TabbedDemo() {
6869
footer.setWidthFull();
6970
footer.setJustifyContentMode(JustifyContentMode.END);
7071
footer.add(codeCB, orientationCB);
71-
72-
tabs.addSelectedChangeListener(
73-
e -> {
74-
removeAll();
75-
Component currentDemo = demos.get(tabs.getSelectedTab());
76-
this.add(tabs, currentDemo);
77-
if (currentDemo instanceof SplitLayoutDemo) {
78-
currentLayout = (SplitLayoutDemo) currentDemo;
79-
this.add(footer);
80-
updateSplitterPosition();
81-
updateSplitterOrientation();
82-
} else {
83-
currentLayout = null;
84-
}
85-
});
86-
72+
73+
this.add(tabs);
74+
this.add(new Div());
75+
this.add(footer);
8776
setSizeFull();
8877
}
8978

@@ -118,26 +107,60 @@ public void addDemo(Component demo) {
118107

119108
/**
120109
* @param demo the demo instance
121-
* @param name the demo name (tab label)
110+
* @param label the demo name (tab label)
122111
* @param sourceCodeUrl the url of the demo, <b>null</b> to not show source code section.
123112
*/
113+
@Deprecated
124114
public void addDemo(Component demo, String label, String sourceCodeUrl) {
125-
if (!demo.getId().isPresent()) {
126-
demo.setId("content");
127-
}
128-
Tab tab = new Tab(label);
129-
if (sourceCodeUrl != null) {
130-
demos.put(tab, new SplitLayoutDemo(demo, sourceCodeUrl));
131-
} else {
132-
demos.put(tab, demo);
115+
this.addDemo(demo.getClass(), label, sourceCodeUrl);
116+
}
117+
118+
public void addDemo(Class<? extends Component> clazz, String label, String sourceCodeUrl){
119+
if(!clazz.isAnnotationPresent(Route.class)) {
120+
throw new IllegalArgumentException(clazz + " must be annotated as Route");
133121
}
122+
RouterLink tab = new RouterLink(label, clazz);
134123
tabs.add(tab);
135124
}
136125

137126
public void addDemo(Component demo, String label) {
138127
addDemo(demo, label, null);
139128
}
140129

130+
@Override
131+
public void showRouterLayoutContent(HasElement content) {
132+
Component demo = (Component)content;
133+
if (!demo.getId().isPresent()) {
134+
demo.setId("content");
135+
}
136+
137+
DemoSource demoSource = demo.getClass().getAnnotation(DemoSource.class);
138+
String sourceCodeUrl = null;
139+
if (demoSource != null) {
140+
sourceCodeUrl = demoSource.value();
141+
if (sourceCodeUrl.equals(DemoSource.GITHUB_SOURCE)) {
142+
sourceCodeUrl = Optional.ofNullable(this.getClass().getAnnotation(GithubLink.class))
143+
.map(githubLink -> githubLink.value() + "/blob/master/src/test/java/"
144+
+ demo.getClass().getName().replace('.', '/') + ".java")
145+
.orElse(null);
146+
}
147+
content = new SplitLayoutDemo(demo, sourceCodeUrl);
148+
currentLayout = (SplitLayoutDemo) content;
149+
updateSplitterPosition();
150+
updateSplitterOrientation();
151+
this.footer.setVisible(true);
152+
} else {
153+
currentLayout = null;
154+
this.footer.setVisible(false);
155+
}
156+
this.getElement().insertChild(1, content.getElement());
157+
}
158+
159+
@Override
160+
public void removeRouterLayoutContent(HasElement oldContent) {
161+
this.getElement().removeChild(1);
162+
}
163+
141164
private void updateSplitterPosition() {
142165
boolean b = codeCB.getValue();
143166
if (b) {
@@ -157,4 +180,15 @@ private void updateSplitterOrientation() {
157180
currentLayout.setOrientation(Orientation.VERTICAL);
158181
}
159182
}
183+
184+
@Override
185+
public void beforeEnter(BeforeEnterEvent event) {
186+
if(TabbedDemo.class.isAssignableFrom(event.getNavigationTarget())) {
187+
RouterLink first = tabs.getFirstRoute();
188+
if(first != null) {
189+
event.forwardTo(first.getHref());
190+
}
191+
}
192+
}
193+
160194
}

src/test/java/com/flowingcode/vaadin/addons/demo/Demo.java

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,16 @@
2020
package com.flowingcode.vaadin.addons.demo;
2121

2222
import com.flowingcode.vaadin.addons.GithubLink;
23-
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
24-
import com.vaadin.flow.component.textfield.TextField;
2523
import com.vaadin.flow.router.Route;
24+
import com.vaadin.flow.router.RouteAlias;
2625

2726
/** Hello world! */
2827
@Route("")
28+
@RouteAlias("demo")
2929
@GithubLink("https://github.com/FlowingCode/CommonsDemo")
3030
public class Demo extends TabbedDemo {
3131

3232
public Demo() {
33-
final String sourceCodeUrl =
34-
"https://github.com/FlowingCode/CommonsDemo/blob/master/src/test/java/com/flowingcode/vaadin/addons/demo/Demo.java";
35-
VerticalLayout vl = new VerticalLayout();
36-
VerticalLayout vl2 = new VerticalLayout();
37-
VerticalLayout vl3 = new VerticalLayout();
38-
vl.setSizeFull();
39-
vl.add(new TextField("Hello"));
40-
41-
addDemo(vl, "Demo 1", sourceCodeUrl);
42-
43-
vl2.add(new TextField("Hi"));
44-
addDemo(vl2, "Demo 2", sourceCodeUrl);
45-
46-
TextField tf = new TextField("Demo Without Source Code");
47-
tf.setWidthFull();
48-
vl3.add(tf);
49-
addDemo(vl3, "Demo Without Source Code");
50-
5133
addDemo(new SampleDemo());
5234
addDemo(new SampleDemoDefault());
5335
}

src/test/java/com/flowingcode/vaadin/addons/demo/SampleDemo.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
import com.vaadin.flow.component.html.Div;
2323
import com.vaadin.flow.component.html.Span;
2424
import com.vaadin.flow.router.PageTitle;
25+
import com.vaadin.flow.router.Route;
2526

27+
@Route(value = "demo/demo4", layout = Demo.class)
2628
@PageTitle("Demo 4")
27-
@DemoSource("https://github.com/FlowingCode/CommonsDemo/blob/master/src/test/java/com/flowingcode/vaadin/addons/demo/SampleDemo.java")
2829
public class SampleDemo extends Div {
2930

3031
public SampleDemo() {

src/test/java/com/flowingcode/vaadin/addons/demo/SampleDemoDefault.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import com.vaadin.flow.component.html.Div;
2323
import com.vaadin.flow.component.html.Span;
2424
import com.vaadin.flow.router.PageTitle;
25+
import com.vaadin.flow.router.Route;
2526

27+
@Route(value = "demo/demo5", layout = Demo.class)
2628
@PageTitle("Demo 5")
2729
@DemoSource
2830
public class SampleDemoDefault extends Div {

0 commit comments

Comments
 (0)