Owl tutorial for now, we will build a simple Todo list application. The app should satisfy the following requirements:
- Let the user create and remove tasks.
- Tasks can be mark as completed.
- Tasks can be filter to display active or completed tasks.
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
Owl tutorial Todo will separated by 3 part.
- Setting up the Project : Part 1
- Adding the First Component
- Displaying a List of Tasks
- Basic CSS Layout
- Extracting Task as a Subcomponent
- Adding Tasks (Part 1) : Part 2
- Adding Tasks (Part 2)
- Toggling Tasks
- Deleting Tasks
- Using a Store : Part 3
- Saving Tasks in Local Storage
- Filtering Tasks
- The Final Touch
- Final Code
1. Setting up the Project
Owl 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 index.html
file, 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" />
</head>
<body>
<script src="owl.js"></script>
<script src="app.js"></script>
</body>
</html>
Next, leave app.css
empty for now. It will be useful later to style our application. app.js
is where we will write all our code. For now, let’s 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, download the latest version of owl.js
from the Owl repository (you can use owl.min.js
if you prefer). Make sure you download the owl.iife.js
or owl.iife.min.js
file because these are built to run directly in the browser. Rename it to owl.js
.
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 like hello owl 2.x.y
in the console.
2. Adding the First Component
An Owl tutorial application is made out of components, with a single root component. Let us start by defining a Root
component. Replace the content of the function in app.js
with the following code:
const { Component, mount, xml } = owl;
// Owl Components
class Root extends Component {
static template = xml`<div>todo app</div>`;
}
mount(Root, document.body);
Reloading the page in a browser should display a message.
The code is simple: we define a component with an inline template, then mount it in the document body.
Note 1: In a larger project, we would split the code into multiple files, with components in a subfolder 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 support to 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 assignment 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 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 replace it with the translated values.
3. Displaying a List of Tasks
Now Owl tutorial 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 create by the user, it offers no guarantee that it is unique. So, we will generate a uniqueid
number for each task.text
: 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 Root extends Component {
static template = 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.text"/></span>
</div>
</t>
</div>`;
tasks = [
{
id: 1,
text: "buy milk",
isCompleted: true,
},
{
id: 2,
text: "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 rendering context contains the properties of the component. 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 with t-att
makes it dynamic. Owl will evaluate the expression and set it as the value of the attribute.
4. Basic CSS Layout
So far, our Owl tutorial 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:
<div class="task" t-att-class="task.isCompleted ? 'done' : ''">
.task.done {
opacity: 0.7;
}
Notice that we have here another use of a dynamic attribute.
5. Extracting Task as a Subcomponent
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
// -------------------------------------------------------------------------
class Task extends Component {
static template = 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.text"/></span>
</div>`;
static props = ["task"];
}
// -------------------------------------------------------------------------
// Root Component
// -------------------------------------------------------------------------
class Root extends Component {
static template = xml`
<div class="task-list">
<t t-foreach="tasks" t-as="task" t-key="task.id">
<Task task="task"/>
</t>
</div>`;
static components = { Task };
tasks = [
{
id: 1,
text: "buy
milk",
isCompleted: true,
},
{
id: 2,
text: "clean house",
isCompleted: false,
},
];
}
// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------
mount(Root, document.body, { dev: true });
A lot of stuff happened here:
- First, we now have a subcomponent
Task
, defined on top of the file. - Whenever we define a subcomponent, it needs to be added to the static
components
key of its parent so Owl can get a reference to it. - The
Task
component has aprops
key: this is only useful for validation purposes. It says that eachTask
should be given exactly one prop, namedtask
. If this is not the case, Owl will throw an error. This is extremely useful when refactoring components. - Finally, to activate the props validation, we need to set Owl’s mode to
dev
. This is done in the last argument of themount
function. Note that this should be removed when an app is used in a real production environment sincedev
mode is slightly slower due to extra checks and validations.
Stay tuned for the next part where we will add more features to our TodoApp, such as adding tasks dynamically and managing task states effectively.
Read the Owl Framework documentation for more details and advanced features.
Discover more from teguhteja.id
Subscribe to get the latest posts sent to your email.
Pingback: Owl: Odoo Framework 101 - teguhteja.id