diff --git a/README.md b/README.md index 14934aeab..e29084f80 100644 --- a/README.md +++ b/README.md @@ -390,9 +390,9 @@ Finally, use the static methods in `org.jboss.elemento.mathml.MathML` to create # Flow -The module `elemento-flow` provides a way to execute a list of asynchronous tasks in parallel or sequentially, or to execute a single task repeatedly as long as certain conditions are met. +The class [Flow](https://hal.github.io/elemento/apidocs/org/jboss/elemento/flow/Flow.html) provides methods to execute a list of asynchronous tasks in parallel or sequentially, or to execute a single task repeatedly as long as certain conditions are met. -See the API documentation of [Flow](https://hal.github.io/elemento/apidocs/org/jboss/elemento/flow/Flow.html) for more details. +See the API documentation of [`org.jboss.elemento.flow`](https://hal.github.io/elemento/apidocs/org/jboss/elemento/flow/package-summary.html) for more details. ## Parallel @@ -447,20 +447,21 @@ Flow.repeat(new FlowContext(), currentTime) Elemento offers a very basic router. The router is minimal invasive and built around a few simple concepts: -- `Route`: Annotation that can be used to decorate pages. An annotation processor collects all classes annotated with `@Route` and generates an implementation of `Routes`. -- `Routes`: Provides a map of places and their corresponding pages. This can be used to register all places in one go. +- `@Route`: Annotation to mark a page implementation as place. An annotation processor collects all pages annotated with `@Route` and generates an instance of `Places`. Routes can have parameters like in `/contacts/:contactId`. - `Place`: Data class that represents a place in an application. A place is identified by a route, and can have an optional title and a custom root element. If present the children of the root element are replaced by the elements of the page. -- `Page`: Simple interface that represents a collection of HTML elements. Implementations need to implement a single method: `Iterable elements()` +- `Places`: Builder to set up places. Supports nested places and loaders. +- `Page`: Simple interface that represents a collection of HTML elements. Implementations need to implement a single method: `Iterable elements(Place, Parameter, LoaderData)`. - `PlaceManager`: Class that keeps track of registered places, handles navigation events, and updates the DOM accordingly. The place manager can be customized using builder like methods and has a `start()` method to show the initial page. +- `Loader`: Functional interface to asynchronously load data, before a page is added to the DOM. The loader function gets the place and parameters as input and returns a promise of the data to be loaded: `Promise load(Place place, Parameter parameter)`. See the API documentation of [PlaceManager](https://hal.github.io/elemento/apidocs/org/jboss/elemento/router/PlaceManager.html) for more details. ```java -@Route("/") -public class HomePage implements Page { + @Route("/home") +public static class HomePage implements Page { @Override - public Iterable elements() { + public Iterable elements(Place place, Parameter parameter, LoaderData data) { return singletonList(div() .add(h(1, "Welcome")) .add(p().textContent("Hello world!")) @@ -468,15 +469,15 @@ public class HomePage implements Page { } } -public class Application { +public static class Application { public void entryPoint() { body().add(div().id("main")); new PlaceManager() .root(By.id("main")) - .register(new Place("/"), HomePage::new) + .register(place("/home"), HomePage::new) // could also be registered with - // .register(RoutesImpl.INSTANCE.places()); + // .register(new GeneratedPlaces()) .start(); } } diff --git a/logger/src/main/java/org/jboss/elemento/logger/Logger.java b/logger/src/main/java/org/jboss/elemento/logger/Logger.java index 483b76453..1204c0eaf 100644 --- a/logger/src/main/java/org/jboss/elemento/logger/Logger.java +++ b/logger/src/main/java/org/jboss/elemento/logger/Logger.java @@ -83,6 +83,12 @@ public class Logger { // ------------------------------------------------------ static API + /** + * Retrieves or creates a logger with the specified category. + * + * @param category the category of the logger + * @return the logger instance + */ public static Logger getLogger(String category) { Logger logger; if (category == null || category.isEmpty()) { @@ -98,17 +104,33 @@ public static Logger getLogger(String category) { return logger; } + /** + * Sets the global log level. + * + * @param level the desired log level to be set + */ public static void setLevel(Level level) { Logger.level = level; console.info("Set global log level to %s", level.name()); } + /** + * Sets the log level for a given category. + * + * @param category the category for which to set the log level + * @param level the log level to be set + */ public static void setLevel(String category, Level level) { levelOverrides.addLevel(category, level); applyOverrides(); console.info("Set log level for %s to %s", category, level.name()); } + /** + * Resets the log level for the specified category to the global log level. + * + * @param category the category for which the log level needs to be reset + */ @JsMethod public static void resetLevel(String category) { levelOverrides.removeLevel(category); diff --git a/router-processor-cdi/src/main/java/org/jboss/elemento/router/processor/CdiCodeGenerator.java b/router-processor-cdi/src/main/java/org/jboss/elemento/router/processor/CdiCodeGenerator.java index b818a59c9..8d3c7454b 100644 --- a/router-processor-cdi/src/main/java/org/jboss/elemento/router/processor/CdiCodeGenerator.java +++ b/router-processor-cdi/src/main/java/org/jboss/elemento/router/processor/CdiCodeGenerator.java @@ -19,6 +19,7 @@ import java.util.List; import javax.annotation.processing.Filer; + import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; diff --git a/router/src/demo/java/PlaceManagerDemo.java b/router/src/demo/java/PlaceManagerDemo.java index 51f89e6dd..7183db868 100644 --- a/router/src/demo/java/PlaceManagerDemo.java +++ b/router/src/demo/java/PlaceManagerDemo.java @@ -56,7 +56,7 @@ public void entryPoint() { .root(By.id("main")) .register(place("/home"), HomePage::new) // could also be registered with - // .register(RoutesImpl.INSTANCE.places()) + // .register(new GeneratedPlaces()) .start(); } } diff --git a/router/src/main/java/org/jboss/elemento/router/AfterPlaceHandler.java b/router/src/main/java/org/jboss/elemento/router/AfterPlaceHandler.java index 582599bc4..af2cd1397 100644 --- a/router/src/main/java/org/jboss/elemento/router/AfterPlaceHandler.java +++ b/router/src/main/java/org/jboss/elemento/router/AfterPlaceHandler.java @@ -15,8 +15,17 @@ */ package org.jboss.elemento.router; +/** + * An interface that represents a handler for performing actions after a place is changed. + */ @FunctionalInterface public interface AfterPlaceHandler { + /** + * Executes the specified actions after a place is changed. + * + * @param placeManager the PlaceManager instance that manages the places + * @param place the new place that has been changed to + */ void afterPlace(PlaceManager placeManager, Place place); } diff --git a/router/src/main/java/org/jboss/elemento/router/BeforePlaceHandler.java b/router/src/main/java/org/jboss/elemento/router/BeforePlaceHandler.java index fe83ed9a0..4a590473a 100644 --- a/router/src/main/java/org/jboss/elemento/router/BeforePlaceHandler.java +++ b/router/src/main/java/org/jboss/elemento/router/BeforePlaceHandler.java @@ -15,6 +15,9 @@ */ package org.jboss.elemento.router; +/** + * This functional interface represents a handler that is executed before a place transition occurs. + */ @FunctionalInterface public interface BeforePlaceHandler { diff --git a/router/src/main/java/org/jboss/elemento/router/Loader.java b/router/src/main/java/org/jboss/elemento/router/Loader.java index 843333315..ba23e98c9 100644 --- a/router/src/main/java/org/jboss/elemento/router/Loader.java +++ b/router/src/main/java/org/jboss/elemento/router/Loader.java @@ -17,8 +17,21 @@ import elemental2.promise.Promise; +/** + * Functional interface representing a loader function that loads data for a given place. + * + * @param the type of data to be loaded + */ @FunctionalInterface public interface Loader { + /** + * Loads data for a given place using a specified parameter. + * + * @param place the place for which data needs to be loaded + * @param parameter the parameter used for loading data + * @param the type of data to be loaded + * @return a Promise representing the asynchronous loading operation + */ Promise load(Place place, Parameter parameter); } diff --git a/router/src/main/java/org/jboss/elemento/router/LoaderData.java b/router/src/main/java/org/jboss/elemento/router/LoaderData.java index 98d658983..1cc3b3e6b 100644 --- a/router/src/main/java/org/jboss/elemento/router/LoaderData.java +++ b/router/src/main/java/org/jboss/elemento/router/LoaderData.java @@ -15,6 +15,9 @@ */ package org.jboss.elemento.router; +/** + * The LoaderData class represents data loaded by a loader. It can be used to retrieve loaded data. + */ public class LoaderData { public static final LoaderData NONE = new LoaderData(null); diff --git a/router/src/main/java/org/jboss/elemento/router/Parameter.java b/router/src/main/java/org/jboss/elemento/router/Parameter.java index 62b569de0..5883f07d2 100644 --- a/router/src/main/java/org/jboss/elemento/router/Parameter.java +++ b/router/src/main/java/org/jboss/elemento/router/Parameter.java @@ -20,6 +20,10 @@ import static java.util.Collections.emptyMap; +/** + * The Parameter class is used to represent parameters in a route path. A parameter is a part of the path that starts with a + * colon (e.g. ":id"). Parameters are used to retrieve values from a path. + */ public class Parameter { static boolean isParameter(String value) { diff --git a/router/src/main/java/org/jboss/elemento/router/Path.java b/router/src/main/java/org/jboss/elemento/router/Path.java index 891c96255..9d5d60042 100644 --- a/router/src/main/java/org/jboss/elemento/router/Path.java +++ b/router/src/main/java/org/jboss/elemento/router/Path.java @@ -28,7 +28,7 @@ static String normalize(String path) { } static String[] split(String path) { - if (path != null && !path.isEmpty() && !path.isBlank()) { + if (path != null && !path.trim().isEmpty()) { String normalizedNoSlash = normalize(path).substring(1); if (!normalizedNoSlash.isEmpty()) { return normalizedNoSlash.split("/"); diff --git a/router/src/main/java/org/jboss/elemento/router/Place.java b/router/src/main/java/org/jboss/elemento/router/Place.java index 3b48a5860..8ba18943f 100644 --- a/router/src/main/java/org/jboss/elemento/router/Place.java +++ b/router/src/main/java/org/jboss/elemento/router/Place.java @@ -28,8 +28,14 @@ import static org.jboss.elemento.router.Path.normalize; /** - * Represents a place in an application. A place is identified by a route, and can have an optional title and a custom root - * element. + * Represents a place in an application. A place is identified by a route that can have parameters, an optional title, a custom + * root and an optional {@link Loader}. element. + *

+ * If the route has parameters, the {@link PlaceManager} will collect it and pass it to the page when calling + * {@link Page#elements(Place, Parameter, LoaderData)}. + *

+ * If the page has a {@link Loader}, the {@link PlaceManager} will call it and pass the loaded data as {@link LoaderData} to the + * page when calling {@link Page#elements(Place, Parameter, LoaderData)}. *

* If a title is given, the {@link PlaceManager} will change the document title accordingly. If a custom root selector or * element is given, the {@link PlaceManager} will replace the contents of that element with the {@link Page} registered for @@ -53,7 +59,7 @@ public static Place place(String route) { Loader loader; Place(String route) { - if (route == null || route.isEmpty() || route.isBlank()) { + if (route == null || route.trim().isEmpty()) { throw new IllegalArgumentException("Route must not be null or empty!"); } this.route = normalize(route); @@ -94,7 +100,7 @@ public String toString() { builder.append(", custom root"); } if (loader != null) { - builder.append(", with loader"); + builder.append(", "); } builder.append(')'); return builder.toString(); diff --git a/router/src/main/java/org/jboss/elemento/router/PlaceManager.java b/router/src/main/java/org/jboss/elemento/router/PlaceManager.java index 2ac57ad2a..88afdb204 100644 --- a/router/src/main/java/org/jboss/elemento/router/PlaceManager.java +++ b/router/src/main/java/org/jboss/elemento/router/PlaceManager.java @@ -44,6 +44,7 @@ import static org.jboss.elemento.Elements.div; import static org.jboss.elemento.Elements.h; import static org.jboss.elemento.Elements.p; +import static org.jboss.elemento.Elements.pre; import static org.jboss.elemento.Elements.removeChildrenFrom; import static org.jboss.elemento.EventType.bind; import static org.jboss.elemento.EventType.click; @@ -312,13 +313,15 @@ private Promise gotoPlace(PlaceManagerStruct pms) { logger.debug("Load data for %s", pms.place); return pms.place.loader.load(pms.place, pms.parameter) .then(data -> { - logger.debug("Create page for %s", pms.place); + logger.debug("Data loaded successfully. Create page for %s", pms.place); pms.data = new LoaderData(data); pms.page = pageSupplier.get(); return gotoPage(pms); }) .catch_(error -> { - pms.data = new LoaderData(String.valueOf(error)); + String errorAsString = String.valueOf(error); + logger.error("Unable to load page for %s: %s", pms.place, errorAsString); + pms.data = new LoaderData(errorAsString); pms.page = noData(pms.place); return gotoPage(pms); }); @@ -368,7 +371,7 @@ private Page notFound(Place place) { private Page noData(Place place) { logger.debug("No data for %s", place); if (noData != null) { - return notFound.get(); + return noData.get(); } else { return new DefaultNoData(); } @@ -395,8 +398,9 @@ private static class PlaceManagerStruct { private static final String ROOT_STYLE = "display:grid;place-items:center;height:100vh"; private static final String CONTAINER_STYLE = "width:50%;height:50%;"; - private static final String HEADER_STYLE = "font-size:3rem;text-align:center"; - private static final String PARAGRAPH_STYLE = "font-size:1.5rem;text-align:center"; + private static final String HEADER_STYLE = "font-size:3rem;text-align:center;margin-bottom:1rem"; + private static final String PARAGRAPH_STYLE = "font-size:1.5rem;text-align:center;margin-bottom:1rem"; + private static final String ERROR_STYLE = "font-size:1.2rem;text-align:center;text-wrap:wrap;"; private static class DefaultNotFound implements Page { @@ -422,7 +426,8 @@ public Iterable elements(Place place, Parameter parameter, LoaderDa .add(div().style(CONTAINER_STYLE) .add(h(1, "No data").style(HEADER_STYLE)) .add(p().style(PARAGRAPH_STYLE) - .add("The data for page '" + place.route + "' could not be loaded: " + error))) + .add("The data for page '" + place.route + "' could not be loaded.")) + .add(pre().style(ERROR_STYLE).textContent(error))) .element()); } } diff --git a/router/src/main/java/org/jboss/elemento/router/Places.java b/router/src/main/java/org/jboss/elemento/router/Places.java index b96a3e787..18f249365 100644 --- a/router/src/main/java/org/jboss/elemento/router/Places.java +++ b/router/src/main/java/org/jboss/elemento/router/Places.java @@ -22,6 +22,10 @@ import static org.jboss.elemento.router.Path.normalize; +/** + * Represents a collection of places in an application. Each place is associated with a page supplier. Supports nested places + * and loaders. + */ public class Places implements Iterable>> { // ------------------------------------------------------ factory @@ -45,16 +49,29 @@ public Iterator>> iterator() { // ------------------------------------------------------ builder + /** + * Adds a Place and the corresponding page supplier to the collection of places. + */ public Places add(Place place, Supplier page) { pages.put(place, page); return this; } + /** + * Adds all the places from the given Places object to this places object. + */ public Places add(Places places) { pages.putAll(places.pages); return this; } + /** + * Adds the children places to the current collection of places. Each child place is created by appending the child's route + * to the given parent path. The child places and their corresponding page suppliers are added to the current collection. + * + * @param path the parent path to append to the child routes + * @param places the child places to add + */ public Places children(String path, Places places) { for (Map.Entry> entry : places.pages.entrySet()) { Place child = new Place(failSafeRoute(path, entry.getKey()), entry.getKey()); @@ -63,6 +80,9 @@ public Places children(String path, Places places) { return this; } + /** + * Assigns a given loader for a specific place. + */ public Places loader(Place place, Loader loader) { if (pages.containsKey(place)) { for (Place p : pages.keySet()) {