Updated to version 1.5.0, with futures for navigation, expanded testing, and the navigateBackAndClear function.
This commit is contained in:
parent
48e150c3c3
commit
ac011b7382
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>com.andrewlalis</groupId>
|
||||
<artifactId>javafx-scene-router</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<version>1.5.0</version>
|
||||
<name>JavaFX Scene Router</name>
|
||||
<description>A library that provides a router implementation for JavaFX, for browser-like navigation between pages.</description>
|
||||
<url>https://github.com/andrewlalis/javafx-scene-router</url>
|
||||
|
|
|
@ -105,6 +105,16 @@ public class RouteHistory {
|
|||
currentItemIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any history ahead of the current route item, such that the user
|
||||
* cannot navigate forward.
|
||||
*/
|
||||
public void clearForward() {
|
||||
if (currentItemIndex + 1 < items.size()) {
|
||||
items.subList(currentItemIndex + 1, items.size()).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of "breadcrumbs", or a representation of the current history
|
||||
* and indication of where we are in that history.
|
||||
|
@ -140,4 +150,20 @@ public class RouteHistory {
|
|||
public int getCurrentItemIndex() {
|
||||
return currentItemIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("RouteHistory:\n");
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
var item = items.get(i);
|
||||
sb.append(String.format("%4d route = \"%s\", context = %s", i, item.route(), item.context()));
|
||||
if (i == currentItemIndex) {
|
||||
sb.append(" <--- Current Item");
|
||||
}
|
||||
if (i + 1 < items.size()) {
|
||||
sb.append(String.format("%n"));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
package com.andrewlalis.javafx_scene_router;
|
||||
|
||||
/**
|
||||
* A listener that's notified when the router it's attached to navigates to
|
||||
* a specific, pre-defined route. Usually used to do something once the user
|
||||
* has navigated to a route.
|
||||
*/
|
||||
public interface RouteSelectionListener {
|
||||
/**
|
||||
* Called when a specific, pre-defined route is selected.
|
||||
* @param context The context that was provided when the user navigated to
|
||||
* the route. This may be null.
|
||||
*/
|
||||
void onRouteSelected(Object context);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ import java.io.IOException;
|
|||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A router that shows different content in a pane depending on which route is
|
||||
|
@ -34,7 +36,7 @@ import java.util.function.Consumer;
|
|||
*/
|
||||
public class SceneRouter {
|
||||
private final RouterView view;
|
||||
private final Map<String, Parent> routeMap = new HashMap<>();
|
||||
private final Map<String, Supplier<Parent>> routeMap = new HashMap<>();
|
||||
private final RouteHistory history = new RouteHistory();
|
||||
private final ObservableList<BreadCrumb> breadCrumbs = FXCollections.observableArrayList();
|
||||
private final StringProperty currentRouteProperty = new SimpleStringProperty(null);
|
||||
|
@ -73,7 +75,7 @@ public class SceneRouter {
|
|||
* @return This router.
|
||||
*/
|
||||
public SceneRouter map(String route, Parent node) {
|
||||
routeMap.put(route, node);
|
||||
routeMap.put(route, () -> node);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -104,40 +106,85 @@ public class SceneRouter {
|
|||
* Navigates to a given route, with a given context object.
|
||||
* @param route The route to navigate to.
|
||||
* @param context The context that should be available at that route.
|
||||
* @return A completable future that completes once navigation is done.
|
||||
*/
|
||||
public void navigate(String route, Object context) {
|
||||
public CompletableFuture<Void> navigate(String route, Object context) {
|
||||
String oldRoute = currentRouteProperty.get();
|
||||
Object oldContext = history.getCurrentContext();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
history.push(route, context);
|
||||
setCurrentNode(route, oldRoute, oldContext);
|
||||
cf.complete(null);
|
||||
});
|
||||
return cf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a given route, without any context.
|
||||
* @param route The route to navigate to.
|
||||
* @return A completable future that completes once navigation is done.
|
||||
*/
|
||||
public void navigate(String route) {
|
||||
navigate(route, null);
|
||||
public CompletableFuture<Void> navigate(String route) {
|
||||
return navigate(route, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to navigate back.
|
||||
* Attempts to navigate back to the previous route.
|
||||
* @return True if the router will navigate back.
|
||||
*/
|
||||
public void navigateBack() {
|
||||
public CompletableFuture<Boolean> navigateBack() {
|
||||
String oldRoute = currentRouteProperty.get();
|
||||
Object oldContext = history.getCurrentContext();
|
||||
Platform.runLater(() -> history.back().ifPresent(prev -> setCurrentNode(prev.route(), oldRoute, oldContext)));
|
||||
if (!history.canGoBack()) return CompletableFuture.completedFuture(false);
|
||||
CompletableFuture<Boolean> cf = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
RouteHistoryItem prev = history.back().orElseThrow();
|
||||
setCurrentNode(prev.route(), oldRoute, oldContext);
|
||||
cf.complete(true);
|
||||
});
|
||||
return cf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to navigate back to the previous route, and then erase all
|
||||
* forward route history.
|
||||
* <p>
|
||||
* For example, suppose the history looks like this:<br>
|
||||
* "A" -> "B" -> "C"<br>
|
||||
* where the router is currently at C. Then, if this method is called,
|
||||
* the router will go back to B, and remove C from the history.
|
||||
* </p>
|
||||
* @return True if the router will navigate back.
|
||||
*/
|
||||
public CompletableFuture<Boolean> navigateBackAndClear() {
|
||||
return navigateBack()
|
||||
.thenCompose(success -> {
|
||||
if (!success) return CompletableFuture.completedFuture(false);
|
||||
CompletableFuture<Boolean> cf = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
history.clearForward();
|
||||
cf.complete(true);
|
||||
});
|
||||
return cf;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to navigate forward.
|
||||
* @return A future that resolves to true if forward navigation was successful.
|
||||
*/
|
||||
public void navigateForward() {
|
||||
public CompletableFuture<Boolean> navigateForward() {
|
||||
String oldRoute = currentRouteProperty.get();
|
||||
Object oldContext = history.getCurrentContext();
|
||||
Platform.runLater(() -> history.forward().ifPresent(next -> setCurrentNode(next.route(), oldRoute, oldContext)));
|
||||
if (!history.canGoForward()) return CompletableFuture.completedFuture(false);
|
||||
CompletableFuture<Boolean> cf = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
RouteHistoryItem next = history.forward().orElseThrow();
|
||||
setCurrentNode(next.route(), oldRoute, oldContext);
|
||||
cf.complete(true);
|
||||
});
|
||||
return cf;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,7 +249,7 @@ public class SceneRouter {
|
|||
}
|
||||
|
||||
private Parent getMappedNode(String route) {
|
||||
Parent node = routeMap.get(route);
|
||||
Parent node = routeMap.get(route).get();
|
||||
if (node == null) throw new IllegalArgumentException("Route " + route + " is not mapped to any node.");
|
||||
return node;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.andrewlalis.javafx_scene_router.test;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
|
||||
public class RouteAController implements RouteSelectionListener {
|
||||
public int routeSelectedCount = 0;
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
routeSelectedCount++;
|
||||
System.out.println("Route A selected.");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
package com.andrewlalis.javafx_scene_router;
|
||||
package com.andrewlalis.javafx_scene_router.test;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteHistory;
|
||||
import com.andrewlalis.javafx_scene_router.RouteHistoryItem;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
@ -39,7 +42,7 @@ public class RouteHistoryTest {
|
|||
assertTrue(history.canGoBack());
|
||||
var prev = history.back();
|
||||
assertTrue(prev.isPresent());
|
||||
assertEquals(new RouteHistoryItem("a", "a"), prev.get());
|
||||
Assertions.assertEquals(new RouteHistoryItem("a", "a"), prev.get());
|
||||
assertFalse(history.canGoBack());
|
||||
assertTrue(history.back().isEmpty());
|
||||
}
|
||||
|
@ -73,6 +76,22 @@ public class RouteHistoryTest {
|
|||
assertEquals(-1, history.getCurrentItemIndex());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearForward() {
|
||||
var history = new RouteHistory();
|
||||
history.push("a", "a");
|
||||
history.push("b", "b");
|
||||
history.push("c", "c");
|
||||
assertEquals(3, history.getItems().size());
|
||||
assertEquals(2, history.getCurrentItemIndex());
|
||||
history.back();
|
||||
assertEquals(3, history.getItems().size());
|
||||
assertEquals(1, history.getCurrentItemIndex());
|
||||
history.clearForward();
|
||||
assertEquals(1, history.getCurrentItemIndex());
|
||||
assertEquals(2, history.getItems().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBreadCrumbs() {
|
||||
var h1 = new RouteHistory();
|
|
@ -0,0 +1,69 @@
|
|||
package com.andrewlalis.javafx_scene_router.test;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
|
||||
import com.andrewlalis.javafx_scene_router.SceneRouter;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class SceneRouterTest {
|
||||
@Test
|
||||
public void testNavigate() {
|
||||
var router = getSampleRouter();
|
||||
// Make some assertions prior to navigation.
|
||||
assertTrue(router.getHistory().getItems().isEmpty());
|
||||
assertFalse(router.navigateBack().join());
|
||||
assertFalse(router.navigateForward().join());
|
||||
assertNull(router.currentRouteProperty().get());
|
||||
|
||||
// Test some basic navigation.
|
||||
var contextA = "CONTEXT";
|
||||
router.navigate("A", contextA).join();
|
||||
assertEquals(1, router.getHistory().getItems().size());
|
||||
assertEquals("A", router.currentRouteProperty().get());
|
||||
assertEquals(contextA, router.getContext());
|
||||
assertEquals(1, router.getBreadCrumbs().size());
|
||||
assertTrue(router.getBreadCrumbs().getFirst().current());
|
||||
|
||||
router.navigate("B").join();
|
||||
assertEquals(2, router.getHistory().getItems().size());
|
||||
assertEquals("B", router.currentRouteProperty().get());
|
||||
assertNull(router.getContext());
|
||||
assertEquals(2, router.getBreadCrumbs().size());
|
||||
assertTrue(router.getBreadCrumbs().getLast().current());
|
||||
assertFalse(router.getBreadCrumbs().getFirst().current());
|
||||
|
||||
// Test that navigating back and forward works.
|
||||
assertTrue(router.navigateBack().join());
|
||||
assertEquals("A", router.currentRouteProperty().get());
|
||||
assertEquals(contextA, router.getContext());
|
||||
|
||||
assertTrue(router.navigateForward().join());
|
||||
assertEquals("B", router.currentRouteProperty().get());
|
||||
assertNull(router.getContext());
|
||||
assertFalse(router.navigateForward().join());
|
||||
|
||||
// Test that navigateBackAndClear works.
|
||||
assertTrue(router.navigateBackAndClear().join());
|
||||
assertEquals("A", router.currentRouteProperty().get());
|
||||
assertEquals(1, router.getHistory().getItems().size());
|
||||
}
|
||||
|
||||
private SceneRouter getSampleRouter() {
|
||||
CompletableFuture<SceneRouter> future = new CompletableFuture<>();
|
||||
Platform.startup(() -> {
|
||||
SceneRouter router = new SceneRouter(new AnchorPaneRouterView());
|
||||
router.map("A", SceneRouterTest.class.getResource("/routeA.fxml"));
|
||||
router.map("B", new BorderPane(new Label("Hello from route B")));
|
||||
router.map("C", new HBox(new Label("Hello from route C")));
|
||||
future.complete(router);
|
||||
});
|
||||
return future.join();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
module com.andrewlalis.javafx_scene_router.test {
|
||||
requires javafx.fxml;
|
||||
requires javafx.graphics;
|
||||
requires javafx.controls;
|
||||
|
||||
requires com.andrewlalis.javafx_scene_router;
|
||||
|
||||
requires org.junit.jupiter.api;
|
||||
|
||||
exports com.andrewlalis.javafx_scene_router.test to javafx.fxml, org.junit.platform.commons;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox
|
||||
xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.javafx_scene_router.test.RouteAController"
|
||||
>
|
||||
<Label text="Hello from route A"/>
|
||||
</VBox>
|
Loading…
Reference in New Issue