@larzilla/boilit-router v1.2.4
BoiLit Router
An easy to use dynamic routing package for Lit 2.x. Note the context of this document will be based on TypeScript
Installation
Run yarn add @larzilla/boilit-router
After installation
Make sure that your server is setup to always load your index
file or route, as this is crucial for your app to behave properly as a single page application.
How To Use Routing
In some place in your application, create a routes
file, somewhere like src/config/routes.ts
is a good start. BoiLit Router comes with some base routes for fallback and example, which is an instance of RouteCollection
class. You can opt to use the base routes by importing them directly, or just creating a new RouteCollection
class of your own.
404 Not Found Route
When there is no matching route, /404
will be the fallback. To show your own content, add a /404
route to your RouteCollection
with a matching component, and it will be rendered. You can opt to not have a /404
route, which will return an empty div
rendered from the RouterOutlet.defaultTag
.
Adding Routes
Every route added is required to have a leading /
for consistency, which uses the RouteCollection.add(routePath: string, route: Route)
method.
The Route
class has two required parameters, the first is the name of the registered tag via the @customElement
decorator in your LitComponent
, and the second is a require()
call to the component matching that registered tag. This will make it possible to lazy load the components and register the necessary components with the dom.
Example of adding a route
routes.add(
"/my/new/route", // The path you want to match. Always lead with a forward slash! "/"
new Route("my-custom-tag", require("@myapp/the/path/to/MyComponent")) // Route object with custom tag name and matching component
);
Example routes.ts
// Using base example routes
import { baseRoutes as routes } from "@larzilla/boilit-router";
const routes = new RouteCollection();
routes.add("/", new Route("home-view", require("@views/HomeView")));
routes.add("/about", new Route("about-view", require("@views/AboutView")));
export default routes;
Or if you do not want to use the base routes, which is the more likley scenario, then use
// Use your own clean set of routes
import { Route, RouteCollection } from "@larzilla/boilit-router";
const routes = new RouteCollection();
routes.add("/", new Route("home-view", require("@views/HomeView")));
routes.add("/about", new Route("about-view", require("@views/AboutView")));
export default routes;
Route Options
The Route
class also has 2 additional constructor paramters.
meta
paramater
This parameter can be used to pass in a meta title
, description
, and then vars
which are Map<String, any>
types to preload data into the routes, before they render. The fourth is an isPrivate: boolean
which can be used as a flag to protect the route.
The following example is just to illustrate the idea of usage. Note that the meta parameter should follow the structure of the RouteMetaData
type. While the RouteMetaData
type is not strict to allow ease of use with static routes, it should be followed when actually passing in meta data.
// say this is a file which retrieved some data or was just setup somewhere.
const myRouteVars = new Map();
myRouteVars.set('var1', [1, 2, 3]);
myRouteVars.set('var2', 'MyString');
myRouteVars.set('var3', new Map());
myRouteVars.set('var4', { heading: 'My title'});
myRouteVars.set('var5', 'My Other Title');
/// Follow the pattern of this type --> import RouteMetaData from "@larzilla/boilit-router"
const myRouteMeta = {
title: 'Page Title',
description: 'My page description',
vars: myRouteVars
}
export myRouteMeta;
Then in your routes.ts
import { Route, RouteCollection } from "@larzilla/boilit-router";
import { myRouteVars } from "wherever/myRoutevars";
const routes = new RouteCollection();
routes.add(
"/myprivateroute", new Route("home-view", require("@views/HomeView"), myRouteVars, true);
);
That is pretty much all there is to using routes, you can go about preloading data how ever you want, and then pass it in as shown above. As you can see, what data is passed into the vars
is very flexible to allow room for any needed data structure within.
Using the RouterOutlet
Component
Using the RouterOutlet
in your app is fairly simple, it is as easy as using any other LitElement
by including a router-outlet
tag in any render()
html you want. The router-outlet
tag takes in the routes setup in your routes.ts
and will render every matching route.
See an example of the an AppRoot
component`
import {html, css, LitElement} from 'lit';
import {customElement} from 'lit/decorators.js';
import '@larzilla/boilit-router/outlet';
import routes from '@your/config/routes';
@customElement('layout-container')
export default class AppRoot extends LitElement {
static override styles = css`
.app-root-container {
width: 80%;
margin: 0 auto;
}`;
override render() {
return html`
<div class="app-root-container">
<header></
<div class="route-view">
<!--- ROUTER OUTLET RENDER BEGIN -->
<router-outlet .routes=${routes}></router-outlet>
<!--- ROUTER OUTLET RENDER END -->
</div>
</div>
`;
}
}
That about covers static routes, mostly everthing is taken care of if you do not care about page titles and description. For dynamic routes, any component attached to the route will just need couple of small changes, but still fairly easy, which we'll get into next.
Dynamic Routes
Dynamic route follow a common pattern of leading the portion of the URL you want to be a parameter with a colon, such as.
// Dynamic route
routes.add(
"/dynamic/:firstValue/example/:secondValue", // See the portions lead by a colon.
new Route("dynamic-view", require("@views/DynamicView")) // And then as before, use your component that will process the route.
);
Views that will parse data to render
When routes are run through the router, the RouterOutlet
will take the RouteMetaData
and combined it will any parmeters that were found in the dynamic route, into a RenderProps
object. If the the route is static, it will still pass the RenderProps
object with any data from the route.
Using the RenderProps
Every component rendered through the get a .props
attribute with the value of a compiled RenderProps
object containing the RouteMetaData
and the parameters from the url. Too access this data, see the following example.
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import RenderProps from "../core/RenderProps";
@customElement("dynamic-example-view")
export class DynamicExampleView extends LitElement {
static override styles = css`
.dynamic-example-view {
text-align: center;
}
h2 {
font-size: 4em;
font-weight: bold;
}
.dynamic-example-message {
padding: 1em;
margin: 1em;
background-color: #eee;
border: 1px solid #ccc;
border-radius: 5px;
}
`;
@property()
props: RenderProps = new RenderProps({});
override render() {
return html`
<div class="dynamic-example-view">
<h2>Dynamic Example</h2>
<div class="dynamic-example-message">
<p>
This is an example of a dynamic routes in the boilit-router package.
</p>
<p>
First Value (:firstValue) = ${this.props.getParam(":firstValue")}
</p>
<p>
Second Value (:secondValue) = ${this.props.getParam(":secondValue")}
</p>
</div>
</div>
`;
}
}
Accessing the Dynamic Paramters
Notice in the above example, the getParam()
calls. To get the value you want, just pass the same exact value from the route into the getParam()
method, including the colon for consistency.
Example of route and getting the value at render
// ... in routes.ts
routes.add(
"/my/product/:productId/option/:productOption",
new Route("product-view", require("@views/ProductView"))
);
// Now...
// ... in the component render()
// Make sure props is defined when mounting.
@property()
props: RenderProps = new RenderProps({});
override render () {
return html`
<div class="dynamic-example-view">
<h2>My Product Page</h2>
<div class="dynamic-example-message">
<p>Product ID: ${this.props.getParam(':productId')}</p>
<p>Product Option: ${this.props.getParam(':productOption')}</p>
</div>
</div>
`;
}
That is all that is needed to get dynamic parameters.
Getting Query String Parameters
The RenderProps
class will use a URLSearchParams
class with all of the window.location.search
values. All of the data will be available through the getQueryParam()
method.
// ... LitElement `render()` to get ?name=value&street=value from the url.
override render () {
return html`
<div class="dynamic-example-view">
<h2>My Product Page</h2>
<div class="dynamic-example-message">
<p>'name' Get Var: ${this.props.getQueryParam('name')}</p>
<p>'street' Get Var: ${this.props.getQueryParam('street')}</p>
</div>
</div>
`;
}
Getting the title
, description
, and vars
.
It is very similar to getting params. Title and description do not need a parameters, but var will require the string value key as it was defined.
- Get the title:
this.getTitle()
- Get the title:
this.getTitle()
- Get a a value from
vars
title:this.getVar('my-var-key')
Of course lifecyle methods could be used as needed to have more concise calls in the render()
, but that is subjective to each developer.
Linking to Routes
Ok, so now that we can setup routes and get data, we need to link to them by using the RouteLink
component, and then using the route-link
tag with a parameter to tell it where it points to.
Example
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import "@larzilla/boilit-router/link";
@customElement("layout-nav-main")
export default class LayoutNavMain extends LitElement {
static override styles = css`
route-link {
margin: 15px;
}
route-link:hover {
cursor: pointer;
}
`;
override render() {
return html`
<div class="layout-nav-main">
<route-link uri="/">Home</route-link>
<route-link uri="/products">Products</route-link>
<route-link uri="/product/specials">Product Specials</route-link>
<route-link uri="/about">About</route-link>
<route-link uri="/Contact">Contact</route-link>
</div>
`;
}
}
That is all there is to creating a link to a route.
Future Plans
As this is in the early stages, work is being done to handle query strings with PopState.
Contributing
Anyone that would like to contribute, feel free to put in a pull request and I will take a look.