[IMP] router: replace t-routecomponent directive by RouteComponent

closes #287
This commit is contained in:
Géry Debongnie
2019-09-11 13:34:29 +02:00
parent 4fc3423682
commit 471fd49063
10 changed files with 108 additions and 217 deletions
+1
View File
@@ -14,6 +14,7 @@ owl
Observer Observer
router router
Link Link
RouteComponent
Router Router
store store
Store Store
+6 -6
View File
@@ -8,7 +8,7 @@
- [Route Definition](#route-definition) - [Route Definition](#route-definition)
- [Router](#router) - [Router](#router)
- [Navigation Guards](#navigation-guards) - [Navigation Guards](#navigation-guards)
- [`t-routecomponent`](#t-routecomponent) - [RouteComponent](#routecomponent)
- [Link](#link) - [Link](#link)
## Overview ## Overview
@@ -26,8 +26,8 @@ The Owl router support the following features:
- route redirection - route redirection
- navigation guards - navigation guards
- parameterized routes - parameterized routes
- a `<Link>` component - a `<Link/>` component
- a `t-routecomponent` directive - a `<RouteComponent/>` component
Note that it is still in early stage of developments, and there are probably Note that it is still in early stage of developments, and there are probably
still some issues. 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 A navigation guard is a function that returns a promise, which either resolves
to `true` (the navigation is accepted), or to another destination object. 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): to the currently active route (if any):
```xml ```xml
<div t-name="App"> <div t-name="App">
<NavBar /> <NavBar />
<t t-routecomponent="1"/> <RouteComponent />
</div> </div>
``` ```
+2 -1
View File
@@ -11,12 +11,13 @@ import { ConnectedComponent } from "./store/connected_component";
import { Store } from "./store/store"; import { Store } from "./store/store";
import * as _utils from "./utils"; import * as _utils from "./utils";
import { Link } from "./router/Link"; import { Link } from "./router/Link";
import { RouteComponent } from "./router/RouteComponent";
import { Router } from "./router/Router"; import { Router } from "./router/Router";
export { Component } from "./component/component"; export { Component } from "./component/component";
export { QWeb }; export { QWeb };
export const core = { EventBus, Observer }; export const core = { EventBus, Observer };
export const router = { Router, Link }; export const router = { Router, RouteComponent, Link };
export const store = { Store, ConnectedComponent }; export const store = { Store, ConnectedComponent };
export const utils = _utils; export const utils = _utils;
+27
View File
@@ -0,0 +1,27 @@
import { Component } from "../component/component";
export const ROUTE_COMPONENT_TEMPLATE_NAME = "__owl__-router-component";
export const ROUTE_COMPONENT_TEMPLATE = `
<t t-foreach="routes" t-as="route">
<t t-if="env.router.currentRouteName === route.name">
<t t-component="{{route.component}}" t-props="env.router.currentParams"/>
</t>
</t>`;
export class RouteComponent extends Component<any, {}, {}> {
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
});
}
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Env } from "../component/component"; import { Env } from "../component/component";
import { QWeb } from "../qweb/index"; 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 { LINK_TEMPLATE, LINK_TEMPLATE_NAME } from "./Link";
import { shallowEqual } from "../utils"; import { shallowEqual } from "../utils";
@@ -87,7 +87,7 @@ export class Router {
// setup link and directive // setup link and directive
env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE); env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
QWeb.addDirective(makeDirective(<RouterEnv>env)); env.qweb.addTemplate(ROUTE_COMPONENT_TEMPLATE_NAME, ROUTE_COMPONENT_TEMPLATE);
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
-31
View File
@@ -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;
}
};
}
@@ -1,9 +1,10 @@
import { Component } from "../../src/component/component"; import { Component } from "../../src/component/component";
import { RouterEnv } from "../../src/router/Router"; import { RouterEnv } from "../../src/router/Router";
import { RouteComponent, ROUTE_COMPONENT_TEMPLATE_NAME } from "../../src/router/RouteComponent";
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers"; import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
import { TestRouter } from "./TestRouter"; import { TestRouter } from "./TestRouter";
describe("router directive t-routecomponent", () => { describe("RouteComponent", () => {
let fixture: HTMLElement; let fixture: HTMLElement;
let env: RouterEnv; let env: RouterEnv;
let router: TestRouter | null = null; let router: TestRouter | null = null;
@@ -25,7 +26,7 @@ describe("router directive t-routecomponent", () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-routecomponent="1" /> <RouteComponent />
</div> </div>
<span t-name="About">About</span> <span t-name="About">About</span>
<span t-name="Users">Users</span> <span t-name="Users">Users</span>
@@ -34,7 +35,7 @@ describe("router directive t-routecomponent", () => {
class About extends Component<any, any, any> {} class About extends Component<any, any, any> {}
class Users extends Component<any, any, any> {} class Users extends Component<any, any, any> {}
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
components = { About, Users }; static components = { RouteComponent };
} }
const routes = [ const routes = [
@@ -51,21 +52,22 @@ describe("router directive t-routecomponent", () => {
await router.navigate({ to: "users" }); await router.navigate({ to: "users" });
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><span>Users</span></div>"); expect(fixture.innerHTML).toBe("<div><span>Users</span></div>");
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 () => { test("can render parameterized route", async () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-routecomponent="1" /> <RouteComponent />
</div> </div>
<span t-name="Book">Book <t t-esc="props.title"/></span> <span t-name="Book">Book <t t-esc="props.title"/></span>
</templates> </templates>
`); `);
class Book extends Component<any, any, any> {} class Book extends Component<any, any, any> {}
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
components = { Book }; static components = { RouteComponent };
} }
const routes = [{ name: "book", path: "/book/{{title}}", component: Book }]; const routes = [{ name: "book", path: "/book/{{title}}", component: Book }];
@@ -74,14 +76,13 @@ describe("router directive t-routecomponent", () => {
const app = new App(env); const app = new App(env);
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>Book 1984</span></div>"); expect(fixture.innerHTML).toBe("<div><span>Book 1984</span></div>");
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
}); });
test("can render parameterized route with suffixes", async () => { test("can render parameterized route with suffixes", async () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
<div t-name="App"> <div t-name="App">
<t t-routecomponent="1" /> <RouteComponent />
</div> </div>
<span t-name="Book">Book <t t-esc="props.title"/>|<t t-esc="incVal"/></span> <span t-name="Book">Book <t t-esc="props.title"/>|<t t-esc="incVal"/></span>
</templates> </templates>
@@ -92,7 +93,7 @@ describe("router directive t-routecomponent", () => {
} }
} }
class App extends Component<any, any, any> { class App extends Component<any, any, any> {
components = { Book }; static components = { RouteComponent };
} }
const routes = [{ name: "book", path: "/book/{{title}}/{{val.number}}", component: Book }]; 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); const app = new App(env);
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>Book 1984|124</span></div>"); expect(fixture.innerHTML).toBe("<div><span>Book 1984|124</span></div>");
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
}); });
}); });
-2
View File
@@ -4,8 +4,6 @@ import { QWeb } from "../../src/qweb/index";
export class TestRouter extends Router { export class TestRouter extends Router {
destroy() { destroy() {
window.removeEventListener("popstate", (this as any)._listener); 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 // remove component defined inroutes
for (let key in QWeb.components) { for (let key in QWeb.components) {
@@ -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;
}"
`;
@@ -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;
}"
`;