diff --git a/README.md b/README.md index adefff21c..174b655d6 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ A complete documentation for Owl can be found here: The most important sections are: -- [Quick Start](doc/learning/quick_start.md) +- [Tutorial: TodoList application](doc/learning/tutorial_todoapp.md) +- [QWeb templating language](doc/reference/qweb_templating_language.md) - [Component](doc/reference/component.md) - [Hooks](doc/reference/hooks.md) diff --git a/doc/learning/environment.md b/doc/learning/environment.md deleted file mode 100644 index 8bfb69b16..000000000 --- a/doc/learning/environment.md +++ /dev/null @@ -1,47 +0,0 @@ -# 🦉 Environment 🦉 - -An environment is an object which contains a [`QWeb` instance](../reference/qweb.md). -Whenever a root component is created, it is assigned an environment (see the -reference section on [environment](../reference/environment.md). This environment -is then automatically given to each sub components (and accessible in the `this.env` property). - -The environment is mostly static. Each application is free to add anything to -the environment, which is very useful, since this can be accessed by each sub -component. - -Some good use cases for the environment is: - -- some configuration keys, -- session information, -- generic services (such as doing rpcs, or accessing local storage). - -Doing it this way means that components are easily testable: we can simply -create a test environment with mock services. - -For example: - -```js -async function myEnv() { - const templates = await loadTemplates(); - const qweb = new QWeb({ templates }); - const session = getSession(); - - return { - _t: myTranslateFunction, - session: session, - qweb: qweb, - services: { - localStorage: localStorage, - rpc: rpc - }, - debug: false, - inMobileMode: true - }; -} - -async function start() { - App.env = await myEnv(); - const app = new App(); - await app.mount(document.body); -} -``` diff --git a/doc/learning/tutorial_todoapp.md b/doc/learning/tutorial_todoapp.md index 10659875f..6e96c3234 100644 --- a/doc/learning/tutorial_todoapp.md +++ b/doc/learning/tutorial_todoapp.md @@ -181,14 +181,14 @@ class App extends Component { } ``` -The template contains a [`t-foreach`](../reference/qweb.md#loops) loop to iterate +The template contains a [`t-foreach`](../reference/qweb_templating_language.md#loops) loop to iterate through the tasks. It can find the `tasks` list from the component, since the component is the rendering context. Note that we use the `id` of each task as a `t-key`, which is very common. There are two css classes: `task-list` and `task`, that we will use in the next section. Finally, notice the use of the `t-att-checked` attribute: -prefixing an attribute by [`t-att`](../reference/qweb.md#dynamic-attributes) makes +prefixing an attribute by [`t-att`](../reference/qweb_templating_language.md#dynamic-attributes) makes it dynamic. Owl will evaluate the expression and set it as the value of the attribute. diff --git a/doc/readme.md b/doc/readme.md index 2c2f89b80..d7d27e612 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -9,9 +9,11 @@ - [Environment](reference/environment.md) - [Event Bus](reference/event_bus.md) - [Hooks](reference/hooks.md) -- [Misc](reference/misc.md) +- [Miscellaneous Components](reference/misc.md) - [Observer](reference/observer.md) -- [QWeb](reference/qweb.md) +- [Props](reference/props.md) +- [QWeb Templating Language](reference/qweb_templating_language.md) +- [QWeb Engine](reference/qweb_engine.md) - [Router](reference/router.md) - [Store](reference/store.md) - [Tags](reference/tags.md) @@ -21,7 +23,6 @@ - [Quick Start: create an (almost) empty Owl application](learning/quick_start.md) - [Tutorial: create a TodoList application](learning/tutorial_todoapp.md) -- [Environment: what it is and what it should contain](learning/environment.md) ## Miscellaneous diff --git a/doc/reference/component.md b/doc/reference/component.md index 3e9a1933a..da4ae60fb 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -14,11 +14,11 @@ - [Composition](#composition) - [Event Handling](#event-handling) - [Form Input Bindings](#form-input-bindings) - - [`t-key` Directive](#t-key-directive) - [Semantics](#semantics) - [Props Validation](#props-validation) - [References](#references) - [Slots](#slots) + - [Dynamic sub components](#dynamic-sub-components) - [Asynchronous Rendering](#asynchronous-rendering) - [Error Handling](#error-handling) - [Functional Components](#functional-components) @@ -42,7 +42,7 @@ OWL components are the building blocks for user interface. They are designed to 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). +exclusively done by a [QWeb](qweb_templating_language.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. @@ -81,7 +81,7 @@ a state object is defined, by using the `useState` hook. It is not mandatory to An Owl component is a small class which represents a component or some UI element. It exists in the context of an [environment](environment.md) (`env`), which is propagated from a -parent to its children. The environment needs to have a [QWeb](qweb.md) instance, which +parent to its children. The environment needs to have a [QWeb](qweb_templating_language.md) 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 @@ -166,7 +166,7 @@ to be called in the constructor. - **`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. +- **`env`** (Object): the component [environment](environment.md), which contains a QWeb instance. - **`props`** (Object): this is an object containing all the properties given by the parent to a child component. For example, in the following situation, @@ -502,66 +502,8 @@ the static `components` key, then fallbacks on the global registry. _Props_: In this example, the child component will receive the object `{count: 4}` in its constructor. This will be assigned to the `props` variable, which can be accessed on the component (and also, in the template). Whenever the state is updated, then -the sub component will also be updated automatically. - -Note that there are restrictions on valid prop names: `class`, `style` and any -string which starts with `t-` are not allowed. - -It is not common, but sometimes we need a dynamic component name and/or dynamic props. In this case, -the `t-component` directive can also be used to accept dynamic values with string interpolation (like the [`t-attf-`](qweb.md#dynamic-attributes) directive): - -```xml -
- -
-``` - -```js -class ParentComponent { - static components = { ChildComponent1, ChildComponent2 }; - state = { id: 1 }; -} -``` - -And the `t-props` directive can be used to specify totally dynamic props: - -```xml -
- -
-``` - -```js -class ParentComponent { - static components = { Child }; - some = { obj: { a: 1, b: 2 } }; -} -``` - -There is an even more dynamic way to use `t-component`: its value can be an -expression evaluating to an actual component class. In that case, this is the -class that will be used to create the component: - -```js -class A extends Component { - static template = xml`child a`; -} -class B extends Component { - static template = xml`child b`; -} -class App extends Component { - static template = xml``; - - state = { child: "a" }; - - get myComponent() { - return this.state.child === "a" ? A : B; - } -} -``` - -In this example, the component `App` selects dynamically the concrete sub -component class. +the sub component will also be updated automatically. See the [props section](props.md) +for more information. **CSS and style:** Owl allows the parent to declare additional css classes or style for the sub component: css declared in `class`, `style`, `t-att-class` or `t-att-style` will be added to the @@ -787,69 +729,6 @@ update a number whenever the change is done. Note: the online playground has an example to show how it works. -### `t-key` Directive - -Even though Owl tries to be as declarative as possible, the DOM does not fully -expose its state declaratively in the DOM tree. For example, the scrolling state, -the current user selection, the focused element or the state of an input are not -set as attribute in the DOM tree. This is why we use a virtual dom -algorithm to keep the actual DOM node as much as possible. - -However, in some situations, this is not enough, and we need to help Owl decide -if an element is actually the same, or is a different element with the same -properties. - -Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]` -and we render them in this template: - -```xml -

-``` - -The result will be two `

` tags with text `a` and `b`. Now, if we swap them, -and rerender the template, Owl needs to know what the intent is: - -- should Owl actually swap the DOM nodes, -- or should it keep the DOM nodes, but with an updated text content? - -This might look trivial, but it actually matters. These two possibilities lead -to different results in some cases. For example, if the user selected the text -of the first `p`, swapping them will keep the selection while updating the -text content will not. - -There are many other cases where this is important: `input` tags with their -value, css classes and animations, scroll position... - -So, the `t-key` directive is used to give an identity to an element. It allows -Owl to understand if different elements of a list are actually different or not. - -The above example could be modified by adding an ID: `[{id: 1, text: "a"}, {id: 2, text: "b"}]`. -Then, the template could look like this: - -```xml -

-``` - -The `t-key` directive is useful for lists (`t-foreach`). A key should be -a unique number or string (objects will not work: they will be cast to the -`"[object Object]"` string, which is obviously not unique). - -Also, the key can be set on a `t` tag or on its children. The following variations -are all equivalent: - -```xml -

- -

- - -

- - - -

- -``` ### Semantics @@ -1145,6 +1024,49 @@ be considered the `default` slot. For example: ``` +### Dynamic sub components + +It is not common, but sometimes we need a dynamic component name. In this case, +the `t-component` directive can also be used to accept dynamic values with string interpolation (like the [`t-attf-`](qweb_templating_language.md#dynamic-attributes) directive): + +```xml +

+ +
+``` + +```js +class ParentComponent { + static components = { ChildComponent1, ChildComponent2 }; + state = { id: 1 }; +} +``` + +There is an even more dynamic way to use `t-component`: its value can be an +expression evaluating to an actual component class. In that case, this is the +class that will be used to create the component: + +```js +class A extends Component { + static template = xml`child a`; +} +class B extends Component { + static template = xml`child b`; +} +class App extends Component { + static template = xml``; + + state = { child: "a" }; + + get myComponent() { + return this.state.child === "a" ? A : B; + } +} +``` + +In this example, the component `App` selects dynamically the concrete sub +component class. + ### Asynchronous Rendering Working with asynchronous code always adds a lot of complexity to a system. Whenever diff --git a/doc/reference/environment.md b/doc/reference/environment.md index 40d19caff..798366611 100644 --- a/doc/reference/environment.md +++ b/doc/reference/environment.md @@ -1,6 +1,14 @@ # 🦉 Environment 🦉 -An environment is an object which contains a [`QWeb` instance](qweb.md). Whenever +## Content + +- [Overview](#overview) +- [Setting an Environment](#setting-an-environment) +- [Content of an Environment](#content-of-an-environment) + +## Overview + +An environment is an object which contains a [`QWeb` instance](qweb_engine.md). Whenever a root component is created, it is assigned an environment (see [below](#setting-an-environment) for more info on this). This environment is then automatically given to each sub component (and accessible in the `this.env` @@ -12,15 +20,20 @@ property). A B ``` -This way, all components share the same `QWeb` instance. +This way, all components share the same `QWeb` instance. Owl internally requires +that the environment has a `qweb` key which maps to a +[`QWeb`](qweb_engine.md) instance. This is the QWeb instance that will be used to +render each templates in this specific component tree. Note that if no `QWeb` +instance is provided, Owl will simply generate it on the fly. -Note: some additional information about what should go into an environment -can be found in the [learning section](../learning/environment.md). +The environment is mostly static. Each application is free to add anything to +the environment, which is very useful, since this can be accessed by each sub +component. ## Setting an environment An Owl application needs an [environment](environment.md) to be executed. The -environment has an important key: the [QWeb](qweb.md) instance, which will render +environment has an important key: the [QWeb](qweb_engine.md) instance, which will render all templates. Whenever a root component `App` is mounted, Owl will setup a valid environment by @@ -51,3 +64,42 @@ by simply doing this: ```js Component.env = myEnv; // will be the default env for all components ``` + +## Content of an Environment + +Some good use cases for additional keys in the environment are: + +- some configuration keys, +- session information, +- generic services (such as doing rpcs, or accessing local storage). + +Doing it this way means that components are easily testable: we can simply +create a test environment with mock services. + +For example: + +```js +async function myEnv() { + const templates = await loadTemplates(); + const qweb = new QWeb({ templates }); + const session = getSession(); + + return { + _t: myTranslateFunction, + session: session, + qweb: qweb, + services: { + localStorage: localStorage, + rpc: rpc + }, + debug: false, + inMobileMode: true + }; +} + +async function start() { + App.env = await myEnv(); + const app = new App(); + await app.mount(document.body); +} +``` diff --git a/doc/reference/hooks.md b/doc/reference/hooks.md index e486bf0b6..7774c5ab8 100644 --- a/doc/reference/hooks.md +++ b/doc/reference/hooks.md @@ -301,11 +301,11 @@ key, and components references are accessed with `comp`. Notes: - if used on a component, the reference will be set in the `refs` -variable between `willPatch` and `patched`, + variable between `willPatch` and `patched`, - on a component, accessing `ref.el` will get the root node of the component. The `t-ref` directive also accepts dynamic values with string interpolation -(like the [`t-attf-`](qweb.md#dynamic-attributes) and +(like the [`t-attf-`](qweb_templating_language.md#dynamic-attributes) and `t-component` directives). For example, ```xml diff --git a/doc/reference/props.md b/doc/reference/props.md new file mode 100644 index 000000000..cc1761ee3 --- /dev/null +++ b/doc/reference/props.md @@ -0,0 +1,97 @@ +# 🦉 Props 🦉 + +## Content + +- [Overview](#overview) +- [Definition](#definition) +- [Good Practices](#good-practices) +- [Dynamic Props](#dynamic-props) + +## Overview + +In Owl, `props` (short for _properties_) is an object which contains every piece +of data given to a component by its parent. + +```js +class Child extends Component { + static template = xml`
`; +} + +class Parent extends Component { + static template = xml`
`; + static components = { Child }; + state = useState({ a: "fromparent" }); +} +``` + +In this example, the `Child` component receives two props from its parent: `a` +and `b`. They are collected into a `props` object by Owl, with each value being +evaluated in the context of the parent. So, `props.a` is equal to `'fromparent'` and +`props.b` is equal to `'string'`. + +Note that `props` is an object that only makes sense from the perspective of the +child component. + +## Definition + +The `props` object is made of every attributes defined on the template, with the +following exceptions: + +- every attribute starting with `t-` are not props (they are QWeb directives), +- `style` and `class` attributes are excluded as well (they are applied by Owl on + the root element of the component). + +In the following example: + +```xml +
+ + + +
+``` + +the `props` object contains the following keys: + +- for `ComponentA`: `a` and `b`, +- for `ComponentB`: `model`, +- for `ComponentC`: empty object + +## Good Practices + +A `props` object is a collection of values that come from the parent. As such, +they are owned by the parent, and should never be modified by the child: + +```js +class MyComponent extends Component { + constructor(parent, props) { + super(parent, props); + props.a.b = 43; // Never do that!!! + } +} +``` + +Props should be considered readonly, from the perspective of the child component. +If there is a need to modify them, then the request to update them should be +sent to the parent (for example, with an event). + +Any value can go in a props. Strings, objects, classes, or even callbacks could +be given to a child component (but then, in the case of callbacks, communicating +with events seems more appropriate). + +## Dynamic Props + +The `t-props` directive can be used to specify totally dynamic props: + +```xml +
+ +
+``` + +```js +class ParentComponent { + static components = { Child }; + some = { obj: { a: 1, b: 2 } }; +} +``` diff --git a/doc/reference/qweb_engine.md b/doc/reference/qweb_engine.md new file mode 100644 index 000000000..4a97aea1a --- /dev/null +++ b/doc/reference/qweb_engine.md @@ -0,0 +1,151 @@ +# 🦉 QWeb Engine 🦉 + +## Content + +- [Overview](#overview) +- [Reference](#reference) + +## Overview + +[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary +templating engine used by Odoo. The QWeb class in the OWL project is an +implementation of that specification with a few interesting points: + +- it compiles templates into functions that output a virtual DOM instead of a + string. This is necessary for the component system. +- it has a few extra directives: `t-component`, `t-on`, ... + +We present in this section the engine, not the templating language. + +## Reference + +This section is about the javascript code that implements the `QWeb` specification. +Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be +instantiated: + +```js +const qweb = new owl.QWeb(); +``` + +Its API is quite simple: + +- **`constructor(config)`**: constructor. Takes an optional configuration object + with an optional `templates` string to add initial + templates (see `addTemplates` for more information on format of the string) + and an optional `translateFn` translate function (see the section on + [translations](#translations)). + + ```js + const qweb = new owl.QWeb({ templates: TEMPLATES, translateFn: _t }); + ``` + +- **`addTemplate(name, xmlStr, allowDuplicate)`**: add a specific template. + + ```js + qweb.addTemplate("mytemplate", "
hello
"); + ``` + + If the optional `allowDuplicate` is set to `true`, then `QWeb` will simply + ignore templates added for a second time. Otherwise, `QWeb` will crash. + +- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name` + attribute). + + ```js + const TEMPLATES = ` + +
main
+
other component
+
`; + qweb.addTemplates(TEMPLATES); + ``` + +- **`render(name, context, extra)`**: renders a template. This returns a `vnode`, + which is a virtual representation of the DOM (see [vdom doc](../architecture/vdom.md)). + + ```js + const vnode = qweb.render("App", component); + ``` + +- **`renderToString(name, context)`**: renders a template, but returns an html + string. + + ```js + const str = qweb.renderToString("someTemplate", somecontext); + ``` + +- **`registerTemplate(name, template)`**: static function to register a global + QWeb template. This is useful for commonly used components accross the + application, and for making a template available to an application without + having a reference to the actual QWeb instance. + + ```js + QWeb.registerTemplate("mytemplate", `
some template
`); + ``` + +- **`registerComponent(name, Component)`**: static function to register an OWL Component + to QWeb's global registry. Globally registered Components can be used in + templates (see the `t-component` directive). This is useful for commonly used + components accross the application. + + ```js + class Dialog extends owl.Component { ... } + QWeb.registerComponent("Dialog", Dialog); + + ... + + class ParentComponent extends owl.Component { ... } + qweb.addTemplate("ParentComponent", "
"); + ``` + +In some way, a `QWeb` instance is the core of an Owl application. It is the only +mandatory element of an [environment](environment.md). As such, it +has an extra responsibility: it can act as an event bus for internal communication +between Owl classes. This is the reason why `QWeb` actually extends [EventBus](event_bus.md). + +### Translations + +If properly setup, Owl QWeb engine can translate all rendered templates. To do +so, it needs a translate function, which takes a string and returns a string. + +For example: + +```js +const translations = { + hello: "bonjour", + yes: "oui", + no: "non" +}; +const translateFn = str => translations[str] || str; + +const qweb = new QWeb({ translateFn }); +``` + +Once setup, all rendered templates will be translated using `translateFn`: + +- each text node will be replaced with its translation, +- each of the following attribute values will be translated as well: `title`, + `placeholder`, `label` and `alt`, +- translating text nodes can be disabled with the special attribute `t-translation`, + if its value is `off`. + +So, with the above `translateFn`, the following templates: + +```xml +
hello
+
hello
+
Are you sure?
+ +``` + +will be rendered as: + +```xml +
bonjour
+
hello
+
Are you sure?
+ +``` + +Note that the translation is done during the compilation of the template, not +when it is rendered. diff --git a/doc/reference/qweb.md b/doc/reference/qweb_templating_language.md similarity index 71% rename from doc/reference/qweb.md rename to doc/reference/qweb_templating_language.md index c687327d0..29f141d15 100644 --- a/doc/reference/qweb.md +++ b/doc/reference/qweb_templating_language.md @@ -1,10 +1,9 @@ -# 🦉 QWeb 🦉 +# 🦉 QWeb Templating Language🦉 ## Content - [Overview](#overview) - [Directives](#directives) -- [QWeb Engine](#qweb-engine) - [Reference](#reference) - [White Spaces](#white-spaces) - [Root Nodes](#root-nodes) @@ -21,14 +20,11 @@ ## Overview -[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary templating engine used by Odoo. It is based on the XML format, and used +[QWeb](https://www.odoo.com/documentation/13.0/reference/qweb.html) is the primary +templating engine used by Odoo. It is based on the XML format, and used mostly to generate HTML. In OWL, QWeb templates are compiled into functions that generate a virtual dom representation of the HTML. -Template directives are specified as XML attributes prefixed with `t-`, for instance `t-if` for conditionals, with elements and other attributes being rendered directly. - -To avoid element rendering, a placeholder element `` is also available, which executes its directive but doesn’t generate any output in and of itself. - ```xml
Some string @@ -40,29 +36,32 @@ To avoid element rendering, a placeholder element `` is also available, which
``` -The QWeb class in the OWL project is an implementation of that specification -with a few interesting points: +Template directives are specified as XML attributes prefixed with `t-`, for +instance `t-if` for conditionals, with elements and other attributes being +rendered directly. -- it compiles templates into functions that output a virtual DOM instead of a - string. This is necessary for the component system. -- it has a few extra directives: `t-component`, `t-on`, ... +To avoid element rendering, a placeholder element `` is also available, which +executes its directive but doesn’t generate any output in and of itself. + +We present in this section the templating language, including its Owl specific +extensions. ## Directives -We present here a list of all standard QWeb directives: - -| Name | Description | -| ------------------------------ | ------------------------------------------------------------ | -| `t-esc` | [Outputting safely a value](#outputting-data) | -| `t-raw` | [Outputting value, without escaping](#outputting-data) | -| `t-set`, `t-value` | [Setting variables](#setting-variables) | -| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) | -| `t-foreach`, `t-as` | [Loops](#loops) | -| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) | -| `t-call` | [Rendering sub templates](#rendering-sub-templates) | -| `t-debug`, `t-log` | [Debugging](#debugging) | -| `t-translation` | [Disabling the translation of a node](#translations) | -| `t-name` | [Defining a template (not really a directive)](#qweb-engine) | +For reference, here is a list of all standard QWeb directives: + +| Name | Description | +| ------------------------------ | -------------------------------------------------------------- | +| `t-esc` | [Outputting safely a value](#outputting-data) | +| `t-raw` | [Outputting value, without escaping](#outputting-data) | +| `t-set`, `t-value` | [Setting variables](#setting-variables) | +| `t-if`, `t-elif`, `t-else`, | [conditionally rendering](#conditionals) | +| `t-foreach`, `t-as` | [Loops](#loops) | +| `t-att`, `t-attf-*`, `t-att-*` | [Dynamic attributes](#dynamic-attributes) | +| `t-call` | [Rendering sub templates](#rendering-sub-templates) | +| `t-debug`, `t-log` | [Debugging](#debugging) | +| `t-translation` | [Disabling the translation of a node](#translations) | +| `t-name` | [Defining a template (not really a directive)](qweb_engine.md) | The component system in Owl requires additional directives, to express various needs. Here is a list of all Owl specific directives: @@ -71,104 +70,14 @@ 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)](component.md#t-key-directive) | +| `t-key` | [Defining a key (to help virtual dom reconciliation)](#loops) | | `t-on-*` | [Event handling](component.md#event-handling) | | `t-transition` | [Defining an animation](animations.md#css-transitions) | | `t-slot` | [Rendering a slot](component.md#slots) | | `t-model` | [Form input bindings](component.md#form-input-bindings) | -## QWeb Engine - -This section is about the javascript code that implements the `QWeb` specification. -Owl exports a `QWeb` class in `owl.QWeb`. To use it, it just needs to be -instantiated: - -```js -const qweb = new owl.QWeb(); -``` - -Its API is quite simple: - -- **`constructor(config)`**: constructor. Takes an optional configuration object - with an optional `templates` string to add initial - templates (see `addTemplates` for more information on format of the string) - and an optional `translateFn` translate function (see the section on - [translations](#translations)). - - ```js - const qweb = new owl.QWeb({ templates: TEMPLATES, translateFn: _t }); - ``` - -- **`addTemplate(name, xmlStr, allowDuplicate)`**: add a specific template. - - ```js - qweb.addTemplate("mytemplate", "
hello
"); - ``` - - If the optional `allowDuplicate` is set to `true`, then `QWeb` will simply - ignore templates added for a second time. Otherwise, `QWeb` will crash. - -- **`addTemplates(xmlStr)`**: add a list of templates (identified by `t-name` - attribute). - - ```js - const TEMPLATES = ` - -
main
-
other component
-
`; - qweb.addTemplates(TEMPLATES); - ``` - -- **`render(name, context, extra)`**: renders a template. This returns a `vnode`, - which is a virtual representation of the DOM (see [vdom doc](../architecture/vdom.md)). - - ```js - const vnode = qweb.render("App", component); - ``` - -- **`renderToString(name, context)`**: renders a template, but returns an html - string. - - ```js - const str = qweb.renderToString("someTemplate", somecontext); - ``` - -- **`registerTemplate(name, template)`**: static function to register a global - QWeb template. This is useful for commonly used components accross the - application, and for making a template available to an application without - having a reference to the actual QWeb instance. - - ```js - QWeb.registerTemplate("mytemplate", `
some template
`); - ``` - -- **`registerComponent(name, Component)`**: static function to register an OWL Component - to QWeb's global registry. Globally registered Components can be used in - templates (see the `t-component` directive). This is useful for commonly used - components accross the application. - - ```js - class Dialog extends owl.Component { ... } - QWeb.registerComponent("Dialog", Dialog); - - ... - - class ParentComponent extends owl.Component { ... } - qweb.addTemplate("ParentComponent", "
"); - ``` - -In some way, a `QWeb` instance is the core of an Owl application. It is the only -mandatory element of an [environment](environment.md). As such, it -has an extra responsibility: it can act as an event bus for internal communication -between Owl classes. This is the reason why `QWeb` actually extends [EventBus](event_bus.md). - ## Reference -We define in this section the specification of how `QWeb` templates should be -rendered. Note that we only document here the standard QWeb specification. Owl -specific extensions are documented in various other parts of the documentation. - ### White Spaces White spaces in a template are handled in a special way: @@ -479,16 +388,67 @@ into the global context. ``` -Owl QWeb is used as the template engine for components. Components are frequently -updated, and reuse as much of the previous DOM as possible. Loops offer a specific -problem for this usecase: how does the template engine know if two rows have -been swapped, or if the content of these rows was changed? To help Owl with that, -there is an additional directive: [`t-key`](component.md#t-key-directive). + +Even though Owl tries to be as declarative as possible, the DOM does not fully +expose its state declaratively in the DOM tree. For example, the scrolling state, +the current user selection, the focused element or the state of an input are not +set as attribute in the DOM tree. This is why we use a virtual dom +algorithm to keep the actual DOM node as much as possible. + +However, in some situations, this is not enough, and we need to help Owl decide +if an element is actually the same, or is a different element with the same +properties. + +Consider the following situation: we have a list of two items `[{text: "a"}, {text: "b"}]` +and we render them in this template: ```xml -

- +

+``` + +The result will be two `

` tags with text `a` and `b`. Now, if we swap them, +and rerender the template, Owl needs to know what the intent is: + +- should Owl actually swap the DOM nodes, +- or should it keep the DOM nodes, but with an updated text content? + +This might look trivial, but it actually matters. These two possibilities lead +to different results in some cases. For example, if the user selected the text +of the first `p`, swapping them will keep the selection while updating the +text content will not. + +There are many other cases where this is important: `input` tags with their +value, css classes and animations, scroll position... + +So, the `t-key` directive is used to give an identity to an element. It allows +Owl to understand if different elements of a list are actually different or not. + +The above example could be modified by adding an ID: `[{id: 1, text: "a"}, {id: 2, text: "b"}]`. +Then, the template could look like this: + +```xml +

+``` + +The `t-key` directive is useful for lists (`t-foreach`). A key should be +a unique number or string (objects will not work: they will be cast to the +`"[object Object]"` string, which is obviously not unique). + +Also, the key can be set on a `t` tag or on its children. The following variations +are all equivalent: + +```xml +

+

+ + +

+ + + +

+ ``` If there is no `t-key` directive, Owl will use the index as a default key. @@ -543,23 +503,9 @@ will result in : ### Translations -If properly setup, Owl QWeb engine can translate all rendered templates. To do -so, it needs a translate function, which takes a string and returns a string. - -For example: - -```js -const translations = { - hello: "bonjour", - yes: "oui", - no: "non" -}; -const translateFn = str => translations[str] || str; - -const qweb = new QWeb({ translateFn }); -``` - -Once setup, all rendered templates will be translated using `translateFn`: +By default, QWeb specify that templates should be translated. If this behaviour +is not wanted, there is a `t-translation` directive which can turn off +translations (if it is set to the `off` value), with the following rules: - each text node will be replaced with its translation, - each of the following attribute values will be translated as well: `title`, @@ -567,26 +513,8 @@ Once setup, all rendered templates will be translated using `translateFn`: - translating text nodes can be disabled with the special attribute `t-translation`, if its value is `off`. -So, with the above `translateFn`, the following templates: - -```xml -

hello
-
hello
-
Are you sure?
- -``` - -will be rendered as: - -```xml -
bonjour
-
hello
-
Are you sure?
- -``` - -Note that the translation is done during the compilation of the template, not -when it is rendered. +See [here](qweb_engine.md#translations) for more information on how to setup a +translate function in Owl QWeb. ### Debugging diff --git a/doc/tooling.md b/doc/tooling.md index 98f1e3602..19b1f97d7 100644 --- a/doc/tooling.md +++ b/doc/tooling.md @@ -44,7 +44,7 @@ it easier to scale application to larger size. To do so, Owl currently has a small helper that makes it easy to define a template inside a javascript (or typescript) file: the [`xml`](reference/tags.md#xml-tag) -helper. With this, a template is automatically registered to [QWeb](reference/qweb.md). +helper. With this, a template is automatically registered to [QWeb](reference/qweb_engine.md). This means that the template and the javascript code can be defined in the same file. It is not currently possible to add css to the same file, but Owl may diff --git a/src/component/component.ts b/src/component/component.ts index 39cbef254..bda72e5f9 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -282,9 +282,7 @@ export class Component { return Promise.resolve(); } if (!(target instanceof HTMLElement)) { - let message = `Component '${ - this.constructor.name - }' cannot be mounted: the target is not a valid DOM node.`; + let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`; message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`; throw new Error(message); }