Skip to content

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 as Promise<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 a Page implementation 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 returns LoadData<?> and accepts no parameters, this is used as a loader for the place. If a loader is specified using the loader attribute, 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 implement LoadData<T>. If used on a static method, the method must return LoadData<?> 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:

java
// 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:

java
// 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:

java
parameter.get("name")    // → "my/file" (decoded)
parameter.getRaw("name") // → "my%2Ffile" (encoded)

Encoding Behavior by Method

MethodEncoding 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?/edit is invalid
  • Multiple trailing optional parameters are allowed: /a/:b?/:c?
  • Required parameters can precede optional ones: /a/:b/:c?

Example

java
@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());
        }
    }
}
java
// 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:

java
@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:

xml
<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:

xml
<module>
    <inherits name="org.jboss.elemento.Router"/>
</module>