Route Templates
- Defining Route Templates Using
@Route
,@RouteAlias
and@RoutePrefix
- Optional Route Parameter Modifier
- Wildcard Route Parameter Modifier
- Route Parameters Matching a Regex
- Wildcard Route Parameter Using Regex
- Route Template Priority
Route templates define the possibility of including parameters into a route.
A route template is a sequence of segments /
seg_1/
seg_2/
…/
seg_n, where each seg_i (1≤i≤n) is either a fixed segment (a string not starting with :
) or a parameter segment according to the below rule, and at least one segment is of the latter kind.
Route template parameters must comply to the following syntax, :
parameter_name[modifier][(
regex)
] where:
-
parameter_name
is the name of the parameter used to retrieve the value of the parameter when a url matching the template is resolved on the server. -
modifier
is optional and may be one of the following:-
?
defines an optional parameter which might be missing from the URL being resolved. -
*
defines a wildcard parameter which can be used only as the last segment in the template, resolving all segment values at the end of the URL.
-
-
regex
is also optional and defines the regex used to match the parameter value. The regex is compiled usingjava.util.regex.Pattern
and should not contain segment delimiter sign/
. If regex is missing, the parameter accepts any value.
Example: route template parameters example:
product/:identifier/:category?/resource/:id([0-9]*)/:path*
Route template parameters are defined as segments within @Route
, @RouteAlias
, and @RoutePrefix
annotation values.
Parameter values may be retrieved from a BeforeEvent
instance on any Component
in the navigation chain, either parent layout or navigation target.
Parameter values are strings, and convenience methods (described later) exist for converting them to numeric and Boolean types.
Values may be empty, except if the parameter is defined in the last segment (this is because trailing slashes are removed before the URL is parsed).
Defining Route Templates Using @Route
, @RouteAlias
and @RoutePrefix
The final route template is the result of the value composition from the @Route
, @RouteAlias
, and @RoutePrefix
annotations as explained in Router Layouts and Nested Router Targets.
Any route segment defined by these annotations may represent a route parameter.
Note
|
Considering the final template composed from @Route , @RouteAlias and @RoutePrefix values, the parameter names have to be unique.
If a parameter name is found more than once, then when a url is processed the values in the parameters matching will overwrite and only the last value is provided.
|
Example: A simple route where the parameter is defined as a middle segment.
@Route("user/:userID/edit")
public class UserProfileEdit extends Div implements BeforeEnterObserver {
private String userID;
@Override
public void beforeEnter(BeforeEnterEvent event) {
userID = event.getRouteParameters().get("userID").get();
}
}
Note
|
BeforeEnterEvent provides getRouteParameters() which return a RouteParameters instance.
This object contains the parameter values retrieved from the handled URL.
|
Example: Following example demonstrates the use of all annotations to configure two routes on the same view.
In case of routes defined for ForumThreadView
:
-
"threadID/:threadID"
route will resolve URLs matchingforum/category/:categoryID/threadID/:threadID
and -
"threadID/:threadID/comment"
route alias resolves into templateforum/category/:categoryID/threadID/:threadID/comment
.
ForumView
has an empty route for which only the value of its @RoutePrefix
will be used, so it will resolve URLs matching forum/category/:categoryID
.
@Route(value = "")
@RoutePrefix("forum/category/:categoryID")
public class ForumView extends Div implements RouterLayout,
BeforeEnterObserver {
private String categoryID;
private String threadID;
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
final RouteParameters urlParameters = beforeEnterEvent.getRouteParameters();
threadID = null;
categoryID = urlParameters.get("categoryID").get();
urlParameters.get("threadID").ifPresent(value -> threadID = value);
}
}
@Route(value = "threadID/:threadID", layout = ForumView.class)
@RouteAlias(value = "threadID/:threadID/comment", layout = ForumView.class)
public class ForumThreadView extends Div implements BeforeEnterObserver {
private String threadID;
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
threadID = beforeEnterEvent.getRouteParameters().get("threadID").get();
if ("comment".equals(getLastSegment(beforeEnterEvent))) {
new CommentDialog().open();
}
}
}
Note
|
As seen in ForumView , the defined route contains only one parameter, that is categoryID .
However, when used as a layout together with ForumThreadView target, it’s also able to access the parameter defined by ForumThreadView routing annotations.
|
Optional Route Parameter Modifier
A Route parameter may be defined as optional, which means that it may or may not be present in the resolved URL.
Example: Following route defined as user/:userID?/edit
accepts both user/edit
and user/123/edit
resolved URLs.
In the second case, parameter’s value userID
is 123
, while in the first case the Optional
provided by event.getRouteParameters().get("userID")
wraps a null
value.
@Route("user/:userID?/edit")
public class UserProfileEdit extends Div implements BeforeEnterObserver {
private String userID;
@Override
public void beforeEnter(BeforeEnterEvent event) {
userID = event.getRouteParameters().get("userID").
orElse(CurrentUser.get().getUserID());
}
}
Note
|
Optional parameters are eagerly matched from left to right.
For instance, giving the template path/to/:param1?/:param2? following urls match it:
|
-
path/to
with no parameter, -
path/to/value1
, whereparam1
=value1
, -
path/to/value1/value2
, whereparam1
=value1
andparam2
=value2
.
Wildcard Route Parameter Modifier
The wildcard parameter may be defined only as the last segment of the route template matching all segments at the end of the URL.
A wildcard parameter is also optional so it’ll match also no segments at the end of the url, in which case, it’s value when retrieved from RouteParameters
is an empty Optional
.
Example: api/:path*
template may resolve path api/com/vaadin/flow
, where the value of parameter path
is "com/vaadin/flow"
.
@Route("api/:path*")
public class ApiViewer extends Div implements BeforeEnterObserver {
private String path;
@Override
public void beforeEnter(BeforeEnterEvent event) {
path = event.getRouteParameters().get("path").orElse("");
}
}
Note
|
Please notice that since the value can be null we’re using orElse("") method of Optional to retrieve it.
|
A more convenient method of accessing the value of a wildcard parameter is getWildcard
method of RouteParameters
.
getWildcard
method returns an empty list if the value of the parameter is missing.
@Route("api/:path*")
public class ApiViewer extends Div implements BeforeEnterObserver {
private List<String> pathSegments;
@Override
public void beforeEnter(BeforeEnterEvent event) {
pathSegments = event.getRouteParameters().getWildcard("path");
}
}
Route Parameters Matching a Regex
So far, in all examples discussed, the parameter templates accept any value. However, in many cases we expect a specific value for a parameter and we want the view to be shown only when that specific value is present in the URL. This may be achieved by defining a regex for the parameter.
Example: Following example limit the value of the userID
parameter to contain only 9 digits at most making it suitable for an Integer
:
@Route("user/:userID?([0-9]{1,9})/edit")
public class UserProfileEdit extends Div implements BeforeEnterObserver {
private Integer userID;
@Override
public void beforeEnter(BeforeEnterEvent event) {
userID = event.getRouteParameters().getInteger("userID").
orElse(CurrentUser.get().getUserID());
}
}
Note
|
RouteParameters provide also methods to access typed parameter values, that is getInteger , getLong and getBoolean .
Also RouteParameterRegex class define the regex values for these types so the route defined in the above example may be written as @Route("user/:userID?(" + RouteParameterRegex.INTEGER + ")/edit")
|
Wildcard Route Parameter Using Regex
In case of wildcard parameters the regex is applied to all segments at the end of the url, individually. In case one segment fails to match the regex the whole template fails to match the URL.
Example: Following route api/:path*(com|vaadin|flow)
accepts only one of the com
, vaadin
or flow
values as any value of the segments which follow after api
segment.
-
Resolved examples:
-
api/com/vaadin/flow
, where parameterpath
has value"com/vaadin/flow"
. -
api/com/flow
, where parameterpath
has value"com/flow"
-
api/flow/vaadin
, where parameterpath
has value"flow/vaadin"
-
-
Unresolved example:
-
api/com/vaadin/framework
.
-
@Route("api/:path*(com|vaadin|flow)")
public class ApiViewer extends Div implements BeforeEnterObserver {
}
Note
|
Regarding optional parameters which are eagerly matched from left to right, giving the template path/to/:param1?([0-9]*)/:param2?([a-z]*) following urls match it:
|
-
path/to
with no parameter, -
path/to/123
, whereparam1
=123
, -
path/to/123/qwe
, whereparam1
=123
andparam2
=qwe
,
while path/to/qwe/123
do not match the template.
Route Template Priority
For an application with a complex structure, the list of route templates may bring some overlapping in the definition of parameters for each route.
The Router engine will deny by default any attempt to register the same route for more than one view.
Also a route containing optional parameters is in conflict with the same route without the parameters and the last to be register will fail.
The failure consists in a InvalidRouteConfigurationException
being thrown during route registration leading to the termination of the application.
Example: Following configuration will fail since both are resolving items/show
and this is obvious at configuration time.
@Route("items/show")
public static class ShowAllView extends Div {
}
// This route will fail when registered and application is terminated.
@Route("items/show/:filter?")
public static class SearchView extends Div {
}
Note
|
One way to fix this is to make filter parameter mandatory, by removing the optional modifier.
The resulted route will look like @Route("items/show/:filter") .
The other possibility is to remove ShowAllView class and show all items using SearchView when the filter parameter is missing.
|
However, since identifying all possible ambiguities between route templates is computationally intractable, a priority mechanism has to be used when the url is resolved, instead of failing the application when a conflicting route is registered.
Thus, depending on the parameter modifier and the order the routes are registered, one route has priority over the others.
This is applicable for any defined route, on the same navigation view or another view, and using both @Route
or @RouteAlias
.
When resolving a URL, the matcher determines the final route template to apply by matching each URL segment with a template segment in the same position. If at any URL segment there is more than one matching template segment, the following priority order applies:
-
Static segment.
-
Mandatory parameter.
-
Optional parameter.
-
Next segments following the optional parameter.
-
Wildcard parameter.
Note
| We recommend taking care to avoid overlap when defining static routes using annotations, because not all conflicts are caught and annotation discovery order is not fully deterministic. In case of dynamically registered route, the registration order is the developer’s responsibility. |
Example: In the example bellow:
-
items/show
will always resolve intoShowAllView
navigation target, regardless of the order the routes are registered. -
items/phone
will be resolved intoItemView
andidentifier
parameter will have value"phone"
. That’s becauseshow
is a static segment within a registered route and has priority over the parameter in the other route.
@Route("items/:identifier")
public static class ItemView extends Div {
}
@Route("items/show")
public static class ShowAllView extends Div {
}
Same is valid when using @RouteAlias
on the same navigation target.
Example: Following URLs are resolved by different routes registered on the same navigation target.
-
thread/last
is resolved by@RouteAlias("last")
. -
thread/123
is resolved by@RouteAlias(":messageID(" + RouteParameterRegex.INTEGER + ")")
and parametermessageID
will be provided with value"123"
. -
thread/web
is resolved by@RouteAlias(":something?")
and parametersomething
is provided with value"web"
.
@Route(":something?")
@RouteAlias(":messageID(" + RouteParameterRegex.INTEGER + ")")
@RouteAlias("last")
@RoutePrefix("thread")
public static class ThreadView extends Div implements BeforeEnterObserver {
private Integer messageID;
private String something;
private boolean last;
@Override
public void beforeEnter(BeforeEnterEvent event) {
last = "last".equals(getLastSegment(event));
messageID = null;
something = null;
if (!last) {
final RouteParameters urlParameters = event.getRouteParameters();
urlParameters.getInteger("messageID")
.ifPresent(value -> messageID = value);
urlParameters.get("something")
.ifPresent(value -> something = value);
}
}
}
Note
|
Even though @Route(":something?") is the first one defined, it’s the last to try resolving a url because it’s parameter is optional.
|
Note
|
In above example, since all templates resolve into the same navigation target, different parameters are passed to the view.
And even though messageID is a mandatory parameter, it might miss from the RouteParameters when the url is resolved by one of the routes not containing messageID parameter.
|
A wildcard template is the last to process the ending segments of a url, if any other registered Route templates failed.
Example: Here we define 3 route templates where the first two contain wildcard parameters. Here, the templates are:
-
component/:identifier/:path*
-
component/:identifier/:tab(api)/:path*
-
component/:identifier/:tab(overview|samples|links|reviews|discussions)
Any url matched by the any of last two templates is matched by the first one as well. However, due to the priority rules, only urls not matched by the last two templates will end up being processed by the first one, thus:
-
component/button/api/com/vaadin/flow/button
will be processed by thecomponent/:identifier/:tab(api)/:path*
with parameters:-
identifier
=button
-
tab
=api
-
path
=com/vaadin/flow/button
-
-
component/grid/com/vaadin/flow/grid
will be processed by thecomponent/:identifier/:path*
with parameters:-
identifier
=grid
-
path
=com/vaadin/flow/grid
-
-
component/label/links
will be processed by thecomponent/:identifier/:tab(overview|samples|links|reviews|discussions)
with parameters:-
identifier
=label
-
tab
=links
-
@Route(value = ":path*" , layout = ParentView.class)
public static class PathView extends Div {
}
@Route(value = ":tab(api)/:path*", layout = ParentView.class)
public static class ApiView extends Div {
}
@Route(value = ":tab(overview|samples|links|reviews|discussions)", layout = ParentView.class)
public static class OthersView extends Div {
}
@RoutePrefix("component/:identifier")
public static class ParentView extends Div implements RouterLayout {
}
46B0E3D9-C0F2-4F03-AE24-18DEC764A601