Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -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,
};
}
12 changes: 12 additions & 0 deletions awesome_owl/static/src/card/card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<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-out="this.props.title"/>
<p class="card-text" t-out="this.props.content"/>
</div>
</div>
</t>
</templates>

14 changes: 14 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component, useState } from '@odoo/owl'

export class Counter extends Component {
static template = "awesome_owl.counter";
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 component expects its parent to pass an onChange callback, so it is worth making that contract explicit in static props. It's helpful to everyone and you will avoid the "no explicit props declared" warning too 👍

Suggested change
static template = "awesome_owl.counter";
static template = "awesome_owl.counter";
static props = {
onChange: Function,
};


setup() {
this.state = useState({ value: 0 });
}

increment() {
this.state.value++;
this.props.onChange();
}
}
15 changes: 15 additions & 0 deletions awesome_owl/static/src/counter/counter.xml
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.counter">
<div class="p-4 text-center bg-light border rounded shadow-sm m-3">
<h2 class="text-secondary mb-3">Owl Counter</h2>
<p class="fs-3 fw-bold text-primary">
Counter: <t t-out="state.value"/>
</p>
<button class="btn btn-primary btn-lg mt-2" t-on-click="increment">
Increment
</button>
</div>
</t>
</templates>

4 changes: 2 additions & 2 deletions awesome_owl/static/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { mountComponent } from "@web/env";
import { Playground } from "./playground";

const config = {
dev: true,
name: "Owl Tutorial"
dev: true,
name: "Owl Tutorial"
};

// Mount the Playground component when the document.body is ready
Expand Down
16 changes: 14 additions & 2 deletions awesome_owl/static/src/playground.js
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 { 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 template = "awesome_owl.playground"
static components = { Counter, Card, TodoList };

setup() {
this.state = useState({ sum: 0 });
}

updateSum() {
this.state.sum++;
}
}
18 changes: 14 additions & 4 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.playground">
<div class="p-3">
hello world
<Counter onChange.bind="updateSum"/>
<Counter onChange.bind="updateSum"/>
<div t-out="this.state.sum"/>

<Card
title="'Hello, world!'"
content="'this is a card component. do with this information what you will .'"/>

<div>
<h2 class="text-center text-secondary mb-3">Todo</h2>
<div class="d-flex justify-content-center">
<TodoList/>
</div>
</div>
</t>

</templates>

11 changes: 11 additions & 0 deletions awesome_owl/static/src/todo/todo_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component } from "@odoo/owl";

export class TodoItem extends Component {
static template = "awesome_owl.todo_item";

static props = {
todo: Object,
toggleState: Function,
removeTodo: Function,
};
}
31 changes: 31 additions & 0 deletions awesome_owl/static/src/todo/todo_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todo_item">
<div class="p-2 my-1 border-bottom d-flex align-items-center justify-content-between bg-white rounded shadow-sm px-3"
t-att-class="props.todo.isCompleted ? 'text-muted text-decoration-line-through font-italic' : ''">

<div class="d-flex align-items-center flex-grow-1 text-start">
<input type="checkbox"
class="form-check-input me-3"
t-att-checked="props.todo.isCompleted"
t-on-change="() => props.toggleState(props.todo.id)" />

<span class="text-secondary fw-bold me-3">#<t t-out="props.todo.id"/></span>
<span class="text-dark"><t t-out="props.todo.description"/></span>
</div>

<div class="d-flex align-items-center gap-2">
<t t-if="props.todo.isCompleted">
<span class="badge bg-success rounded-pill px-2 py-1">Done</span>
</t>
<t t-else="">
<span class="badge bg-warning text-dark rounded-pill px-2 py-1">Pending</span>
</t>

<span class="fa fa-remove text-danger cp ms-2"
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 is a clickable control, so it should be a button rather than a span. Buttons are keyboard-focusable by default so it's preferable to use them in these situations 👍

Suggested change
<span class="fa fa-remove text-danger cp ms-2"
<button type="button" class="btn btn-link text-danger p-0 ms-2" t-on-click="() => props.removeTodo(props.todo.id)">
<i class="fa fa-remove"/>
</button>

t-on-click="() => props.removeTodo(props.todo.id)" />
</div>
</div>
</t>

</templates>
49 changes: 49 additions & 0 deletions awesome_owl/static/src/todo/todo_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, useState, useRef, onMounted } from "@odoo/owl";
import { TodoItem } from "../todo/todo_item";

export class TodoList extends Component {
static template = "awesome_owl.todo_list";
static components = { TodoItem };

setup() {
this.items = useState([{ id: 1, description: "Buy milk", isCompleted: true }]);
this.nextId = 2;
this.inputRef = useRef("input");

onMounted(() => {
if (this.inputRef.el) this.inputRef.el.focus();
});
}

createItem(ev) {
if (ev.keyCode === 13) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

keyCode works in many browsers but it is less self-explanatory and we like self-explanatory. Checking the key name directly reads like the behavior we want: create an item when the user presses Enter.

Suggested change
if (ev.keyCode === 13) {
if (ev.key === "Enter") {

const description = ev.target.value.trim();

if (!description) {
return;
}

this.items.push({
id: this.nextId++,
description: description,
isCompleted: false
});

ev.target.value = "";
}
}

toggleItem(item_id) {
const todo = this.items.find(t => t.id === item_id);
if (todo) {
todo.isCompleted = !todo.isCompleted;
}
}

deleteItem(item_id) {
const index = this.items.findIndex((t) => t.id === item_id);
if (index >= 0) {
this.items.splice(index, 1);
}
}
}
26 changes: 26 additions & 0 deletions awesome_owl/static/src/todo/todo_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todo_list">
<div class="w-100 p-3 bg-white border rounded shadow-sm text-center" style="max-width: 500px; margin: 0 auto;">

<div class="mb-3">
<input type="text"
t-ref="input"
class="form-control form-control-lg border-primary shadow-sm"
placeholder="Add a new task.."
t-on-keyup="createItem" />
</div>

<t t-if="items.length > 0">
<t t-foreach="items" t-as="todo" t-key="todo.id">
<TodoItem todo="todo"
toggleState="toggleItem.bind(this)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can use Owl dedicated .bind syntax for passing component methods as callbacks. It's the Owl-native way to wire child events back to the parent. It's just to show you 👍

Suggested change
toggleState="toggleItem.bind(this)"
toggleState.bind="toggleItem"

removeTodo="deleteItem.bind(this)"/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

And so same here if you want 👍

</t>
</t>
<t t-else="">
<p class="text-muted my-4 small font-italic">You currently have on tasks.</p>
</t>
</div>
</t>
</templates>
84 changes: 84 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# automatically generated file by the runbot nightly ruff checks, do not modify
# for ruff version 0.15.0 (or higher)
# note: 'E241', 'E272', 'E201', 'E221' are ignored on runbot in test files when formating a table like structure (more than two space)
# some rules present here are not enabled on runbot (yet) but are still advised to follow when possible : ["B904", "COM812", "E741", "EM101", "I001", "RET", "RUF021", "RUF102", "TRY002", "UP006", "UP007"]


target-version = "py310"

[lint]
preview = true
external = ["OLS"]
select = [
"BLE", # flake8-blind-except
"C", # flake8-comprehensions
"COM", # flake8-commas
"E", # pycodestyle Error
"EM", # flake8-errmsg
"EXE", # flake8-executable
"F", # Pyflakes
"FA", # flake8-future-annotations
"FLY", # flynt
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PLC", # Pylint Convention
"PLE", # Pylint Error
"PLW", # Pylint Warning
"PYI", # flake8-pyi
"RET", # flake8-return
"RUF", # Ruff-specific rules
"SIM", # flake8-simplify
"SLOT", # flake8-slots
"T", # flake8-print
"TC", # flake8-type-checking
"TID", # flake8-tidy-imports
"TRY", # tryceratops
"UP", # pyupgrade
"W", # pycodestyle Warning
"YTT", # flake8-2020
]
ignore = [
"C408", # unnecessary-collection-call
"C420", # unnecessary-dict-comprehension-for-iterable
"C901", # complex-structure
"E266", # multiple-leading-hashes-for-block-comment
"E501", # line-too-long
"E713", # not-in-test
"EM102", # f-string-in-exception
"FA100", # future-rewritable-type-annotation
"PGH003", # blanket-type-ignore
"PIE790", # unnecessary-placeholder
"PIE808", # unnecessary-range-start
"PLC2701", # import-private-name
"PLW2901", # redefined-loop-name
"RUF001", # ambiguous-unicode-character-string
"RUF005", # collection-literal-concatenation
"RUF012", # mutable-class-default
"RUF067", # non-empty-init-module
"RUF100", # unused-noqa
"SIM102", # collapsible-if
"SIM108", # if-else-block-instead-of-if-exp
"SIM117", # multiple-with-statements
"TID252", # relative-imports
"TRY003", # raise-vanilla-args
"TRY300", # try-consider-else
"TRY400", # error-instead-of-exception
"UP031", # printf-string-formatting
]

[lint.per-file-ignores]
"**/__init__.py" = [
"F401", # unused-import
]

[lint.isort]
# https://www.odoo.com/documentation/latest/contributing/development/coding_guidelines.html#imports
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
known-first-party = ["odoo"]
known-local-folder = ["odoo.addons"]