mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
committed by
Géry Debongnie
parent
24c569e6d5
commit
7cdd1aa9a1
+18
-1
@@ -87,6 +87,21 @@ It's API is quite simple:
|
|||||||
const vnode = qweb.render("App", widget);
|
const vnode = qweb.render("App", widget);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **`register(name, Component)`**: static function to register an OWL Component
|
||||||
|
to QWeb's global registry. Globally registered Components can be used in
|
||||||
|
templates (see the `t-widget` directive). This is useful for commonly used
|
||||||
|
components accross the application.
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Dialog extends owl.Component { ... }
|
||||||
|
QWeb.register("Dialog", Dialog);
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
class ParentWidget extends owl.Component { ... }
|
||||||
|
qweb.addTemplate("ParentWidget", "<div><t t-widget='Dialog'/></div>");
|
||||||
|
```
|
||||||
|
|
||||||
## QWeb Specification
|
## QWeb Specification
|
||||||
|
|
||||||
We define in this section the specification of how `QWeb` templates should be
|
We define in this section the specification of how `QWeb` templates should be
|
||||||
@@ -429,7 +444,9 @@ class ParentWidget {
|
|||||||
|
|
||||||
Whenever the template is rendered, it will automatically create the subwidget
|
Whenever the template is rendered, it will automatically create the subwidget
|
||||||
`ChildWidget` at the correct place. It needs to find the reference to the
|
`ChildWidget` at the correct place. It needs to find the reference to the
|
||||||
actual component class in the special `widgets` key.
|
actual component class in the special `widgets` key, or the class registered in
|
||||||
|
QWeb's global registry (see `register` function of QWeb). It first looks inside
|
||||||
|
the local `widgets` key, then fallbacks on the global registry.
|
||||||
|
|
||||||
In this example, the child widget will receive the object `{count: 4}` in its
|
In this example, the child widget will receive the object `{count: 4}` in its
|
||||||
constructor. This will be assigned to the `props` variable, which can be accessed
|
constructor. This will be assigned to the `props` variable, which can be accessed
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ let nextID = 1;
|
|||||||
export class QWeb {
|
export class QWeb {
|
||||||
templates: { [name: string]: Template } = {};
|
templates: { [name: string]: Template } = {};
|
||||||
utils = UTILS;
|
utils = UTILS;
|
||||||
|
static widgets = Object.create(null);
|
||||||
|
|
||||||
// dev mode enables better error messages or more costly validations
|
// dev mode enables better error messages or more costly validations
|
||||||
static dev: boolean = false;
|
static dev: boolean = false;
|
||||||
@@ -157,6 +158,13 @@ export class QWeb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static register(name: string, Component: any) {
|
||||||
|
if (QWeb.widgets[name]) {
|
||||||
|
throw new Error(`Component '${name}' has already been registered`);
|
||||||
|
}
|
||||||
|
QWeb.widgets[name] = Component;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a template to the internal template map. Note that it is not
|
* Add a template to the internal template map. Note that it is not
|
||||||
* immediately compiled.
|
* immediately compiled.
|
||||||
@@ -269,6 +277,9 @@ export class QWeb {
|
|||||||
// pollute the rendering context by adding some keys in it.
|
// pollute the rendering context by adding some keys in it.
|
||||||
ctx.code.unshift(" let owner = context;");
|
ctx.code.unshift(" let owner = context;");
|
||||||
}
|
}
|
||||||
|
if (ctx.shouldDefineQWeb) {
|
||||||
|
ctx.code.unshift(" let QWeb = this.constructor;");
|
||||||
|
}
|
||||||
if (ctx.shouldDefineUtils) {
|
if (ctx.shouldDefineUtils) {
|
||||||
ctx.code.unshift(" let utils = this.utils;");
|
ctx.code.unshift(" let utils = this.utils;");
|
||||||
}
|
}
|
||||||
@@ -616,6 +627,7 @@ export class Context {
|
|||||||
rootContext: Context;
|
rootContext: Context;
|
||||||
caller: Element | undefined;
|
caller: Element | undefined;
|
||||||
shouldDefineOwner: boolean = false;
|
shouldDefineOwner: boolean = false;
|
||||||
|
shouldDefineQWeb: boolean = false;
|
||||||
shouldDefineUtils: boolean = false;
|
shouldDefineUtils: boolean = false;
|
||||||
shouldProtectContext: boolean = false;
|
shouldProtectContext: boolean = false;
|
||||||
inLoop: boolean = false;
|
inLoop: boolean = false;
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ QWeb.addDirective({
|
|||||||
atNodeEncounter({ ctx, value, node }): boolean {
|
atNodeEncounter({ ctx, value, node }): boolean {
|
||||||
ctx.addLine("//WIDGET");
|
ctx.addLine("//WIDGET");
|
||||||
ctx.rootContext.shouldDefineOwner = true;
|
ctx.rootContext.shouldDefineOwner = true;
|
||||||
|
ctx.rootContext.shouldDefineQWeb = true;
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
let props = node.getAttribute("t-props");
|
let props = node.getAttribute("t-props");
|
||||||
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
let keepAlive = node.getAttribute("t-keepalive") ? true : false;
|
||||||
@@ -458,7 +459,9 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
ctx.addIf(`!w${widgetID}`);
|
ctx.addIf(`!w${widgetID}`);
|
||||||
// new widget
|
// new widget
|
||||||
ctx.addLine(`let W${widgetID} = context.widgets['${value}'];`);
|
ctx.addLine(
|
||||||
|
`let W${widgetID} = context.widgets && context.widgets['${value}'] || QWeb.widgets['${value}'];`
|
||||||
|
);
|
||||||
|
|
||||||
// maybe only do this in dev mode...
|
// maybe only do this in dev mode...
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ exports[`animations t-transition combined with t-widget 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -19,7 +20,7 @@ exports[`animations t-transition combined with t-widget 1`] = `
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['Child'];
|
let W4 = context.widgets && context.widgets['Child'] || QWeb.widgets['Child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
@@ -41,6 +42,7 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -57,7 +59,7 @@ exports[`animations t-transition combined with t-widget and t-if 1`] = `
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['Child'];
|
let W4 = context.widgets && context.widgets['Child'] || QWeb.widgets['Child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"Child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -20,7 +21,7 @@ exports[`class and style attributes with t-widget dynamic t-att-style is properl
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['child'];
|
let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
@@ -39,6 +40,7 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -55,7 +57,7 @@ exports[`class and style attributes with t-widget t-att-class is properly added/
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['child'];
|
let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
@@ -78,6 +80,7 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
context = Object.create(context);
|
context = Object.create(context);
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
@@ -107,7 +110,7 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
|
|||||||
w7 = false
|
w7 = false
|
||||||
}
|
}
|
||||||
if (!w7) {
|
if (!w7) {
|
||||||
let W7 = context.widgets['ChildWidget'];
|
let W7 = context.widgets && context.widgets['ChildWidget'] || QWeb.widgets['ChildWidget'];
|
||||||
if (!W7) {throw new Error(\`Cannot find the definition of widget \\"ChildWidget\\"\`)}
|
if (!W7) {throw new Error(\`Cannot find the definition of widget \\"ChildWidget\\"\`)}
|
||||||
w7 = new W7(owner, props7);
|
w7 = new W7(owner, props7);
|
||||||
context.__owl__.cmap[key8] = w7.__owl__.id;
|
context.__owl__.cmap[key8] = w7.__owl__.id;
|
||||||
@@ -127,6 +130,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -143,7 +147,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['child'];
|
let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[key5] = w4.__owl__.id;
|
context.__owl__.cmap[key5] = w4.__owl__.id;
|
||||||
@@ -162,6 +166,7 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin
|
|||||||
"function anonymous(context,extra
|
"function anonymous(context,extra
|
||||||
) {
|
) {
|
||||||
let utils = this.utils;
|
let utils = this.utils;
|
||||||
|
let QWeb = this.constructor;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.utils.h;
|
var h = this.utils.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
@@ -177,7 +182,7 @@ exports[`random stuff/miscellaneous t-props should not be undefined (snapshottin
|
|||||||
w4 = false
|
w4 = false
|
||||||
}
|
}
|
||||||
if (!w4) {
|
if (!w4) {
|
||||||
let W4 = context.widgets['child'];
|
let W4 = context.widgets && context.widgets['child'] || QWeb.widgets['child'];
|
||||||
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
if (!W4) {throw new Error(\`Cannot find the definition of widget \\"child\\"\`)}
|
||||||
w4 = new W4(owner, props4);
|
w4 = new W4(owner, props4);
|
||||||
context.__owl__.cmap[4] = w4.__owl__.id;
|
context.__owl__.cmap[4] = w4.__owl__.id;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Env } from "../src/component";
|
import { Component, Env } from "../src/component";
|
||||||
|
import { QWeb } from "../src/qweb_core";
|
||||||
import {
|
import {
|
||||||
makeDeferred,
|
makeDeferred,
|
||||||
makeTestFixture,
|
makeTestFixture,
|
||||||
@@ -849,6 +850,30 @@ describe("composition", () => {
|
|||||||
expect(children(widget)[0].__owl__.parent).toBe(widget);
|
expect(children(widget)[0].__owl__.parent).toBe(widget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can use widgets from the global registry", async () => {
|
||||||
|
QWeb.register("WidgetB", WidgetB);
|
||||||
|
env.qweb.addTemplate("ParentWidget", `<div><t t-widget="WidgetB"/></div>`);
|
||||||
|
class ParentWidget extends Widget {}
|
||||||
|
const widget = new ParentWidget(env);
|
||||||
|
await widget.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><div>world</div></div>");
|
||||||
|
delete QWeb.widgets['WidgetB'];
|
||||||
|
});
|
||||||
|
|
||||||
|
test("don't fallback to global registry if widget defined locally", async () => {
|
||||||
|
QWeb.register("WidgetB", WidgetB); // should not use this widget
|
||||||
|
env.qweb.addTemplate("ParentWidget", `<div><t t-widget="WidgetB"/></div>`);
|
||||||
|
env.qweb.addTemplate("AnotherWidgetB", `<span>Belgium</span>`);
|
||||||
|
class AnotherWidgetB extends Widget {}
|
||||||
|
class ParentWidget extends Widget {
|
||||||
|
widgets = { WidgetB: AnotherWidgetB };
|
||||||
|
}
|
||||||
|
const widget = new ParentWidget(env);
|
||||||
|
await widget.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>Belgium</span></div>");
|
||||||
|
delete QWeb.widgets['WidgetB'];
|
||||||
|
});
|
||||||
|
|
||||||
test("throw a nice error if it cannot find widget", async () => {
|
test("throw a nice error if it cannot find widget", async () => {
|
||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
env.qweb.addTemplate(
|
env.qweb.addTemplate(
|
||||||
|
|||||||
Reference in New Issue
Block a user