mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] component: add .bind suffix to props for easy binding
This commit is contained in:
committed by
Samuel Degueldre
parent
7b95315b87
commit
4cde06d685
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user