From 18ab61be285dd85e1dce46f62870034475c070e1 Mon Sep 17 00:00:00 2001
From: andrewlalis
Date: Sun, 24 Dec 2023 08:31:48 -0500
Subject: [PATCH] Updated to 1.3.0, added more listener shenanigans.
---
README.md | 21 ++++-
pom.xml | 2 +-
.../RouteSelectionListener.java | 5 +
.../javafx_scene_router/SceneRouter.java | 93 +++++++++++++++----
4 files changed, 101 insertions(+), 20 deletions(-)
create mode 100644 src/main/java/com/andrewlalis/javafx_scene_router/RouteSelectionListener.java
diff --git a/README.md b/README.md
index ad53de0..83f5dc5 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ for that purpose, I've created javafx-scene-router. It allows you to initialize
a router that controls the content of a Pane or similar, and depending on what
route is selected, different content will be shown in that pane.
-# Usage
+## Usage
Add the following dependency to your `pom.xml`:
```xml
@@ -83,3 +83,22 @@ public class MainController {
}
}
```
+
+## Reactivity
+
+The SceneRouter has been designed to be used in reactive JavaFX projects, and
+includes a few ways of doing this:
+
+- The `currentRouteProperty` can be bound to, or have a listener attached, to
+update each time the current route changes.
+- You can `getBreadCrumbs()` to get an observable list of breadcrumbs that
+changes each time the route's history changes.
+- You can use `addRouteChangeListener` to add a listener that's notified each
+time the route has changed, with additional context and the previous route.
+- You can use `addRouteSelectionListener` to add a listener for a specific
+route, that will be notified only when the router selects that route.
+- You can make any of your controllers for route nodes implement `RouteSelectionListener`,
+in which case they'll automatically be registered using `addRouteSelectionListener`.
+Note that this **does not** apply to routes mapped using a pre-loaded node, as
+in the `map(String route, Parent node)` method. Only methods which take a `URL`
+to a resource work with this.
diff --git a/pom.xml b/pom.xml
index b1d4388..c2b16d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.andrewlalis
javafx-scene-router
- 1.2.0
+ 1.3.0
JavaFX Scene Router
A library that provides a router implementation for JavaFX, for browser-like navigation between pages.
https://github.com/andrewlalis/javafx-scene-router
diff --git a/src/main/java/com/andrewlalis/javafx_scene_router/RouteSelectionListener.java b/src/main/java/com/andrewlalis/javafx_scene_router/RouteSelectionListener.java
new file mode 100644
index 0000000..34d2a37
--- /dev/null
+++ b/src/main/java/com/andrewlalis/javafx_scene_router/RouteSelectionListener.java
@@ -0,0 +1,5 @@
+package com.andrewlalis.javafx_scene_router;
+
+public interface RouteSelectionListener {
+ void onRouteSelected(Object context);
+}
diff --git a/src/main/java/com/andrewlalis/javafx_scene_router/SceneRouter.java b/src/main/java/com/andrewlalis/javafx_scene_router/SceneRouter.java
index fd4a2d8..ed64432 100644
--- a/src/main/java/com/andrewlalis/javafx_scene_router/SceneRouter.java
+++ b/src/main/java/com/andrewlalis/javafx_scene_router/SceneRouter.java
@@ -1,8 +1,9 @@
package com.andrewlalis.javafx_scene_router;
import javafx.application.Platform;
-import javafx.beans.property.ListProperty;
-import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@@ -11,8 +12,7 @@ import javafx.scene.layout.Pane;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.function.Consumer;
/**
@@ -34,10 +34,18 @@ import java.util.function.Consumer;
*
*/
public class SceneRouter {
+ public interface RouteChangeListener {
+ void routeChanged(String route, Object context, String oldRoute, Object oldContext);
+ }
+
private final Pane viewPane = new Pane();
private final Map routeMap = new HashMap<>();
private final RouteHistory history = new RouteHistory();
- private final ListProperty breadCrumbs = new SimpleListProperty<>();
+ private final ObservableList breadCrumbs = FXCollections.observableArrayList();
+ private final StringProperty currentRouteProperty = new SimpleStringProperty(null);
+
+ private final List routeChangeListeners = new ArrayList<>();
+ private final Map> routeSelectionListeners = new HashMap<>();
/**
* Constructs the router.
@@ -47,6 +55,14 @@ public class SceneRouter {
/**
* Maps the given route to a node, so that when the route is selected, the
* given node is shown.
+ *
+ * Note that by supplying a pre-loaded JavaFX node, the SceneRouter is
+ * no longer able to check if the node's controller implements
+ * {@link RouteSelectionListener}, and so you'll need to register the
+ * controller manually with {@link #addRouteSelectionListener(String, RouteSelectionListener)}
+ * in order to have the controller be notified when its contents are
+ * shown.
+ *
* @param route The route.
* @param node The node to show.
* @return This router.
@@ -66,7 +82,7 @@ public class SceneRouter {
* @return This router.
*/
public SceneRouter map(String route, URL fxml, Consumer> controllerCustomizer) {
- return map(route, loadNode(fxml, controllerCustomizer));
+ return map(route, loadNode(route, fxml, controllerCustomizer));
}
/**
@@ -85,9 +101,11 @@ public class SceneRouter {
* @param context The context that should be available at that route.
*/
public void navigate(String route, Object context) {
+ String oldRoute = currentRouteProperty.get();
+ Object oldContext = history.getCurrentContext();
Platform.runLater(() -> {
history.push(route, context);
- setCurrentNode(getMappedNode(route));
+ setCurrentNode(route, oldRoute, oldContext);
});
}
@@ -103,18 +121,18 @@ public class SceneRouter {
* Attempts to navigate back.
*/
public void navigateBack() {
- Platform.runLater(() -> history.back()
- .ifPresent(prev -> setCurrentNode(getMappedNode(prev.route())))
- );
+ String oldRoute = currentRouteProperty.get();
+ Object oldContext = history.getCurrentContext();
+ Platform.runLater(() -> history.back().ifPresent(prev -> setCurrentNode(prev.route(), oldRoute, oldContext)));
}
/**
* Attempts to navigate forward.
*/
public void navigateForward() {
- Platform.runLater(() -> history.forward()
- .ifPresent(next -> setCurrentNode(getMappedNode(next.route())))
- );
+ String oldRoute = currentRouteProperty.get();
+ Object oldContext = history.getCurrentContext();
+ Platform.runLater(() -> history.forward().ifPresent(next -> setCurrentNode(next.route(), oldRoute, oldContext)));
}
/**
@@ -143,6 +161,14 @@ public class SceneRouter {
return viewPane;
}
+ /**
+ * Gets a property that refers to the router's current route.
+ * @return The route property.
+ */
+ public StringProperty currentRouteProperty() {
+ return currentRouteProperty;
+ }
+
/**
* Gets an observable list of {@link BreadCrumb} that is updated each time
* the router's navigation history is updated.
@@ -152,6 +178,25 @@ public class SceneRouter {
return breadCrumbs;
}
+ /**
+ * Adds a listener that will be notified each time the current route changes.
+ * @param listener The listener that will be notified.
+ */
+ public void addRouteChangeListener(RouteChangeListener listener) {
+ routeChangeListeners.add(listener);
+ }
+
+ /**
+ * Adds a listener that will be notified when the route changes to a
+ * specified route.
+ * @param route The route to listen for.
+ * @param listener The listener to use.
+ */
+ public void addRouteSelectionListener(String route, RouteSelectionListener listener) {
+ List listenerList = routeSelectionListeners.computeIfAbsent(route, s -> new ArrayList<>());
+ listenerList.add(listener);
+ }
+
private Parent getMappedNode(String route) {
Parent node = routeMap.get(route);
if (node == null) throw new IllegalArgumentException("Route " + route + " is not mapped to any node.");
@@ -161,19 +206,31 @@ public class SceneRouter {
/**
* Internal method to actually set this router's view pane to a particular
* node. This is called any time a route changes.
- * @param node The node to set.
+ * @param route The route to go to.
+ * @param oldRoute The previous route that the router was at.
+ * @param oldContext The context of the previous route.
*/
- private void setCurrentNode(Parent node) {
- viewPane.getChildren().setAll(node);
+ private void setCurrentNode(String route, String oldRoute, Object oldContext) {
+ viewPane.getChildren().setAll(getMappedNode(route));
breadCrumbs.setAll(history.getBreadCrumbs());
+ currentRouteProperty.set(route);
+ for (var listener : routeChangeListeners) {
+ listener.routeChanged(route, getContext(), oldRoute, oldContext);
+ }
+ for (var listener : routeSelectionListeners.getOrDefault(route, Collections.emptyList())) {
+ listener.onRouteSelected(getContext());
+ }
}
- private Parent loadNode(URL resource, Consumer controllerCustomizer) {
+ private Parent loadNode(String route, URL resource, Consumer controllerCustomizer) {
FXMLLoader loader = new FXMLLoader(resource);
try {
Parent p = loader.load();
+ T controller = loader.getController();
+ if (controller instanceof RouteSelectionListener rsl) {
+ addRouteSelectionListener(route, rsl);
+ }
if (controllerCustomizer != null) {
- T controller = loader.getController();
if (controller == null) throw new IllegalStateException("No controller found when loading " + resource.toString());
controllerCustomizer.accept(controller);
}