Owl 组件

Odoo的Javascript框架使用了一个名为Owl的自定义组件框架。它是一个声明式组件系统,受到Vue和React的启发。组件使用 QWeb模板 定义,并且使用一些Owl特定的指令进行增强。官方的 Owl文档<https://github.com/odoo/owl/blob/master/doc/readme.md> _包含了完整的参考和教程。

重要

尽管代码可以在 web 模块中找到,但它是从一个单独的GitHub存储库中维护的。因此,对Owl的任何修改都应通过https://github.com/odoo/owl上的拉取请求进行。

注解

目前,所有的Odoo版本(从14版本开始)共享同一个Owl版本。

使用 Owl 组件

Owl 文档 已经详细记录了 Owl 框架,因此本页仅提供 Odoo 特定的信息。但首先,让我们看看如何在 Odoo 中创建一个简单的组件。

const { useState } = owl.hooks;
const { xml } = owl.tags;

class MyComponent extends Component {
    setup() {
        this.state = useState({ value: 1 });
    }

    increment() {
        this.state.value++;
    }
}
MyComponent.template = xml
    `<div t-on-click="increment">
        <t t-esc="state.value">
    </div>`;

这个例子展示了Owl作为一个库在全局命名空间中可用,作为 owl :它可以像Odoo中的大多数库一样简单地使用。请注意,我们在这里将模板定义为静态属性,但没有使用 static 关键字,这在某些浏览器中不可用(Odoo的javascript代码应符合Ecmascript 2019标准)。

我们在javascript代码中使用 xml 助手定义模板。然而,这只是为了入门。实际上,Odoo中的模板应该在xml文件中定义,以便进行翻译。在这种情况下,组件应该只定义模板名称。

实际上,大多数组件应该定义2或3个文件,位于同一个位置:一个javascript文件(my_component.js),一个模板文件(my_component.xml)和可选的scss(或css)文件(my_component.scss)。然后,这些文件应该被添加到某个资源包中。Web框架将负责加载javascript/css文件,并将模板加载到Owl中。

以下是如何定义上述组件的方法:

const { useState } = owl.hooks;

class MyComponent extends Component {
    ...
}
MyComponent.template = 'myaddon.MyComponent';

现在模板位于相应的xml文件中:

<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="myaddon.MyComponent">
  <div t-on-click="increment">
    <t t-esc="state.value"/>
  </div>
</t>

</templates>

注解

模板名称应遵循约定 addon_name.ComponentName.

另请参阅

最佳实践

首先,组件是类,因此它们有一个构造函数。但是构造函数是javascript中不可重写的特殊方法。由于这是Odoo中偶尔有用的模式,我们需要确保Odoo中没有任何组件直接使用构造函数方法。相反,组件应该使用 setup 方法:

// correct:
class MyComponent extends Component {
    setup() {
        // initialize component here
    }
}

// incorrect. Do not do that!
class IncorrectComponent extends Component {
    constructor(parent, props) {
        // initialize component here
    }
}

另一个好的实践是使用一致的模板命名约定: addon_name.ComponentName 。这样可以防止Odoo插件之间的名称冲突。

参考列表

Odoo 网页客户端是使用 Owl 组件构建的。为了更方便,Odoo JavaScript 框架提供了一套通用组件,可以在一些常见情况下重复使用,例如下拉菜单、复选框或日期选择器。本页面解释了如何使用这些通用组件。

技术名称

简短描述

ActionSwiper

一个滑动组件,用于在触摸滑动时执行操作

CheckBox

一个简单的复选框组件,旁边带有标签

ColorList

可供选择的颜色列表

Dropdown

全功能下拉菜单

Notebook

一个使用选项卡导航页面的组件

Pager

一个用于处理分页的小组件

SelectMenu

一个下拉组件,用于在不同选项之间进行选择

TagsList

以圆角标签形式显示的标签列表

动作轮播

位置

@web/core/action_swiper/action_swiper

描述

这是一个组件,可以在元素水平滑动时执行操作。Swiper将目标元素包装起来,以添加操作。一旦用户释放swiper通过其宽度的一部分,操作就会执行。

<ActionSwiper onLeftSwipe="Object" onRightSwipe="Object">
  <SomeElement/>
</ActionSwiper>

使用该组件的最简单方法是在xml模板中直接将其用于目标元素,如上所示。但有时,您可能想要扩展现有元素,而不想复制模板。这也是可能的。

如果您想扩展现有元素的行为,您必须直接将该元素包装在内部。此外,您可以有条件地添加属性来管理元素何时可以进行滑动、其动画以及执行操作所需的最小滑动部分。

您可以使用此组件轻松地与记录、消息、列表中的项目等进行交互。

ActionSwiper使用示例

下面的示例创建了一个基本的ActionSwiper组件。在这里,可以在两个方向上进行滑动。

<ActionSwiper
  onRightSwipe="
    {
      action: '() => Delete item',
      icon: 'fa-delete',
      bgColor: 'bg-danger',
    }"
  onLeftSwipe="
    {
      action: '() => Star item',
      icon: 'fa-star',
      bgColor: 'bg-warning',
    }"
>
  <div>
    Swipable item
  </div>
</ActionSwiper>

注解

在使用从右到左(RTL)语言时,操作会被排列。

属性

名称

类型

描述

animationOnMove

Boolean

可选布尔值,用于确定在滑动过程中是否存在翻译效果

animationType

String

可选动画,用于在滑动结束后使用 (bounceforwards)

onLeftSwipe

Object

如果存在,则可以向左滑动 actionswiper

onRightSwipe

Object

如果存在,则可以向右滑动 actionswiper

swipeDistanceRatio

Number

可选的最小宽度比率,必须滑动才能执行操作

您可以同时使用 onLeftSwipeonRightSwipe 属性。

左/右滑动所使用的 Object 必须包含:

  • action,它是作为回调的可调用 Function。一旦在给定方向上完成滑动,就会执行该操作。

  • icon 是要使用的图标类,通常用于表示动作。它必须是一个 string

  • bgColor 是背景颜色,用于装饰操作。可以是以下之一 bootstrap contextual color (danger, info, secondary, successwarning)。

这些值必须提供以定义 swiper 的行为和视觉效果。

示例:扩展现有组件

在下面的示例中,您可以使用 xpath 来包装现有元素到 ActionSwiper 组件中。在这里,一个 swiper 已经被添加到邮件中标记消息为已读。

<xpath expr="//*[hasclass('o_Message')]" position="after">
  <ActionSwiper
    onRightSwipe="messaging.device.isMobile and messageView.message.isNeedaction ?
      {
        action: () => messageView.message.markAsRead(),
        icon: 'fa-check-circle',
        bgColor: 'bg-success',
      } : undefined"
  />
</xpath>
<xpath expr="//ActionSwiper" position="inside">
  <xpath expr="//*[hasclass('o_Message')]" position="move"/>
</xpath>

复选框

位置

@web/core/checkbox/checkbox

描述

这是一个简单的复选框组件,旁边有一个标签。复选框与标签相连:每当单击标签时,复选框就会切换。

<CheckBox value="boolean" disabled="boolean" t-on-change="onValueChange">
  Some Text
</CheckBox>

属性

名称

类型

描述

value

boolean

如果为真,则复选框被选中,否则未选中

disabled

boolean

如果为真,则复选框被禁用,否则它是启用的

颜色列表

位置

@web/core/colorlist/colorlist

描述

ColorList 允许您从预定义列表中选择颜色。默认情况下,该组件显示当前选定的颜色,并且在 canToggle 属性存在之前不可扩展。不同的属性可以改变其行为,始终展开列表,或使其在单击后充当切换器,以显示可用颜色的列表,直到选择为止。

属性

名称

类型

描述

canToggle

boolean

可选。颜色列表是否可以在单击时展开列表

colors

array

在组件中显示的颜色列表。每个颜色都有一个唯一的 id

forceExpanded

boolean

可选。如果为真,则列表始终展开

isExpanded

boolean

可选。如果为 true,则默认展开列表

onColorSelected

function

选择颜色后执行的回调函数

selectedColor

number

可选。所选颜色的 id

颜色 id 的如下:

Id

颜色

0

无颜色

1

Red

2

Orange

3

Yellow

4

Light blue

5

Dark purple

6

Salmon pink 鲑鱼粉

7

Medium blue

8

Dark blue

9

Fuchsia

12

Green

11

Purple

位置

@web/core/dropdown/dropdown@web/core/dropdown/dropdown_item

描述

当点击切换按钮时,Dropdown 允许您显示一个包含项目列表的菜单。它们可以与 DropdownItems 结合使用,以在选中项目时调用回调并关闭菜单。

下拉菜单是出奇复杂的组件,它们提供的功能列表如下:

  • 点击时切换项目列表

  • 点击外部关闭

  • 在选中项目时调用函数

  • 当选择一个项目时,可选择关闭项目列表

  • SIY:自己设计样式

  • 支持多级子菜单下拉框

  • 可配置的热键,用于打开/关闭下拉菜单或选择下拉菜单项

  • 键盘导航(箭头、Tab、Shift+Tab、Home、End、Enter 和 Escape)

  • 每当页面滚动或调整大小时重新定位自身

  • 智能地选择它应该打开的方向(从右到左的方向会自动处理)。

  • 直接兄弟下拉菜单:当一个打开时,悬停时切换其他菜单

要正确使用 <Dropdown/> 组件,您需要填充两个 OWL slots

  • default slot: 它包含您的下拉菜单的 切换 元素。默认情况下,点击事件将附加到此元素以打开和关闭下拉菜单。

  • content slot: 它包含下拉菜单本身的 元素,并在弹出框中渲染。虽然不是强制性的,但你可以在此插槽中放置一些 DropdownItem,当这些项被选中时,下拉菜单将自动关闭。

<Dropdown>
  <!-- The content of the "default" slot is the component's toggle -->
  <button class="my-btn" type="button">
    Click me to toggle the dropdown menu!
  </button>

  <!-- The "content" slot is rendered inside the menu that pops up next to the toggle -->
  <t t-set-slot="content">
    <DropdownItem onSelected="selectItem1">Menu Item 1</DropdownItem>
    <DropdownItem onSelected="selectItem2">Menu Item 2</DropdownItem>
  </t>
</Dropdown>

嵌套下拉菜单

Dropdown 可以嵌套,只需将新的 Dropdown 组件放入其他 dropdown 的内容槽中即可。当父 dropdown 打开时,子 dropdown 会在悬停时自动打开。

默认情况下,选择 DropdownItem 会关闭整个 Dropdown 树。

Example

此示例展示了如何创建一个嵌套的文件下拉菜单,并为新建子元素添加子菜单。

<Dropdown>
  <button>File</button>
  <t t-set-slot="content">
    <DropdownItem onSelected="() => this.onItemSelected('file-save')">Save</DropdownItem>
    <DropdownItem onSelected="() => this.onItemSelected('file-open')">Open</DropdownItem>

    <Dropdown>
      <button>New</button>
      <t t-set-slot="content">
        <DropdownItem onSelected="() => this.onItemSelected('file-new-document')">Document</DropdownItem>
        <DropdownItem onSelected="() => this.onItemSelected('file-new-spreadsheet')">Spreadsheet</DropdownItem>
      </t>
    </Dropdown>
  </t>
</Dropdown>

在下面的示例中,我们递归调用模板以显示树状结构。

<t t-name="addon.MainTemplate">
  <div>
    <t t-call="addon.RecursiveDropdown">
      <t t-set="name" t-value="'Main Menu'" />
      <t t-set="items" t-value="state.menuItems" />
    </t>
  </div>
</t>

<t t-name="addon.RecursiveDropdown">
  <Dropdown>
    <button t-esc="name"></button>
    <t t-set-slot="content">
      <t t-foreach="items" t-as="item" t-key="item.id">

        <!-- If this item has no child: make it a <DropdownItem/> -->
        <DropdownItem t-if="!item.childrenTree.length" onSelected="() => this.onItemSelected(item)" t-esc="item.name"/>

        <!-- Else: recursively call the current dropdown template. -->
        <t t-else="" t-call="addon.RecursiveDropdown">
          <t t-set="name" t-value="item.name" />
          <t t-set="items" t-value="item.childrenTree" />
        </t>
      </t>
    </t>
  </Dropdown>
</t>

受控下拉菜单

如果需要,你也可以使用代码打开或关闭下拉菜单。为此,你必须使用 useDropdownState 钩子以及 state 属性。 useDropdownState 返回一个包含 openclose 方法(以及一个 isOpen 获取器)的对象。 将该对象传递给你想要控制的下拉菜单的 state 属性,调用相应的函数现在应该可以打开和关闭你的下拉菜单。

如果不想在切换时添加默认的点击处理程序,也可以将 manual 设置为 true

Example

以下示例展示了一个在挂载时自动打开的下拉菜单,并且点击内部按钮时只有50%的几率会关闭。

import { Component, onMounted } from "@odoo/owl";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { useDropdownState } from "@web/core/dropdown/dropdown_hooks";

class MyComponent extends Component {

  static components = { Dropdown, DropdownItem };
  static template = xml`
    <Dropdown state="this.dropdown">
      <div>My Dropdown</div>

      <t t-set-slot="content">
        <button t-on-click="() => this.mightClose()">Close It!<button>
      </t>
    </Dropdown>
  `;

  setup() {
    this.dropdown = useDropdownState();

    onMounted(() => {
      this.dropdown.open();
    });
  }

  mightClose() {
    if (Math.random() > 0.5) {
      this.dropdown.close();
    }
  }
}

笔记本电脑

位置

@web/core/notebook/notebook

描述

笔记本是用于在选项卡界面中显示多个页面的。选项卡可以位于元素顶部以水平方式显示,也可以位于左侧以垂直布局显示。

有两种方法可以定义您的笔记本页面进行实例化,一种是使用 slot,另一种是通过传递专用的 props

可以通过 isDisabled 属性禁用页面,该属性可以直接设置在插槽节点上,或者在页面声明中设置,如果 Notebook 使用作为 props 提供的 pages。一旦禁用,相应的标签页将变为灰色并设置为非活动状态。

属性

名称

类型

描述

anchors

object

可选。允许在不可见选项卡内部的元素之间进行锚点导航。

className

string

可选。设置在组件根部的类名。

defaultPage

string

可选。默认显示的页面 id

icons

array

optional. 标签页中使用的图标列表。

orientation

string

可选。选项卡方向是 水平 还是 垂直

onPageUpdate

function

可选项。页面更改后执行的回调函数。

pages

array

可选。包含从模板填充的 page 列表。

Example

第一种方法是将页面设置在组件的插槽中。

<Notebook orientation="'vertical'">
  <t t-set-slot="page_1" title="'Page 1'" isVisible="true">
    <h1>My First Page</h1>
    <p>It's time to build Owl components. Did you read the documentation?</p>
  </t>
  <t t-set-slot="page_2" title="'2nd page'" isVisible="true">
    <p>Wise owl's silent flight. Through the moonlit forest deep, guides my path to code</p>
  </t>
</Notebook>

另一种定义页面的方法是通过传递 props。如果某些页面共享相同的结构,则此方法很有用。首先为您可能使用的每个页面模板创建一个组件。

import { Notebook } from "@web/core/notebook/notebook";

class MyTemplateComponent extends owl.Component {
  static template = owl.tags.xml`
    <h1 t-esc="props.title" />
    <p t-esc="props.text" />
  `;
}

class MyComponent extends owl.Component {
  get pages() {
    return [
      {
        Component: MyTemplateComponent,
        title: "Page 1",
        props: {
          title: "My First Page",
          text: "This page is not visible",
        },
      },
      {
        Component: MyTemplateComponent,
        id: "page_2",
        title: "Page 2",
        props: {
          title: "My second page",
          text: "You're at the right place!",
        },
      },
    ]
  }
}
MyComponent.template = owl.tags.xml`
  <Notebook defaultPage="'page_2'" pages="pages" />
`;

这里展示了两个例子:

具有垂直和水平布局的示例

分页器

位置

@web/core/pager/pager

描述

分页器是一个小组件,用于处理分页。一个页面由一个 offset 和一个 limit (页面大小)定义。它显示当前页面和元素的 total 数量,例如 “9-12 / 20”。在前面的示例中, offset 是 8, limit 是 4, total 是 20。它有两个按钮(”上一页”和”下一页”),用于在页面之间导航。

注解

分页器可以在任何地方使用,但它的主要用途是在控制面板中。请参阅 usePager 钩子以操作控制面板的分页器。

<Pager offset="0" limit="80" total="50" onUpdate="doSomething" />

属性

名称

类型

描述

offset

number

页面中第一个元素的索引。它从0开始,但分页器显示 offset + 1

limit

number

页面大小。 offsetlimit 的和对应于页面上最后一个元素的索引。

total

number

页面可以达到的元素总数。

onUpdate

function

当分页器修改页面时调用的函数。此函数可以是异步的,当此函数执行时,分页器不能被编辑。

isEditable

boolean

允许点击当前页面进行编辑(默认为 true)。

withAccessKey

boolean

在前一页按钮上绑定访问键 p,在下一页按钮上绑定访问键 n`(默认为 `true)。

SelectMenu

位置

@web/core/select_menu/select_menu

描述

当您希望实现的功能超出原生 select 元素时,可以使用此组件。您可以定义自己的选项模板,允许在选项之间进行搜索,或将它们分组到子部分中。

注解

优先使用原生的 HTML select 元素,因为它默认提供了无障碍功能,并且在移动设备上具有更好的用户界面。该组件旨在用于更复杂的用例,以克服原生元素的限制。

属性

名称

类型

描述

choices

array

可选。要在下拉菜单中显示的 choice 列表。

class

string

可选。设置在 SelectMenu 组件根元素上的类名。

groups

array

可选。包含要在下拉菜单中显示的 choicesgroup 列表。

multiSelect

boolean

optional. 启用多选。当启用多选时,选中的值会以 标签 的形式显示在 SelectMenu 输入框中。

togglerClass

string

可选的。设置在切换按钮上的类名。

required

boolean

可选。选中的值是否可以取消选择。

searchable

boolean

optional. 下拉菜单中是否显示搜索框。

searchPlaceholder

string

可选。显示为搜索框占位符的文本。

value

any

optional. 当前选中的值。它可以是任何类型的值。

onSelect

function

optional. 选择选项时执行的回调函数。

choice 的形状如下:

  • value 是选项的实际值。它通常是一个技术字符串,但可以是 any 类型。

  • label 是与选项关联的显示文本。它通常是一个更友好且经过翻译的 string

group 的形状如下:

  • choices 是为此组显示的 choice 列表。

  • label 是与组关联的显示文本。这是一个显示在组顶部的 字符串

Example

在以下示例中,SelectMenu 将显示四个选项。其中一个选项显示在选项顶部,因为它没有关联的组,而其他选项则按其组的标签分隔。

import { SelectMenu } from "@web/core/select_menu/select_menu";

class MyComponent extends owl.Component {
  get choices() {
    return [
        {
          value: "value_1",
          label: "First value"
        }
    ]
  }
  get groups() {
    return [
      {
          label: "Group A",
          choices: [
              {
                value: "value_2",
                label: "Second value"
              },
              {
                value: "value_3",
                label: "Third value"
              }
          ]
      },
      {
          label: "Group B",
          choices: [
              {
                value: "value_4",
                label: "Fourth value"
              }
          ]
      }
    ]
  }
}
MyComponent.template = owl.tags.xml`
  <SelectMenu
    choices="choices"
    groups="groups"
    value="'value_2'"
  />
`;

你也可以自定义切换器的外观,并使用适当的组件 slot 来为选项设置自定义模板。

MyComponent.template = owl.tags.xml`
  <SelectMenu
    choices="choices"
    groups="groups"
    value="'value_2'"
  >
    Make a choice!
    <t t-set-slot="choice" t-slot-scope="choice">
      <span class="coolClass" t-esc="'👉 ' + choice.data.label + ' 👈'" />
    </t>
  </SelectMenu>
`;
SelectMenu 使用与自定义示例

当 SelectMenu 用于多选时,value 属性必须是一个包含所选选项值的 Array

SelectMenu 多选使用示例

对于更高级的用例,您可以使用 bottomArea 插槽自定义下拉菜单的底部区域。在这里,我们选择显示一个按钮,并在搜索输入中设置相应的值。

MyComponent.template = owl.tags.xml`
  <SelectMenu
      choices="choices"
  >
      <span class="select_menu_test">Select something</span>
      <t t-set-slot="bottomArea" t-slot-scope="select">
          <div t-if="select.data.searchValue">
              <button class="btn text-primary" t-on-click="() => this.onCreate(select.data.searchValue)">
                  Create this article "<i t-esc="select.data.searchValue" />"
              </button>
          </div>
      </t>
  </SelectMenu>
`;
SelectMenu 底部区域自定义示例

TagsList

位置

@web/core/tags_list/tags_list

描述

该组件可以以圆角药丸的形式显示标签列表。这些标签可以简单地列出一些值,也可以编辑,允许删除项目。可以通过 itemsVisible 属性限制显示的项目数量。如果列表超过此限制,则会在最后一个标签旁边以圆圈形式显示额外项目的数量。

属性

名称

类型

描述

displayBadge

boolean

optional. 标签是否显示为徽章。

displayText

boolean

optional. 是否显示带有文本的标签。

itemsVisible

number

optional. 列表中可见标签的限制。

tags

array

提供给组件的 tag 元素列表。

tag 的形状如下:

  • colorIndex 是一个可选的颜色 ID。

  • icon 是一个可选的图标,显示在文本之前。

  • id 是标签的唯一标识符。

  • img 是一个可选的图像,显示在文本之前的圆圈中。

  • onClick 是一个可选的回调函数,可以传递给元素。这使得父元素能够根据点击的标签处理任何功能。

  • onDelete 是一个可选的回调函数,可以传递给该元素。这使得从标签列表中移除项目成为可能,并且必须由父元素处理。

  • text 是与标签关联的显示 字符串

Example

在下一个示例中,使用 TagsList 组件来显示多个标签。由开发者从父组件处理当标签被按下或删除按钮被点击时会发生什么。

import { TagsList } from "@web/core/tags_list/tags_list";

class Parent extends Component {
  setup() {
    this.tags = [{
        id: "tag1",
        text: "Earth"
    }, {
        colorIndex: 1,
        id: "tag2",
        text: "Wind",
        onDelete: () => {...}
    }, {
        colorIndex: 2,
        id: "tag3",
        text: "Fire",
        onClick: () => {...},
        onDelete: () => {...}
    }];
  }
}
Parent.components = { TagsList };
Parent.template = xml`<TagsList tags="tags" />`;

根据赋予每个标签的属性,它们的外观和行为将有所不同。

使用不同属性和属性的 TagsList 示例