Router
Elemento offers a very basic router. The router is minimally invasive and built around a few simple concepts:
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. Routes can have parameters such as in/contacts/:contactId. Finally, a place can also have a loader assigned to it.Places
Builder to set up places. It supports nested places, combines places, and assigns loaders to places.LoadData<T>
Functional interface to asynchronously load data before a page is created and added to the DOM. The function gets the place and parameters as input, returns a promise of the data to be loaded, and is defined asPromise<T> load(Place place, Parameter parameter).Page
Simple interface that represents a collection of HTML elements. Implementations should be cheap to create and need to implement a single method:Iterable<HTMLElement> elements(Place, Parameter, LoadedData).PlaceManager
Main class of the router module. Performs the following tasks, among others:- keeps track of registered places
- handles navigation events
- parses route parameters
- calls loader functions
- updates the DOM and browser history
The place manager can be customized using builder-like methods and has a start() method to show the initial page.
Annotations
Creating a Place instance can be time-consuming (especially in large applications). This can be automated by using annotations, where an annotation processor takes over the tedious work of creating the Place instances. You can use the following annotations for this purpose:
@Route
Annotation to mark aPageimplementation as a place. The annotation requires a route and can have an optional title, root selector, and reference to a named loader. If the annotated page has exactly one public, static method that returnsLoadData<?>and accepts no parameters, this is used as a loader for the place. If a loader is specified using theloaderattribute, there has to be a matching class or static method annotated with@Loader.@Loader
Annotation to mark a class or public static method as a named loader. If used on a class, the class has to implementLoadData<T>. If used on a static method, the method must returnLoadData<?>and not accept any parameters.
You can also mix and match your own Places instance with the generated one (see below).
URL Encoding
Route parameter values that contain special URL characters (/, ?, #, &, =, spaces) are handled transparently by the router.
Automatic Encoding
Use the overloaded goTo(), href(), and Link.link() methods that accept a route template and parameter values. Pass raw/decoded values — encoding is handled internally:
// Navigate with encoded parameters
placeManager.goTo("/resource/:name", "my/file");
placeManager.goTo("/time/:area/:location", "America", "New_York");
// Generate href with encoded parameters
String url = placeManager.href("/resource/:name", "my/file");
// Create a link with encoded parameters
Link.link(placeManager, "/resource/:name", "my/file");Manual Encoding
When building paths yourself for use with goTo(String path) or href(String path), use the encoding utilities:
// Build an encoded path from a template
String path = Parameter.encodePath("/resource/:name", "my/file");
// → "/resource/my%2Ffile"
placeManager.goTo(path);
// Encode a single value
String encoded = Parameter.encode("my/file"); // → "my%2Ffile"Reading Parameters
Parameter.get() always returns decoded values. Use Parameter.getRaw() for the raw/encoded form:
parameter.get("name") // → "my/file" (decoded)
parameter.getRaw("name") // → "my%2Ffile" (encoded)Encoding Behavior by Method
| Method | Encoding behavior |
|---|---|
Parameter.encode(value) | Encodes a raw value → URL-safe |
Parameter.decode(value) | Decodes a URL-encoded value → raw |
Parameter.encodePath(route, values...) | Encodes values automatically |
Parameter.get(name) | Returns decoded value |
Parameter.getRaw(name) | Returns encoded value |
Parameter.getOrDefault(name, default) | Returns decoded value |
PlaceManager.goTo(route, values...) | Encodes values automatically |
PlaceManager.goTo(path) | No encoding — path used as-is |
PlaceManager.href(route, values...) | Encodes values automatically |
PlaceManager.href(path) | No encoding — path used as-is |
Link.link(pm, route, values...) | Encodes values automatically |
Link.link(pm, route) | No encoding — route used as-is |
Optional Parameters
Routes can have optional parameters using the :param? syntax (trailing ?). Optional parameters must be at the end of the route. A route like /users/:id? matches both /users and /users/123.
Rules
- Optional parameters use the
:name?syntax - Optional parameters must be trailing —
/users/:id?/editis invalid - Multiple trailing optional parameters are allowed:
/a/:b?/:c? - Required parameters can precede optional ones:
/a/:b/:c?
Example
@Route("/users/:id?")
public class UsersPage implements Page {
@Override
public Iterable<HTMLElement> elements(Place place, Parameter parameter, LoadedData data) {
if (parameter.has("id")) {
// show single user
String id = parameter.get("id");
return asList(h(1, "User " + id).element());
} else {
// show user list
return asList(h(1, "All Users").element());
}
}
}Navigation
// Navigate to the user list (optional param omitted)
placeManager.goTo("/users");
// Navigate to a specific user (optional param provided)
placeManager.goTo("/users/:id?", "123");
// Build paths with optional params
Parameter.encodePath("/users/:id?"); // → "/users"
Parameter.encodePath("/users/:id?", "123"); // → "/users/123"
Parameter.encodePath("/a/:b/:c?", "1"); // → "/a/1"
Parameter.encodePath("/a/:b/:c?", "1", "2"); // → "/a/1/2"Sample
Here's an example showing most of the concepts in action:
@Route("/time/:area/:location")
public class TimePage implements Page {
// This is used as the loader for the place
public static LoadData<String> loadTime() {
return (place, parameter) -> {
String area = parameter.get("area");
String location = parameter.get("location");
String url = "https://worldtimeapi.org/api/timezone/" + area + "/" + location;
return fetch(url)
.then(Response::json)
.then(json -> {
JsPropertyMap<String> map = Js.cast(json);
return Promise.resolve(map.get("datetime"));
});
};
}
@Override
public Iterable<HTMLElement> elements(Place place, Parameter parameter, LoadedData data) {
String area = parameter.get("area");
String location = parameter.get("location");
String currentTime = data.get();
return asList(
h(1, "Current time").element(),
p()
.add("It's ")
.add(span().text(currentTime))
.add(" in " + area + "/" + location)
.element());
}
}
@Route(value = "/", title = "Home")
public class HomePage implements Page {
@Override
public Iterable<HTMLElement> elements(Place place, Parameter parameter, LoadedData data) {
return asList(
h(1, "Welcome").element(),
p()
.add("What time is it in ")
.add(a("/time/Europe/Berlin").textNode("Berlin"))
.add("?")
.element());
}
}
public class Application {
public void entryPoint() {
body().add(div().id("main"));
Places allPlaces = places()
.add(new AnnotatedPlaces()) // generated places instance
.add(place("/foo"), FooPage::new)
.add(place("/bar"), BarPage::new)
.children("/level1", places()
.add(place("/"), Level1Page::new) // index page
.add(places() // siblings
.add(place("/foo"), Foo1Page::new)
.add(place("/bar"), Bar1Page::new))
.children("/level2", places()
.add(place("/foo"), Foo2Page::new)
.add(place("/bar"), Bar2Page::new)));
PlaceManager placeManager = new PlaceManager()
.root(By.id("main"))
.register(allPlaces);
placeManager.start();
}
}See the API documentation of PlaceManager for more details.
Dependency
Add the following dependency to use elemento-router:
<dependency>
<groupId>org.jboss.elemento</groupId>
<artifactId>elemento-router</artifactId>
<version>2.5.4</version>
</dependency>In your GWT module, inherit from org.jboss.elemento.Router:
<module>
<inherits name="org.jboss.elemento.Router"/>
</module>