From 47a0665b638cf41d17da7f6bd521e86c6d6472f0 Mon Sep 17 00:00:00 2001 From: Ali Mihandoost Date: Mon, 14 Mar 2022 00:45:51 +0330 Subject: [PATCH 1/4] refactor(router): refactor to simpel router object --- package/router/src/router.ts | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/package/router/src/router.ts b/package/router/src/router.ts index 52898dd83..60e04f12e 100644 --- a/package/router/src/router.ts +++ b/package/router/src/router.ts @@ -2,14 +2,14 @@ import {joinParameterList, logger, routeSignalProvider} from './core'; import {routeChangeSignal} from './signal'; import {clickTrigger} from './trigger-click'; import {popstateTrigger} from './trigger-popstate'; -import type {InitOptions, Route} from './type'; +import type {InitOptions, Route, RoutesConfig} from './type'; -export {routeChangeSignal}; +export type {Route, RoutesConfig} from './type'; /** * Initial and config the Router. */ -export function initialRouter(options?: InitOptions): void { +function initial(options?: InitOptions): void { logger.logMethodArgs('initialRouter', {options}); clickTrigger.enable = options?.clickTrigger ?? true; @@ -29,7 +29,7 @@ export function initialRouter(options?: InitOptions): void { * * @example */ -export function makeUrl(route: Partial): string { +function makeUrl(route: Partial): string { logger.logMethodArgs('makeUrl', {route}); let href = ''; @@ -52,3 +52,25 @@ export function makeUrl(route: Partial): string { return href; } + + +export const router = { + get currentRoute(): Route { + const route = routeChangeSignal.value; + if (route == null) { + throw (new Error('route_not_initialized')); + } + return route; + }, + + initial, + + makeUrl, + + outlet, + + /** + * Signal interface of 'route-change' signal. + */ + signal: routeChangeSignal, +} as const; From 45852a809a9f48fb09cf9a8c5ecc3f6519f106cd Mon Sep 17 00:00:00 2001 From: Ali Mihandoost Date: Mon, 14 Mar 2022 00:46:04 +0330 Subject: [PATCH 2/4] feat(router): make outlet --- package/router/src/router.ts | 92 ++++++++++++++++++++++++++++++++++++ package/router/src/type.ts | 7 ++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/package/router/src/router.ts b/package/router/src/router.ts index 60e04f12e..8d0a3bcaf 100644 --- a/package/router/src/router.ts +++ b/package/router/src/router.ts @@ -53,6 +53,98 @@ function makeUrl(route: Partial): string { return href; } +/** + * The result of calling the current route's render() callback base on routesConfig. + * + * outlet return `routesConfig.list[routesConfig.map(currentRoute)].render(currentRoute)` + * + * if `routesConfig.map()` return noting or not found in the list the "404" route will be used. + * if route location is app root and `routesConfig.map()` return noting then redirect to home automatically + * + * ```ts + * const routes: routesConfig = { + * map: (route: Route) => route.sectionList[0]?.toString(), + * + * list: { + * 'about': { + * render: () => html``, + * }, + * 'product-list': { + * render: () => { + * import('./page-product-list.js'); // lazy loading page + * html``, + * } + * }, + * 'contact': { + * render: () => html``, + * }, + * + * 'home': { + * render: () => html``, + * }, + * '404': { + * render: () => html``, + * }, + * }, + * }; + + * router.outlet(routes); + * ``` + */ +function outlet(routesConfig: RoutesConfig): unknown { + logger.logMethodArgs('outlet', {routesConfig}); + + const currentRoute = routeChangeSignal.value; + if (currentRoute == null) { + logger.accident('outlet', 'route_not_initialized', 'Signal "route-change" not dispatched yet'); + return; + } + + let page = routesConfig.map(currentRoute); + + if (page == null && currentRoute.sectionList.length === 0) { // root + logger.incident( + 'outlet', + 'redirect_to_home', + 'Route location is app root and routesConfig.map() return noting then redirect to home automatically', + ); + + page = 'home'; + + if (typeof routesConfig.list[page]?.render !== 'function') { // 'home' not defined! + logger.accident( + 'outlet', + 'no_render_for_home', + 'routesConfig.list["home"] not defined', + {page, currentRoute, routesConfig}, + ); + routesConfig.list[page] = {render: () => 'Home Page!'}; + } + } + + if (page == null || typeof routesConfig.list[page]?.render !== 'function') { // 404 + logger.accident( + 'outlet', + 'redirect_to_404', + 'Requested page not defined in routesConfig.list', + {page, currentRoute, routesConfig}, + ); + + page = '404'; + + if (typeof routesConfig.list[page]?.render !== 'function') { // 404 + logger.accident( + 'outlet', + 'no_render_for_404', + 'Page "404" not defined in routesConfig.list', + {page, currentRoute, routesConfig}, + ); + routesConfig.list[page] = {render: () => '404 Not Found!'}; + } + } + + return routesConfig.list[page].render(currentRoute); +} export const router = { get currentRoute(): Route { diff --git a/package/router/src/type.ts b/package/router/src/type.ts index 1aff51831..d7b6a3516 100644 --- a/package/router/src/type.ts +++ b/package/router/src/type.ts @@ -44,4 +44,9 @@ export interface InitOptions { popstateTrigger?: boolean; } - +export interface RoutesConfig { + map: (route: Route) => string | undefined; + list: Record unknown; + }>; +} From 54576f0397df1d1471467564529a01b4e75335bf Mon Sep 17 00:00:00 2001 From: Ali Mihandoost Date: Mon, 14 Mar 2022 00:46:20 +0330 Subject: [PATCH 3/4] feat(demo): router outlet demo --- demo/router/index.html | 4 +++- demo/router/index.ts | 50 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/demo/router/index.html b/demo/router/index.html index 111599368..af0eb8cef 100644 --- a/demo/router/index.html +++ b/demo/router/index.html @@ -12,11 +12,13 @@

Check the console

    - +
+
+ diff --git a/demo/router/index.ts b/demo/router/index.ts index 7f2717663..7d32f374a 100644 --- a/demo/router/index.ts +++ b/demo/router/index.ts @@ -1,9 +1,45 @@ -import {initialRouter, routeChangeSignal} from '@alwatr/router'; +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import {router} from '@alwatr/router'; +import type {Route, RoutesConfig} from '@alwatr/router'; -initialRouter(); +/** + * Initial and config the Router. + */ +router.initial(); -routeChangeSignal.addListener((route) => { - console.info('routeChangeSignal', route); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.querySelector('textarea')!.value = JSON.stringify(route, null, 2); -}); +const routes: RoutesConfig = { + map: (route: Route): string | undefined => route.sectionList[0]?.toString(), + + list: { + 'about': { + render: (): string => '

About Page

', + }, + 'product-list': { + render: (): string => '

Product List ...

', + }, + 'contact': { + render: (): string => '

Product Page

', + }, + + 'home': { + render: (): string => '

Home Page

', + }, + '404': { + render: (): string => '

404 Not Found!

', + }, + }, +}; + +/** + * Your render process, can be lit-element requestUpdate or any other framework request render method. + */ +function render(): void { + console.info('render'); + document.querySelector('textarea')!.value = JSON.stringify(router.currentRoute, null, 2); + document.querySelector('.render')!.innerHTML = router.outlet(routes) as string; +} + +/** + * Request update in route change. + */ +router.signal.addListener(render); From 4782e4a935bb6cfd5e21cc875dea28b2da154547 Mon Sep 17 00:00:00 2001 From: Ali Mihandoost Date: Mon, 14 Mar 2022 04:03:10 +0330 Subject: [PATCH 4/4] doc(router): document new APIs --- package/router/README.md | 84 ++++++++++++++++++++++++++++++++++-- package/router/src/router.ts | 9 +++- package/router/src/type.ts | 2 +- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/package/router/README.md b/package/router/README.md index f8ababd1a..acb172eec 100644 --- a/package/router/README.md +++ b/package/router/README.md @@ -4,7 +4,85 @@ Elegant powerful router (fundamental advance browser page routing) based on the ## Example usage -```js -import { initialRouter } from 'https://esm.run/@alwatr/router'; -initialRouter(); +### Prepare + +```ts +import { router } from 'https://esm.run/@alwatr/router'; + +/** + * Initial and config the Router. + */ +router.initial(); + +/** + * Add listener to `route-change` signal. + */ +router.signal.addListener((route) => { + console.log(route); +}); +``` + +### Rout object + +Example page url: `https://example.com/product/100/book?cart=1&color=white#description` + +```ts +interface Route +{ + sectionList: Array; // [product, 100, book] + queryParamList: ParamList; // {cart: 1, color: 'white'} + hash: string; // '#description' +} +``` + +### Dynamic page rendering + +```ts +const routes: routesConfig = { + map: (route: Route) => route.sectionList[0]?.toString(), + + list: { + 'about': { + render: () => html``, + }, + 'product-list': { + render: () => { + import('./page-product-list.js'); // lazy loading page + html``, + } + }, + 'contact': { + render: () => html``, + }, + + 'home': { + render: () => html``, + }, + '404': { + render: () => html``, + }, + }, +}; + +... + +// Any render function can be used. +render() { + router.outlet(routes); +} + +... + +// Request update (call render again) on route change. +router.signal.addListener(() => this.requestUpdate()); +``` + +### Make link from semantic route + +`router.makeUrl(route)` + +Make anchor valid href from route. + +```html + ``` diff --git a/package/router/src/router.ts b/package/router/src/router.ts index 8d0a3bcaf..72415bb02 100644 --- a/package/router/src/router.ts +++ b/package/router/src/router.ts @@ -27,7 +27,11 @@ function initial(options?: InitOptions): void { /** * Make anchor valid href from route. * - * @example + * Example: + * + * ```html + * + * ``` */ function makeUrl(route: Partial): string { logger.logMethodArgs('makeUrl', {route}); @@ -146,6 +150,9 @@ function outlet(routesConfig: RoutesConfig): unknown { return routesConfig.list[page].render(currentRoute); } +/** + * The Router API. + */ export const router = { get currentRoute(): Route { const route = routeChangeSignal.value; diff --git a/package/router/src/type.ts b/package/router/src/type.ts index d7b6a3516..6180d9c6a 100644 --- a/package/router/src/type.ts +++ b/package/router/src/type.ts @@ -6,7 +6,7 @@ export interface Route // href: https://example.com/product/100/book?cart=1&color=white#description sectionList: Array; // [product, 100, book] queryParamList: ParamList; // {cart: 1, color: 'white'} - hash: string; // '#header' + hash: string; // '#description' } // @TODO: description