This PR adds a generic local router, that handles on-page navigation using query params. It is indeed just called with the generic name "local router", as most realistically this is the only place where such a router is used, and in such cases this router should be enough.
It is used like this:
import {LocalRouter} from "mojave/url/LocalRouter";
import {stringParameter, numberParameter, booleanParameter} from "mojave/url/router-parameters";
const router = new LocalRouter();
router
// register your routes with name, handler and param normalizers
// there is a list of predefined param normalizers. They are supposed to receive the param with the given name
// and return either the normalized version in the right type, or throw an error
.on(
"list",
param => this.handleList(param.page, param.query),
{
page: numberParameter(),
query: stringParameter(""),
}
)
.on(
"add",
() => this.addAction(),
{}
)
.on<{id: number}>(
"edit",
params => this.editAction(params.id),
{
id: numberParameter(),
someFlag: booleanParameter(true),
}
)
// initialize the router
.init("list", {page: 1});
router.navigate("edit", {id: 5});
So, you basically define routes, their handlers and how the parameters are normalized. The parameter normalization can fail:
in case of route match it just won't be a match
in case of .navigate() it will throw an error (as it is a bug basically).
We have proper type support in .on(), unfortunately in the case of .navigate() we don't have TypeScript support, as the types are lost at this point (you can't statically have a dynamic indexed type list). But the call will – thanks to the normalizers – be properly normalized + error checked at runtime (which is important, as there are a lot of user-provided values involved).
The normalizers are pretty simple functions. They receive the raw value and either transform it (if possible) or just throw an error.
Edit Refactored:
so now we also have proper sanitation in .init() and .navigate(), by refactoring the parameters.
They are now a function, that takes an optional default value and return a function that parses the given value:
If the given value is valid, use it.
If the given value is invalid (or missing) and there is a default, use the default value (= optional parameter)
If the given value is invalid (or missing) and there is NO default value, throw.
So, to reiterate:
passing a default: optional parameter
no default: required parameter
This simplification / refactoring enables us to properly fill incomplete .init() and .navigate() calls (like above) with the default values. Also the initializers are now strictly type safe.
The normalizers were also extended to pass values in the correct type straight through. This enables us to have REAL type safety even in .init() and .navigate().
I also added an early check if the fallbackRoute in .init() isn't defined.
This PR adds a generic local router, that handles on-page navigation using query params. It is indeed just called with the generic name "local router", as most realistically this is the only place where such a router is used, and in such cases this router should be enough.
It is used like this:
So, you basically define routes, their handlers and how the parameters are normalized. The parameter normalization can fail:
.navigate()
it will throw an error (as it is a bug basically).We have proper type support in
.on()
, unfortunately in the case of.navigate()
we don't have TypeScript support, as the types are lost at this point (you can't statically have a dynamic indexed type list). But the call will – thanks to the normalizers – be properly normalized + error checked at runtime (which is important, as there are a lot of user-provided values involved).The normalizers are pretty simple functions. They receive the raw value and either transform it (if possible) or just throw an error.
Edit Refactored: so now we also have proper sanitation in
.init()
and.navigate()
, by refactoring the parameters.They are now a function, that takes an optional default value and return a function that parses the given value:
So, to reiterate:
This simplification / refactoring enables us to properly fill incomplete
.init()
and.navigate()
calls (like above) with the default values. Also the initializers are now strictly type safe.The normalizers were also extended to pass values in the correct type straight through. This enables us to have REAL type safety even in
.init()
and.navigate()
.I also added an early check if the
fallbackRoute
in.init()
isn't defined.Supersedes and closes #265