[FIX] t-model takes precedence over t-on-input

With this commit, we make sure that the code for t-model is run before
t-on-input event handler.  This is useful to make sure that the state
reflected by t-model is up-to-date.

closes #1295
This commit is contained in:
Géry Debongnie
2022-11-22 12:52:06 +01:00
committed by Sam Degueldre
parent 9a5edb4590
commit 4a5316ced5
3 changed files with 83 additions and 45 deletions
+33 -33
View File
@@ -606,39 +606,6 @@ export class CodeGenerator {
} }
} }
// event handlers
for (let ev in ast.on) {
const name = this.generateHandlerCode(ev, ast.on[ev]);
const idx = block!.insertData(name, "hdlr");
attrs[`block-handler-${idx}`] = ev;
}
// t-ref
if (ast.ref) {
this.target.hasRef = true;
const isDynamic = INTERP_REGEXP.test(ast.ref);
if (isDynamic) {
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref");
attrs["block-ref"] = String(idx);
} else {
let name = ast.ref;
if (name in this.target.refInfo) {
// ref has already been defined
this.helpers.add("multiRefSetter");
const info = this.target.refInfo[name];
const index = block!.data.push(info[0]) - 1;
attrs["block-ref"] = String(index);
info[1] = `multiRefSetter(refs, \`${name}\`)`;
} else {
let id = generateId("ref");
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
const index = block!.data.push(id) - 1;
attrs["block-ref"] = String(index);
}
}
}
// t-model // t-model
let tModelSelectedExpr; let tModelSelectedExpr;
if (ast.model) { if (ast.model) {
@@ -685,6 +652,39 @@ export class CodeGenerator {
attrs[`block-handler-${idx}`] = eventType; attrs[`block-handler-${idx}`] = eventType;
} }
// event handlers
for (let ev in ast.on) {
const name = this.generateHandlerCode(ev, ast.on[ev]);
const idx = block!.insertData(name, "hdlr");
attrs[`block-handler-${idx}`] = ev;
}
// t-ref
if (ast.ref) {
this.target.hasRef = true;
const isDynamic = INTERP_REGEXP.test(ast.ref);
if (isDynamic) {
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref");
attrs["block-ref"] = String(idx);
} else {
let name = ast.ref;
if (name in this.target.refInfo) {
// ref has already been defined
this.helpers.add("multiRefSetter");
const info = this.target.refInfo[name];
const index = block!.data.push(info[0]) - 1;
attrs["block-ref"] = String(index);
info[1] = `multiRefSetter(refs, \`${name}\`)`;
} else {
let id = generateId("ref");
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
const index = block!.data.push(id) - 1;
attrs["block-ref"] = String(index);
}
}
}
const dom = xmlDoc.createElement(ast.tag); const dom = xmlDoc.createElement(ast.tag);
for (const [attr, val] of Object.entries(attrs)) { for (const [attr, val] of Object.entries(attrs)) {
if (!(attr === "class" && val === "")) { if (!(attr === "class" && val === "")) {
@@ -120,15 +120,15 @@ exports[`t-model directive can also define t-on directive on same event, part 1
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { toNumber } = helpers; let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-handler-0=\\"input\\" block-attribute-1=\\"value\\" block-handler-2=\\"input\\"/></div>\`); let block1 = createBlock(\`<div><input block-attribute-0=\\"value\\" block-handler-1=\\"input\\" block-handler-2=\\"input\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
const expr1 = 'text'; const expr1 = 'text';
let attr1 = bExpr1[expr1]; let attr1 = bExpr1[expr1];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2]); let hdlr2 = [ctx['onInput'], ctx];
return block1([attr1, hdlr1, hdlr2]);
} }
}" }"
`; `;
@@ -139,25 +139,25 @@ exports[`t-model directive can also define t-on directive on same event, part 2
let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { toNumber } = helpers; let { toNumber } = helpers;
let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-handler-0=\\"click\\" block-attribute-1=\\"checked\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-handler-3=\\"click\\" block-attribute-4=\\"checked\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-handler-6=\\"click\\" block-attribute-7=\\"checked\\" block-handler-8=\\"click\\"/></div>\`); let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-attribute-0=\\"checked\\" block-handler-1=\\"click\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-attribute-3=\\"checked\\" block-handler-4=\\"click\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-attribute-6=\\"checked\\" block-handler-7=\\"click\\" block-handler-8=\\"click\\"/></div>\`);
return function template(ctx, node, key = \\"\\") { return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state']; const bExpr1 = ctx['state'];
const expr1 = 'choice'; const expr1 = 'choice';
let attr1 = bExpr1[expr1] === 'One'; let attr1 = bExpr1[expr1] === 'One';
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let hdlr3 = [ctx['onClick'], ctx]; let hdlr2 = [ctx['onClick'], ctx];
const bExpr2 = ctx['state']; const bExpr2 = ctx['state'];
const expr2 = 'choice'; const expr2 = 'choice';
let attr2 = bExpr2[expr2] === 'Two'; let attr2 = bExpr2[expr2] === 'Two';
let hdlr4 = [(ev) => { bExpr2[expr2] = ev.target.value; }]; let hdlr3 = [(ev) => { bExpr2[expr2] = ev.target.value; }];
let hdlr5 = [ctx['onClick'], ctx]; let hdlr4 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state']; const bExpr3 = ctx['state'];
const expr3 = 'choice'; const expr3 = 'choice';
let attr3 = bExpr3[expr3] === 'Three'; let attr3 = bExpr3[expr3] === 'Three';
let hdlr6 = [(ev) => { bExpr3[expr3] = ev.target.value; }]; let hdlr5 = [(ev) => { bExpr3[expr3] = ev.target.value; }];
return block1([hdlr1, attr1, hdlr2, hdlr3, attr2, hdlr4, hdlr5, attr3, hdlr6]); let hdlr6 = [ctx['onClick'], ctx];
return block1([attr1, hdlr1, hdlr2, attr2, hdlr3, hdlr4, attr3, hdlr5, hdlr6]);
} }
}" }"
`; `;
@@ -410,6 +410,25 @@ exports[`t-model directive on an textarea 1`] = `
}" }"
`; `;
exports[`t-model directive t-model is applied before t-on-input 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { toNumber } = helpers;
let block1 = createBlock(\`<div><input block-attribute-0=\\"value\\" block-handler-1=\\"input\\" block-handler-2=\\"input\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const bExpr1 = ctx['state'];
const expr1 = 'text';
let attr1 = bExpr1[expr1];
let hdlr1 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
let hdlr2 = [ctx['onInput'], ctx];
return block1([attr1, hdlr1, hdlr2]);
}
}"
`;
exports[`t-model directive t-model on an input with an undefined value 1`] = ` exports[`t-model directive t-model on an input with an undefined value 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
+19
View File
@@ -625,4 +625,23 @@ describe("t-model directive", () => {
await mount(Test, fixture); await mount(Test, fixture);
expect(fixture.querySelector("select")!.value).toEqual("b"); expect(fixture.querySelector("select")!.value).toEqual("b");
}); });
test("t-model is applied before t-on-input", async () => {
expect.assertions(3);
class SomeComponent extends Component {
static template = xml`
<div>
<input t-model="state['text']" t-on-input="onInput"/>
</div>
`;
state = useState({ text: "", other: "" });
onInput(ev: InputEvent) {
expect(this.state.text).toBe("Beam me up, Scotty");
expect((ev.target as HTMLInputElement).value).toBe("Beam me up, Scotty");
}
}
await mount(SomeComponent, fixture);
const input = fixture.querySelector("input")!;
await editInput(input, "Beam me up, Scotty");
});
}); });