|
/** |
|
* @typedef {Record<string | number, string>} Routes |
|
*/ |
|
|
|
/** |
|
* @typedef {Object} RouterOptions |
|
* |
|
* @property {string} rootSelector |
|
* @property {string} linkSelector |
|
*/ |
|
|
|
/** @type {Routes} */ |
|
let _routes; |
|
/** @type {RouterOptions} */ |
|
let _routerOptions; |
|
/** @type {Array<HTMLAnchorElement>} */ |
|
let _routerLinks; |
|
|
|
/** @type {RouterOptions} */ |
|
const _defaultRouterOptions = { |
|
rootSelector: '[data-router-root]', |
|
linkSelector: '[data-router-link]', |
|
}; |
|
|
|
/** |
|
* Navigate to a specific route |
|
* @param {string} [route] - Route to navigate to |
|
* @returns {Promise<boolean | Error>} |
|
* @throws {Error} - When root selector is not found |
|
* @throws {Error} - When route is not found |
|
*/ |
|
const _goToRoute = async (route) => { |
|
try { |
|
const raw = await fetch(route); |
|
const html = await raw.text(); |
|
const rootElement = document.querySelector(_routerOptions.rootSelector); |
|
|
|
if (!rootElement) { |
|
throw Error( |
|
`Unable to find element with css selector: ${_routerOptions.rootSelector}` |
|
); |
|
} |
|
|
|
rootElement.innerHTML = html; |
|
|
|
return true; |
|
} catch { |
|
throw Error(`Unable to get html route: ${_routes[route]}`); |
|
} |
|
}; |
|
|
|
/** |
|
* Handle the navigation change |
|
* @returns {Promise<boolean>} |
|
*/ |
|
const _handleLocationChange = async () => { |
|
const path = window.location.pathname; |
|
const route = _routes[path] || _routes['404']; |
|
|
|
return _goToRoute(route); |
|
}; |
|
|
|
/** |
|
* Get an array of HTML anchor elements |
|
* @param {string} [selector] - CSS selector to find the router links |
|
* @returns {Array<HTMLAnchorElement>} |
|
*/ |
|
const _getRouterLinks = (selector) => { |
|
const links = document.querySelectorAll(selector); |
|
|
|
if (!links) |
|
throw Error( |
|
`Unable to find element with css selector: ${_routerOptions.rootSelector}` |
|
); |
|
|
|
return [...links]; |
|
}; |
|
|
|
/** |
|
* Handles the click handler for the router link |
|
* @param {MouseEvent} [e] - MouseEvent on click of router link |
|
*/ |
|
const _handleRouterLinkClick = (e) => { |
|
e.preventDefault(); |
|
|
|
/** @type {HTMLAnchorElement} [target] */ |
|
const target = e.target; |
|
|
|
window.history.pushState({}, '', target.href); |
|
|
|
_handleLocationChange(); |
|
}; |
|
|
|
/** |
|
* Sets and handles the window popstate event |
|
*/ |
|
const _addPopstateEvent = () => { |
|
window.addEventListener('popstate', _handleLocationChange); |
|
}; |
|
|
|
/** |
|
* Sets and handles the router link click events |
|
*/ |
|
const _addRouterLinkEvents = () => { |
|
_routerLinks.forEach((route) => { |
|
route.addEventListener('click', _handleRouterLinkClick); |
|
}); |
|
}; |
|
|
|
/** |
|
* Main export to initialize the routes |
|
* @param {Routes} [routes] - Routes |
|
* @param {RouterOptions} [options] - Options for routes |
|
* @returns {Promise<boolean>} |
|
*/ |
|
export const initializeRoutes = async ( |
|
routes, |
|
options = _defaultRouterOptions |
|
) => { |
|
_routes = routes; |
|
_routerOptions = options; |
|
_routerLinks = _getRouterLinks(_routerOptions.linkSelector); |
|
|
|
_addPopstateEvent(); |
|
_addRouterLinkEvents(); |
|
|
|
return await _handleLocationChange(); |
|
}; |
|
|
|
/** |
|
* Go to a specific route |
|
* @param {string} [path] |
|
* @returns {Promise<boolean>} |
|
*/ |
|
export const goToRoute = (route) => { |
|
const routeToGoTo = _routes[route] || _routes['404']; |
|
|
|
return _goToRoute(routeToGoTo); |
|
}; |