第 1 章:Owl 组件¶
本章介绍 Owl 框架,这是一个为 Odoo 量身定制的组件系统。OWL 的主要构建块是 组件 和 模板。
在 Owl 中,用户界面的每个部分都由一个组件管理:组件持有逻辑,并定义用于渲染用户界面的模板。在实践中,组件表现为一个继承 Component 类的小型 JavaScript 类。
在开始练习之前,请确保您已完成本 教程简介 中描述的所有步骤。
本章各练习的答案托管在 官方 Odoo 教程仓库 中。建议您先尝试自己解题,不要看答案!
在本章中,我们使用 owl_playground 插件模块,它提供了一个仅包含 Owl 和少量其他文件的简化环境。目标是学习 Owl 本身,而不依赖 Odoo Web 客户端代码。开始使用前,请在浏览器中打开 /owl_playground/playground 路由:应该会显示一个带有 hello world 文本的 Owl 组件。
示例:一个 Counter 组件¶
首先,让我们看一个简单的示例。下面展示的 Counter 组件维护一个内部数值,将其显示出来,并在用户点击按钮时更新它。
import { Component, useState } from "@odoo/owl";
class Counter extends Component {
static template = "my_module.Counter";
setup() {
this.state = useState({ value: 0 });
}
increment() {
this.state.value++;
}
}
Counter 组件指定了要渲染的模板名称。该模板以 XML 编写,定义了界面的一部分:
<templates xml:space="preserve">
<t t-name="my_module.Counter" owl="1">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</t>
</templates>
您可能注意到了 owl="1" 这个临时属性,它让 Odoo 能够区分 Owl 模板与旧 JavaScript 框架的模板。注意 Owl 模板与 QWeb 模板并不相同:它们可以包含额外的指令,例如 t-on-click。
1. 显示计数器¶
第一个练习:在位于 owl_playground/static/src/ 的 Playground 组件中实现一个计数器。要查看结果,可以在浏览器中访问 /owl_playground/playground 路由。
Exercise
修改
playground.js,使其像上面的示例一样充当计数器。您需要使用 useState hook,以便每当该组件读取的 state 对象的任何部分被修改时,组件都会重新渲染。在同一个组件中,创建一个
increment方法。修改
playground.xml中的模板,使其显示您的计数器变量。使用 t-esc 输出数据。在模板中添加一个按钮,并在该按钮上指定 t-on-click 属性,以便在每次点击按钮时触发
increment方法。
Tip
浏览器下载的 Odoo JavaScript 文件是经过压缩的。出于调试目的,文件未经压缩时更便于使用。切换到 带资源的调试模式,使文件不被压缩。
2. 将计数器提取为组件¶
目前我们已经把计数器的逻辑放在 Playground 组件中,下面看看如何从中创建 子组件。
Exercise
将计数器代码从
Playground组件中提取到一个新的Counter组件中。您可以先在同一个文件中完成,但完成后,请更新代码,将
Counter移到它自己的文件夹和文件中,并从./counter/counter相对导入。确保模板位于同名的独立文件中。
Important
别忘了在您的 JavaScript 文件中写上 /** @odoo-module **/。更多信息请参阅 这里。
3. 待办组件¶
我们将在 owl_playground/static/src/ 中创建新组件来跟踪待办事项列表。这将通过多个练习循序渐进地完成,逐步引入各种概念。
Exercise
创建一个
Todo组件,它在 props 中接收一个todo对象并显示出来。显示效果应类似 3. buy milk。如果任务已完成,为该任务添加 Bootstrap 类
text-muted和text-decoration-line-through。为此,您可以使用 动态属性。修改
owl_playground/static/src/playground.js和owl_playground/static/src/playground.xml,先通过一些硬编码的 props 显示您的新Todo组件以进行测试。Example
setup() { ... this.todo = { id: 3, description: "buy milk", done: false }; }
更多内容
4. Props 校验¶
Todo 组件有一个隐式 API。它期望在 props 中以指定格式接收待办事项的描述:id、description 和 done。让我们把这个 API 表达得更明确。我们可以添加 props 定义,让 Owl 在 dev mode 中执行校验步骤。您可以在 App configuration 中启用开发者模式。
为每个组件进行 props 验证是一个良好的实践。
5. 待办事项列表¶
现在,让我们显示待办事项列表,而不是只显示一个待办事项。目前仍可硬编码该列表。
Exercise
修改代码以显示待办事项列表而不是单个待办事项。创建一个新的
TodoList组件来容纳Todo组件,并在其模板中使用 t-foreach。思考应如何使用
t-key指令来设置键值。
6. 添加待办事项¶
到目前为止,我们列表中的待办事项都是硬编码的。让我们允许用户向列表中添加待办事项,使其更加实用。
Exercise
在任务列表上方添加一个输入框,其占位符为 Enter a new task。
在
keyup事件上添加一个名为addTodo的 事件处理器。实现
addTodo,检查是否按下了回车键(ev.keyCode === 13),如果是,则创建一个新的待办事项,其描述为输入框的当前内容,并清空输入框的所有内容。请确保待办事项具有唯一的 id。它可以只是一个每添加一个待办事项就递增的计数器。
将待办事项列表包装在
useState钩子中,让 Owl 知道在列表被修改时应更新界面。加分项:如果输入框为空,则不做任何操作。
this.todos = useState([]);
更多内容
7. 聚焦输入框¶
我们来看看如何通过 t-ref 和 useRef 来访问 DOM。
Exercise
更多内容
8. 切换待办事项状态¶
现在,让我们添加新功能:将待办事项标记为已完成。这一点其实比想象中更棘手。状态的所有者与显示该状态的组件并不相同。因此,Todo 组件需要向其父组件传达待办事项的状态需要切换。一种经典做法是使用 callback prop toggleState。
Exercise
在任务的 id 之前添加带有属性
type="checkbox"的输入框,当状态done为真时必须勾选。Tip
如果
t-att指令的计算结果为假值,QWeb 不会创建对应的属性。添加回调 props
toggleState。在
Todo组件的输入框上添加click事件处理程序,并确保它以待办事项的 id 调用toggleState函数。让它跑起来!
9. 删除待办事项¶
最后一笔是让用户能够删除待办事项。
Exercise
添加新的回调 prop
removeTodo。在
Todo组件的模板中插入<span class="fa fa-remove"/>。每当用户点击它时,应调用
removeTodo方法。Tip
如果您使用数组来存储待办事项列表,可以使用 JavaScript 的
splice函数从中移除一个待办事项。
// find the index of the element to delete
const index = list.findIndex((elem) => elem.id === elemId);
if (index >= 0) {
// remove the element at index from list
list.splice(index, 1);
}
10. 带插槽的通用卡片¶
Owl 拥有强大的 插槽 系统,让您可以编写通用组件。这有助于将界面不同部分之间的通用布局抽象复用。
Exercise
在
Counter和Todolist组件之间插入一个新的Card组件。对该卡片使用以下 Bootstrap HTML 结构:<div class="card" style="width: 18rem;"> <img src="..." class="card-img-top" alt="..." /> <div class="card-body"> <h5 class="card-title">Card title</h5> <p class="card-text"> Some quick example text to build on the card title and make up the bulk of the card's content. </p> <a href="#" class="btn btn-primary">Go somewhere</a> </div> </div>
该组件应有两个插槽:一个用于标题,一个用于内容(默认插槽)。应可以按如下方式使用
Card组件:<Card> <t t-set-slot="title">Card title</t> <p class="card-text">Some quick example text...</p> <a href="#" class="btn btn-primary">Go somewhere</a> </Card>
加分项:如果未提供
title插槽,则h5元素应完全不渲染。
更多内容
11. 全面的 props 校验¶
Exercise
为
Card组件添加 prop 校验。尝试在 props 校验系统中表达:它需要一个
default插槽和一个可选的title插槽。