From 471fd49063983da02670070c0ddd23322c195687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 11 Sep 2019 13:34:29 +0200 Subject: [PATCH] [IMP] router: replace t-routecomponent directive by RouteComponent closes #287 --- doc/readme.md | 1 + doc/router.md | 12 +- src/index.ts | 3 +- src/router/RouteComponent.ts | 27 +++ src/router/Router.ts | 4 +- src/router/directive.ts | 31 ---- ...rective.test.ts => RouteComponent.test.ts} | 20 +-- tests/router/TestRouter.ts | 2 - .../__snapshots__/RouteComponent.test.ts.snap | 60 +++++++ .../__snapshots__/directive.test.ts.snap | 165 ------------------ 10 files changed, 108 insertions(+), 217 deletions(-) create mode 100644 src/router/RouteComponent.ts delete mode 100644 src/router/directive.ts rename tests/router/{directive.test.ts => RouteComponent.test.ts} (86%) create mode 100644 tests/router/__snapshots__/RouteComponent.test.ts.snap delete mode 100644 tests/router/__snapshots__/directive.test.ts.snap diff --git a/doc/readme.md b/doc/readme.md index 3e189226..d3a3a4d3 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -14,6 +14,7 @@ owl Observer router Link + RouteComponent Router store Store diff --git a/doc/router.md b/doc/router.md index e9202a71..ace4fdbf 100644 --- a/doc/router.md +++ b/doc/router.md @@ -8,7 +8,7 @@ - [Route Definition](#route-definition) - [Router](#router) - [Navigation Guards](#navigation-guards) - - [`t-routecomponent`](#t-routecomponent) + - [RouteComponent](#routecomponent) - [Link](#link) ## Overview @@ -26,8 +26,8 @@ The Owl router support the following features: - route redirection - navigation guards - parameterized routes -- a `` component -- a `t-routecomponent` directive +- a `` component +- a `` component Note that it is still in early stage of developments, and there are probably still some issues. @@ -148,15 +148,15 @@ async function protectRoute({ env, to }) { A navigation guard is a function that returns a promise, which either resolves to `true` (the navigation is accepted), or to another destination object. -### `t-routecomponent` +### `RouteComponent` -The `t-routecomponent` directive directs Owl to render the component associated +The `RouteComponent` component directs Owl to render the component associated to the currently active route (if any): ```xml
- +
``` diff --git a/src/index.ts b/src/index.ts index 19444df0..28907d81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,12 +11,13 @@ import { ConnectedComponent } from "./store/connected_component"; import { Store } from "./store/store"; import * as _utils from "./utils"; import { Link } from "./router/Link"; +import { RouteComponent } from "./router/RouteComponent"; import { Router } from "./router/Router"; export { Component } from "./component/component"; export { QWeb }; export const core = { EventBus, Observer }; -export const router = { Router, Link }; +export const router = { Router, RouteComponent, Link }; export const store = { Store, ConnectedComponent }; export const utils = _utils; diff --git a/src/router/RouteComponent.ts b/src/router/RouteComponent.ts new file mode 100644 index 00000000..164cdef1 --- /dev/null +++ b/src/router/RouteComponent.ts @@ -0,0 +1,27 @@ +import { Component } from "../component/component"; + +export const ROUTE_COMPONENT_TEMPLATE_NAME = "__owl__-router-component"; +export const ROUTE_COMPONENT_TEMPLATE = ` + + + + + `; + +export class RouteComponent extends Component { + template = ROUTE_COMPONENT_TEMPLATE_NAME; + routes: any[] = []; + constructor(parent, props) { + super(parent, props); + const router = this.env.router; + for (let name of router.routeIds) { + const route = router.routes[name]; + if (route.component) { + this.routes.push({ + name: route.name, + component: "__component__" + route.name + }); + } + } + } +} diff --git a/src/router/Router.ts b/src/router/Router.ts index 9d45025e..91dd3525 100644 --- a/src/router/Router.ts +++ b/src/router/Router.ts @@ -1,6 +1,6 @@ import { Env } from "../component/component"; import { QWeb } from "../qweb/index"; -import { makeDirective } from "./directive"; +import { ROUTE_COMPONENT_TEMPLATE, ROUTE_COMPONENT_TEMPLATE_NAME } from "./RouteComponent"; import { LINK_TEMPLATE, LINK_TEMPLATE_NAME } from "./Link"; import { shallowEqual } from "../utils"; @@ -87,7 +87,7 @@ export class Router { // setup link and directive env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE); - QWeb.addDirective(makeDirective(env)); + env.qweb.addTemplate(ROUTE_COMPONENT_TEMPLATE_NAME, ROUTE_COMPONENT_TEMPLATE); } //-------------------------------------------------------------------------- diff --git a/src/router/directive.ts b/src/router/directive.ts deleted file mode 100644 index 3ae2ee9c..00000000 --- a/src/router/directive.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { RouterEnv } from "./Router"; - -export function makeDirective(env: RouterEnv) { - return { - name: "routecomponent", - priority: 13, - atNodeEncounter({ node }): boolean { - let first = true; - const router = env.router; - for (let name of router.routeIds) { - const route = router.routes[name]; - if (route.component) { - // make new t t-component element - const comp = node.ownerDocument.createElement("t"); - comp.setAttribute("t-component", "__component__" + route.name); - comp.setAttribute( - first ? "t-if" : "t-elif", - `env.router.currentRouteName === '${route.name}'` - ); - first = false; - for (let param of route.params) { - comp.setAttribute(param, `env.router.currentParams.${param}`); - } - node.appendChild(comp); - } - } - node.removeAttribute("t-routecomponent"); - return false; - } - }; -} diff --git a/tests/router/directive.test.ts b/tests/router/RouteComponent.test.ts similarity index 86% rename from tests/router/directive.test.ts rename to tests/router/RouteComponent.test.ts index cf9ddbb8..06526a58 100644 --- a/tests/router/directive.test.ts +++ b/tests/router/RouteComponent.test.ts @@ -1,9 +1,10 @@ import { Component } from "../../src/component/component"; import { RouterEnv } from "../../src/router/Router"; +import { RouteComponent, ROUTE_COMPONENT_TEMPLATE_NAME } from "../../src/router/RouteComponent"; import { makeTestEnv, makeTestFixture, nextTick } from "../helpers"; import { TestRouter } from "./TestRouter"; -describe("router directive t-routecomponent", () => { +describe("RouteComponent", () => { let fixture: HTMLElement; let env: RouterEnv; let router: TestRouter | null = null; @@ -25,7 +26,7 @@ describe("router directive t-routecomponent", () => { env.qweb.addTemplates(`
- +
About Users @@ -34,7 +35,7 @@ describe("router directive t-routecomponent", () => { class About extends Component {} class Users extends Component {} class App extends Component { - components = { About, Users }; + static components = { RouteComponent }; } const routes = [ @@ -51,21 +52,22 @@ describe("router directive t-routecomponent", () => { await router.navigate({ to: "users" }); await nextTick(); expect(fixture.innerHTML).toBe("
Users
"); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates[ROUTE_COMPONENT_TEMPLATE_NAME].fn.toString()).toMatchSnapshot(); + }); test("can render parameterized route", async () => { env.qweb.addTemplates(`
- +
Book
`); class Book extends Component {} class App extends Component { - components = { Book }; + static components = { RouteComponent }; } const routes = [{ name: "book", path: "/book/{{title}}", component: Book }]; @@ -74,14 +76,13 @@ describe("router directive t-routecomponent", () => { const app = new App(env); await app.mount(fixture); expect(fixture.innerHTML).toBe("
Book 1984
"); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); }); test("can render parameterized route with suffixes", async () => { env.qweb.addTemplates(`
- +
Book |
@@ -92,7 +93,7 @@ describe("router directive t-routecomponent", () => { } } class App extends Component { - components = { Book }; + static components = { RouteComponent }; } const routes = [{ name: "book", path: "/book/{{title}}/{{val.number}}", component: Book }]; @@ -101,6 +102,5 @@ describe("router directive t-routecomponent", () => { const app = new App(env); await app.mount(fixture); expect(fixture.innerHTML).toBe("
Book 1984|124
"); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); }); }); diff --git a/tests/router/TestRouter.ts b/tests/router/TestRouter.ts index cf6064d4..22177841 100644 --- a/tests/router/TestRouter.ts +++ b/tests/router/TestRouter.ts @@ -4,8 +4,6 @@ import { QWeb } from "../../src/qweb/index"; export class TestRouter extends Router { destroy() { window.removeEventListener("popstate", (this as any)._listener); - delete QWeb.DIRECTIVE_NAMES.routecomponent; - QWeb.DIRECTIVES = QWeb.DIRECTIVES.filter(d => d.name !== "routecomponent"); // remove component defined inroutes for (let key in QWeb.components) { diff --git a/tests/router/__snapshots__/RouteComponent.test.ts.snap b/tests/router/__snapshots__/RouteComponent.test.ts.snap new file mode 100644 index 00000000..85497b2e --- /dev/null +++ b/tests/router/__snapshots__/RouteComponent.test.ts.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RouteComponent can render simple cases 1`] = ` +"function anonymous(context,extra +) { + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let owner = context; + let result; + context = Object.create(context); + var h = this.h; + var _1 = context['routes']; + if (!_1) { throw new Error('QWeb error: Invalid loop expression')} + var _2 = _3 = _1; + if (!(_1 instanceof Array)) { + _2 = Object.keys(_1); + _3 = Object.values(_1); + } + var _length2 = _2.length; + for (let i = 0; i < _length2; i++) { + context.route_first = i === 0; + context.route_last = i === _length2 - 1; + context.route_index = i; + context.route = _2[i]; + context.route_value = _3[i]; + if (context['env'].router.currentRouteName===context['route'].name) { + //COMPONENT + let def5; + let templateId7 = String(-6 - i); + let w6 = templateId7 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId7]] : false; + let vn8 = {}; + result = vn8; + let props6 = Object.assign({}, context['env'].router.currentParams); + if (w6 && w6.__owl__.renderPromise && !w6.__owl__.vnode) { + if (utils.shallowEqual(props6, w6.__owl__.renderProps)) { + def5 = w6.__owl__.renderPromise; + } else { + w6.destroy(); + w6 = false; + } + } + if (!w6) { + let componentKey6 = (context['route'].component); + let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]; + if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')} + w6 = new W6(parent, props6); + parent.__owl__.cmap[templateId7] = w6.__owl__.id; + def5 = w6.__prepare(); + def5 = def5.then(vnode=>{if (w6.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: templateId7, hook: {insert(vn) {let nvn=w6.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w6.destroy();}}});utils.defineProxy(vn8, pvnode);w6.__owl__.pvnode = pvnode;}); + } else { + def5 = def5 || w6.__updateProps(props6, extra.forceUpdate, extra.patchQueue); + def5 = def5.then(()=>{if (w6.__owl__.isDestroyed) {return};let pvnode=w6.__owl__.pvnode;utils.defineProxy(vn8, pvnode);}); + } + extra.promises.push(def5); + } + } + return result; +}" +`; diff --git a/tests/router/__snapshots__/directive.test.ts.snap b/tests/router/__snapshots__/directive.test.ts.snap deleted file mode 100644 index be3ce663..00000000 --- a/tests/router/__snapshots__/directive.test.ts.snap +++ /dev/null @@ -1,165 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`router directive t-routecomponent can render parameterized route 1`] = ` -"function anonymous(context,extra -) { - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let owner = context; - let result; - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - result = vn1; - if (context['env'].router.currentRouteName==='book') { - //COMPONENT - let def3; - let w4 = 4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[4]] : false; - let _2_index = c1.length; - c1.push(null); - let props4 = {title:context['env'].router.currentParams.title}; - if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { - if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { - def3 = w4.__owl__.renderPromise; - } else { - w4.destroy(); - w4 = false; - } - } - if (!w4) { - let componentKey4 = \`__component__book\`; - let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]; - if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} - w4 = new W4(parent, props4); - parent.__owl__.cmap[4] = w4.__owl__.id; - def3 = w4.__prepare(); - def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); - } else { - def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue); - def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); - } - extra.promises.push(def3); - } - return result; -}" -`; - -exports[`router directive t-routecomponent can render parameterized route with suffixes 1`] = ` -"function anonymous(context,extra -) { - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let owner = context; - let result; - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - result = vn1; - if (context['env'].router.currentRouteName==='book') { - //COMPONENT - let def3; - let w4 = 4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[4]] : false; - let _2_index = c1.length; - c1.push(null); - let props4 = {title:context['env'].router.currentParams.title,val:context['env'].router.currentParams.val}; - if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { - if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { - def3 = w4.__owl__.renderPromise; - } else { - w4.destroy(); - w4 = false; - } - } - if (!w4) { - let componentKey4 = \`__component__book\`; - let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]; - if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} - w4 = new W4(parent, props4); - parent.__owl__.cmap[4] = w4.__owl__.id; - def3 = w4.__prepare(); - def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); - } else { - def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue); - def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); - } - extra.promises.push(def3); - } - return result; -}" -`; - -exports[`router directive t-routecomponent can render simple cases 1`] = ` -"function anonymous(context,extra -) { - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let owner = context; - let result; - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - result = vn1; - if (context['env'].router.currentRouteName==='about') { - //COMPONENT - let def3; - let w4 = 4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[4]] : false; - let _2_index = c1.length; - c1.push(null); - let props4 = {}; - if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { - if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { - def3 = w4.__owl__.renderPromise; - } else { - w4.destroy(); - w4 = false; - } - } - if (!w4) { - let componentKey4 = \`__component__about\`; - let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]; - if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} - w4 = new W4(parent, props4); - parent.__owl__.cmap[4] = w4.__owl__.id; - def3 = w4.__prepare(); - def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); - } else { - def3 = def3 || w4.__updateProps(props4, extra.forceUpdate, extra.patchQueue); - def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); - } - extra.promises.push(def3); - } - else if (context['env'].router.currentRouteName==='users') { - //COMPONENT - let def6; - let w7 = 7 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[7]] : false; - let _5_index = c1.length; - c1.push(null); - let props7 = {}; - if (w7 && w7.__owl__.renderPromise && !w7.__owl__.vnode) { - if (utils.shallowEqual(props7, w7.__owl__.renderProps)) { - def6 = w7.__owl__.renderPromise; - } else { - w7.destroy(); - w7 = false; - } - } - if (!w7) { - let componentKey7 = \`__component__users\`; - let W7 = context.constructor.components[componentKey7] || QWeb.components[componentKey7]; - if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')} - w7 = new W7(parent, props7); - parent.__owl__.cmap[7] = w7.__owl__.id; - def6 = w7.__prepare(); - def6 = def6.then(vnode=>{if (w7.__owl__.isDestroyed){return}let pvnode=h(vnode.sel, {key: 7, hook: {insert(vn) {let nvn=w7.__mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});c1[_5_index]=pvnode;w7.__owl__.pvnode = pvnode;}); - } else { - def6 = def6 || w7.__updateProps(props7, extra.forceUpdate, extra.patchQueue); - def6 = def6.then(()=>{if (w7.__owl__.isDestroyed) {return};let pvnode=w7.__owl__.pvnode;c1[_5_index]=pvnode;}); - } - extra.promises.push(def6); - } - return result; -}" -`;