第一章:Owl 组件

本章介绍了 Owl 框架 <https://github.com/odoo/owl> _,一个专为 Odoo 定制的组件系统。OWL 的主要构建模块是 components <https://github.com/odoo/owl/blob/master/doc/reference/component.md> _ 和 templates <https://github.com/odoo/owl/blob/master/doc/reference/templates.md> _。

在Owl中,用户界面的每个部分都由组件管理:它们保存逻辑并定义用于呈现用户界面的模板。实际上,组件由一个小的JavaScript类表示,该类是 Component 类的子类。

在开始练习之前,请确保您已经按照本教程介绍中描述的所有步骤进行操作: 教程介绍

每个章节的练习解决方案都托管在 官方Odoo教程存储库。建议先尝试解决问题,不要看解决方案!

小技巧

如果您使用 Chrome 作为您的网络浏览器,您可以安装 Owl Devtools 扩展。这个扩展提供了许多功能,帮助您理解和分析任何 Owl 应用程序。

Video: How to use the DevTools

在本章中,我们使用 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++;
    }
}

The Counter component specifies the name of the template to render. The template is written in XML and defines a part of user interface:

<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. 显示计数器

作为第一个练习,让我们在 Playground 组件中实现一个计数器,该组件位于 owl_playground/static/src/。要查看结果,您可以使用浏览器访问 /owl_playground/playground 路由。

Exercise

  1. 修改 playground.js 使其像上面的示例一样作为计数器。您需要使用 useState hook 以便在此组件读取的状态对象的任何部分被修改时重新渲染组件。

  2. 在同一组件中创建一个 increment 方法。

  3. 修改 playground.xml 中的模板,以便显示您的计数变量。使用 t-esc 输出数据。

  4. 在模板中添加一个按钮,并在按钮中指定一个 t-on-click 属性,以便在按钮被点击时触发 increment 方法。

../../../_images/counter.png

小技巧

浏览器下载的Odoo JavaScript文件是被压缩的。为了调试方便,最好不要压缩文件。切换到 带资源的调试模式 ,这样文件就不会被压缩。

2. 在组件中提取计数器

现在我们在 Playground 组件中有一个计数器的逻辑,让我们看看如何从中创建一个 sub-component

Exercise

  1. Playground 组件中提取计数器代码到一个新的 Counter 组件中。

  2. 你可以先在同一个文件中完成,但是一旦完成后,请更新你的代码,将 Counter 移动到它自己的文件夹和文件中。从 ./counter/counter 相对导入它。确保模板在它自己的文件中,文件名相同。

重要

不要忘记在您的JavaScript文件中添加 /** @odoo-module **/。更多信息请参考 这里

3. 待办事项组件

我们将在 owl_playground/static/src/ 中创建新的组件,以跟踪待办事项列表。这将通过多个练习逐步完成,介绍各种概念。

Exercise

  1. 创建一个 Todo 组件,它在 props 中接收一个 todo 对象,并将其显示出来。它应该显示类似于 3. 买牛奶 的内容。

  2. 如果任务已完成,可以在任务上添加Bootstrap类 text-mutedtext-decoration-line-through 。为了实现这一点,您可以使用 dynamic attributes <https://github.com/odoo/owl/blob/master/doc/reference/templates.md#dynamic-attributes> _。

  3. 修改 owl_playground/static/src/playground.jsowl_playground/static/src/playground.xml 以显示你的新 Todo 组件,并使用一些硬编码的 props 进行测试。

    Example

    setup() {
        ...
        this.todo = { id: 3, description: "buy milk", done: false };
    }
    
../../../_images/todo.png

4. 属性验证

组件 Todo 有一个隐式的 API。它期望在其 props 中接收一个特定格式的 todo 对象的描述:iddescriptiondone。让我们将该 API 更加明确。我们可以添加一个 props 定义,让 Owl 在 dev mode 中执行验证步骤 <https://github.com/odoo/owl/blob/master/doc/reference/app.md#dev-mode>`_。您可以在 App 配置 中激活 dev mode。

对于每个组件进行属性验证是一个好的实践。

Exercise

  1. Todo 组件中添加 props validation

  2. 打开浏览器的开发工具,切换到 Console 标签,并确保在开发模式下通过了属性验证,在 owl_playground 中默认激活了开发模式。开发模式可以通过修改 owl_playground/static/src/main.jsmount 函数的 config 参数中的 dev 属性来激活和停用。

  3. 从 props 中删除 done 并重新加载页面。验证应该失败。

5. 待办事项清单

现在,让我们显示一个待办事项列表,而不仅仅是一个待办事项。现在,我们仍然可以硬编码列表。

Exercise

  1. 将代码更改为显示一个待办事项列表而不仅仅是一个。创建一个新的 TodoList 组件来容纳 Todo 组件,并在其模板中使用 t-foreach

  2. 考虑如何使用 t-key 指令进行键控。

../../../_images/todo_list.png

6. 添加一个待办事项

到目前为止,我们列表中的待办事项是硬编码的。让我们通过允许用户向列表中添加待办事项使其更加有用。

Exercise

  1. 在任务列表上方添加一个输入框,占位符为 输入新任务

  2. 添加一个 event handlerkeyup 事件上命名为 addTodo

  3. 实现 addTodo 函数来检查是否按下了回车键 (ev.keyCode === 13),如果是的话,使用输入框当前的内容创建一个新的待办事项,并清空输入框中的所有内容。

  4. 确保todo具有唯一的id。它可以只是一个计数器,在每个todo上递增。

  5. 将待办事项列表包裹在 useState 钩子中,让 Owl 知道当列表被修改时应该更新 UI。

  6. 奖励分:如果输入为空,则不执行任何操作。

    this.todos = useState([]);
    
../../../_images/create_todo.png

另请参阅

Owl: Reactivity

7. 聚焦输入框

让我们看看如何使用 t-refuseRef 来访问 DOM。

Exercise

  1. 当仪表板被 mounted 时,将焦点放在前一个练习中的 input 上。 这应该从 TodoList 组件中完成。

  2. 奖励点:将代码提取到一个专门的 hook useAutofocus 中,放在一个新的 owl_playground/utils.js 文件中。

8. 切换待办事项

现在,让我们添加一个新功能:将待办事项标记为已完成。这实际上比人们想象的要棘手。状态的所有者与显示状态的组件不同。因此, Todo 组件需要向其父组件传达待办事项状态需要切换的信息。一种经典的方法是使用 callback prop <https://github.com/odoo/owl/blob/master/doc/reference/props.md#binding-function-props> _ toggleState

Exercise

  1. 在任务的id之前添加一个带有属性 type="checkbox" 的输入框,如果状态 done 为真,则必须选中。

    小技巧

    QWeb 不会创建使用 t-att 指令计算的属性,如果它的值为假值。

  2. 添加一个回调属性 toggleState.

  3. Todo 组件的输入框上添加一个 click 事件处理程序,并确保它使用 todo id 调用 toggleState 函数。

  4. 让它工作起来!

../../../_images/toggle_todo.png

9. 删除待办事项

最后一步是让用户删除待办事项。

Exercise

  1. 添加一个新的回调属性 removeTodo.

  2. Todo 组件的模板中插入 <span class="fa fa-remove"/>

  3. 每当用户点击它时,它应该调用 removeTodo 方法。

    小技巧

    如果你正在使用数组来存储待办事项清单,你可以使用 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);
}
../../../_images/delete_todo.png

10. 通用卡片与插槽

Owl 有一个强大的 slot 系统,允许您编写通用组件。这对于在界面的不同部分之间因式分解常见布局非常有用。

Exercise

  1. CounterTodolist 组件之间插入一个新的 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>
    
  2. 此组件应该有两个插槽:一个用于标题,一个用于内容(默认插槽)。应该可以按照以下方式使用 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>
    
  3. 奖励分:如果未提供 title 插槽,则根本不应该呈现 h5

../../../_images/card1.png

11. 大量的属性验证

Exercise

  1. Card 组件上添加属性验证。

  2. 尝试在 props 验证系统中表达需要一个 default 插槽和一个可选的 title 插槽。