原文链接:https://www.alanhou.org/odoo-14-owl-todolist/
1、组件树
Root
/ \
A B
/ \
C D
2、状态(state):各组件可管理其自身的本地状态。这是一个简单的ES6类,没有特殊规则:
const { Component, useState } = owl;
const { xml } = owl.tags;
class Counter extends Component {
static template = xml`
<button t-on-click="state.value++">
Click Me! [<t t-esc="state.value"/>]
</button>`;
state = useState({ value: 0 });
}
class App extends Component {
static template = xml`
<div>
<span>Hello Owl</span>
<Counter />
</div>`;
static components = { Counter };
}
const app = new App();
app.mount(document.body);
owl_todoapp
(function () { const {Component, Store} = owl; // const {xml} = owl.tags; //用来插入xml代码片段的 const {whenReady} = owl.utils; //工具类 const {useRef, useDispatch, useState, useStore} = owl.hooks; //钩子 // ------------------------------------------------------------------------- // Store // ------------------------------------------------------------------------- const actions = { addTask({state}, title) { title = title.trim(); if (title) { const task = { id: state.nextId++, title: title, isCompleted: false, }; state.tasks.push(task); } }, toggleTask({state}, id) { const task = state.tasks.find((t) => t.id === id); task.isCompleted = !task.isCompleted; }, deleteTask({state}, id) { const index = state.tasks.findIndex((t) => t.id === id); state.tasks.splice(index, 1); }, }; const initialState = { nextId: 1, tasks: [], }; // ------------------------------------------------------------------------- // Task Component 可点击任务标题来切换复选框状态:
// ------------------------------------------------------------------------- const TASK_TEMPLATE = xml/* xml */` <div class="task" t-att-class="props.task.isCompleted ? 'done' : ''"> <input type="checkbox" t-att-checked="props.task.isCompleted" t-att-id="props.task.id" t-on-click="dispatch('toggleTask', props.task.id)"/> <label t-att-for="props.task.id"><t t-esc="props.task.title"/></label> <span class="delete" t-on-click="dispatch('deleteTask', props.task.id)">🗑</span> </div>`; class Task extends Component { static template = TASK_TEMPLATE; static props = ["task"]; dispatch = useDispatch(); } // ------------------------------------------------------------------------- // App Component // ------------------------------------------------------------------------- const APP_TEMPLATE = xml/* xml */` <div class="todo-app"> <input placeholder="Enter a new task" t-on-keyup="addTask" t-ref="add-input"/> <div class="task-list"> <Task t-foreach="displayedTasks" t-as="task" t-key="task.id" task="task"/> </div> <div class="task-panel" t-if="tasks.length"> <div class="task-counter"> <t t-esc="displayedTasks.length"/> <t t-if="displayedTasks.length lt tasks.length"> / <t t-esc="tasks.length"/> </t> task(s) </div> <div> <span t-foreach="['all', 'active', 'completed']" t-as="f" t-key="f" t-att-class="{active: filter.value===f}" t-on-click="setFilter(f)" t-esc="f"/> </div> </div> </div>`; class App extends Component { static template = APP_TEMPLATE; static components = {Task}; inputRef = useRef("add-input"); tasks = useStore((state) => state.tasks); filter = useState({value: "all"}); dispatch = useDispatch(); mounted() { this.inputRef.el.focus(); } addTask(ev) { // 13 is keycode for ENTER if (ev.keyCode === 13) { this.dispatch("addTask", ev.target.value); ev.target.value = ""; } } get displayedTasks() { switch (this.filter.value) { case "active": return this.tasks.filter((t) => !t.isCompleted); case "completed": return this.tasks.filter((t) => t.isCompleted); case "all": return this.tasks; } } setFilter(filter) { this.filter.value = filter; } } // ------------------------------------------------------------------------- // Setup code // ------------------------------------------------------------------------- function makeStore() { const localState = window.localStorage.getItem("todoapp"); const state = localState ? JSON.parse(localState) : initialState; const store = new Store({state, actions}); store.on("update", null, () => { localStorage.setItem("todoapp", JSON.stringify(store.state)); }); return store; } function setup() { owl.config.mode = "dev"; App.env.store = makeStore(); const app = new App(); app.mount(document.body); } whenReady(setup); })();
.todo-app { width: 300px; margin: 50px auto; background: aliceblue; padding: 10px; } .todo-app > input { display: block; margin: auto; } .task-list { margin-top: 8px; } .task { font-size: 18px; color: #111111; display: grid; grid-template-columns: 30px auto 30px; }
//在用户鼠标悬浮到任务上方时添加视觉反馈:
.task:hover { background-color: #def0ff; } .task > input { margin: auto; } .delete { opacity: 0; cursor: pointer; text-align: center; } .task:hover .delete { opacity: 1; } .task.done { opacity: 0.7; }
//对已完成任务的标题添加中划线
.task.done label { text-decoration: line-through; } .task-panel { color: #0088ff; margin-top: 8px; font-size: 14px; display: flex; } .task-panel .task-counter { flex-grow: 1; } .task-panel span { padding: 5px; cursor: pointer; } .task-panel span.active { font-weight: bold; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>OWL Todo App</title> <link rel="stylesheet" href="app.css" /> <script src="owl.js"></script> <script src="app.js"></script> </head> <body></body> </html>
owl.js 在 https://github.com/odoo/owl