-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[ADD] awesome_owl, awesome_dashboard: partially implement web framework tutorial #1303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 19.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,36 @@ | ||
| import { Component } from "@odoo/owl"; | ||
| import { Component, onWillStart, useState } from "@odoo/owl"; | ||
| import { registry } from "@web/core/registry"; | ||
| import { useService } from "@web/core/utils/hooks"; | ||
| import { _t } from "@web/core/l10n/translation"; | ||
|
|
||
| import { DashboardItem } from "./dashboard_item/dashboard_item"; | ||
| import { Layout } from "@web/search/layout" | ||
| import { PieChart } from "./pie_chart/pie_chart"; | ||
|
|
||
|
|
||
| class AwesomeDashboard extends Component { | ||
| static template = "awesome_dashboard.AwesomeDashboard"; | ||
| static components = { Layout, DashboardItem, PieChart }; | ||
|
|
||
| setup() { | ||
| this.action = useService("action"); | ||
| this.state = useState({ | ||
| stats: useService("awesome_dashboard.statistics"), | ||
| }); | ||
| } | ||
|
|
||
| async openCustomersKanban() { | ||
| this.action.doAction('base.action_partner_form'); | ||
| } | ||
|
|
||
| async openLeads() { | ||
| this.action.doAction({ | ||
| type: 'ir.actions.act_window', | ||
| name: _t('Lots of Leads'), | ||
| res_model: 'crm.lead', | ||
| views: [[false, 'list'], [false, 'form']], | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| .o_dashboard { | ||
| background-color: gray | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,20 @@ | |||||||||||||||||
| <templates xml:space="preserve"> | ||||||||||||||||||
|
|
||||||||||||||||||
| <t t-name="awesome_dashboard.AwesomeDashboard"> | ||||||||||||||||||
| hello dashboard | ||||||||||||||||||
| <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'"> | ||||||||||||||||||
|
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its preferable to declare the
Suggested change
|
||||||||||||||||||
| <DashboardItem size="2"> | ||||||||||||||||||
| <p>Counter: <t t-esc="this.state.stats.nb_new_orders"/></p> | ||||||||||||||||||
| </DashboardItem> | ||||||||||||||||||
|
|
||||||||||||||||||
| <DashboardItem size="1"> | ||||||||||||||||||
| <PieChart data="this.state.stats.orders_by_size"/> | ||||||||||||||||||
| </DashboardItem> | ||||||||||||||||||
| </Layout> | ||||||||||||||||||
| </t> | ||||||||||||||||||
|
|
||||||||||||||||||
| </templates> | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import {Component} from "@odoo/owl" | ||
|
|
||
|
|
||
| export class DashboardItem extends Component { | ||
| static template = "awesome_dashboard.dashboard_item" | ||
| static props = { | ||
| size: { | ||
| type: Number, | ||
| default: 1, | ||
| optional: true, | ||
| }, | ||
| slots: { | ||
| type: Object, | ||
| shape: { | ||
| default: true | ||
| } | ||
| }, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <t t-name="awesome_dashboard.dashboard_item"> | ||
| <div class="card m-2 border-dark" t-attf-style="width: {{18*props.size}}rem;"> | ||
| <div class="card-body"> | ||
| <t t-slot="default"/> | ||
| </div> | ||
| </div> | ||
| </t> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||
| import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef, useState } from "@odoo/owl" | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently import but not used 😄
Suggested change
|
||||||||||||||||
| import { loadJS } from "@web/core/assets"; | ||||||||||||||||
| import { getColor } from "@web/core/colors/colors"; | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| export class PieChart extends Component { | ||||||||||||||||
| static template = "awesome_dashboard.pie_chart" | ||||||||||||||||
| static props = { | ||||||||||||||||
| data: Object, | ||||||||||||||||
| optional: true | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This declares
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| setup() { | ||||||||||||||||
| onWillStart(async () => { | ||||||||||||||||
| await loadJS("/web/static/lib/Chart/Chart.js") | ||||||||||||||||
| }) | ||||||||||||||||
|
|
||||||||||||||||
| this.canvasRef = useRef("canvas"); | ||||||||||||||||
|
|
||||||||||||||||
| if (this.props.data) { | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh but actually it won't happend since you only reach that
Suggested change
|
||||||||||||||||
| onMounted(() => { | ||||||||||||||||
| this.renderChart() | ||||||||||||||||
| }) | ||||||||||||||||
|
|
||||||||||||||||
| onPatched(() => { | ||||||||||||||||
| this.chart.destroy() | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
| this.renderChart() | ||||||||||||||||
| }) | ||||||||||||||||
|
|
||||||||||||||||
| onWillUnmount(() => { | ||||||||||||||||
| this.chart.destroy() | ||||||||||||||||
| }); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| renderChart() { | ||||||||||||||||
| const keys = Object.keys(this.props.data) | ||||||||||||||||
| const values = Object.values(this.props.data) | ||||||||||||||||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because
Suggested change
|
||||||||||||||||
| const colors = keys.map((_, index) => getColor(index)); | ||||||||||||||||
|
|
||||||||||||||||
| this.chart = new Chart(this.canvasRef.el, { | ||||||||||||||||
| type: "pie", | ||||||||||||||||
| data: { | ||||||||||||||||
| labels: keys, | ||||||||||||||||
| datasets: [ | ||||||||||||||||
| { | ||||||||||||||||
| backgroundColor: colors, | ||||||||||||||||
| data: values, | ||||||||||||||||
| }, | ||||||||||||||||
| ] | ||||||||||||||||
| } | ||||||||||||||||
| }); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <t t-name="awesome_dashboard.pie_chart"> | ||
| <canvas t-ref="canvas" /> | ||
| </t> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { registry } from "@web/core/registry"; | ||
| import { rpc } from "@web/core/network/rpc"; | ||
| import { reactive } from "@odoo/owl"; | ||
|
|
||
|
|
||
| const statisticsService = { | ||
| start() { | ||
| let stats = reactive({}) | ||
|
|
||
| async function getStats() { | ||
| let updates = await rpc("/awesome_dashboard/statistics"); | ||
| Object.assign(stats, updates); | ||
| } | ||
|
|
||
| getStats() | ||
| setInterval(getStats, 10*1000) | ||
|
|
||
| return stats | ||
| } | ||
| } | ||
|
|
||
| registry.category("services").add("awesome_dashboard.statistics", statisticsService); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Component, useState } from "@odoo/owl"; | ||
|
|
||
|
|
||
| export class Card extends Component { | ||
| static template = "awesome_owl.card"; | ||
| static props = { | ||
| title: String, | ||
| slots: { | ||
| type: Object, | ||
| shape: { | ||
| default: true | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| setup() { | ||
| this.state = useState({ | ||
| state: false, | ||
| }) | ||
| } | ||
|
|
||
| toggleState() { | ||
| this.state.state = !this.state.state; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <t t-name="awesome_owl.card"> | ||
| <div class="card d-inline-block m-2" style="width: 18rem;"> | ||
| <div class="card-body"> | ||
| <h5 class="card-title"> | ||
| <t t-out="props.title"/> | ||
| <button t-on-click="toggleState">toggle show</button> | ||
| </h5> | ||
| <p class="card-text" t-if="state.state"> | ||
| <t t-slot="default"/> | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </t> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||
| import { Component, useState } from "@odoo/owl"; | ||||||
|
|
||||||
|
|
||||||
| export class Counter extends Component { | ||||||
| static template = "awesome_owl.counter"; | ||||||
| static prop = { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😉
Suggested change
|
||||||
| onChange: { | ||||||
| type: Function, | ||||||
| optional: true, | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| setup() { | ||||||
| this.state = useState({ value: 0 }); | ||||||
| } | ||||||
|
|
||||||
| increment() { | ||||||
| this.state.value++; | ||||||
| if (this.props.onChange) { this.props.onChange(); } | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <t t-name="awesome_owl.counter"> | ||
| <div class="m-2 p-2 border d-inline-block"> | ||
| <p>Counter: <t t-esc="state.value"/></p> | ||
| <button class="btn btn-primary" t-on-click="increment">Increment</button> | ||
| </div> | ||
| </t> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,17 @@ | ||
| import { Component } from "@odoo/owl"; | ||
| import {Component, useState} from "@odoo/owl"; | ||
| import { Card } from "./card/card"; | ||
| import { Counter } from "./counter/counter"; | ||
| import { TodoList } from "./todo/todo_list"; | ||
|
|
||
| export class Playground extends Component { | ||
| static template = "awesome_owl.playground"; | ||
| static components = { Counter, Card, TodoList }; | ||
|
|
||
| setup() { | ||
| this.sum = useState({ value: 0 }); | ||
| } | ||
|
|
||
| incrementSum() { | ||
| this.sum.value++ | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| 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, | ||
| } | ||
| }, | ||
| toggleState: { | ||
| type: Function, | ||
| optional: true | ||
| }, | ||
|
|
||
| deleteTodo: { | ||
| type: Function, | ||
| optional: true | ||
| } | ||
| }; | ||
|
|
||
| onChange() { | ||
| if (this.props.toggleState) { | ||
| this.props.toggleState(this.props.todo.id); | ||
| } | ||
| } | ||
|
|
||
| onRemove() { | ||
| if (this.props.deleteTodo) { | ||
| this.props.deleteTodo(this.props.todo.id) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||||||||
| <templates xml:space="preserve"> | ||||||||||
|
|
||||||||||
| <t t-name="awesome_owl.todo_item"> | ||||||||||
| <div class="form-check"> | ||||||||||
| <input class="form-check-input" type="checkbox" t-on-change="onChange"/> | ||||||||||
| <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"/> | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||
| </label> | ||||||||||
| </div> | ||||||||||
| </t> | ||||||||||
|
|
||||||||||
| </templates> | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||
| import { Component, useState } from "@odoo/owl" | ||||||
| import { TodoItem } from "./todo_item"; | ||||||
| import { Autofocus } from "../utils"; | ||||||
|
|
||||||
|
|
||||||
| export class TodoList extends Component { | ||||||
| static template = "awesome_owl.todo_list"; | ||||||
| static components = { TodoItem }; | ||||||
|
|
||||||
| setup() { | ||||||
| this.nextId = 0; | ||||||
| this.todos = useState([]); | ||||||
| Autofocus("input") | ||||||
| } | ||||||
|
|
||||||
| addTodo(event) { | ||||||
| if (event.key === 'Enter' && event.target.value !== '') { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
| this.todos.push({ | ||||||
| id: this.nextId++, | ||||||
| description: event.target.value, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
| isCompleted: false | ||||||
| }); | ||||||
|
|
||||||
| event.target.value = ''; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| listToggleComplete(itemId) { | ||||||
| const todoItem = this.todos.find(item => item.id === itemId); | ||||||
| if (todoItem) { | ||||||
| todoItem.isCompleted = !todoItem.isCompleted; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| removeTodo(itemId) { | ||||||
| const todoIndex = this.todos.findIndex((todo) => todo.id === itemId); | ||||||
| if (todoIndex >= 0) { | ||||||
| this.todos.splice(todoIndex, 1); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
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 😄