@zyrab/domo-router
Version:
History-based SPA router for Domo. Supports nested routes, navigation guards, and SSG-friendly behavior.
66 lines (59 loc) • 2.69 kB
JavaScript
// router.rendering.js
import { _root, _routes, _listeners, _previousUrl } from "./state.js";
import { info, match, notify, parseUrl, path, resolveLayout } from "./utils.js";
/**
* Renders the component associated with a route into the root element.
* Updates document title and meta description if provided in route meta.
* Handles component output as HTMLElement, string, or throws an error.
* @param {{component: Function, meta: object}} routeData - The component function and its meta data.
* @param {object} params - Parameters extracted from the URL.
* @returns {Promise<void>}
*/
export async function render({ component, meta, layout }, params) {
try {
const comp = await component(params);
const content = comp.build();
const currentLayout = resolveLayout(layout);
_root?.replaceChildren();
if (content instanceof HTMLElement) {
currentLayout ? _root.appendChild(currentLayout(content)) : _root.appendChild(content);
} else if (typeof content === "string") {
const wrapper = document.createElement("div");
wrapper.textContent = content;
currentLayout ? _root.appendChild(currentLayout(wrapper)) : _root.appendChild(wrapper);
} else {
throw new Error("❌ Unsupported component output type. Component must return an HTMLElement or a string.");
}
if (meta.title) {
document.title = meta.title;
}
const descriptionMeta = document.querySelector("meta[name='description']");
if (meta.description && descriptionMeta) {
descriptionMeta.setAttribute("content", meta.description);
}
} catch (error) {
console.error("❌ Router rendering error:", error);
// Fallback to a 404/error component if defined
const fallbackRoute = await _routes["*"]?.component({ error: error.message });
const errorLayout = resolveLayout(_routes["*"].layout);
if (fallbackRoute) {
_root.replaceChildren();
errorLayout ? _root.appendChild(errorLayout(fallbackRoute.build())) : _root.appendChild(fallbackRoute.build());
}
}
}
/**
* Loads a route based on the given URL, renders its component, and notifies listeners.
* @param {string} url - The URL to load.
* @returns {Promise<void>}
*/
export async function load(url) {
const { segments, pureUrl } = parseUrl(url); // Use info to get route data
const { routeData, params, outlet } = match(segments); // Recalculate info based on target URL
if (path() !== url) {
history.pushState(null, "", pureUrl);
}
if (url === _previousUrl) return;
await render(routeData, params, outlet);
if (_listeners.length > 0) notify(info());
}