Professional Documents
Culture Documents
https://github.com/odoo/owl/blob/ma
ster/doc/readme.md
Learning Owl
Reference
You will find here a complete reference of every feature, class or object provided by
Owl.
Animations
Browser
Component
Content
Concurrency Model
Configuration
Context
Environment
Event Bus
Event Handling
Error Handling
Hooks
Mounting a component
Miscellaneous Components
Observer
Props
Props Validation
QWeb Templating Language
QWeb Engine
Router
Store
Slots
Tags
Utils
Other Topics
This section provides miscellaneous document that explains some topics which
cannot be considered either a tutorial, or reference documentation.
This project will be an opportunity to discover and learn some important Owl
concepts, such as components, store, and how to organize an application.
Content
For this tutorial, we will do a very simple project, with static files and no additional
tooling. The first step is to create the following file structure:
todoapp/
index.html
app.css
app.js
owl.js
The entry point for this application is the file index.html, which should have the
following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" />
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
Then, app.css can be left empty for now. It will be useful later on to style our
application. app.js is where we will write all our code. For now, let's just put the
following code:
(function () {
console.log("hello owl", owl.__info__.version);
})();
Note that we put everything inside an immediately executed function to avoid
leaking anything to the global scope.
Finally, owl.js should be the last version downloaded from the Owl repository (you
can use owl.min.js if you prefer). Be aware that you should download
the owl.iife.js or owl.iife.min.js, because these files are built to run directly on the
browser (other files such as owl.cjs.js are built to be bundled by other tools).
Now, the project should be ready. Loading the index.html file into a browser should
show an empty page, with the title Owl Todo App, and it should log a message such
as hello owl 1.0.0 in the console.
An Owl application is made out of components, with a single root component. Let us
start by defining an App component. Replace the content of the function in app.js by
the following code:
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components
class App extends Component {
static template = xml`<div>todo app</div>`;
}
// Setup code
function setup() {
mount(App, { target: document.body });
}
whenReady(setup);
Now, reloading the page in a browser should display a message.
The code is pretty simple, but let us explain the last line in more detail. The browser
tries to execute the javascript code in app.js as quickly as possible, and it could
happen that the DOM is not ready yet when we try to mount the App component. To
avoid this situation, we use the whenReady helper to delay the execution of
the setup function until the DOM is ready.
Note 1: in a larger project, we would split the code in multiple files, with components
in a sub folder, and a main file that would initialize the application. However, this is a
very small project, and we want to keep it as simple as possible.
Note 2: this tutorial uses the static class field syntax. This is not yet supported by all
browsers. Most real projects will transpile their code, so this is not a problem, but for
this tutorial, if you need the code to work on every browser, you will need to translate
each static keyword to an assignation to the class:
class App extends Component {}
App.template = xml`<div>todo app</div>`;
Note 3: writing inline templates with the xml helper is nice, but there is no syntax
highlighting, and this makes it very easy to have malformed xml. Some editors
support syntax highlighting for this situation. For example, VS Code has an
addon Comment tagged template, which, if installed, will properly display tagged
templates:
static template = xml /* xml */`<div>todo app</div>`;
Note 4: Large applications will probably want to be able to translate templates. Using
inline templates makes it slightly harder, since we need additional tooling to extract
the xml from the code, and to replace it with the translated values.
Now that the basics are done, it is time to start thinking about tasks. To accomplish
what we need, we will keep track of the tasks as an array of objects with the following
keys:
id: a number. It is extremely useful to have a way to uniquely identify tasks. Since the
title is something created/edited by the user, it offers no guarantee that it is unique.
So, we will generate a unique id number for each task.
title: a string, to explain what the task is about.
isCompleted: a boolean, to keep track of the status of the task
Now that we decided on the internal format of the state, let us add some demo data
and a template to the App component:
class App extends Component {
static template = xml/* xml */ `
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<div class="task">
<input type="checkbox" t-att-checked="task.isCompleted"/>
<span><t t-esc="task.title"/></span>
</div>
</t>
</div>`;
tasks = [
{
id: 1,
title: "buy milk",
isCompleted: true,
},
{
id: 2,
title: "clean house",
isCompleted: false,
},
];
}
The template contains a t-foreach 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 makes it dynamic. Owl will evaluate the expression and set it as the value of the
attribute.
So far, our task list looks quite bad. Let us add the following to app.css:
.task-list {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}
.task {
font-size: 18px;
color: #111111;
}
This is better. Now, let us add an extra feature: completed tasks should be styled a
little differently, to make it clearer that they are not as important. To do that, we will
add a dynamic css class on each task:
It is now clear that there should be a Task component to encapsulate the look and
behavior of a task.
This Task component will display a task, but it cannot own the state of the task: a
piece of data should only have one owner. Doing otherwise is asking for trouble. So,
the Task component will get its data as a prop. This means that the data is still owned
by the App component, but can be used by the Task component (without modifying
it).
Since we are moving code around, it is a good opportunity to refactor the code a
little bit:
// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml /* xml */`
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"/>
<span><t t-esc="props.task.title"/></span>
</div>`;
class Task extends Component {
static template = TASK_TEMPLATE;
static props = ["task"];
}
// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml /* xml */`
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>`;
tasks = [
...
];
}
// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function setup() {
owl.config.mode = "dev";
mount(App, { target: document.body });
}
whenReady(setup);
A lot of stuff happened here:
We still use a list of hardcoded tasks. It's really time to give the user a way to add
tasks himself. The first step is to add an input to the App component. But this input
will be outside of the task list, so we need to adapt App template, js, and css:
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask"/>
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
</div>
addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
const title = ev.target.value.trim();
ev.target.value = "";
console.log('adding task', title);
// todo
}
}
.todo-app {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}
.task-list {
margin-top: 8px;
}
We now have a working input, which log to the console whenever the user adds a
task. Notice that when you load the page, the input is not focused. But adding tasks is
a core feature of a task list, so let us make it as fast as possible by focusing the input.
mounted() {
this.inputRef.el.focus();
}
The inputRef is defined as a class field, so it is equivalent to defining it in the
constructor. It simply instructs Owl to keep a reference to anything with the
corresponding t-ref keyword. We then implement the mounted lifecycle method,
where we now have an active reference that we can use to focus the input.
In the previous section, we did everything except implement the code that actually
create tasks! So, let us do that now.
8. Toggling tasks
If you tried to mark a task as completed, you may have noticed that the text did not
change in opacity. This is because there is no code to modify the isCompleted flag.
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 an event in Task and listen for it in App.
In Task, change the input to:
<input type="checkbox" t-att-checked="props.task.isCompleted" t-on-
click="toggleTask"/>
and add the toggleTask method:
toggleTask() {
this.trigger('toggle-task', {id: this.props.task.id});
}
We now need to listen for that event in the App template:
<div class="task-list" t-on-toggle-task="toggleTask">
and implement the toggleTask code:
toggleTask(ev) {
const task = this.tasks.find(t => t.id === ev.detail.id);
task.isCompleted = !task.isCompleted;
}
9. Deleting tasks
Let us now add the possibility do delete tasks. To do that, we first need to add a trash
icon on each task, then we will proceed just like in the previous section.
.delete {
opacity: 0;
cursor: pointer;
text-align: center;
}
.task:hover .delete {
opacity: 1;
}
deleteTask() {
this.trigger('delete-task', {id: this.props.task.id});
}
And now, we need to listen to the delete-task event in App:
<div class="task-list" t-on-toggle-task="toggleTask" t-on-delete-
task="deleteTask">
deleteTask(ev) {
const index = this.tasks.findIndex(t => t.id === ev.detail.id);
this.tasks.splice(index, 1);
}
Looking at the code, it is apparent that we now have code to handle tasks scattered
in more than one place. Also, it mixes UI code and business logic code. Owl has a way
to manage state separately from the user interface: a Store.
Let us use it in our application. This is a pretty large refactoring (for our application),
since it involves extracting all task related code out of the components. Here is the
new content of the app.js file:
const { Component, Store, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useStore } = owl.hooks;
// -------------------------------------------------------------------------
// Store
// -------------------------------------------------------------------------
const actions = {
addTask({ state }, title) {
title = title.trim();
if (title) {
const task = {
id: state.nextId++,
title: title,
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
},
};
const initialState = {
nextId: 1,
tasks: [],
};
// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<span><t t-esc="props.task.title"/></span>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑
</span>
</div>`;
class Task extends Component {
static template = TASK_TEMPLATE;
static props = ["task"];
dispatch = useDispatch();
}
// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ `
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-
input"/>
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
</div>`;
inputRef = useRef("add-input");
tasks = useStore((state) => state.tasks);
dispatch = useDispatch();
mounted() {
this.inputRef.el.focus();
}
addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value);
ev.target.value = "";
}
}
}
// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function setup() {
owl.config.mode = "dev";
const store = new Store({ actions, state: initialState });
App.env.store = store;
mount(App, { target: document.body });
}
whenReady(setup);
Now, our TodoApp works great, except if the user closes or refresh the browser! It is
really inconvenient to only keep the state of the application in memory. To fix this, we
will save the tasks in the local storage. With our current codebase, it is a simple
change: only the setup code needs to be updated.
function makeStore() {
const localState = window.localStorage.getItem("todoapp");
const state = localState ? JSON.parse(localState) : initialState;
const store = new Store({ state, actions });
store.on("update", null, () => {
localStorage.setItem("todoapp", JSON.stringify(store.state));
});
return store;
}
function setup() {
owl.config.mode = "dev";
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
The key point is to use the fact that the store is an EventBus which triggers
an update event whenever it is updated.
We are almost done, we can add/update/delete tasks. The only missing feature is the
possibility to display the task according to their completed status. We will need to
keep track of the state of the filter in App, then filter the visible tasks according to its
value.
// on top of file, readd useState:
const { useRef, useDispatch, useState, useStore } = owl.hooks;
// in App:
filter = useState({value: "all"})
get displayedTasks() {
switch (this.filter.value) {
case "active": return this.tasks.filter(t => !t.isCompleted);
case "completed": return this.tasks.filter(t => t.isCompleted);
case "all": return this.tasks;
}
}
setFilter(filter) {
this.filter.value = filter;
}
Finally, we need to display the visible filters. We can do that, and at the same time,
display the number of tasks in a small panel below the main list:
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/>
<div class="task-list">
<t t-foreach="displayedTasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>
<div class="task-panel" t-if="tasks.length">
<div class="task-counter">
<t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length">
/ <t t-esc="tasks.length"/>
</t>
task(s)
</div>
<div>
<span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f"
t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)"
t-esc="f"/>
</div>
</div>
</div>
.task-panel {
color: #0088ff;
margin-top: 8px;
font-size: 14px;
display: flex;
}
.task-panel .task-counter {
flex-grow: 1;
}
.task-panel span {
padding: 5px;
cursor: pointer;
}
.task-panel span.active {
font-weight: bold;
}
Notice here that we set dynamically the class of the filter with the object syntax: each
key is a class that we want to set if its value is truthy.
Our list is feature complete. We can still add a few extra details to improve the user
experience.
.task:hover {
background-color: #def0ff;
}
.task.done label {
text-decoration: line-through;
}
Final code
Our application is now complete. It works, the UI code is well separated from the
business logic code, it is testable, all under 150 lines of code (template included!).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OWL Todo App</title>
<link rel="stylesheet" href="app.css" />
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
(function () {
const { Component, Store, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
const { useRef, useDispatch, useState, useStore } = owl.hooks;
// -------------------------------------------------------------------------
// Store
// -------------------------------------------------------------------------
const actions = {
addTask({ state }, title) {
title = title.trim();
if (title) {
const task = {
id: state.nextId++,
title: title,
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
},
};
const initialState = {
nextId: 1,
tasks: [],
};
// -------------------------------------------------------------------------
// Task Component
// -------------------------------------------------------------------------
const TASK_TEMPLATE = xml/* xml */ `
<div class="task" t-att-class="props.task.isCompleted ? 'done' : ''">
<input type="checkbox" t-att-checked="props.task.isCompleted"
t-att-id="props.task.id"
t-on-click="dispatch('toggleTask', props.task.id)"/>
<label t-att-for="props.task.id"><t t-esc="props.task.title"/></label>
<span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑
</span>
</div>`;
// -------------------------------------------------------------------------
// App Component
// -------------------------------------------------------------------------
const APP_TEMPLATE = xml/* xml */ `
<div class="todo-app">
<input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-
input"/>
<div class="task-list">
<Task t-foreach="displayedTasks" t-as="task" t-key="task.id"
task="task"/>
</div>
<div class="task-panel" t-if="tasks.length">
<div class="task-counter">
<t t-esc="displayedTasks.length"/>
<t t-if="displayedTasks.length lt tasks.length">
/ <t t-esc="tasks.length"/>
</t>
task(s)
</div>
<div>
<span t-foreach="['all', 'active', 'completed']"
t-as="f" t-key="f"
t-att-class="{active: filter.value===f}"
t-on-click="setFilter(f)"
t-esc="f"/>
</div>
</div>
</div>`;
inputRef = useRef("add-input");
tasks = useStore((state) => state.tasks);
filter = useState({ value: "all" });
dispatch = useDispatch();
mounted() {
this.inputRef.el.focus();
}
addTask(ev) {
// 13 is keycode for ENTER
if (ev.keyCode === 13) {
this.dispatch("addTask", ev.target.value);
ev.target.value = "";
}
}
get displayedTasks() {
switch (this.filter.value) {
case "active":
return this.tasks.filter((t) => !t.isCompleted);
case "completed":
return this.tasks.filter((t) => t.isCompleted);
case "all":
return this.tasks;
}
}
setFilter(filter) {
this.filter.value = filter;
}
}
// -------------------------------------------------------------------------
// Setup code
// -------------------------------------------------------------------------
function makeStore() {
const localState = window.localStorage.getItem("todoapp");
const state = localState ? JSON.parse(localState) : initialState;
const store = new Store({ state, actions });
store.on("update", null, () => {
localStorage.setItem("todoapp", JSON.stringify(store.state));
});
return store;
}
function setup() {
owl.config.mode = "dev";
const env = { store: makeStore() };
mount(App, { target: document.body, env });
}
whenReady(setup);
})();
.todo-app {
width: 300px;
margin: 50px auto;
background: aliceblue;
padding: 10px;
}
.task-list {
margin-top: 8px;
}
.task {
font-size: 18px;
color: #111111;
display: grid;
grid-template-columns: 30px auto 30px;
}
.task:hover {
background-color: #def0ff;
}
.delete {
opacity: 0;
cursor: pointer;
text-align: center;
}
.task:hover .delete {
opacity: 1;
}
.task.done {
opacity: 0.7;
}
.task.done label {
text-decoration: line-through;
}
.task-panel {
color: #0088ff;
margin-top: 8px;
font-size: 14px;
display: flex;
}
.task-panel .task-counter {
flex-grow: 1;
}
.task-panel span {
padding: 5px;
cursor: pointer;
}
.task-panel span.active {
font-weight: bold;
}
Quick Overview 🦉
Owl components in an application are used to define a (dynamic) tree of
components.
Root
/ \
A B
/ \
C D
State: each component can manage its own local state. It is a simple ES6 class, there
are no special rules:
state = { value: 0 };
increment() {
this.state.value++;
this.render();
}
}
The example above shows a component with a local state. Note that since there is
nothing magical to the state object, we need to manually call the render function
whenever we update it. This can quickly become annoying (and not efficient if we do
it too much). There is a better way: using the useState hook, which transforms an
object into a reactive version of itself:
const { useState } = owl.hooks;
increment() {
this.state.value++;
}
}
add() {
this.trigger("add-to-order", { line: this.props.line });
}
}
addToOrder(event) {
const line = event.detail.line;
line.quantity++;
}
}
Overview
Simple html file
With a static server
Standard Javascript project
Overview
Each software project has its specific needs. Many of these needs can be solved with
some tooling: webpack, gulp, css preprocessor, bundlers, transpilers, ...
Because of that, it is usually not simple to just start a project. Some frameworks
provide their own tooling to help with that. But then, you have to integrate and learn
how these applications work.
Owl is designed to be used with no tooling at all. Because of that, Owl can "easily" be
integrated in a modern build toolchain. In this section, we will discuss a few different
setups to start a project. Each of these setups has advantages and disadvantages in
different situations.
The simplest possible setup is the following: a simple javascript file with your code. To
do that, let us create the following file structure:
hello_owl/
index.html
owl.js
app.js
The file owl.js can be downloaded from the last release published
at https://github.com/odoo/owl/releases. It is a single javascript file which export all
Owl into the global owl object.
Now, index.html should contain the following:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
And app.js should look like this:
const { Component, mount } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components
class App extends Component {
static template = xml`<div>Hello Owl</div>`;
}
// Setup code
function setup() {
mount(App, target: { document.body })
}
whenReady(setup);
Now, simply loading this html file in a browser should display a welcome message.
This setup is not fancy, but it is extremely simple. There are no tooling at all required.
It can be slightly optimized by using the minified build of Owl.
The previous setup has a big disadvantage: the application code is located in a single
file. Obviously, we could split it in several files and add multiple <script> tags in the
html page, but then we need to make sure the script are inserted in the proper order,
we need to export each file content in global variables and we lose autocompletion
across files.
There is a low tech solution to this issue: using native javascript modules. This
however has a requirement: for security reasons, browsers will not accept modules on
content served through the file protocol. This means that we need to use a static
server.
Let us start a new project with the following file structure:
hello_owl/
src/
app.js
index.html
main.js
owl.js
As previously, the file owl.js can be downloaded from the last release published
at https://github.com/odoo/owl/releases.
Now, index.html should contain the following:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
<script src="owl.js"></script>
<script src="main.js" type="module"></script>
</head>
<body></body>
</html>
Not that the main.js script tag has the type="module" attribute. This means that the
browser will parse the script as a module, and load all its dependencies.
Here is the content of app.js and main.js:
// app.js ----------------------------------------------------------------------
const { Component, mount } = owl;
const { xml } = owl.tags;
// main.js ---------------------------------------------------------------------
import { App } from "./app.js";
function setup() {
mount(App, { target: document.body });
}
owl.utils.whenReady(setup);
The main.js file import the app.js file. Note that the import statement has a .js suffix,
which is important. Most text editor can understand this syntax and will provide
autocompletion.
Now, to execute this code, we need to serve the src folder statically. A low tech way
to do that is to use for example the python SimpleHTTPServer feature:
$ cd src
$ python -m SimpleHTTPServer 8022 # now content is available at localhost:8022
Another more "javascripty" way to do it is to create a npm application. To do that, we
can add the following package.json file at the root of the project:
{
"name": "hello_owl",
"version": "0.1.0",
"description": "Starting Owl app",
"main": "src/index.html",
"scripts": {
"serve": "serve src"
},
"author": "John",
"license": "ISC",
"devDependencies": {
"serve": "^11.3.0"
}
}
We can now install the serve tool with the command npm install, and then, start a
static server with the simple npm run serve command.
The previous setup works, and is certainly good for some usecases, including quick
prototyping. However, it lacks some useful features, such as livereload, a test suite, or
bundling the code in a single file.
Each of these features, and many others, can be done in many different ways. Since it
is really not trivial to configure such a project, we provide here an example that can
be used as a starting point.
hello_owl/
public/
index.html
src/
components/
App.js
main.js
tests/
components/
App.test.js
helpers.js
.gitignore
package.json
webpack.config.js
This project as a public folder, meant to contain all static assets, such as images and
styles. The src folder has the javascript source code, and finally, tests contains the
test suite.
Here is the content of index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Owl</title>
</head>
<body></body>
</html>
Note that there are no <script> tag here. They will be injected by webpack. Now, let's
have a look at the javascript files:
// src/components/App.js -------------------------------------------------------
import { Component, tags, useState } from "@odoo/owl";
// src/main.js -----------------------------------------------------------------
import { utils, mount } from "@odoo/owl";
import { App } from "./components/App";
function setup() {
mount(App, { target: document.body });
}
utils.whenReady(setup);
// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl";
let fixture;
beforeEach(() => {
fixture = makeTestFixture();
});
afterEach(() => {
fixture.remove();
});
describe("App", () => {
test("Works as expected...", async () => {
await mount(App, { target: fixture });
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
click(fixture, "div");
await nextTick();
expect(fixture.innerHTML).toBe("<div>Hello World</div>");
});
});
// tests/helpers.js ------------------------------------------------------------
import { Component } from "@odoo/owl";
import "regenerator-runtime/runtime";
npm run build # build the full application in prod mode in dist/
Overview
Unit Tests
Overview
It is a good practice to test applications and components to ensure that they behave
as expected. There are many ways to test a user interface: manual testing, integration
testing, unit testing, ...
In this section, we will discuss how to write unit tests for components.
Unit Tests
Writing unit tests for Owl components really depends on the testing framework used
in a project. But usually, it involves the following steps:
To help with this, it is useful to have a helper.js file that contains some common
utility functions:
export function makeTestFixture() {
let fixture = document.createElement("div");
document.body.appendChild(fixture);
return fixture;
}
return {
qweb: new QWeb(templates),
..., // each service can be mocked here
};
}
With such a file, a typical test suite for Jest will look like this:
// in SomeComponent.test.js
import { SomeComponent } from "../../src/ui/SomeComponent";
import { nextTick, makeTestFixture, makeTestEnv} from '../helpers';
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
let fixture: HTMLElement;
let env: Env;
beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
// we set here the default environment for each component created in the test
Component.env = env;
});
afterEach(() => {
fixture.remove();
});
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("SomeComponent", () => {
test("component behaves as expected", async () => {
const props = {...}; // depends on the component
const comp = await mount(SomeComponent, { target: fixture, props });
// do some assertions
expect(...).toBe(...);
fixture.querySelector('button').click();
await nextTick();
function afterUpdates() {
return new Promise((resolve, reject) => {
let timer = setTimeout(poll, 20);
let counter = 0;
function poll() {
counter++;
if (owl.Component.scheduler.tasks.length) {
if (counter > 10) {
reject(new Error("timeout"));
} else {
timer = setTimeout(poll);
}
} else {
resolve();
}
}
});
}
To do so, Owl has two small helpers that make it easy to define a template or a
stylesheet inside a javascript (or typescript) file: the xml and css helper.
This means that the template, the style and the javascript code can be defined in the
same file. For example:
// -----------------------------------------------------------------------------
// TEMPLATE
// -----------------------------------------------------------------------------
const TEMPLATE = xml/* xml */ `
<div class="main">
<Sidebar/>
<Content />
</div>`;
// -----------------------------------------------------------------------------
// STYLE
// -----------------------------------------------------------------------------
const STYLE = css/* css */ `
.main {
display: grid;
grid-template-columns: 200px auto;
}
`;
// -----------------------------------------------------------------------------
// CODE
// -----------------------------------------------------------------------------
class Main extends Component {
static template = TEMPLATE;
static style = STYLE;
static components = { Sidebar, Content };
// rest of component...
}
Note that the above example has an inline xml comment, just after the xml call. This is
useful for some editor plugins, such as the VS Code addon Comment tagged template,
which, if installed, add syntax highlighting to the content of the template string.
How to debug Owl applications 🦉
Non trivial applications become quickly more difficult to understand. It is then useful
to have a solid understanding of what is going on. To help with that, logging useful
information is extremely valuable. There is a javascript file which can be evaluated in
an application.
Once it is executed, it will log a lot of information on each component main hooks.
The following code is a minified version to make it easier to copy/paste:
REFERENCE
Animations 🦉
Animation is a complex topic. There are many different use cases, and many solutions
and technologies. Owl only supports some basic use cases.
Sometimes, using pure CSS is enough. For these use cases, Owl is not really
necessary: it just needs to render a DOM element with a specific class. For example:
btn {
background-color: gray;
}
.flash {
transition: background 0.5s;
}
.flash:active {
background-color: #41454a;
transition: background 0s;
}
will produce a nice flash effect whenever the user clicks (or activates with the
keyboard) the button.
CSS Transitions
At node destruction:
For example, a simple fade in/out effect can be done with this:
<div>
<div t-if="state.flag" class="square" t-transition="fade">Hello</div>
</div>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
The t-transition directive can be applied on a node element or on a component.
Notes:
Owl does not support more than one transition on a single node, so the t-
transition expression must be a single value (i.e. no space allowed).
SCSS Mixins
If you use SCSS, you can use mixins to make generic animations. Here is an exemple
with a fade in / fade out animation:
.#{$name}_fade-enter {
opacity: 0;
}
.#{$name}_fade-leave-to {
opacity: 0;
}
}
Usage:
<SomeTag t-transition="o_notification_fade"/>
rowser 🦉
Content
Overview
Browser Content
Overview
The browser object contains some browser native APIs, such as setTimeout, that are
used by Owl and its utility functions. They are exposed with the intent of making
them mockable if necessary.
owl.browser.setTimeout === window.setTimeout; // return true
For now, this object contains some functions that are not used by Owl. They will
eventually be removed in Owl 2.0.
Browser Content
OWL Component 🦉
Content
Overview
Example
Reference
o Reactive System
o Properties
o Static Properties
o Methods
o Lifecycle
constructor(parent, props)
setup()
willStart()
mounted()
willUpdateProps(nextProps)
willPatch()
patched(snapshot)
willUnmount()
catchError(error)
o Root Component
o Composition
o Form Input Bindings
o References
o Dynamic sub components
o Functional Components
o SVG Components
Overview
OWL components are the building blocks for user interface. They are designed to be:
Example
increment() {
this.state.value++;
}
}
<button t-name="ClickCounter" t-on-click="increment">
Click Me! [<t t-esc="state.value"/>]
</button>
Note that this code is written in ESNext style, so it will only run on the latest browsers
without a transpilation step.
This example shows how a component should be defined: it simply subclasses the
Component class. If no static template key is defined, then Owl will use the
component's name as template name. Here, a state object is defined, by using
the useState hook. It is not mandatory to use the state object, but it is certainly
encouraged. The result of the useState call is observed, and any change to it will
cause a rerendering.
Reference
Reactive system
OWL components are normal javascript classes. So, changing a component internal
state does nothing more:
increment() {
this.state.value++;
}
}
Clicking on the Counter component defined above will call the increment method, but
it will not rerender the component. To fix that, one could add an explicit call
to render in increment:
increment() {
this.state.value++;
this.render();
}
However, it may be simple in this case, but it quickly become cumbersome, as a
component get more complex, and its internal state is modified by more than one
method.
increment() {
this.state.value++;
}
}
Obviously, we can call the useState hook more than once:
const { useState } = owl.hooks;
increment(counter) {
counter.value++;
}
}
Note that hooks are subject to one important rule: they need to be called in the
constructor.
Properties
Static Properties
template (string, optional): if given, this is the name of the QWeb template that will
render the component. Note that there is a helper xml to make it easy to define an
inline template.
Methods
o first-child: with this option, the component will be prepended inside the
target,
o last-child (default value): with this option, the component will be appended
in the target element,
o self: the target will be used as the root element for the component. This
means that the target has to be an HTMLElement (and not a document
fragment). In this situation, it is possible that the component cannot be
unmounted. For example, if its target is document.body.
Note that the render method is asynchronous, so one cannot observe the
updated DOM in the same stack frame.
Method Description
setup setup
willUpdateProp
async, before props update
s
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.
constructor(parent, props) {
super(parent, props);
this.state = useState({someValue: true});
this.template = 'mytemplate';
}
Note that with ESNext class fields, the constructor method does not need to be
implemented in most cases:
...
}
Hook functions can be called in the constructor.
setup()
setup is run just after the component is constructed. It is a lifecycle method, very
similar to the constructor, except that it does not receive any argument.
It is a valid method to call hook functions. Note that one of the main reason to have
the setup hook in the component lifecycle is to make it possible to monkey patch it. It
is a common need in the Odoo ecosystem.
setup() {
useSetupAutofocus();
}
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.
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.
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.
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 information from the DOM. For
example, the current position of the scrollbar.
Note that modifying the state 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.
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 component is not in the DOM.
Updating the component state in this hook is possible, but not encouraged. One
needs to be careful, because updates here will create an additional rendering, which
in turn will cause other calls to the patched method. 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 listeners, for example.
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.
catchError(error)
The catchError method is useful when we need to intercept and properly react to
(rendering) errors that occur in some sub components. See the page on error
handling.
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:
class App extends owl.Component { ... }
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.
<div t-name="ParentComponent">
<span>some text</span>
<MyComponent info="13" />
</div>
class ParentComponent extends owl.Component {
static components = { MyComponent: MyComponent};
...
}
In this example, the ParentComponent's template creates a component MyComponent just
after the span. The info key will be added to the subcomponent's props. Each props is
a string which represents a javascript (QWeb) expression, so it is dynamic. If it is
necessary to give a string, this can be done by quoting it: someString="'somevalue'".
Note that the rendering context for the template is the component itself. This means
that the template can access state (if it exists), props, env, or any methods defined in
the component.
<div t-name="ParentComponent">
<ChildComponent count="state.val" />
</div>
class ParentComponent {
static components = { ChildComponent };
state = useState({ val: 4 });
}
Whenever the template is rendered, it will automatically create the
subcomponent ChildComponent at the correct place. It needs to find the reference to
the actual component class in the special static components key, or the class registered
in QWeb's global registry (see register function of QWeb). It first looks inside 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. See the props section 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 root component element.
<div t-name="ParentComponent">
<MyComponent class="someClass" style="font-weight:bold;" info="13" />
</div>
Warning: there is a small caveat with dynamic class attributes: since Owl needs to be
able to add/remove proper classes whenever necessary, it needs to be aware of the
possible classes. Otherwise, it will not be able to make the difference between a valid
css class added by the component, or other custom code, and a class that need to be
removed. This is why we only support the explicit syntax with a class object:
_updateInputValue(event) {
this.state.text = event.target.value;
}
}
<div>
<input t-on-input="_updateInputValue" />
<span t-esc="state.text" />
</div>
This works. However, this requires a little bit of plumbing code. Also, the plumbing
code is slightly different if you need to interact with a checkbox, or with radio
buttons, or with select tags.
To help with this situation, Owl has a builtin directive t-model: its value should be an
observed value in the component (usually state.someValue). With the t-
model directive, we can write a shorter code, equivalent to the previous example:
class Form extends owl.Component {
state = { text: "" };
}
<div>
<input t-model="state.text" />
<span t-esc="state.text" />
</div>
The t-model directive works with <input>, <input type="checkbox">, <input
type="radio">, <textarea> and <select>:
<div>
<div>Text in an input: <input t-model="state.someVal"/></div>
<div>Textarea: <textarea t-model="state.otherVal"/></div>
<div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
<div>Selection:
<select t-model="state.color">
<option value="">Select a color</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
</div>
<div>
Selection with radio buttons:
<span>
<input type="radio" name="color" id="red" value="red" t-
model="state.color"/>
<label for="red">Red</label>
</span>
<span>
<input type="radio" name="color" id="blue" value="blue" t-
model="state.color" />
<label for="blue">Blue</label>
</span>
</div>
</div>
Like event handling, the t-model directive accepts the following modifiers:
Modifier Description
For example:
References
The useRef hook is useful when we need a way to interact with some inside part of a
component, rendered by Owl. It can work either on a DOM node, or on a component,
tagged by the t-ref directive. See the hooks section for more detail.
As a short example, here is how we could set the focus on a given input:
<div>
<input t-ref="input"/>
<button t-on-click="focusInput">Click</button>
</div>
import { useRef } from "owl/hooks";
focusInput() {
this.inputRef.el.focus();
}
}
The useRef hook can also be used to get a reference to an instance of a sub
component rendered by Owl. In that case, we need to access it with the comp property
instead of el:
<div>
<SubComponent t-ref="sub"/>
<button t-on-click="doSomething">Click</button>
</div>
import { useRef } from "owl/hooks";
doSomething() {
this.subRef.comp.doSomeThingElse();
}
}
Note that these two examples uses the suffix ref to name the reference. This is not
mandatory, but it is a useful convention, so we do not forget to access it with
the el or comp suffix.
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- directive):
<div t-name="ParentComponent">
<t t-component="ChildComponent{{id}}" />
</div>
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:
class A extends Component<any, any, any> {
static template = xml`<span>child a</span>`;
}
class B extends Component<any, any, any> {
static template = xml`<span>child b</span>`;
}
class App extends Component<any, any, any> {
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
get myComponent() {
return this.state.child === "a" ? A : B;
}
}
In this example, the component App selects dynamically the concrete sub component
class.
Note that the t-component directive can only be used on <t> nodes.
Functional Components
Owl does not exactly have functional components. However, there is an extremely
close alternative: calling sub templates.
A stateless functional component in react is usually some kind of function that maps
props to a virtual dom (often with jsx). So, basically, almost like a template rendered
with props. In Owl, this can be done by simply defining a template, that will access
the props object:
const Welcome = xml`<h1>Hello, <t t-esc="props.name"/></h1>`;
SVG Components
Owl Content 🦉
Here is a complete visual representation of everything exported by the owl global
object.
For example, Component is available at owl.Component and EventBus is exported
as owl.core.EventBus.
browser
Component misc
Context AsyncRoot
QWeb Portal
mount router
Store Link
useState RouteComponent
config Router
mode
core tags
EventBus css
Observer xml
hooks utils
onWillStart debounce
onMounted escape
onWillUpdateProps loadJS
onWillPatch loadFile
onPatched shallowEqual
onWillUnmount whenReady
useContext
useState
useRef
useComponent
useEnv
useSubEnv
useStore
useDispatch
useGetters
Note that for convenience, the useState hook is also exported at the root of
the owl object.
Concurrency Model 🦉
Content
Overview
Rendering Components
Semantics
Asynchronous Rendering
Overview
Owl was designed from the very beginning with asynchronous components. This
comes from the willStart and the willUpdateProps lifecycle hooks. With these
methods, it is possible to build complex highly concurrent applications.
Owl concurrent mode has several benefits: it makes it possible to delay the rendering
until some asynchronous operation is complete, it makes it possible to lazy load
libraries, while keeping the previous screen completely functional. It is also good for
performance reasons: Owl uses it to only apply the result of many different
renderings only once in an animation frame. Owl can cancel a rendering that is no
longer relevant, restart it, reuse it in some cases.
But even though using concurrency is quite simple (and is the default behaviour),
asynchrony is difficult, because it introduces an additional dimension that vastly
increase the complexity of an application. This section will explain how Owl manages
this complexity, how concurrent rendering works in a general way.
Rendering Components
The word rendering is a little vague, so, let us explain more precisely the process by
which Owl components are displayed on a screen.
Virtual rendering
This phase represent the process of rendering a template, in memory, which create a
virtual representation of the desired component html). The output of this phase is a
virtual DOM.
Patching
Once a rendering is complete, it will be applied on the next animation frame. This is
done synchronously: the whole component tree is patched to the real DOM.
Semantics
A
/ \
B C
/ \
D E
Here is what happen whenever we mount the root component (with some code
like app.mount(document.body)).
1. willStart is called on A
2. when it is done, template A is rendered.
o component B is created
a. willStart is called on B
b. template B is rendered
o component C is created
a. willStart is called on C
b. template C is rendered
component D is created
a. willStart is called on D
b. template D is rendered
component E is created
a. willStart is called on E
b. template E is rendered
3. each components are patched into a detached DOM element, in the following
order: E, D, C, B, A. (so the actual full DOM tree is created in one pass)
4. the component A root element is actually appended to document.body
5. The method mounted is called recursively on all components in the following
order: E, D, C, B, A.
Scenario 2: rerendering a component. Now, let's assume that the user clicked on
some button in C, and this results in a state update, which is supposed to:
update D,
remove E,
add new component F.
A
/ \
B C
/ \
D F
Here is what Owl will do:
o willUnmount hook on E
o destruction of E,
ii. mounted hook is called on F, patched hooks are called on D, C
Tags are very small helpers to make it easy to write inline templates. There is only one
currently available tag: xml, but we plan to add other tags later, such as a css tag,
which will be used to write single file components.
Asynchronous Rendering
There are two different common problems with Owl asynchronous rendering model:
any component can delay the rendering (initial and subsequent) of the whole
application
for a given component, there are two independant situations that will trigger an
asynchronous rerendering: a change in the state, or a change in the props. These
changes may be done at different times, and Owl has no way of knowing how to
reconcile the resulting renderings.
2. Maybe move the asynchronous logic in a store, which then triggers (mostly)
synchronous renderings
3. Lazy loading external libraries is a good use case for async rendering. This is
mostly fine, because we can assume that it will only takes a fraction of a
second, and only once (see owl.utils.loadJS)
4. For all the other cases, the AsyncRoot component is there to help you. When
this component is met, a new rendering sub tree is created, such that the
rendering of that component (and its children) is not tied to the rendering of
the rest of the interface. It can be used on an asynchronous component, to
prevent it from delaying the rendering of the whole interface, or on a
synchronous one, such that its rendering isn't delayed by other (asynchronous)
components. Note that this directive has no effect on the first rendering, but
only on subsequent ones (triggered by state or props changes).
5. <div t-name="ParentComponent">
6. <SyncChild />
7. <AsyncRoot>
8. <AsyncChild/>
9. </AsyncRoot>
</div>
Config 🦉
The Owl framework is designed to work in many situations. However, it is sometimes
necessary to customize some behaviour. This is done by using the
global config object. It provides two settings:
mode (default value: prod),
enableTransitions (default value: true).
Mode
By default, Owl is in production mode, this means that it will try to do its job fast, and
skip some expensive operations. However, it is sometimes necessary to have better
information on what is going on, this is the purpose of the dev mode.
Owl has a mode flag, in owl.config.mode. Its default value is prod, but it can be set
to dev:
owl.config.mode = "dev";
Note that templates compiled with the prod settings will not be recompiled. So,
changing this setting is best done at startup.
An important job done by the dev mode is to validate props for each component
creation and update. Also, extra props will cause an error.
enableTransitions
Transitions are usually nice, but they can cause issues in some specific cases, such as
automated tests. It is uncomfortable having to wait for a transition to end before
moving to the next step.
Context 🦉
Content
Overview
Example
Reference
o Context
o useContext
Overview
Example
Assume that we have an application with various components which needs to render
differently depending on the size of the device. Here is how we could proceed to
make sure that the information is properly shared. First, let us create a context, and
add it to the environment: