From 32921ecc20325798206e245394628b0235b0bb45 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 3 Jun 2019 15:12:21 +0200 Subject: [PATCH] [IMP] qweb: t-on with t-widget: allow to bind args --- src/qweb_extensions.ts | 20 ++- tests/__snapshots__/component.test.ts.snap | 160 +++++++++++++++++++++ tests/__snapshots__/qweb.test.ts.snap | 2 +- tests/component.test.ts | 88 ++++++++++++ tests/qweb.test.ts | 2 +- 5 files changed, 264 insertions(+), 8 deletions(-) diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index c76ed222..c4a60714 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -183,7 +183,7 @@ QWeb.addDirective({ //------------------------------------------------------------------------------ const T_WIDGET_MODS_CODE = Object.assign({}, MODS_CODE, { - self: "if (e.target !== vn.elm) {return}", + self: "if (e.target !== vn.elm) {return}" }); /** @@ -359,7 +359,7 @@ QWeb.addDirective({ let keepAlive = node.getAttribute("t-keepalive") ? true : false; // t-on- events and t-transition - const events: [string, string[], string][] = []; + const events: [string, string[], string, string][] = []; let transition: string = ""; const attributes = (node).attributes; const props: { [key: string]: string } = {}; @@ -368,7 +368,12 @@ QWeb.addDirective({ const value = attributes[i].textContent!; if (name.startsWith("t-on-")) { const [eventName, ...mods] = name.slice(5).split("."); - events.push([eventName, mods, value]); + let extraArgs; + let handlerName = value.replace(/\(.*\)/, function(args) { + extraArgs = args.slice(1, -1); + return ""; + }); + events.push([eventName, mods, handlerName, extraArgs]); } else if (name === "t-transition") { transition = value; } else if (!name.startsWith("t-")) { @@ -462,7 +467,10 @@ QWeb.addDirective({ updateClassCode = `let cl=w${widgetID}.el.classList;for (let k in ${attVar}) {if (${attVar}[k]) {cl.add(k)} else {cl.remove(k)}}`; } let eventsCode = events - .map(function([eventName, mods, handlerName]) { + .map(function([eventName, mods, handlerName, extraArgs]) { + let params = extraArgs + ? `owner, ${ctx.formatExpression(extraArgs)}` + : "owner"; let handler; if (mods.length > 0) { handler = `function (e) {`; @@ -471,9 +479,9 @@ QWeb.addDirective({ return T_WIDGET_MODS_CODE[mod]; }) .join(""); - handler += `owner['${handlerName}'].call(owner, e);}`; + handler += `owner['${handlerName}'].call(${params}, e);}`; } else { - handler = `owner['${handlerName}'].bind(owner)`; + handler = `owner['${handlerName}'].bind(${params})`; } return `vn.elm.addEventListener('${eventName}', ${handler});`; }) diff --git a/tests/__snapshots__/component.test.ts.snap b/tests/__snapshots__/component.test.ts.snap index 3ac99a21..4947b798 100644 --- a/tests/__snapshots__/component.test.ts.snap +++ b/tests/__snapshots__/component.test.ts.snap @@ -222,6 +222,166 @@ exports[`composition t-widget with dynamic value 2 1`] = ` }" `; +exports[`other directives with t-widget t-on with handler bound to argument 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { + if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { + def3 = w4.__owl__.renderPromise; + } else { + w4.destroy(); + w4 = false; + } + } + if (!w4) { + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, 3));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + +exports[`other directives with t-widget t-on with handler bound to empty object (with non empty inner string) 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { + if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { + def3 = w4.__owl__.renderPromise; + } else { + w4.destroy(); + w4 = false; + } + } + if (!w4) { + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + +exports[`other directives with t-widget t-on with handler bound to empty object 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { + if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { + def3 = w4.__owl__.renderPromise; + } else { + w4.destroy(); + w4 = false; + } + } + if (!w4) { + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + +exports[`other directives with t-widget t-on with handler bound to object 1`] = ` +"function anonymous(context,extra +) { + let utils = this.utils; + let QWeb = this.constructor; + let owner = context; + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //WIDGET + let _2_index = c1.length; + c1.push(null); + let def3; + let w4 = 4 in context.__owl__.cmap ? context.__owl__.children[context.__owl__.cmap[4]] : false; + let props4 = {}; + if (w4 && w4.__owl__.renderPromise && !w4.__owl__.vnode) { + if (utils.shallowEqual(props4, w4.__owl__.renderProps)) { + def3 = w4.__owl__.renderPromise; + } else { + w4.destroy(); + w4 = false; + } + } + if (!w4) { + let widgetKey4 = \`child\`; + let W4 = context.widgets && context.widgets[widgetKey4] || QWeb.widgets[widgetKey4]; + if (!W4) {throw new Error('Cannot find the definition of widget \\"' + widgetKey4 + '\\"')} + w4 = new W4(owner, props4); + context.__owl__.cmap[4] = w4.__owl__.id; + def3 = w4._prepare(); + def3 = def3.then(vnode=>{vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', owner['onEv'].bind(owner, {val: 3}));}};let pvnode=h(vnode.sel, {key: 4, hook: {insert(vn) {let nvn=w4._mount(vnode, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});c1[_2_index]=pvnode;w4.__owl__.pvnode = pvnode;}); + } else { + def3 = def3 || w4._updateProps(props4, extra.forceUpdate, extra.patchQueue); + def3 = def3.then(()=>{if (w4.__owl__.isDestroyed) {return};let pvnode=w4.__owl__.pvnode;c1[_2_index]=pvnode;}); + } + extra.promises.push(def3); + return vn1; +}" +`; + exports[`other directives with t-widget t-on with prevent and self modifiers (order matters) 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/__snapshots__/qweb.test.ts.snap b/tests/__snapshots__/qweb.test.ts.snap index 7ef9544c..0bab4419 100644 --- a/tests/__snapshots__/qweb.test.ts.snap +++ b/tests/__snapshots__/qweb.test.ts.snap @@ -1173,7 +1173,7 @@ exports[`t-on can bind handlers with arguments 1`] = ` }" `; -exports[`t-on can bind handlers with empty object (with non empty inner string 1`] = ` +exports[`t-on can bind handlers with empty object (with non empty inner string) 1`] = ` "function anonymous(context,extra ) { let owner = context; diff --git a/tests/component.test.ts b/tests/component.test.ts index a6ecb056..af07bc27 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -1476,6 +1476,94 @@ describe("other directives with t-widget", () => { expect(widget.n).toBe(1); }); + test("t-on with handler bound to argument", async () => { + expect.assertions(3); + env.qweb.addTemplates(` + +
+
+ `); + class ParentWidget extends Widget { + widgets = { child: Child }; + onEv(n, ev) { + expect(n).toBe(3); + expect(ev.detail).toBe(43); + } + } + class Child extends Widget {} + const widget = new ParentWidget(env); + await widget.mount(fixture); + let child = children(widget)[0]; + child.trigger("ev", 43); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + + test("t-on with handler bound to object", async () => { + expect.assertions(3); + env.qweb.addTemplates(` + +
+
+ `); + class ParentWidget extends Widget { + widgets = { child: Child }; + onEv(o, ev) { + expect(o).toEqual({ val: 3 }); + expect(ev.detail).toBe(43); + } + } + class Child extends Widget {} + const widget = new ParentWidget(env); + await widget.mount(fixture); + let child = children(widget)[0]; + child.trigger("ev", 43); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + + test("t-on with handler bound to empty object", async () => { + expect.assertions(3); + env.qweb.addTemplates(` + +
+
+ `); + class ParentWidget extends Widget { + widgets = { child: Child }; + onEv(o, ev) { + expect(o).toEqual({}); + expect(ev.detail).toBe(43); + } + } + class Child extends Widget {} + const widget = new ParentWidget(env); + await widget.mount(fixture); + let child = children(widget)[0]; + child.trigger("ev", 43); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + + test("t-on with handler bound to empty object (with non empty inner string)", async () => { + expect.assertions(3); + env.qweb.addTemplates(` + +
+
+ `); + class ParentWidget extends Widget { + widgets = { child: Child }; + onEv(o, ev) { + expect(o).toEqual({}); + expect(ev.detail).toBe(43); + } + } + class Child extends Widget {} + const widget = new ParentWidget(env); + await widget.mount(fixture); + let child = children(widget)[0]; + child.trigger("ev", 43); + expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot(); + }); + test("t-on with stop and/or prevent modifiers", async () => { expect.assertions(7); env.qweb.addTemplates(` diff --git a/tests/qweb.test.ts b/tests/qweb.test.ts index b1f49a4d..7c55e0b2 100644 --- a/tests/qweb.test.ts +++ b/tests/qweb.test.ts @@ -896,7 +896,7 @@ describe("t-on", () => { (node).click(); }); - test("can bind handlers with empty object (with non empty inner string", () => { + test("can bind handlers with empty object (with non empty inner string)", () => { expect.assertions(2); qweb.addTemplate( "test",