# 🦉 OWL Component 🦉 ## Content - [Overview](#overview) - [Example](#example) - [Reference](#reference) - [Properties](#properties) - [Static Properties](#static-properties) - [Methods](#methods) - [Lifecycle](#lifecycle) - [Root Component](#root-component) - [Environment](#environment) - [Composition](#composition) - [Event Handling](#event-handling) - [Form Input Bindings](#form-input-bindings) - [`t-key` Directive](#t-key-directive) - [`t-mounted` Directive](#t-mounted-directive) - [Semantics](#semantics) - [Props Validation](#props-validation) - [References](#references) - [Slots](#slots) - [Asynchronous Rendering](#asynchronous-rendering) ## Overview OWL components are the building blocks for user interface. They are designed to be: 1. **declarative:** the user interface should be described in term of the state of the application, not as a sequence of imperative steps. 2. **composable:** each component can seamlessly be created in a parent component by a simple tag or directive in its template. 3. **asynchronous rendering:** the framework will transparently wait for each sub components to be ready before applying the rendering. It uses native promises under the hood. 4. **uses QWeb as a template system:** the templates are described in XML and follow the QWeb specification. This is a requirement for Odoo. OWL components are defined as a subclass of Component. The rendering is exclusively done by a [QWeb](qweb.md) template (which needs to be preloaded in QWeb). Rendering a component generates a virtual dom representation of the component, which is then patched to the DOM, in order to apply the changes in an efficient way. OWL components observe their states, and rerender themselves whenever it is changed. This is done by an [observer](observer.md). ## Example Let us have a look at a simple component: ```javascript class ClickCounter extends owl.Component { state = { value: 0 }; increment() { this.state.value++; } } ``` ```xml ``` Note that this code is written in ESNext style, so it will only run on the latest browsers without a transpilation step. This example show how a component should be defined: it simply subclasses the Component class. If no `template` key is defined, then Owl will use the component's name as template name. Here, a state object is defined. It is not mandatory to use the state object, but it is certainly encouraged. The state object is [observed](observer.md), and any change to it will cause a rerendering. ## Reference An Owl component is a small class which represent a component or some UI element. It exists in the context of an environment (`env`), which is propagated from a parent to its children. The environment needs to have a QWeb instance, which will be used to render the component template. Be aware that the name of the component may be significant: if a component does not define a `template` key, then Owl will lookup in QWeb to find a template with the component name (or one of its ancestor). ### Properties - **`el`** (HTMLElement | null): reference to the DOM root node of the element. It is `null` when the component is not mounted. - **`env`** (Object): the component environment, which contains a QWeb instance. - **`template`** (string, optional): if given, this is the name of the QWeb template that will render the component. - **`state`** (Object): this is the location of the component's state, if there is any. After the willStart method, the `state` property is observed, and each change will cause the component to rerender itself. - **`props`** (Object): this is an object given (in the constructor) by the parent to configure the component. It can be dynamically changed later by the parent, in some case. Note that `props` are owned by the parent, not by the component. As such, it should not ever be modified by the component!! - **`refs`** (Object): the `refs` object contains all references to sub DOM nodes or sub components defined by a `t-ref` directive in the component's template. ### Static Properties - **`props`** (Object, optional): if given, this is an object that describes the type and shape of the (actual) props given to the component. If Owl mode is `dev`, this will be used to validate the props each time the component is created/updated. See [Props Validation](#props-validation) for more information. - **`defaultProps`** (Object, optional): if given, this object define default values for (top-level) props. Whenever `props` are given to the object, they will be altered to add default value (if missing). Note that it does not change the initial object, a new object will be created instead. ### Methods We explain here all the public methods of the `Component` class. - **`mount(target)`** (async): this is the main way a component's hierarchy is added to the DOM: the root component is mounted to a target HTMLElement. Obviously, this is asynchronous, since each children need to be created as well. Most applications will need to call `mount` exactly once, on the root component. - **`unmount()`**: in case a component need to be detached/removed from the DOM, this method can be used. Most applications should not call `unmount`, this is more useful to the underlying component system. - **`render()`** (async): calling this method directly will cause a rerender. Note that this should be very rare to have to do it manually, the Owl framework is most of the time responsible for doing that at an appropriate moment. Note that the render method is asynchronous, so one cannot observe the updated DOM in the same stack frame. - **`shouldUpdate(nextProps)`**: this method is called each time a component's props are updated. It returns a boolean, which indicates if the component should ignore a props update. If it returns false, then `willUpdateProps` will not be called, and no rendering will occur. Its default implementation is to always return true. This is an optimization, similar to React's `shouldComponentUpdate`. Most of the time, this should not be used, but it can be useful if we are handling large number of components. - **`updateEnv(nextEnv)`**: update the environment of a component and all its children. This forces a complete rerender. For example, this could be useful if we have a `isMobile` key in the environment, to decide if we want a mobile interface or a destkop one. - **`destroy()`**. As its name suggests, this method will remove the component, and perform all necessary cleanup, such as unmounting the component, its children, removing the parent/children relationship. This method should almost never be called directly (except maybe on the root component), but should be done by the framework instead. Obviously, these methods are reserved for Owl, and should not be used by Owl users, unless they want to override them. Also, Owl reserves all method names starting with `__`, in order to prevent possible future conflicts with user code whenever Owl needs to change. ### Lifecycle A solid and robust component system needs useful hooks/methods to help developers write components. Here is a complete description of the lifecycle of a owl component: | Method | Description | | ------------------------------------------------ | ----------------------------------------------------- | | **[constructor](#constructorparent-props)** | constructor | | **[willStart](#willstart)** | async, before first rendering | | **[mounted](#mounted)** | just after component is rendered and added to the DOM | | **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update | | **[willPatch](#willpatch)** | just before the DOM is patched | | **[patched](#patchedsnapshot)** | just after the DOM is patched | | **[willUnmount](#willunmount)** | just before removing component from DOM | Notes: - hooks call order is precisely defined: `[willX]` hooks are called first on parent, then on children, and `[Xed]` are called in the reverse order: first children, then parent. - no hook method should ever be called manually. They are supposed to be called by the owl framework whenever it is required. #### `constructor(parent, props)` The constructor is not exactly a hook, it is the regular, normal, constructor of the component. Since it is not a hook, you need to make sure that `super` is called. This is usually where you would set the initial state and the template of the component. ```javascript constructor(parent, props) { super(parent, props); this.state = {someValue: true}; this.template = 'mytemplate'; } ``` Note that with ESNext class fields, the constructor method does not need to be implemented in most cases: ```javascript class ClickCounter extends owl.Component { state = { value: 0 }; ... } ``` #### `willStart()` willStart is an asynchronous hook that can be implemented to perform some action before the initial rendering of a component. It will be called exactly once before the initial rendering. It is useful in some cases, for example, to load external assets (such as a JS library) before the component is rendered. Another use case is to load data from a server. ```javascript async willStart() { await owl.utils.loadJS("my-awesome-lib.js"); } ``` At this point, the component is not yet rendered. Note that a slow `willStart` method will slow down the rendering of the user interface. Therefore, some care should be made to make this method as fast as possible. The component rendering will take place after `willStart` is completed. #### `mounted()` `mounted` is called each time a component is attached to the DOM, after the initial rendering and possibly later if the component was unmounted and remounted. At this point, the component is considered _active_. This is a good place to add some listeners, or to interact with the DOM, if the component needs to perform some measure for example. It is the opposite of `willUnmount`. If a component has been mounted, it will always be unmounted at some point in the future. The mounted method will be called recursively on each of its children. First, the parent, then all its children. Note that the state is now observed. It is however allowed (but not encouraged) to modify the state in the `mounted` hook. Doing so will cause a rerender, which will not be perceptible by the user, but will slightly slow down the component. #### `willUpdateProps(nextProps)` The willUpdateProps is an asynchronous hook, called just before new props are set. This is useful if the component needs some asynchronous task performed, depending on the props (for example, assuming that the props are some record Id, fetching the record data). ```javascript willUpdateProps(nextProps) { return this.loadData({id: nextProps.id}); } ``` This hook is not called during the first render (but willStart is called and performs a similar job). #### `willPatch()` The willPatch hook is called just before the DOM patching process starts. It is not called on the initial render. This is useful to read some information from the DOM. For example, the current position of the scrollbar. Note that modifying the state object is not allowed here. This method is called just before an actual DOM patch, and is only intended to be used to save some local DOM state. Also, it will not be called if the component is not in the DOM (this can happen with components with `t-keepalive`). The return value of this method will be given as the first argument of the corresponding `patched` call. #### `patched(snapshot)` This hook is called whenever a component did actually update its DOM (most likely via a change in its state/props or environment). This method is not called on the initial render. It is useful to interact with the DOM (for example, through an external library) whenever the component was patched. Note that this hook will not be called if the compoent is not in the DOM (this can happen with components with `t-keepalive`). The `snapshot` parameter is the result of the previous `willPatch` call. Updating the compoent state in this hook is possible, but not encouraged. One need to be careful, because updates here will cause rerender, which in turn will cause other calls to patched. So, we need to be particularly careful at avoiding endless cycles. #### `willUnmount()` willUnmount is a hook that is called each time just before a component is unmounted from the DOM. This is a good place to remove some listeners, for example. ```javascript mounted() { this.env.bus.on('someevent', this, this.doSomething); } willUnmount() { this.env.bus.off('someevent', this, this.doSomething); } ``` This is the opposite method of `mounted`. ### Root Component Most of the time, an Owl component will be created automatically by a tag (or the `t-component` directive) in a template. There is however an obvious exception: the root component of an Owl application has to be created manually: ```js class App extends owl.Component { ... } const qweb = new owl.QWeb(TEMPLATES); const env = { qweb: qweb }; const app = new App(env); app.mount(document.body); ``` The root component needs an environment. ### Environment In Owl, an environment is an object with a `qweb` key, which has to be a [QWeb](qweb.md) instance. This qweb instance will be used to render everything. The environment is meant to contain (mostly) static global information and methods for the whole application. For example, settings keys (`mode` to determine if we are in desktop or mobile mode, or `theme`: dark or light), `rpc` methods, session information, ... The environment will be given to each child, unchanged, in the `env` property. This can be very useful to share common information/methods. For example, all rpcs can be made through a `rpc` method in the environment. This makes it very easy to test a component. Updating the environment is not as simple as changing a component's state: its content is not observed, so updates will not be reflected immediately in the user interface. There is however a mechanism to force root widgets to rerender themselves whenever the environment is modified: one only needs to trigger the `update` event on the QWeb instance. For example, a responsive environment could be programmed like this: ```js function setupResponsivePlugin(env) { const isMobile = () => window.innerWidth <= 768; env.isMobile = isMobile(); const updateEnv = owl.utils.debounce(() => { if (env.isMobile !== isMobile()) { env.isMobile = !env.isMobile; env.qweb.trigger('update'); } }, 15); window.addEventListener("resize", updateEnv); } ``` ### Composition The example above shows a QWeb template with a sub component. In a template, components are declared with a tagname corresponding to the class name. It has to be capitalized. ```xml