diff --git a/doc/component.md b/doc/component.md index e940d63e..fd7ecc94 100644 --- a/doc/component.md +++ b/doc/component.md @@ -719,6 +719,15 @@ the order may matter. For instance `t-on-click.prevent.self` will prevent all clicks while `t-on-click.self.prevent` will only prevent clicks on the element itself. +Finally, empty handlers are tolerated as they could be defined only to apply +modifiers. For example, + +```xml + +``` + +This will simply stop the propagation of the event. + ### Form Input Bindings It is very common to need to be able to read the value out of an html `input` (or diff --git a/src/component/directive.ts b/src/component/directive.ts index 7a20ba84..49c2717e 100644 --- a/src/component/directive.ts +++ b/src/component/directive.ts @@ -335,8 +335,10 @@ QWeb.addDirective({ return T_COMPONENT_MODS_CODE[mod]; }) .join(""); - handler += `const fn = owner['${handlerValue}'];`; - handler += `if (fn) { fn.call(${params}, e); } else { owner.${handlerValue}; }`; + if (handlerValue) { + handler += `const fn = owner['${handlerValue}'];`; + handler += `if (fn) { fn.call(${params}, e); } else { owner.${handlerValue}; }`; + } handler += `}`; return `vn.elm.addEventListener('${eventName}', ${handler});`; }) diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts index aefc20f2..c0ed0587 100644 --- a/src/qweb/extensions.ts +++ b/src/qweb/extensions.ts @@ -47,19 +47,24 @@ QWeb.addDirective({ return MODS_CODE[mod]; }) .join(""); - if (!extraArgs) { - handler += `const fn = context['${handlerName}'];`; - handler += `if (fn) { fn.call(${params}, e); } else { context.${handlerName}; }`; - handler += `}`; - ctx.addLine( - `extra.handlers['${eventName}' + ${nodeID}] = extra.handlers['${eventName}' + ${nodeID}] || ${handler};` - ); - ctx.addLine(`p${nodeID}.on['${eventName}'] = extra.handlers['${eventName}' + ${nodeID}];`); + if (handlerName) { + if (!extraArgs) { + handler += `const fn = context['${handlerName}'];`; + handler += `if (fn) { fn.call(${params}, e); } else { context.${handlerName}; }`; + handler += `}`; + ctx.addLine( + `extra.handlers['${eventName}' + ${nodeID}] = extra.handlers['${eventName}' + ${nodeID}] || ${handler};` + ); + ctx.addLine(`p${nodeID}.on['${eventName}'] = extra.handlers['${eventName}' + ${nodeID}];`); + } else { + const handlerKey = `handler${ctx.generateID()}`; + ctx.addLine(`const ${handlerKey} = context['${handlerName}'] && context['${handlerName}'].bind(${params});`); + handler += `if (${handlerKey}) { ${handlerKey}(e); } else { context.${value}; }`; + handler += `}`; + ctx.addLine(`p${nodeID}.on['${eventName}'] = ${handler};`); + } } else { - const handlerKey = `handler${ctx.generateID()}`; - ctx.addLine(`const ${handlerKey} = context['${handlerName}'] && context['${handlerName}'].bind(${params});`); - handler += `if (${handlerKey}) { ${handlerKey}(e); } else { context.${value}; }`; - handler += `}`; + handler += "}"; ctx.addLine(`p${nodeID}.on['${eventName}'] = ${handler};`); } } diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 58d11b50..c4fd2348 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -1058,6 +1058,48 @@ exports[`other directives with t-component t-on with inline statement 1`] = ` }" `; +exports[`other directives with t-component t-on with no handler (only modifiers) 1`] = ` +"function anonymous(context,extra +) { + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let owner = context; + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //COMPONENT + let def3; + let templateId4 = \`__5__\`; + let w4 = templateId4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[templateId4]] : false; + let _2_index = c1.length; + c1.push(null); + let props4 = {}; + if (w4 && w4.__owl__.currentFiber && !w4.__owl__.vnode) { + if (utils.shallowEqual(props4, w4.__owl__.currentFiber.props)) { + def3 = w4.__owl__.currentFiber.promise; + } else { + w4.destroy(); + w4 = false; + } + } + if (!w4) { + let componentKey4 = \`ComponentA\`; + let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]|| context['ComponentA']; + if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} + w4 = new W4(parent, props4); + parent.__owl__.cmap[templateId4] = w4.__owl__.id; + def3 = w4.__prepare(extra.fiber, undefined, undefined); + def3 = def3.then(vnode=>{if (w4.__owl__.isDestroyed){return}vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};let pvnode=h(vnode.sel, {key: templateId4, 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.fiber, undefined, undefined); + 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-component t-on with prevent and self modifiers (order matters) 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 027a7ca2..e6200bce 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -2064,6 +2064,31 @@ describe("other directives with t-component", () => { expect(fixture.innerHTML).toBe('
1
'); }); + test("t-on with no handler (only modifiers)", async () => { + expect.assertions(2); + class ComponentB extends Component { + static template = xml``; + } + class ComponentA extends Component { + static template = xml`

`; + static components = { ComponentB }; + } + class Parent extends Component { + static template = xml`
`; + static components = { ComponentA }; + onEv(ev) { + expect(ev.defaultPrevented).toBe(true); + } + } + const parent = new Parent(env); + await parent.mount(fixture); + + let componentB = children(children(parent)[0])[0]; + componentB.trigger("ev"); + + expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot(); + }); + test("t-if works with t-component", async () => { env.qweb.addTemplate("ParentWidget", `
`); class Child extends Widget {} diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 186f1806..1f94139d 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -1789,6 +1789,22 @@ exports[`t-on t-on combined with t-raw 1`] = ` }" `; +exports[`t-on t-on with empty handler (only modifiers) 1`] = ` +"function anonymous(context,extra +) { + let owner = context; + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + let c2 = [], p2 = {key:2,on:{}}; + var vn2 = h('button', p2, c2); + c1.push(vn2); + p2.on['click'] = function (e) {e.preventDefault();}; + c2.push({text: \`Button\`}); + return vn1; +}" +`; + exports[`t-on t-on with inline statement (function call) 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index 50d9ccb1..d3ec6a36 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -1213,6 +1213,24 @@ describe("t-on", () => { expect(steps).toEqual([1, 2]); }); + test("t-on with empty handler (only modifiers)", () => { + expect.assertions(2); + qweb.addTemplate( + "test", + `
+ +
` + ); + const node = renderToDOM(qweb, "test", {}, { handlers: [] }); + + node.addEventListener('click', (e) => { + expect(e.defaultPrevented).toBe(true); + }); + + const button = (node).getElementsByTagName("button")[0]; + button.click(); + }); + test("t-on combined with t-esc", async () => { expect.assertions(3); qweb.addTemplate("test", `
`);