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" owl="1">
<div t-on-click="increment">
<t t-esc="state.value"/>
</div>
</t>
</templates>
Odoo代码尚未完全转换为Owl,因此需要一种方法来区分Owl模板(新代码)和旧模板(用于组件)。为了以向后兼容的方式实现这一点,所有新模板都应该定义为 owl
属性设置为1。
注解
不要忘记在您的Owl模板中设置 owl="1"
!
注解
模板名称应遵循约定 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 框架提供了一套通用组件,可以在一些常见情况下重复使用,例如下拉菜单、复选框或日期选择器。本页面解释了如何使用这些通用组件。
技术名称 |
简短描述 |
---|---|
一个滑动组件,用于在触摸滑动时执行操作 |
|
一个简单的复选框组件,旁边带有标签 |
|
可供选择的颜色列表 |
|
全功能下拉菜单 |
|
一个使用选项卡导航页面的组件 |
|
一个用于处理分页的小组件 |
动作轮播¶
位置¶
@web/core/action_swiper/action_swiper
描述¶
这是一个组件,可以在元素水平滑动时执行操作。Swiper将目标元素包装起来,以添加操作。一旦用户释放swiper通过其宽度的一部分,操作就会执行。
<ActionSwiper onLeftSwipe="Object" onRightSwipe="Object">
<SomeElement/>
</ActionSwiper>
使用该组件的最简单方法是在xml模板中直接将其用于目标元素,如上所示。但有时,您可能想要扩展现有元素,而不想复制模板。这也是可能的。
如果您想扩展现有元素的行为,您必须直接将该元素包装在内部。此外,您可以有条件地添加属性来管理元素何时可以进行滑动、其动画以及执行操作所需的最小滑动部分。
您可以使用此组件轻松地与记录、消息、列表中的项目等进行交互。
下面的示例创建了一个基本的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)语言时,操作会被排列。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选布尔值,用于确定在滑动过程中是否存在翻译效果 |
|
|
可选动画,用于在滑动结束后使用 ( |
|
|
如果存在,则可以向左滑动 actionswiper |
|
|
如果存在,则可以向右滑动 actionswiper |
|
|
可选的最小宽度比率,必须滑动才能执行操作 |
您可以同时使用 onLeftSwipe
和 onRightSwipe
属性。
左/右滑动所使用的 Object
必须包含:
action
,它是可调用的`Function`,用作回调函数。一旦在给定方向上完成了滑动,就会执行该操作。
icon
是要使用的图标类,通常用于表示动作。它必须是一个string
。
bgColor
是背景颜色,用于装饰操作。可以是以下之一 bootstrap contextual color (danger
,info
,secondary
,success
或warning
)。这些值必须提供以定义 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>
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
如果为真,则复选框被选中,否则未选中 |
|
|
如果为真,则复选框被禁用,否则它是启用的 |
颜色列表¶
位置¶
@web/core/colorlist/colorlist
描述¶
ColorList 允许您从预定义列表中选择颜色。默认情况下,该组件显示当前选定的颜色,并且在 canToggle
属性存在之前不可扩展。不同的属性可以改变其行为,始终展开列表,或使其在单击后充当切换器,以显示可用颜色的列表,直到选择为止。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选。颜色列表是否可以在单击时展开列表 |
|
|
在组件中显示的颜色列表。每个颜色都有一个唯一的 |
|
|
可选。如果为真,则列表始终展开 |
|
|
可选。如果为 true,则默认展开列表 |
|
|
选择颜色后执行的回调函数 |
|
|
可选。所选颜色的 |
颜色 id
的如下:
Id |
颜色 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下拉菜单¶
位置¶
@web/core/dropdown/dropdown
和 @web/core/dropdown/dropdown_item
描述¶
下拉菜单是非常复杂的组件。它们需要提供许多功能,例如:
点击时切换项目列表
直接兄弟下拉菜单:当一个打开时,悬停时切换其他菜单
点击外部关闭
当选择一个项目时,可选择关闭项目列表
当项目被选中时调用一个函数
支持多级子菜单下拉框
SIY:自己设计样式
可配置的热键,用于打开/关闭下拉菜单或选择下拉菜单项
键盘导航(箭头、Tab、Shift+Tab、Home、End、Enter 和 Escape)
每当页面滚动或调整大小时重新定位自身
智能地选择它应该打开的方向(从右到左的方向会自动处理)。
为了一劳永逸地解决这些问题,Odoo 框架提供了一组两个组件:一个 Dropdown
组件(实际的下拉菜单)和 DropdownItem
,用于列表中的每个元素。
<Dropdown>
<t t-set-slot="toggler">
<!-- "toggler" slot content is rendered inside a button -->
Click me to toggle the dropdown menu !
</t>
<!-- "default" slot content is rendered inside a div -->
<DropdownItem onSelected="selectItem1">Menu Item 1</DropdownItem>
<DropdownItem onSelected="selectItem2">Menu Item 2</DropdownItem>
</Dropdown>
属性¶
一个 <Dropdown/>
组件就是一个 <div class="dropdown"/>
,旁边有一个 <button class="dropdown-toggle"/>
,以及菜单的 <div class="dropdown-menu"/>
。这个按钮负责菜单是否在 DOM 中存在。
下拉菜单 |
类型 |
描述 |
---|---|---|
|
布尔值 |
初始下拉菜单打开状态(默认为 |
|
字符串 |
应用于下拉菜单 |
|
字符串 |
应用于toggler `<button class=”dropdown-toggle”/>`的额外CSS类 |
|
字符串 |
通过键盘切换打开的热键 |
|
字符串 |
在切换器上添加工具提示 |
|
函数 |
在打开之前执行逻辑的钩子。可能是异步的。 |
|
布尔值 |
如果为真,则只在单击按钮时切换下拉菜单(默认为 |
|
字符串 |
title attribute content for the |
|
字符串 |
定义所需的菜单打开位置。RTL 方向会自动应用。应为有效的 usePosition hook 位置。(默认值: |
|
|
当设置为 |
一个 <DropdownItem/>
简单地是一个 span (<span class="dropdown-item"/>
)。当一个 <DropdownItem/>
被选中时,它调用它的 onSelected
属性。如果这个属性是一个方法,请确保它被绑定,如果该方法需要使用 this
值。
下拉菜单项 |
类型 |
描述 |
---|---|---|
|
函数 |
当下拉菜单项被选中时将调用的函数。 |
|
|
当项目被选中时,控制哪个父级下拉菜单将关闭:none,closest或all(默认 = |
|
字符串 |
可选热键以选择该项 |
|
字符串 |
如果提供,则DropdownItem将变为 |
|
字符串 |
可选的标题属性,将传递给DropdownItem的根节点。(默认值:未提供) |
|
对象 |
可选对象,包含应添加到根元素数据集的值。这可以使元素在编程中更容易找到,例如在测试或导览中。 |
技术说明¶
渲染后的 DOM 结构如下:
<div class="dropdown">
<button class="dropdown-toggle">Click me !</button>
<!-- following <div/> will or won't appear in the DOM depending on the state controlled by the preceding button -->
<div class="dropdown-menu">
<span class="dropdown-item">Menu Item 1</span>
<span class="dropdown-item">Menu Item 2</span>
</div>
</div>
要正确使用 <Dropdown/>
组件,您需要填充两个 OWL slots :
toggler
插槽:它包含您的下拉菜单的 toggler 元素,并且会在下拉菜单的button
内部呈现(除非将toggler
属性设置为parent
),default
插槽:它包含下拉菜单本身的 元素,并在<div class="dropdown-menu"/>
内呈现。虽然不是强制的,但通常在menu
插槽中至少有一个DropdownItem
。
当多个下拉菜单共享DOM中的同一父元素时,它们被视为一组,并且将相互通知其状态更改。这意味着当其中一个下拉菜单打开时,其他下拉菜单将在鼠标悬停时自动打开,无需点击。
示例:直接兄弟下拉菜单¶
当点击一个下拉切换器(文件,编辑**或**关于),其他的将在悬停时打开。
<div>
<Dropdown>
<t t-set-slot="toggler">File</t>
<DropdownItem onSelected="() => this.onItemSelected('file-open')">Open</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-new-document')">New Document</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-new-spreadsheet')">New Spreadsheet</DropdownItem>
</Dropdown>
<Dropdown>
<t t-set-slot="toggler">Edit</t>
<DropdownItem onSelected="() => this.onItemSelected('edit-undo')">Undo</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('edit-redo')">Redo</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('edit-find')">Search</DropdownItem>
</Dropdown>
<Dropdown>
<t t-set-slot="toggler">About</t>
<DropdownItem onSelected="() => this.onItemSelected('about-help')">Help</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('about-update')">Check update</DropdownItem>
</Dropdown>
</div>
示例:多级下拉菜单(使用 t-call
)¶
这个例子展示了如何创建一个带有子菜单的 文件
下拉菜单,包括 新建
和 另存为...
子元素。
<t t-name="addon.Dropdown.File" owl="1">
<Dropdown>
<t t-set-slot="toggler">File</t>
<DropdownItem onSelected="() => this.onItemSelected('file-open')">Open</DropdownItem>
<t t-call="addon.Dropdown.File.New"/>
<DropdownItem onSelected="() => this.onItemSelected('file-save')">Save</DropdownItem>
<t t-call="addon.Dropdown.File.Save.As"/>
</Dropdown>
</t>
<t t-name="addon.Dropdown.File.New" owl="1">
<Dropdown>
<t t-set-slot="toggler">New</t>
<DropdownItem onSelected="() => this.onItemSelected('file-new-document')">Document</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-new-spreadsheet')">Spreadsheet</DropdownItem>
</Dropdown>
</t>
<t t-name="addon.Dropdown.File.Save.As" owl="1">
<Dropdown>
<t t-set-slot="toggler">Save as...</t>
<DropdownItem onSelected="() => this.onItemSelected('file-save-as-csv')">CSV</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-save-as-pdf')">PDF</DropdownItem>
</Dropdown>
</t>
示例:多级下拉菜单(嵌套)¶
<Dropdown>
<t t-set-slot="toggler">File</t>
<DropdownItem onSelected="() => this.onItemSelected('file-open')">Open</DropdownItem>
<Dropdown>
<t t-set-slot="toggler">New</t>
<DropdownItem onSelected="() => this.onItemSelected('file-new-document')">Document</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-new-spreadsheet')">Spreadsheet</DropdownItem>
</Dropdown>
<DropdownItem onSelected="() => this.onItemSelected('file-save')">Save</DropdownItem>
<Dropdown>
<t t-set-slot="toggler">Save as...</t>
<DropdownItem onSelected="() => this.onItemSelected('file-save-as-csv')">CSV</DropdownItem>
<DropdownItem onSelected="() => this.onItemSelected('file-save-as-pdf')">PDF</DropdownItem>
</Dropdown>
</Dropdown>
示例:递归多级下拉菜单¶
在这个例子中,我们递归调用一个模板来显示类似树形结构的内容。
<t t-name="addon.MainTemplate" owl="1">
<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" owl="1">
<Dropdown>
<t t-set-slot="toggler"><t t-esc="name"/></t>
<t t-foreach="items" t-as="item" t-key="item.id">
<!-- If this item has no child: make it a <DropdownItem/> -->
<t t-if="!item.childrenTree.length">
<DropdownItem onSelected="() => this.onItemSelected(item)" t-esc="item.name"/>
</t>
<!-- 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>
笔记本电脑¶
位置¶
@web/core/notebook/notebook
描述¶
笔记本是用于在选项卡界面中显示多个页面的。选项卡可以位于元素顶部以水平方式显示,也可以位于左侧以垂直布局显示。
有两种方法可以定义您的笔记本页面进行实例化,一种是使用 slot
,另一种是通过传递专用的 props
。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选。允许在不可见选项卡内部的元素之间进行锚点导航。 |
|
|
可选。设置在组件根部的类名。 |
|
|
可选。默认显示的页面 |
|
|
可选。选项卡方向是 |
|
|
可选项。页面更改后执行的回调函数。 |
|
|
可选。包含从模板填充的 |
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" />
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
页面中第一个元素的索引。它从0开始,但分页器显示 |
|
|
页面大小。 |
|
|
页面可以达到的元素总数。 |
|
|
当分页器修改页面时调用的函数。此函数可以是异步的,当此函数执行时,分页器不能被编辑。 |
|
|
允许点击当前页面进行编辑(默认为 |
|
|
将访问键 |