Skip to content

[ADD] awesome_owl, awesome_dashboard: partially implement web framework tutorial#1303

Open
daloe-odoo wants to merge 2 commits into
odoo:19.0from
odoo-dev:19.0-training-web-framework-daloe
Open

[ADD] awesome_owl, awesome_dashboard: partially implement web framework tutorial#1303
daloe-odoo wants to merge 2 commits into
odoo:19.0from
odoo-dev:19.0-training-web-framework-daloe

Conversation

@daloe-odoo
Copy link
Copy Markdown

@daloe-odoo daloe-odoo commented May 28, 2026

add card, counter, and todo components to awesome_owl. add almost reactive pie chart to awesome_dashboard.

task-6253605

@robodoo
Copy link
Copy Markdown

robodoo commented May 28, 2026

Pull request status dashboard

@daloe-odoo daloe-odoo requested a review from Megaaaaaa May 28, 2026 08:59
@daloe-odoo daloe-odoo force-pushed the 19.0-training-web-framework-daloe branch from 157cbf9 to 79d14fc Compare May 28, 2026 09:05
…rk tutorial

add card, counter, and todo components to awesome_owl.
add almost reactive pie chart to awesome_dashboard.
@daloe-odoo daloe-odoo force-pushed the 19.0-training-web-framework-daloe branch from 387b1b1 to af84bf0 Compare June 1, 2026 07:14
@daloe-odoo daloe-odoo changed the title [IMP] awesome_owl: implement web framework 101 tutorial [ADD] awesome_owl, awesome_dashboard: partially implement web framework tutorial Jun 1, 2026
@daloe-odoo
Copy link
Copy Markdown
Author

@Megaaaaaa kind of ready for review

Copy link
Copy Markdown

@Megaaaaaa Megaaaaaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hellooo 👋

Looks pretty good already, here's your review 👍 My comments are mostly about conventions that I wanted to show you or very minimal improvements to the logic.

Don't hesitate to tell me if anything is unclear or if you have question 😉

@@ -1,8 +1,36 @@
import { Component } from "@odoo/owl";
import { Component, onWillStart, useState } from "@odoo/owl";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being extra annoying here, this import is not used 😄

Comment on lines +5 to +10
<t t-set-slot="layout-buttons">
<button t-on-click="openCustomersKanban">Customers</button>
<button t-on-click="openLeads">Leads</button>
</t>

<Layout display="{controlPanel: {} }" className="'o_dashboard h-100'">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its preferable to declare the layout-buttons slot inside the <Layout> component it belongs to. As written, the buttons are defined before the component that should receive the slot. It doesn't make much a difference but we prefer it that way.

Suggested change
<t t-set-slot="layout-buttons">
<button t-on-click="openCustomersKanban">Customers</button>
<button t-on-click="openLeads">Leads</button>
</t>
<Layout display="{controlPanel: {} }" className="'o_dashboard h-100'">
<Layout display="{ controlPanel: {} }" className="'o_dashboard h-100'">
<t t-set-slot="layout-buttons">

@@ -0,0 +1,54 @@
import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef, useState } from "@odoo/owl"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently import but not used 😄

Suggested change
import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef, useState } from "@odoo/owl"
import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef } from "@odoo/owl"

Comment on lines +9 to +11
data: Object,
optional: true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This declares data as a required prop and then adds optional: true as a separate prop definition, which is not the shape Owl expects. That matters here because the dashboard statistics are loaded asynchronously, so orders_by_size can be missing on the first render. The current version can fail before the data arrives, even though the chart is naturally allowed to start empty.

Suggested change
data: Object,
optional: true
}
data: {
type: Object,
optional: true,
},

})

onPatched(() => {
this.chart.destroy()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onPatched can run before a chart instance exists, especially sine data is optional and the first render may be empty 👍

Suggested change
this.chart.destroy()
this.chart?.destroy();


export class Counter extends Component {
static template = "awesome_owl.counter";
static prop = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😉

Suggested change
static prop = {
static props = {

<label t-att-class="props.todo.isCompleted ? 'text-decoration-line-through text-muted' : '' ">
<t t-esc="props.todo.id"/>.
<t t-esc="props.todo.description"/>
<span class="fa fa-remove" t-on-click="onRemove"/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so I already said this to Sreedev so I guess this is the way they show it in the tutorial but being an clickable action, this should be a button not a span. It works great with the mouse but keyboard users and assistive technologies, don't forget to make this kind of action a button in the future 😄

Suggested change
<span class="fa fa-remove" t-on-click="onRemove"/>
<button type="button" class="btn btn-link p-0" t-on-click="onRemove">
<i class="fa fa-remove"/>
</button>

}

addTodo(event) {
if (event.key === 'Enter' && event.target.value !== '') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only rejects an actually empty string, so a todo containing only spaces can still be added. It's up to you really but I would consider this a small bug in the feature. Your call 😄

Suggested change
if (event.key === 'Enter' && event.target.value !== '') {
if (event.key === "Enter" && event.target.value.trim() !== "") {

if (event.key === 'Enter' && event.target.value !== '') {
this.todos.push({
id: this.nextId++,
description: event.target.value,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then if you validate the trimmed value, you should save the trimmed value. Still your call 😄

Suggested change
description: event.target.value,
description: event.target.value.trim(),

import { onMounted, useRef } from "@odoo/owl"


export function Autofocus(refName) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this calling Owl hooks, it would be prefereable to name it like a hook so we get the intention clearer. This would be more of a useAutofocus in this case. I feel like it help makes the difference with a simple utility 👍

Suggested change
export function Autofocus(refName) {
export function useAutofocus(refName) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants