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​


 

 

心有猛虎,细嗅蔷薇