[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 Samuel Degueldre
parent 7b95315b87
commit 4cde06d685
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);
}
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 = {
withDefault,
zero: Symbol("zero"),
@@ -137,4 +154,5 @@ export const UTILS = {
toNumber,
validateProps,
safeOutput,
bind,
};
+13 -4
View File
@@ -975,10 +975,19 @@ export class CodeGenerator {
// props
const props: string[] = [];
let hasSlotsProp = false;
for (let p in ast.props) {
const propName = /^[a-z_]+$/i.test(p) ? p : `'${p}'`;
props.push(`${propName}: ${this.captureExpression(ast.props[p]) || undefined}`);
if (p === "slots") {
for (let propName in ast.props) {
let propValue = this.captureExpression(ast.props[propName]) || undefined;
if (propName.includes(".")) {
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;
}
}
@@ -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 { makeTestFixture, snapshotEverything } from "../helpers";
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
let fixture: HTMLElement;
@@ -176,3 +176,53 @@ describe("basics", () => {
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);
});