@merzin/router
v0.5.3
Published
Router for web components and vanilla function components.
Readme
Router
Router for web components and vanilla function components.
Defining Routes
Routes are defined using the defineRoute function. Function defineRoute
takes two (2) arguments, a pathname or hash and a
Factory<HTMLElement>. You can
use it to map a pathname or a hash to a web component or a vanilla function
component.
Web Component
import { defineRoute } from "@merzin/router";
class LandingPage extends HTMLElement {
constructor() {
super();
this.innerHTML = "<h1>Welcome!</h1>";
}
}
customElements.define("landing-page", LandingPage);
defineRoute("/", LandingPage);
// You can also use the same instance always by instantiating the component
// defineRoute("/", new LandingPage());Vanilla Function Component
import { defineRoute } from "@merzin/router";
function LandingPage() {
const div = document.createElement("div");
div.innerHTML = "<h1>Welcome!</h1>";
return div;
}
defineRoute("/", LandingPage);
// You can also use the same instance always by instantiating the component
// defineRoute("/", LandingPage());Parameterized Routes
You can define parameterized routes using the :param syntax. The parameters
object is passed to the component as an argument.
import { defineRoute } from "@merzin/router";
// Parameterized Web Component
class HelloPage extends HTMLElement {
constructor({ name }: Record<string, string>) {
super();
this.innerHTML = `<h1>Hello, ${name}!</h1>`;
}
}
customElements.define("hello-page", HelloPage);
// Parameterized Vanilla Function Component
function GoodbyePage({ name }: Record<string, string>) {
const div = document.createElement("div");
div.innerHTML = `<h1>Goodbye, ${name}!</h1>`;
return div;
}
defineRoute("/hello/:name", HelloPage);
defineRoute("/goodbye/:name", GoodbyePage);Note: Make sure you match the trailing slash in the pathname if you want to define a route with a trailing slash. For example,
"/hello/:name/"will not match"/hello/John"but will match"/hello/John/".
Modals
Modals are defined by providing a hash instead of a pathname. The hash should only start with "#" and otherwise not include "#" as it is used as a delimiter to allow rendering multiple modals at once.
import { defineRoute } from "@merzin/router";
class LoginModal extends HTMLElement {
constructor() {
super();
this.style.position = "fixed";
this.innerHTML = "<h1>Login</h1>";
}
}
customElements.define("login-modal", LoginModal);
defineRoute("#login", LoginModal);Starting Router
To start the router, you need to call the startRouter function. It takes an
optional argument which is the root element where the router will render the
components. If not provided, it will use document.body as the root element.
import { startRouter } from "@merzin/router";
startRouter(document.getElementById("root")!);The optional argument can also be an object with properties viewRoot and
modalRoot if you need to render view and modals in different places.
import { startRouter } from "@merzin/router";
startRouter({
viewRoot: document.querySelector("main")!,
modalRoot: document.body,
});Extended History API
This library extends the browser history API to provide a way to listen to URL changes and to manage history stack.
Navigating
You can navigate using browser history API. Anchor links are intercepted and use
history.pushState if href is same origin and target is not "_blank".
history.pushState({}, "", "/hello/John");Pushing to modal stack can be done by appending to the hash.
history.pushState({}, "", location.hash + "#login");Navigating forwards and backwards can be done with history.forward() and
history.back(). This library extends these methods to accept an optional
fallback URL. Calling without argument will result in the default behavior.
When calling history.back("/fallback"), it will check if there is a previous
history entry with the same origin. If there is, it will navigate to that entry.
If not, it will call history.replaceState({}, "", "/fallback") instead. This
is useful for ensuring that the user stays on the same page. If you don't want
to provide a fallback URL but still want to prevent navigating away from the
current origin, you can use history.back(true).
Similarly when calling history.forward("/fallback"), it will check if there is
a next history entry with the same origin. If there is, it will navigate to that
entry. If not, it will call history.pushState({}, "", "/fallback") instead.
Calling history.forward(true) will do nothing if there is no next same origin
history entry.
State Change Event
By default we can listen to "popstate" event to detect URL changes. This event
is fired when navigating in the history stack using the browser's back and
forward buttons, or when calling history.back(), history.forward(), or
history.go(). However, there is no event fired when calling
history.pushState() or history.replaceState(). This library extends these
methods to provide a "statechange" event that is fired always when the URL
changes. Class StateChangeEvent contains the following properties:
state: The state object passed tohistory.pushState()orhistory.replaceState().delta: The amount moved in the history stack. Positive values indicate moving forward, negative values indicate moving backward, and zero indicates no movement i.e. replacement.index: The current index in the history stack.
History Stack
This library extends browser history API with history.stack readonly property.
It represents the current session history stack and is of type HistoryStack.
interface HistoryStack {
index: number; // current position in the items list
items: HistoryStackItem[]; // list of history items
}
interface HistoryStackItem {
href: string; // the URL string without origin, i.e. pathname, hash and search
state: { __index: number; [key: string]: any };
}As you may notice HistoryStackItem has a __index property. This is injected
in history.pushState and history.replaceState calls to allow the router to
keep track of the history stack during "popstate" event.
On Back
You can bind a function to be called when the user navigates back in the history
stack with onBack(). This function pushes current URL to the stack and
whenever user navigates to an entry before it the callback gets called. This is
useful for handling cases where you want to perform some action when the user
navigates back, such as clearing selection or hiding context menu.
import { onBack } from "@merzin/router/back";
onBack(() => {});Function
onBack()is not recommended for modals as after resolving it leaves a dangling history entry.
