[IMP] component: add .bind suffix to props for easy binding

This commit is contained in:
Géry Debongnie
2021-12-01 15:51:00 +01:00
committed by Aaron Bohy
parent 894deed13b
commit 14a6289f60
4 changed files with 129 additions and 6 deletions
+18
View File
@@ -124,6 +124,23 @@ export function safeOutput(value: any): ReturnType<typeof toggler> {
return toggler(safeKey, block); return toggler(safeKey, block);
} }
let boundFunctions = new WeakMap();
function bind(ctx: any, fn: Function): Function {
let component = ctx.__owl__.component;
let boundFnMap = boundFunctions.get(component);
if (!boundFnMap) {
boundFnMap = new WeakMap();
boundFunctions.set(component, boundFnMap);
}
let boundFn = boundFnMap.get(fn);
if (!boundFn) {
boundFn = fn.bind(component);
boundFnMap.set(fn, boundFn);
}
return boundFn;
}
export const UTILS = { export const UTILS = {
withDefault, withDefault,
zero: Symbol("zero"), zero: Symbol("zero"),
@@ -137,4 +154,5 @@ export const UTILS = {
toNumber, toNumber,
validateProps, validateProps,
safeOutput, safeOutput,
bind,
}; };
+13 -4
View File
@@ -975,10 +975,19 @@ export class CodeGenerator {
// props // props
const props: string[] = []; const props: string[] = [];
let hasSlotsProp = false; let hasSlotsProp = false;
for (let p in ast.props) { for (let propName in ast.props) {
const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`; let propValue = this.captureExpression(ast.props[propName]) || undefined;
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`); if (propName.includes(".")) {
if (p === "slots") { let [name, suffix] = propName.split(".");
if (suffix === "bind") {
this.helpers.add("bind");
propName = name;
propValue = `bind(ctx, ${propValue})`;
}
}
propName = /^[a-z_]+$/i.test(propName) ? propName : `'${propName}'`;
props.push(`${propName}: ${propValue}`);
if (propName === "slots") {
hasSlotsProp = true; hasSlotsProp = true;
} }
} }
@@ -262,3 +262,49 @@ exports[`basics template string in prop 2`] = `
} }
}" }"
`; `;
exports[`bound functions is referentially equal after update 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].val);
}
}"
`;
exports[`bound functions is referentially equal after update 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { bind } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {val: ctx['state'].val,fn: bind(ctx, ctx['someFunction'])}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`can bind function prop with bind suffix 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`child\`);
}
}"
`;
exports[`can bind function prop with bind suffix 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let { bind } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {doSomething: bind(ctx, ctx['doSomething'])}, key + \`__1\`, node, ctx);
}
}"
`;
+52 -2
View File
@@ -1,6 +1,6 @@
import { Component, mount, useState } from "../../src"; import { Component, mount, onWillUpdateProps, useState } from "../../src";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeTestFixture, snapshotEverything } from "../helpers"; import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
let fixture: HTMLElement; let fixture: HTMLElement;
@@ -176,3 +176,53 @@ describe("basics", () => {
await mount(Parent, fixture); await mount(Parent, fixture);
}); });
}); });
test("can bind function prop with bind suffix", async () => {
class Child extends Component {
static template = xml`child`;
setup() {
this.props.doSomething(123);
}
}
let boundedThing: any = null;
class Parent extends Component {
static template = xml`<Child doSomething.bind="doSomething"/>`;
static components = { Child };
doSomething(val: number) {
expect(val).toBe(123);
boundedThing = this;
}
}
const parent = await mount(Parent, fixture);
expect(boundedThing).toBe(parent);
expect(fixture.innerHTML).toBe("child");
});
test("bound functions is referentially equal after update", async () => {
let isEqual = false;
class Child extends Component {
static template = xml`<t t-esc="props.val"/>`;
setup() {
onWillUpdateProps((nextProps: any) => {
isEqual = nextProps.fn === this.props.fn;
});
}
}
class Parent extends Component {
static template = xml`<Child val="state.val" fn.bind="someFunction"/>`;
static components = { Child };
state = useState({ val: 1 });
someFunction() {}
}
const parent = await mount(Parent, fixture);
parent.state.val = 3;
await nextTick();
expect(fixture.innerHTML).toBe("3");
expect(isEqual).toBe(true);
});