Becklyn / mojave

A library of commonly used JavaScript tools and helpers by Becklyn
BSD 3-Clause "New" or "Revised" License
5 stars 3 forks source link

Add `LocalRouter` #267

Closed apfelbox closed 4 years ago

apfelbox commented 4 years ago
Q A
BC breaks? no
New feature? yes
Improvement? no
Bug fix? no
Deprecations? no
Docs PR missing

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:

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

keichinger commented 4 years ago

Awesome stuff! I really do love the API 😍

apfelbox commented 4 years ago

CI failures seem just to be broken BrowserStack (again). The build works, however.

apfelbox commented 4 years ago

Well, I just refactored quite a bit. Will update the top comment in a bit.