-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[ADD] owl framework 101 #1300
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?
[ADD] owl framework 101 #1300
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 |
|---|---|---|
| @@ -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, | ||
| }; | ||
| } |
| 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> | ||
|
|
| 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"; | ||
|
|
||
| setup() { | ||
| this.state = useState({ value: 0 }); | ||
| } | ||
|
|
||
| increment() { | ||
| this.state.value++; | ||
| this.props.onChange(); | ||
| } | ||
| } | ||
| 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> | ||
|
|
| 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++; | ||
| } | ||
| } |
| 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> | ||
|
|
| 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, | ||
| }; | ||
| } |
| 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" | ||||||||||
|
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 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
|
||||||||||
| t-on-click="() => props.removeTodo(props.todo.id)" /> | ||||||||||
| </div> | ||||||||||
| </div> | ||||||||||
| </t> | ||||||||||
|
|
||||||||||
| </templates> | ||||||||||
| 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) { | ||||||
|
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
|
||||||
| 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); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| 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)" | ||||||
|
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. You can use Owl dedicated
Suggested change
|
||||||
| removeTodo="deleteItem.bind(this)"/> | ||||||
|
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. 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> | ||||||
| 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"] |
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.
This component expects its parent to pass an
onChangecallback, so it is worth making that contract explicit instatic props. It's helpful to everyone and you will avoid the "no explicit props declared" warning too 👍