widget: add t-key option, add better error handling

This commit is contained in:
Géry Debongnie
2019-01-30 11:29:54 +01:00
parent 145dc9b5d7
commit c0c7414b18
4 changed files with 78 additions and 4 deletions
+13 -2
View File
@@ -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();
}
+15 -1
View File
@@ -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;`
);
+4 -1
View File
@@ -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) {
+46
View File
@@ -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)", () => {