猫头鹰组件

Odoo Javascript 框架使用名为 Owl 的自定义组件框架。它是一个声明式组件系统,大致受到 Vue 和 React 的启发。组件使用 QWeb templates 定义,并用一些 Owl 特定指令进行丰富。官方 Owl documentation 包含完整的参考资料和教程。

重要

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

注解

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

使用 Owl 组件

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

import { Component, xml, useState } from "@odoo/owl";

class MyComponent extends Component {
    static template = xml`
        <div t-on-click="increment">
            <t t-esc="state.value">
        </div>
    `;

    setup() {
        this.state = useState({ value: 1 });
    }

    increment() {
        this.state.value++;
    }
}

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

我们在 xml 帮助器的帮助下在 javascript 代码中定义模板。然而,它仅对入门有用。实际上,Odoo 中的模板应该在 xml 文件中定义,以便可以翻译它们。在这种情况下,组件应该只定义模板名称。

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

以下是上述组件的定义方式:

import { Component, useState } from "@odoo/owl";

class MyComponent extends Component {
    static 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 Web 客户端是使用 Owl 组件构建的。为了使其更容易,Odoo javascript 框架提供了一套通用组件,可以在某些常见情况下重用,例如下拉列表、复选框或日期选择器。本页说明如何使用这些通用组件。

技术名称

简短描述

ActionSwiper

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

CheckBox

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

ColorList

可供选择的颜色列表

Dropdown

全功能下拉菜单

Notebook

使用选项卡在页面之间导航的组件

Pager

一个处理分页的小组件

SelectMenu

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

TagsList

以圆形药丸形式显示的标签列表

动作滑动器

地点

@web/core/action_swiper/action_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 colordangerinfosecondarysuccesswarning)之一。

必须给出这些值来定义滑动器的行为和视觉效果。

示例:扩展现有组件

在以下示例中,您可以使用 xpath 来包装 ActionSwiper 组件中的现有元素。此处添加了滑动器以将消息标记为在邮件中已读。

<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

如果为 true,则选中该复选框,否则取消选中

disabled

boolean

如果为 true,则禁用该复选框,否则启用该复选框

颜色表

地点

@web/core/colorlist/colorlist

描述

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

道具

姓名

类型

描述

canToggle

boolean

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

colors

array

要在组件中显示的颜色列表。每种颜色都有一个独特的 id

forceExpanded

boolean

选修的。如果为 true,则列表始终会扩展

isExpanded

boolean

选修的。如果为 true,则默认情况下会展开列表

onColorSelected

function

选择颜色后执行的回调

selectedColor

number

选修的。所选颜色 id

颜色 id 如下:

ID

颜色

0

No color

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

描述

单击切换按钮时,下拉菜单可显示带有项目列表的菜单。它们可以与 DropdownItems 结合使用来调用回调并在选择项目时关闭菜单。

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

  • 单击时切换项目列表

  • 关闭外部点击

  • 选择项目时调用函数

  • 选择项目时可以选择关闭项目列表

  • SIY:自己设计

  • 支持子下拉菜单,最高可达任何级别

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

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

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

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

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

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

  • default 槽:它包含下拉列表的 toggle 元素。默认情况下,单击事件将附加到此元素以打开和关闭下拉列表。

  • content 槽:它包含下拉菜单本身的*元素*,并在弹出窗口内呈现。虽然不是强制性的,但您可以在此插槽中放置一些 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>

嵌套下拉菜单

下拉菜单可以嵌套,为此,只需将新的下拉菜单组件放入其他下拉菜单的内容槽中即可。当父下拉菜单打开时,子下拉菜单将在悬停时自动打开。

默认情况下,选择 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 getter)的对象。将对象赋予您想要控制的下拉列表的 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

如果 Notebook 与 pages 作为 props 一起使用,则可以使用 isDisabled 属性禁用页面,直接在槽节点上设置,或者在页面声明中设置。禁用后,相应的选项卡将变灰并设置为非活动状态。

道具

姓名

类型

描述

anchors

object

选修的。允许锚点导航到选项卡内不可见的元素。

className

string

选修的。类名设置在组件的根目录上。

defaultPage

string

选修的。默认显示的页面 id

icons

array

选修的。选项卡中使用的图标列表。

orientation

string

选修的。制表符方向是 horizontal 还是 vertical

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 { Component, xml } from "@odoo/owl";
import { Notebook } from "@web/core/notebook/notebook";

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

class MyComponent extends Component {
  static template = xml`
    <Notebook defaultPage="'page_2'" pages="pages" />
  `;

  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!",
        },
      },
    ]
  }
}

此处显示了这两个示例:

垂直和水平布局的示例

寻呼机

地点

@web/core/pager/pager

描述

Pager 是一个处理分页的小组件。页面由 offsetlimit (页面大小)定义。它显示当前页面和 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`)。

选择菜单

地点

@web/core/select_menu/select_menu

描述

当您想要执行除使用本机 select 元素之外的操作时,可以使用此组件。您可以定义自己的选项模板,允许在选项之间进行搜索,或将它们分组到小节中。

注解

更喜欢原生 HTML select 元素,因为它默认提供辅助功能,并且在移动设备上具有更好的用户界面。该组件旨在用于更复杂的用例,以克服本机元素的限制。

道具

姓名

类型

描述

choices

array

选修的。要在下拉列表中显示的 choice 列表。

class

string

选修的。在 SelectMenu 组件的根上设置的类名。

groups

array

选修的。 group 列表,包含要在下拉列表中显示的 choices

multiSelect

boolean

选修的。启用多项选择。启用多重选择后,所选值在 SelectMenu 输入中显示为 tag

togglerClass

string

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

required

boolean

选修的。是否可以取消选择选定的值。

searchable

boolean

选修的。搜索框在下拉列表中是否可见。

searchPlaceholder

string

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

value

any

选修的。当前选择的值。它可以来自任何类型。

onSelect

function

选修的。选择选项时执行的回调。

choice 的形状如下:

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

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

group 的形状如下:

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

  • label 是与该组关联的显示文本。这是显示在组顶部的 string

Example

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

import { Component, xml } from "@odoo/owl";
import { SelectMenu } from "@web/core/select_menu/select_menu";

class MyComponent extends Component {
  static template = xml`
    <SelectMenu
      choices="choices"
      groups="groups"
      value="'value_2'"
    />
  `;

  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"
              }
          ]
      }
    ]
  }
}

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

<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 插槽自定义下拉列表的底部区域。在这里,我们选择显示一个按钮,并在搜索输入中设置相应的值。

<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底部区域定制示例

标签列表

地点

@web/core/tags_list/tags_list

描述

该组件可以显示圆形药丸中的标签列表。这些标签可以简单地列出一些值,也可以是可编辑的,允许删除项目。可以使用 itemsVisible 属性限制显示项目的数量。如果列表长于此限制,则附加项目的数量会显示在最后一个标签旁边的圆圈中。

道具

姓名

类型

描述

displayBadge

boolean

选修的。标签是否显示为徽章。

displayText

boolean

选修的。标签是否显示文字。

itemsVisible

number

选修的。列表中可见标签的限制。

tags

array

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

tag 的形状如下:

  • colorIndex 是可选的颜色 ID。

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

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

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

  • onClick 是可以赋予​​元素的可选回调。这允许父元素根据单击的标签处理任何功能。

  • onDelete 是可以赋予​​元素的可选回调。这使得从标签列表中删除该项目成为可能,并且必须由父元素处理。

  • text 是显示的与标签关联的 string

Example

在下一个示例中,TagsList 组件用于显示多个标签。开发人员负责从父级处理按下标签或单击删除按钮时会发生的情况。

import { Component, xml } from "@odoo/owl";
import { TagsList } from "@web/core/tags_list/tags_list";

class Parent extends Component {
  static template = xml`<TagsList tags="tags" />`;
  static components = { TagsList };

  setup() {
    this.tags = [{
        id: "tag1",
        text: "Earth"
    }, {
        colorIndex: 1,
        id: "tag2",
        text: "Wind",
        onDelete: () => {...}
    }, {
        colorIndex: 2,
        id: "tag3",
        text: "Fire",
        onClick: () => {...},
        onDelete: () => {...}
    }];
  }
}

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

使用不同 props 和 attribute 的 TagsList 示例