mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
widget: add t-key option, add better error handling
This commit is contained in:
@@ -142,11 +142,22 @@ export class Widget<T extends WEnv, Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: it is ok to call updateState before the widget is started. In that
|
||||
* This is the safest update method for widget: its job is to update the state
|
||||
* and rerender (if widget is mounted).
|
||||
*
|
||||
* Notes:
|
||||
* - it checks if we do not add extra keys to the state.
|
||||
* - it is ok to call updateState before the widget is started. In that
|
||||
* case, it will simply update the state and will not rerender
|
||||
*/
|
||||
async updateState(nextState: Object) {
|
||||
Object.assign(this.state, nextState);
|
||||
for (let key in nextState) {
|
||||
if (key in this.state) {
|
||||
this.state[key] = nextState[key];
|
||||
} else {
|
||||
throw new Error(`Invalid key: '${key}' does not exist in widget state`);
|
||||
}
|
||||
}
|
||||
if (this.__widget__.isStarted) {
|
||||
return this.render();
|
||||
}
|
||||
|
||||
@@ -698,6 +698,10 @@ const widgetDirective: Directive = {
|
||||
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
||||
ctx.rootContext.shouldDefineOwner = true;
|
||||
let props = node.getAttribute("t-props");
|
||||
let key = node.getAttribute("t-key");
|
||||
if (key) {
|
||||
key = qweb._formatExpression(key);
|
||||
}
|
||||
if (props) {
|
||||
props = props.trim();
|
||||
if (props[0] === "{" && props[props.length - 1] === "}") {
|
||||
@@ -718,10 +722,20 @@ const widgetDirective: Directive = {
|
||||
let defID = ctx.generateID();
|
||||
let widgetID = ctx.generateID();
|
||||
ctx.addLine(`let _${dummyID} = {}; // DUMMY`);
|
||||
let keyID = key && ctx.generateID();
|
||||
if (key) {
|
||||
// we bind a variable to the key (could be a complex expression, so we
|
||||
// want to evaluate it only once)
|
||||
ctx.addLine(`let key${keyID} = ${key};`);
|
||||
}
|
||||
ctx.addLine(`let _${dummyID}_index = c${ctx.parentNode}.length;`);
|
||||
ctx.addLine(`c${ctx.parentNode}.push(_${dummyID});`);
|
||||
ctx.addLine(`let def${defID};`);
|
||||
let templateID = ctx.inLoop ? `String(-${widgetID} - i)` : String(widgetID);
|
||||
let templateID = key
|
||||
? `key${keyID}`
|
||||
: ctx.inLoop
|
||||
? `String(-${widgetID} - i)`
|
||||
: String(widgetID);
|
||||
ctx.addLine(
|
||||
`let w${widgetID} = ${templateID} in context.__widget__.cmap ? context.__widget__.children[context.__widget__.cmap[${templateID}]] : false;`
|
||||
);
|
||||
|
||||
@@ -22,6 +22,9 @@ const template = `
|
||||
<t t-widget="ColorWidget" t-props="{color: state.color}"/>
|
||||
<button t-on-click="addNotif(false)">Add notif</button>
|
||||
<button t-on-click="addNotif(true)">Add sticky notif</button>
|
||||
<t t-foreach="state.test" t-as="blip">
|
||||
<t t-widget="Counter" t-key="blip" t-props="{initialState: 100}"/>
|
||||
</t>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -29,7 +32,7 @@ export class Discuss extends Widget<Env, {}> {
|
||||
name = "discuss";
|
||||
template = template;
|
||||
widgets = { Clock, Counter, ColorWidget };
|
||||
state = { validcounter: true, color: "red" };
|
||||
state = { validcounter: true, color: "red", test: [1, 2, 3] };
|
||||
|
||||
mounted() {}
|
||||
resetCounter(ev: MouseEvent) {
|
||||
|
||||
@@ -109,6 +109,15 @@ describe("basic widget properties", () => {
|
||||
expect(renderCalls).toBe(1);
|
||||
});
|
||||
|
||||
test("updateState does not allow adding extra keys", async () => {
|
||||
const widget = new Widget(env);
|
||||
try {
|
||||
await widget.updateState({ extra: 1 });
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch("Invalid key:");
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps a reference to env", async () => {
|
||||
const widget = new Widget(env);
|
||||
expect(widget.env).toBe(env);
|
||||
@@ -605,6 +614,43 @@ describe("composition", () => {
|
||||
`)
|
||||
);
|
||||
});
|
||||
|
||||
test("sub widgets with some state rendered in a loop", async () => {
|
||||
let n = 1;
|
||||
class ChildWidget extends Widget<WEnv, never> {
|
||||
name = "c";
|
||||
template = `<span><t t-esc="state.n"/></span>`;
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
this.state = { n };
|
||||
n++;
|
||||
}
|
||||
}
|
||||
class Parent extends Widget<WEnv, {}> {
|
||||
name = "p";
|
||||
template = `
|
||||
<div>
|
||||
<t t-foreach="state.numbers" t-as="number">
|
||||
<t t-widget="ChildWidget" t-key="number"/>
|
||||
</t>
|
||||
</div>`;
|
||||
state = {
|
||||
numbers: [1, 2, 3]
|
||||
};
|
||||
widgets = { ChildWidget };
|
||||
}
|
||||
const parent = new Parent(env);
|
||||
await parent.mount(fixture);
|
||||
await parent.updateState({ numbers: [1, 3] });
|
||||
expect(normalize(fixture.innerHTML)).toBe(
|
||||
normalize(`
|
||||
<div>
|
||||
<span>1</span>
|
||||
<span>3</span>
|
||||
</div>
|
||||
`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("props evaluation (with t-props directive)", () => {
|
||||
|
||||
Reference in New Issue
Block a user