From 33cc55c94c74bbdade6baecdc0480ff55cbbba97 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Fri, 19 Dec 2025 10:36:19 +0100 Subject: [PATCH 1/9] [IMP] awesome_owl: added counter, card and todo list components Discover the web framework tutorial - Chapter 1, section 1 to 8 --- awesome_owl/static/src/card/card.js | 9 +++++++++ awesome_owl/static/src/card/card.xml | 15 +++++++++++++++ awesome_owl/static/src/counter/counter.js | 22 ++++++++++++++++++++++ awesome_owl/static/src/counter/counter.xml | 9 +++++++++ awesome_owl/static/src/playground.js | 19 ++++++++++++++++++- awesome_owl/static/src/playground.xml | 9 ++++++++- awesome_owl/static/src/todo/todo-item.js | 11 +++++++++++ awesome_owl/static/src/todo/todo-item.xml | 11 +++++++++++ awesome_owl/static/src/todo/todo-list.js | 15 +++++++++++++++ awesome_owl/static/src/todo/todo-list.xml | 10 ++++++++++ 10 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/todo/todo-item.js create mode 100644 awesome_owl/static/src/todo/todo-item.xml create mode 100644 awesome_owl/static/src/todo/todo-list.js create mode 100644 awesome_owl/static/src/todo/todo-list.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..f934fbf140f --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + content: String, + }; +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..6df30f51cea --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,15 @@ + + + + +
+
+
+

+ +

+
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..d6d92784524 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,22 @@ +import { useState, Component } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = { + onChange: { + type: Function, + optional: true, + }, + }; + + setup() { + this.state = useState({ value: 1 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..5e0ec2135c0 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + + +

Counter:

+ +
+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..6b571e58aa5 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,22 @@ -import { Component } from "@odoo/owl"; +import { markup, useState, Component } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo/todo-list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.state = useState({ sum: 2 }); + } + + incrementSum() { + this.state.sum++; + } + + title1 = "Card 1"; + title2 = "Card 2"; + content1 = "
My content
"; + content2 = markup("
My content
"); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..fb92e1c3ffb 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -5,6 +5,13 @@
hello world
- + + +

The sum is

+ + + + + diff --git a/awesome_owl/static/src/todo/todo-item.js b/awesome_owl/static/src/todo/todo-item.js new file mode 100644 index 00000000000..365c51083be --- /dev/null +++ b/awesome_owl/static/src/todo/todo-item.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo-item"; + static props = { + todo: { + type: Object, + shape: { id: Number, description: String, isCompleted: Boolean}, + }, + }; +} diff --git a/awesome_owl/static/src/todo/todo-item.xml b/awesome_owl/static/src/todo/todo-item.xml new file mode 100644 index 00000000000..0ae91f0bbf5 --- /dev/null +++ b/awesome_owl/static/src/todo/todo-item.xml @@ -0,0 +1,11 @@ + + + + +
+ + +
+
+ +
diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js new file mode 100644 index 00000000000..7febc8037b0 --- /dev/null +++ b/awesome_owl/static/src/todo/todo-list.js @@ -0,0 +1,15 @@ +import { useState, Component } from "@odoo/owl"; +import { TodoItem } from "./todo-item"; + +export class TodoList extends Component { + static template = "awesome_owl.todo-list"; + static components = { TodoItem } + + setup() { + this.state = useState([ + { id: 1, description: "buy water", isCompleted: true }, + { id: 2, description: "buy bread", isCompleted: false }, + { id: 3, description: "buy milk", isCompleted: false }, + ]); + } +} diff --git a/awesome_owl/static/src/todo/todo-list.xml b/awesome_owl/static/src/todo/todo-list.xml new file mode 100644 index 00000000000..0f7bf19737c --- /dev/null +++ b/awesome_owl/static/src/todo/todo-list.xml @@ -0,0 +1,10 @@ + + + + + + + + + + From 3b27e955e8813ac0187b4b94c0bbec9a3e13b343 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Fri, 19 Dec 2025 11:21:23 +0100 Subject: [PATCH 2/9] [IMP] awesome_owl: dynamically add todo items with an input field Discover the web framework tutorial - Chapter 1, section 9 --- awesome_owl/static/src/todo/todo-list.js | 17 ++++++++++++----- awesome_owl/static/src/todo/todo-list.xml | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js index 7febc8037b0..d3b4e280bf3 100644 --- a/awesome_owl/static/src/todo/todo-list.js +++ b/awesome_owl/static/src/todo/todo-list.js @@ -6,10 +6,17 @@ export class TodoList extends Component { static components = { TodoItem } setup() { - this.state = useState([ - { id: 1, description: "buy water", isCompleted: true }, - { id: 2, description: "buy bread", isCompleted: false }, - { id: 3, description: "buy milk", isCompleted: false }, - ]); + this.state = useState({todos: [], nextId: 1}); + } + + input_event_handler(event) { + // keyCode is deprecated, use key instead + if (event.key === "Enter") { + if (event.target.value) { + this.state.todos.push({id: this.state.nextId, description: event.target.value, isCompleted: false}); + this.state.nextId++; + event.target.value = ""; + } + } } } diff --git a/awesome_owl/static/src/todo/todo-list.xml b/awesome_owl/static/src/todo/todo-list.xml index 0f7bf19737c..e4bb2142a1e 100644 --- a/awesome_owl/static/src/todo/todo-list.xml +++ b/awesome_owl/static/src/todo/todo-list.xml @@ -2,7 +2,8 @@ - + + From 901c9c5f9c399a6a1a8dfe50751680145faab07e Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Fri, 19 Dec 2025 11:41:09 +0100 Subject: [PATCH 3/9] [IMP] awesome_owl: auto-focus the input for creating TODOs Chapter 1, section 10 --- awesome_owl/static/src/todo/todo-list.js | 4 +++- awesome_owl/static/src/todo/todo-list.xml | 2 +- awesome_owl/static/src/utils.js | 9 +++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js index d3b4e280bf3..5bc68d78728 100644 --- a/awesome_owl/static/src/todo/todo-list.js +++ b/awesome_owl/static/src/todo/todo-list.js @@ -1,5 +1,6 @@ -import { useState, Component } from "@odoo/owl"; +import { onMounted, useRef, useState, Component } from "@odoo/owl"; import { TodoItem } from "./todo-item"; +import { useAutoFocus } from "../utils" export class TodoList extends Component { static template = "awesome_owl.todo-list"; @@ -7,6 +8,7 @@ export class TodoList extends Component { setup() { this.state = useState({todos: [], nextId: 1}); + useAutoFocus("new-todo-input"); } input_event_handler(event) { diff --git a/awesome_owl/static/src/todo/todo-list.xml b/awesome_owl/static/src/todo/todo-list.xml index e4bb2142a1e..41aa608c3ad 100644 --- a/awesome_owl/static/src/todo/todo-list.xml +++ b/awesome_owl/static/src/todo/todo-list.xml @@ -2,7 +2,7 @@ - + diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..4d9e30c9eff --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,9 @@ +import { useEffect, useRef } from "@odoo/owl"; + +export function useAutoFocus(name) { + let ref = useRef(name); + useEffect( + (el) => el && el.focus(), + () => [ref.el] + ); +} From 92dc4955bea409b3efeb0e961a4ddadeb89aa535 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Fri, 19 Dec 2025 15:42:54 +0100 Subject: [PATCH 4/9] [IMP] awesome_owl: toggling todos --- awesome_owl/static/src/todo/todo-item.js | 1 + awesome_owl/static/src/todo/todo-item.xml | 1 + awesome_owl/static/src/todo/todo-list.js | 5 +++++ awesome_owl/static/src/todo/todo-list.xml | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/awesome_owl/static/src/todo/todo-item.js b/awesome_owl/static/src/todo/todo-item.js index 365c51083be..c5c9d17a994 100644 --- a/awesome_owl/static/src/todo/todo-item.js +++ b/awesome_owl/static/src/todo/todo-item.js @@ -7,5 +7,6 @@ export class TodoItem extends Component { type: Object, shape: { id: Number, description: String, isCompleted: Boolean}, }, + toggleState: Function, }; } diff --git a/awesome_owl/static/src/todo/todo-item.xml b/awesome_owl/static/src/todo/todo-item.xml index 0ae91f0bbf5..29363bffe43 100644 --- a/awesome_owl/static/src/todo/todo-item.xml +++ b/awesome_owl/static/src/todo/todo-item.xml @@ -3,6 +3,7 @@
+
diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js index 5bc68d78728..9ea1d3fe534 100644 --- a/awesome_owl/static/src/todo/todo-list.js +++ b/awesome_owl/static/src/todo/todo-list.js @@ -21,4 +21,9 @@ export class TodoList extends Component { } } } + + toggleState(todoId) { + const selectedTodo = this.state.todos.find(todo => todo.id == todoId) + selectedTodo.isCompleted = !selectedTodo.isCompleted + } } diff --git a/awesome_owl/static/src/todo/todo-list.xml b/awesome_owl/static/src/todo/todo-list.xml index 41aa608c3ad..fef97006df7 100644 --- a/awesome_owl/static/src/todo/todo-list.xml +++ b/awesome_owl/static/src/todo/todo-list.xml @@ -4,7 +4,7 @@ - + From 8e74efeee7a45e1ff458ec9f183abab421f5a2e0 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Fri, 19 Dec 2025 15:49:47 +0100 Subject: [PATCH 5/9] [IMP] awesome_owl: add button to delete TODOs --- awesome_owl/static/src/todo/todo-item.js | 1 + awesome_owl/static/src/todo/todo-item.xml | 1 + awesome_owl/static/src/todo/todo-list.js | 13 +++++++++++-- awesome_owl/static/src/todo/todo-list.xml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/awesome_owl/static/src/todo/todo-item.js b/awesome_owl/static/src/todo/todo-item.js index c5c9d17a994..3f05f1ddc7d 100644 --- a/awesome_owl/static/src/todo/todo-item.js +++ b/awesome_owl/static/src/todo/todo-item.js @@ -8,5 +8,6 @@ export class TodoItem extends Component { shape: { id: Number, description: String, isCompleted: Boolean}, }, toggleState: Function, + removeTodo: Function, }; } diff --git a/awesome_owl/static/src/todo/todo-item.xml b/awesome_owl/static/src/todo/todo-item.xml index 29363bffe43..572f326dc9a 100644 --- a/awesome_owl/static/src/todo/todo-item.xml +++ b/awesome_owl/static/src/todo/todo-item.xml @@ -6,6 +6,7 @@ + diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js index 9ea1d3fe534..d54a0813c34 100644 --- a/awesome_owl/static/src/todo/todo-list.js +++ b/awesome_owl/static/src/todo/todo-list.js @@ -23,7 +23,16 @@ export class TodoList extends Component { } toggleState(todoId) { - const selectedTodo = this.state.todos.find(todo => todo.id == todoId) - selectedTodo.isCompleted = !selectedTodo.isCompleted + const selectedTodo = this.state.todos.find((todo) => todo.id === todoId); + if (selectedTodo) { + selectedTodo.isCompleted = !selectedTodo.isCompleted; + } + } + + removeTodo(todoId) { + const index = this.state.todos.findIndex((todo) => todo.id === todoId); + if (index >= 0) { + this.state.todos.splice(index, 1); + } } } diff --git a/awesome_owl/static/src/todo/todo-list.xml b/awesome_owl/static/src/todo/todo-list.xml index fef97006df7..f38db5b98f1 100644 --- a/awesome_owl/static/src/todo/todo-list.xml +++ b/awesome_owl/static/src/todo/todo-list.xml @@ -4,7 +4,7 @@ - + From 71bde6ff4fefdb7eb15e99065f5fb7cfa821e615 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Mon, 22 Dec 2025 08:37:04 +0100 Subject: [PATCH 6/9] [IMP] awesome_owl: generic cards with slots --- awesome_owl/static/src/card/card.js | 2 +- awesome_owl/static/src/card/card.xml | 4 +--- awesome_owl/static/src/counter/counter.xml | 2 +- awesome_owl/static/src/playground.js | 6 ++--- awesome_owl/static/src/playground.xml | 28 +++++++++++++--------- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index f934fbf140f..e3bf70bb6ff 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -4,6 +4,6 @@ export class Card extends Component { static template = "awesome_owl.card"; static props = { title: String, - content: String, + slots: {type: Object, optional: true}, }; } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 6df30f51cea..2ec51605223 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -5,9 +5,7 @@
-

- -

+
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml index 5e0ec2135c0..08590167539 100644 --- a/awesome_owl/static/src/counter/counter.xml +++ b/awesome_owl/static/src/counter/counter.xml @@ -3,7 +3,7 @@

Counter:

- +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 6b571e58aa5..560db73de0a 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -15,8 +15,6 @@ export class Playground extends Component { this.state.sum++; } - title1 = "Card 1"; - title2 = "Card 2"; - content1 = "
My content
"; - content2 = markup("
My content
"); + someHtmlEscaped = "
My content
"; + someHtml = markup("
My content
"); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index fb92e1c3ffb..5699ac0c128 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,16 +2,22 @@ -
- hello world -
- - -

The sum is

- - - - - + +

+

+
+ + + + + + + +

The sum is

+
+ + +
+
From f9086da598ebb5fa139430e51daec73be018fb9f Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Mon, 22 Dec 2025 09:08:00 +0100 Subject: [PATCH 7/9] [IMP] awesome_owl: add toggle to hide/minimize card contents --- awesome_owl/static/src/card/card.js | 10 +++++++++- awesome_owl/static/src/card/card.xml | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index e3bf70bb6ff..940e461f25c 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -1,4 +1,4 @@ -import { Component } from "@odoo/owl"; +import { useState, Component } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.card"; @@ -6,4 +6,12 @@ export class Card extends Component { title: String, slots: {type: Object, optional: true}, }; + + setup() { + this.state = useState({ visible: true }); + } + + toggleVisible() { + this.state.visible = !this.state.visible; + } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 2ec51605223..57ff80c37b6 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -4,8 +4,11 @@
-
- +
+ + +
+
From 5e88b4d622d308d30e24067b7c6acc0363d76c38 Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Tue, 23 Dec 2025 10:29:13 +0100 Subject: [PATCH 8/9] [IMP] awesome_owl: properly use label for checkbox input --- awesome_owl/static/src/todo/todo-item.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/awesome_owl/static/src/todo/todo-item.xml b/awesome_owl/static/src/todo/todo-item.xml index 572f326dc9a..eb48b99669c 100644 --- a/awesome_owl/static/src/todo/todo-item.xml +++ b/awesome_owl/static/src/todo/todo-item.xml @@ -2,11 +2,13 @@ -
- - - - +
+ + +
From 91d9170f92260aba2be9f8f4fdd941672ce6012b Mon Sep 17 00:00:00 2001 From: "Robin (robal)" Date: Tue, 23 Dec 2025 16:21:14 +0100 Subject: [PATCH 9/9] [LINT] awesome_owl: fix linter issues --- awesome_owl/static/src/card/card.js | 2 +- awesome_owl/static/src/main.js | 3 +-- awesome_owl/static/src/todo/todo-item.js | 2 +- awesome_owl/static/src/todo/todo-list.js | 14 +++++++++----- awesome_owl/static/src/utils.js | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 940e461f25c..84950c3b758 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -4,7 +4,7 @@ export class Card extends Component { static template = "awesome_owl.card"; static props = { title: String, - slots: {type: Object, optional: true}, + slots: { type: Object, optional: true }, }; setup() { diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..1af6c827e0b 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -4,9 +4,8 @@ import { Playground } from "./playground"; const config = { dev: true, - name: "Owl Tutorial" + name: "Owl Tutorial", }; // Mount the Playground component when the document.body is ready whenReady(() => mountComponent(Playground, document.body, config)); - diff --git a/awesome_owl/static/src/todo/todo-item.js b/awesome_owl/static/src/todo/todo-item.js index 3f05f1ddc7d..1f0af4ad808 100644 --- a/awesome_owl/static/src/todo/todo-item.js +++ b/awesome_owl/static/src/todo/todo-item.js @@ -5,7 +5,7 @@ export class TodoItem extends Component { static props = { todo: { type: Object, - shape: { id: Number, description: String, isCompleted: Boolean}, + shape: { id: Number, description: String, isCompleted: Boolean }, }, toggleState: Function, removeTodo: Function, diff --git a/awesome_owl/static/src/todo/todo-list.js b/awesome_owl/static/src/todo/todo-list.js index d54a0813c34..e623b4b98ee 100644 --- a/awesome_owl/static/src/todo/todo-list.js +++ b/awesome_owl/static/src/todo/todo-list.js @@ -1,13 +1,13 @@ -import { onMounted, useRef, useState, Component } from "@odoo/owl"; +import { useState, Component } from "@odoo/owl"; import { TodoItem } from "./todo-item"; -import { useAutoFocus } from "../utils" +import { useAutoFocus } from "../utils"; export class TodoList extends Component { static template = "awesome_owl.todo-list"; - static components = { TodoItem } + static components = { TodoItem }; setup() { - this.state = useState({todos: [], nextId: 1}); + this.state = useState({ todos: [], nextId: 1 }); useAutoFocus("new-todo-input"); } @@ -15,7 +15,11 @@ export class TodoList extends Component { // keyCode is deprecated, use key instead if (event.key === "Enter") { if (event.target.value) { - this.state.todos.push({id: this.state.nextId, description: event.target.value, isCompleted: false}); + this.state.todos.push({ + id: this.state.nextId, + description: event.target.value, + isCompleted: false, + }); this.state.nextId++; event.target.value = ""; } diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js index 4d9e30c9eff..e68b3d8b822 100644 --- a/awesome_owl/static/src/utils.js +++ b/awesome_owl/static/src/utils.js @@ -1,7 +1,7 @@ import { useEffect, useRef } from "@odoo/owl"; export function useAutoFocus(name) { - let ref = useRef(name); + const ref = useRef(name); useEffect( (el) => el && el.focus(), () => [ref.el]