From 047a9c89931f4430c8ac77ad75f67a8e8907847b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sat, 15 Feb 2020 10:18:12 +0100 Subject: [PATCH] [DOC] add dedicated page on event handling part of #632 --- doc/learning/overview.md | 2 +- doc/learning/tutorial_todoapp.md | 2 +- doc/readme.md | 1 + doc/reference/component.md | 123 ------------------- doc/reference/event_handling.md | 136 ++++++++++++++++++++++ doc/reference/misc.md | 2 +- doc/reference/qweb_templating_language.md | 2 +- 7 files changed, 141 insertions(+), 127 deletions(-) create mode 100644 doc/reference/event_handling.md diff --git a/doc/learning/overview.md b/doc/learning/overview.md index 1ef8cd70..1f87a1d9 100644 --- a/doc/learning/overview.md +++ b/doc/learning/overview.md @@ -124,7 +124,7 @@ class Parent extends Component { In this example, the `OrderLine` component trigger a `add-to-order` event. This will generate a DOM event which will bubble along the DOM tree. It will then be intercepted by the parent component, which will then get the line (from the -`detail` key) and then increment its quantity. See the section on [event handling](../reference/component.md#event-handling) +`detail` key) and then increment its quantity. See the page on [event handling](../reference/event_handling.md) for more details on how events work. Note that this example would have also worked if the `OrderLine` component diff --git a/doc/learning/tutorial_todoapp.md b/doc/learning/tutorial_todoapp.md index aa9d255c..1aa4d3a7 100644 --- a/doc/learning/tutorial_todoapp.md +++ b/doc/learning/tutorial_todoapp.md @@ -446,7 +446,7 @@ Now, this is an interesting situation: the task is displayed by the `Task` component, but it is not the owner of its state, so it cannot modify it. Instead, we want to communicate the request to toggle a task to the `App` component. Since `App` is a parent of `Task`, we can -[trigger](../reference/component.md#event-handling) an event in `Task` and listen +[trigger](../reference/event_handling.md) an event in `Task` and listen for it in `App`. In `Task`, change the `input` to: diff --git a/doc/readme.md b/doc/readme.md index a0266e26..6dccb40b 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -24,6 +24,7 @@ provided by Owl. - [Context](reference/context.md) - [Environment](reference/environment.md) - [Event Bus](reference/event_bus.md) +- [Event Handling](reference/event_handling.md) - [Hooks](reference/hooks.md) - [Miscellaneous Components](reference/misc.md) - [Observer](reference/observer.md) diff --git a/doc/reference/component.md b/doc/reference/component.md index 2a5c8d32..72662393 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -12,7 +12,6 @@ - [Lifecycle](#lifecycle) - [Root Component](#root-component) - [Composition](#composition) - - [Event Handling](#event-handling) - [Form Input Bindings](#form-input-bindings) - [References](#references) - [Dynamic sub components](#dynamic-sub-components) @@ -551,128 +550,6 @@ with a class object: ``` -### Event Handling - -In a component's template, it is useful to be able to register handlers on DOM -elements to some specific events. This is what makes a template _alive_. There -are four different use cases. - -1. Register an event handler on a DOM node (_pure_ DOM event) -2. Register an event handler on a component (_pure_ DOM event) -3. Register an event handler on a DOM node (_business_ DOM event) -4. Register an event handler on a component (_business_ DOM event) - -A _pure_ DOM event is directly triggered by a user interaction (e.g. a `click`). - -```xml - -``` - -This will be roughly translated in javascript like this: - -```js -button.addEventListener("click", component.someMethod.bind(component)); -``` - -The suffix (`click` in this example) is simply the name of the actual DOM -event. - -A _business_ DOM event is triggered by a call to `trigger` on a component. - -```xml - -``` - -```js - class MyComponent { - someWhere() { - const payload = ...; - this.trigger('menu-loaded', payload); - } - } -``` - -The call to `trigger` generates an `OwlEvent`, a subclass of [_CustomEvent_](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) -with an additional attribute `originalComponent` (the component that triggered -the event). The generated event is of type `menu-loaded` and dispatches it on -the component's DOM element (`this.el`). The event bubbles and is cancelable. -The parent component listening to event `menu-loaded` will receive the payload -in its `someMethod` handler (in the `detail` property of the event), whenever -the event is triggered. - -```js - class ParentComponent { - someMethod(ev) { - const payload = ev.detail; - ... - } - } -``` - -By convention, we use KebabCase for the name of _business_ events. - -The `t-on` directive allows to prebind its arguments. For example, - -```xml - -``` - -Here, `expr` is a valid Owl expression, so it could be `true` or some variable -from the rendering context. - -One can also directly specify inline statements. For example, - -```xml - -``` - -Here, `state` must be defined in the rendering context (typically the component) -as it will be translated to: - -```js -button.addEventListener("click", () => { - context.state.counter++; -}); -``` - -Warning: inline expressions are evaluated in the context of the template. This -means that they can access the component methods and properties. But if they set -a key, the inline statement will actually not modify the component, but a key in -a sub scope. - -```xml - - -``` - -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 | - -```xml - -``` - -Note that modifiers can be combined (ex: `t-on-click.stop.prevent`), and that -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/doc/reference/event_handling.md b/doc/reference/event_handling.md new file mode 100644 index 00000000..405458d8 --- /dev/null +++ b/doc/reference/event_handling.md @@ -0,0 +1,136 @@ +# 🦉 Event Handling 🦉 + +## Content + +- [Event Handling](#event-handling) +- [Business DOM Events](#business-dom-events) +- [Inline Event Handlers](#inline-event-handlers) +- [Modifiers](#modifiers) + +## Event Handling + +In a component's template, it is useful to be able to register handlers on DOM +elements to some specific events. This is what makes a template _alive_. There +are four different use cases. + +1. Register an event handler on a DOM node (_pure_ DOM event) +2. Register an event handler on a component (_pure_ DOM event) +3. Register an event handler on a DOM node (_business_ DOM event) +4. Register an event handler on a component (_business_ DOM event) + +A _pure_ DOM event is directly triggered by a user interaction (e.g. a `click`). + +```xml + +``` + +This will be roughly translated in javascript like this: + +```js +button.addEventListener("click", component.someMethod.bind(component)); +``` + +The suffix (`click` in this example) is simply the name of the actual DOM +event. + +## Business DOM Events + +A _business_ DOM event is triggered by a call to `trigger` on a component. + +```xml + +``` + +```js + class MyComponent { + someWhere() { + const payload = ...; + this.trigger('menu-loaded', payload); + } + } +``` + +The call to `trigger` generates an `OwlEvent`, a subclass of [_CustomEvent_](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events) +with an additional attribute `originalComponent` (the component that triggered +the event). The generated event is of type `menu-loaded` and dispatches it on +the component's DOM element (`this.el`). The event bubbles and is cancelable. +The parent component listening to event `menu-loaded` will receive the payload +in its `someMethod` handler (in the `detail` property of the event), whenever +the event is triggered. + +```js + class ParentComponent { + someMethod(ev) { + const payload = ev.detail; + ... + } + } +``` + +By convention, we use KebabCase for the name of _business_ events. + +The `t-on` directive allows to prebind its arguments. For example, + +```xml + +``` + +Here, `expr` is a valid Owl expression, so it could be `true` or some variable +from the rendering context. + +## Inline Event Handlers + +One can also directly specify inline statements. For example, + +```xml + +``` + +Here, `state` must be defined in the rendering context (typically the component) +as it will be translated to: + +```js +button.addEventListener("click", () => { + context.state.counter++; +}); +``` + +Warning: inline expressions are evaluated in the context of the template. This +means that they can access the component methods and properties. But if they set +a key, the inline statement will actually not modify the component, but a key in +a sub scope. + +```xml + + +``` + +## Modifiers + +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 | + +```xml + +``` + +Note that modifiers can be combined (ex: `t-on-click.stop.prevent`), and that +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. diff --git a/doc/reference/misc.md b/doc/reference/misc.md index f5b92620..27b1a474 100644 --- a/doc/reference/misc.md +++ b/doc/reference/misc.md @@ -95,7 +95,7 @@ The teleported piece is updated as any other `Component`'s DOM and in the same s Namely the teleported piece will be updated in function of its parents components, and patched as a normal child. -The [_business_ events](component.md#event-handling) triggered by a child component will be stopped +The [_business_ events](event_handling.md#business-dom-events) triggered by a child component will be stopped to not bubble outside of the `target`. They will, on the other hand, be re-directed onto the `Portal`'s root node and bubble up the DOM as if it were triggered by a regular child component. diff --git a/doc/reference/qweb_templating_language.md b/doc/reference/qweb_templating_language.md index c183cb76..8e897755 100644 --- a/doc/reference/qweb_templating_language.md +++ b/doc/reference/qweb_templating_language.md @@ -71,7 +71,7 @@ needs. Here is a list of all Owl specific directives: | `t-component`, `t-props` | [Defining a sub component](component.md#composition) | | `t-ref` | [Setting a reference to a dom node or a sub component](component.md#references) | | `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) | -| `t-on-*` | [Event handling](component.md#event-handling) | +| `t-on-*` | [Event handling](event_handling.md) | | `t-transition` | [Defining an animation](animations.md#css-transitions) | | `t-slot` | [Rendering a slot](slots.md) | | `t-model` | [Form input bindings](component.md#form-input-bindings) |