From 6cb2ead1ef25eadcc7f41fada42d9eec0b01e55b Mon Sep 17 00:00:00 2001 From: raibr Date: Wed, 24 Dec 2025 09:42:39 +0100 Subject: [PATCH] [ADD] Js frameword chapter 1: Enhance playground with card and counter components; add todo list functionality --- awesome_owl/__init__.py | 4 +- awesome_owl/__manifest__.py | 12 ++--- awesome_owl/controllers/__init__.py | 4 +- awesome_owl/controllers/controllers.py | 5 ++- awesome_owl/static/src/card/card.js | 18 ++++++++ awesome_owl/static/src/card/card.xml | 16 +++++++ awesome_owl/static/src/counter/counter.js | 15 +++++++ awesome_owl/static/src/counter/counter.xml | 12 +++++ awesome_owl/static/src/main.js | 2 +- awesome_owl/static/src/owl_test.xml.bk | 37 +++++++++++++++ awesome_owl/static/src/playground.js | 17 ++++++- awesome_owl/static/src/playground.xml | 22 +++++++-- awesome_owl/static/src/todo_item/todo_item.js | 25 +++++++++++ .../static/src/todo_item/todo_item.xml | 12 +++++ awesome_owl/static/src/todo_list/todo_list.js | 45 +++++++++++++++++++ .../static/src/todo_list/todo_list.xml | 24 ++++++++++ awesome_owl/views/templates.xml | 14 +++++- 17 files changed, 260 insertions(+), 24 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/owl_test.xml.bk create mode 100644 awesome_owl/static/src/todo_item/todo_item.js create mode 100644 awesome_owl/static/src/todo_item/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/todo_list.xml diff --git a/awesome_owl/__init__.py b/awesome_owl/__init__.py index 457bae27e11..e046e49fbe2 100644 --- a/awesome_owl/__init__.py +++ b/awesome_owl/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 55002ab81de..ebf7dfbc46d 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -1,24 +1,18 @@ -# -*- coding: utf-8 -*- { 'name': "Awesome Owl", - 'summary': """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - 'description': """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - 'author': "Odoo", 'website': "https://www.odoo.com", - # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list 'category': 'Tutorials', 'version': '0.1', - # any module necessary for this one to work correctly 'depends': ['base', 'web'], 'application': True, @@ -36,8 +30,10 @@ ('include', 'web._assets_bootstrap'), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', - 'awesome_owl/static/src/**/*', + 'awesome_owl/static/src/**/*.js', + 'awesome_owl/static/src/**/*.xml', + 'awesome_owl/static/src/**/*.scss', ], }, - 'license': 'AGPL-3' + 'license': 'AGPL-3', } diff --git a/awesome_owl/controllers/__init__.py b/awesome_owl/controllers/__init__.py index 457bae27e11..e046e49fbe2 100644 --- a/awesome_owl/controllers/__init__.py +++ b/awesome_owl/controllers/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/controllers/controllers.py b/awesome_owl/controllers/controllers.py index bccfd6fe283..a9ed6c0a8ef 100644 --- a/awesome_owl/controllers/controllers.py +++ b/awesome_owl/controllers/controllers.py @@ -1,10 +1,11 @@ from odoo import http from odoo.http import request, route + class OwlPlayground(http.Controller): - @http.route(['/awesome_owl'], type='http', auth='public') + @http.route(['/awesome_owl/playground'], type='http', auth='user') def show_playground(self): """ Renders the owl playground page """ - return request.render('awesome_owl.playground') + return request.render('awesome_owl.playground_template') diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..40e96e1599b --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: { type: String, optional: true }, + slots: { type: Object, optional: true }, + }; + + setup() { + this.state = useState({ flipped: false }); + } + + flipCard() { + console.log("Flipping card"); + this.state.flipped = !this.state.flipped; + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..a6d81f65ba6 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,16 @@ + + + + +
+
+
+ +
+
+

Card is flipped! Click to flip back.

+
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..a8ef9a1fe57 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,15 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + 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..82f3755d910 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,12 @@ + + + + +
+

Counter: +

+ +
+
+ +
diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..01930b075d5 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -1,6 +1,6 @@ import { whenReady } from "@odoo/owl"; import { mountComponent } from "@web/env"; -import { Playground } from "./playground"; +import { Playground } from "./Playground"; const config = { dev: true, diff --git a/awesome_owl/static/src/owl_test.xml.bk b/awesome_owl/static/src/owl_test.xml.bk new file mode 100644 index 00000000000..d918c43b8f3 --- /dev/null +++ b/awesome_owl/static/src/owl_test.xml.bk @@ -0,0 +1,37 @@ + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..6fe1828fa0b 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,18 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoItem } from "./todo_item/todo_item"; +import { TodoList } from "./todo_list/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; -} + static components = { Counter, Card, TodoList, TodoItem }; + + setup() { + this.state = useState({ sum: 2 }); + } + + incrementSum() { + this.state.sum++; + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..e9ec3e01826 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,26 @@ - +
- hello world +

Hello Owl Playground!

+

Sum: +

+
+ + + + + + + + +

This is a card with custom content!

+ +
+ +
-
+ \ No newline at end of file diff --git a/awesome_owl/static/src/todo_item/todo_item.js b/awesome_owl/static/src/todo_item/todo_item.js new file mode 100644 index 00000000000..fffa9f2aa1e --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.js @@ -0,0 +1,25 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + static props = { + id: Number, + text: String, + isCompleted: Boolean, + onToggle: { type: Function, optional: true }, + onRemove: { type: Function, optional: true }, + }; + + toggleCompletion() { + console.log("Toggling completion for todo id:", this.props.id); + if (this.props.onToggle) { + console.log("Calling onToggle for todo id:", this.props.id); + this.props.onToggle(this.props.id); + } + } + removeItem(){ + if (this.props.onRemove) { + this.props.onRemove(this.props.id); + } + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml new file mode 100644 index 00000000000..3a40f5028a4 --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -0,0 +1,12 @@ + + + + +
+ +
+ +
+ + + diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..f5ad6fd19b2 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,45 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "../todo_item/todo_item"; +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + setup() { + this.todoListRef = useRef("todo_list"); + this.todoInputRef = useRef("new_todo_input"); + onMounted(() => { + this.todoInputRef.el.focus(); + }); + this.state = useState({ + todos: [ + ], + newTodoText: "", + }); + } + addTodo() { + if (this.state.newTodoText.trim() === "") { + return; + } + const newTodo = { + id: Date.now(), + text: this.state.newTodoText, + isCompleted: false, + }; + this.state.todos.push(newTodo); + this.state.newTodoText = ""; + this.todoListRef.el.style.backgroundColor = 'white'; + } + toggleTodo(todoId) { + const todo = this.state.todos.find(t => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + //change the color of the list + this.todoListRef.el.style.backgroundColor = this.state.todos.every(t => t.isCompleted) ? 'lightgreen' : 'white'; + } + onRemoveTodo(todoId) { + this.state.todos = this.state.todos.filter(t => t.id !== todoId); + //change the color of the list + this.todoListRef.el.style.backgroundColor = (this.state.todos.every(t => t.isCompleted) && this.state.todos.length > 0) ? 'lightgreen' : 'white'; + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..4aebaee3bef --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,24 @@ + + + + +

Hello Owl Playground!

+ +
+
+
+ + +
+ +
+ + + +
+
+
+ +
+ +
diff --git a/awesome_owl/views/templates.xml b/awesome_owl/views/templates.xml index aa54c1a7241..7a67c739642 100644 --- a/awesome_owl/views/templates.xml +++ b/awesome_owl/views/templates.xml @@ -1,6 +1,6 @@ - + + + Owl Framework Test + /awesome_owl/playground + self + +