mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] qweb: add support for event capture
In this commit, we uses the "!" as suffix to designate an event that should be captured. This is inspired by the Vue source code. This solves the issue of communicating additional information to the underlying virtual dom. However, this will most likely disappear when we rewrite the vdom as a virtual block system. closes #650
This commit is contained in:
@@ -111,11 +111,12 @@ In order to remove the DOM event details from the event handlers (like calls to
|
||||
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be
|
||||
specified as additional suffixes of the `t-on` directive.
|
||||
|
||||
| Modifier | Description |
|
||||
| ---------- | ----------------------------------------------------------------- |
|
||||
| `.stop` | calls `event.stopPropagation()` before calling the method |
|
||||
| `.prevent` | calls `event.preventDefault()` before calling the method |
|
||||
| `.self` | calls the method only if the `event.target` is the element itself |
|
||||
| Modifier | Description |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `.stop` | calls `event.stopPropagation()` before calling the method |
|
||||
| `.prevent` | calls `event.preventDefault()` before calling the method |
|
||||
| `.self` | calls the method only if the `event.target` is the element itself |
|
||||
| `.capture` | bind the event handler in [capture](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) mode. |
|
||||
|
||||
```xml
|
||||
<button t-on-click.stop="someMethod">Do something</button>
|
||||
|
||||
@@ -299,6 +299,8 @@ QWeb.addDirective({
|
||||
}
|
||||
let eventsCode = events
|
||||
.map(function([name, value]) {
|
||||
const capture = name.match(/\.capture/);
|
||||
name = capture ? name.replace(/\.capture/, "") : name;
|
||||
const { event, handler } = makeHandlerCode(
|
||||
ctx,
|
||||
name,
|
||||
@@ -306,6 +308,9 @@ QWeb.addDirective({
|
||||
false,
|
||||
T_COMPONENT_MODS_CODE
|
||||
);
|
||||
if (capture) {
|
||||
return `vn.elm.addEventListener('${event}', ${handler}, true);`;
|
||||
}
|
||||
return `vn.elm.addEventListener('${event}', ${handler});`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
@@ -40,7 +40,10 @@ export function makeHandlerCode(
|
||||
putInCache: boolean,
|
||||
modcodes = MODS_CODE
|
||||
): HandlerInfo {
|
||||
const [event, ...mods] = fullName.slice(5).split(".");
|
||||
let [event, ...mods] = fullName.slice(5).split(".");
|
||||
if (mods.includes("capture")) {
|
||||
event = "!" + event;
|
||||
}
|
||||
if (!event) {
|
||||
throw new Error("Missing event name with t-on directive");
|
||||
}
|
||||
|
||||
+18
-6
@@ -70,8 +70,12 @@ function handleEvent(event: Event, vnode: VNode) {
|
||||
on = (vnode.data as VNodeData).on;
|
||||
|
||||
// call event handler(s) if exists
|
||||
if (on && on[name]) {
|
||||
invokeHandler(on[name], vnode, event);
|
||||
if (on) {
|
||||
if (on[name]) {
|
||||
invokeHandler(on[name], vnode, event);
|
||||
} else if (on["!" + name]) {
|
||||
invokeHandler(on["!" + name], vnode, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +104,17 @@ function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
|
||||
if (!on) {
|
||||
for (name in oldOn) {
|
||||
// remove listener if element was changed or existing listeners removed
|
||||
oldElm.removeEventListener(name, oldListener, false);
|
||||
const capture = name.charAt(0) === "!";
|
||||
name = capture ? name.slice(1) : name;
|
||||
oldElm.removeEventListener(name, oldListener, capture);
|
||||
}
|
||||
} else {
|
||||
for (name in oldOn) {
|
||||
// remove listener if existing listener removed
|
||||
if (!on[name]) {
|
||||
oldElm.removeEventListener(name, oldListener, false);
|
||||
const capture = name.charAt(0) === "!";
|
||||
name = capture ? name.slice(1) : name;
|
||||
oldElm.removeEventListener(name, oldListener, capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,13 +131,17 @@ function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
|
||||
if (!oldOn) {
|
||||
for (name in on) {
|
||||
// add listener if element was changed or new listeners added
|
||||
elm.addEventListener(name, listener, false);
|
||||
const capture = name.charAt(0) === "!";
|
||||
name = capture ? name.slice(1) : name;
|
||||
elm.addEventListener(name, listener, capture);
|
||||
}
|
||||
} else {
|
||||
for (name in on) {
|
||||
// add listener if new listener added
|
||||
if (!oldOn[name]) {
|
||||
elm.addEventListener(name, listener, false);
|
||||
const capture = name.charAt(0) === "!";
|
||||
name = capture ? name.slice(1) : name;
|
||||
elm.addEventListener(name, listener, capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,6 +742,44 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`other directives with t-component t-on with .capture modifier 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"__template__2\\"
|
||||
let utils = this.constructor.utils;
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
// Component 'Child'
|
||||
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
|
||||
let props2 = {};
|
||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||
w2.destroy();
|
||||
w2 = false;
|
||||
}
|
||||
if (w2) {
|
||||
w2.__updateProps(props2, extra.fiber, undefined);
|
||||
let pvnode = w2.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey2 = \`Child\`;
|
||||
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
|
||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||
w2 = new W2(parent, props2);
|
||||
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
|
||||
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['capture'](e);}, true);}});});
|
||||
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w2.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`other directives with t-component t-on with getter as handler 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
|
||||
@@ -2192,6 +2192,30 @@ describe("other directives with t-component", () => {
|
||||
expect(steps).toEqual(["click"]);
|
||||
});
|
||||
|
||||
test("t-on with .capture modifier", async () => {
|
||||
const steps: string[] = [];
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`<div><span t-on-click="click">hey</span></div>`;
|
||||
click() {
|
||||
steps.push("click");
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Component {
|
||||
static components = { Child };
|
||||
static template = xml`<div><Child t-on-click.capture="capture"/></div>`;
|
||||
capture() {
|
||||
steps.push("captured");
|
||||
}
|
||||
}
|
||||
const widget = new ParentWidget();
|
||||
await widget.mount(fixture);
|
||||
|
||||
fixture.querySelector("span")!.click();
|
||||
expect(env.qweb.templates[ParentWidget.template].fn.toString()).toMatchSnapshot();
|
||||
expect(steps).toEqual(["captured", "click"]);
|
||||
});
|
||||
|
||||
test("t-on on destroyed components", async () => {
|
||||
const steps: string[] = [];
|
||||
let child;
|
||||
|
||||
@@ -2425,6 +2425,26 @@ exports[`t-on t-on combined with t-raw 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-on t-on with .capture modifier 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1,on:{}};
|
||||
let vn1 = h('div', p1, c1);
|
||||
extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onCapture'](e);};
|
||||
p1.on['!click'] = extra.handlers['!click__2__'];
|
||||
let c3 = [], p3 = {key:3,on:{}};
|
||||
let vn3 = h('button', p3, c3);
|
||||
c1.push(vn3);
|
||||
extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
|
||||
p3.on['click'] = extra.handlers['click__4__'];
|
||||
c3.push({text: \`Button\`});
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-on t-on with empty handler (only modifiers) 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
|
||||
@@ -1627,6 +1627,31 @@ describe("t-on", () => {
|
||||
|
||||
expect(steps).toEqual(["onClick"]);
|
||||
});
|
||||
|
||||
test("t-on with .capture modifier", () => {
|
||||
expect.assertions(2);
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div t-on-click.capture="onCapture">
|
||||
<button t-on-click="doSomething">Button</button>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const steps: string[] = [];
|
||||
const owner = {
|
||||
onCapture() {
|
||||
steps.push("captured");
|
||||
},
|
||||
doSomething() {
|
||||
steps.push("normal");
|
||||
}
|
||||
};
|
||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||
|
||||
const button = (<HTMLElement>node).getElementsByTagName("button")[0];
|
||||
button.click();
|
||||
expect(steps).toEqual(["captured", "normal"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("t-ref", () => {
|
||||
|
||||
Reference in New Issue
Block a user