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 框架提供了一套通用组件,可以在一些常见情况下重复使用,例如下拉菜单、复选框或日期选择器。本页面解释了如何使用这些通用组件。
技术名称 |
简短描述 |
---|---|
一个滑动组件,用于在触摸滑动时执行操作 |
|
一个简单的复选框组件,旁边带有标签 |
|
可供选择的颜色列表 |
|
全功能下拉菜单 |
|
一个使用选项卡导航页面的组件 |
|
一个用于处理分页的小组件 |
|
一个下拉组件,用于在不同选项之间进行选择 |
|
以圆角标签形式显示的标签列表 |
动作轮播¶
位置¶
@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
描述¶
当点击切换按钮时,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>
下拉菜单属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
添加到下拉菜单的可选类名 |
|
|
可选,如果为 true,则禁用下拉菜单,用户将无法再打开它。(默认值: |
|
|
可选的项目列表,将作为 DropdownItems 显示在下拉菜单中 |
|
|
可选地定义菜单的期望打开位置。RTL 方向会自动应用。应为有效的 usePosition 钩子位置。(默认值: |
|
|
在打开之前调用的可选函数。可能是异步的。 |
|
|
在打开后立即调用的可选函数。 |
|
|
可选函数,在打开或关闭后调用(提供一个布尔值作为唯一参数,表示下拉菜单是否打开)。 |
|
|
带有 |
|
|
可选,当为 true 时,Dropdown 组件将不会为切换器添加点击事件监听器。这允许更灵活地控制何时打开下拉菜单。(应与 |
|
|
可选地覆盖下拉菜单的导航选项(参见 |
|
|
可选,如果为 true,则在鼠标悬停时保持 Dropdown 菜单的位置不变,当菜单内容变化时提供更好的用户体验。 |
|
|
可选,允许获取下拉菜单的引用,(期望从 |
DropdownItem 属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选值,添加到根 span 的类名中(支持字符串和 OWL 类名对象表示法)。 |
|
|
当选择下拉项时调用的可选函数。 |
|
|
可选,控制当选择该项时应关闭哪个父级下拉菜单: |
|
|
可选对象,表示添加到根元素的属性。 |
重要
在为你的组件编写自定义 CSS 时,不要忘记菜单元素不在切换按钮旁边,而是在覆盖容器的内部,位于文档的底部。因此,使用 menuClass
和 class
属性可以更轻松地编写选择器。(这种 DOM 技巧让我们避免了许多 z-index 问题。)
嵌套下拉菜单¶
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
返回一个包含 open
和 close
方法(以及一个 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();
}
}
}
DropdownGroup¶
位置: @web/core/dropdown/dropdown_group
您可以使用 DropdownGroup 组件使 Dropdowns 共享一个共同的组,这意味着当其中一个 Dropdown 打开时,其他 Dropdown 会在鼠标悬停时自动打开,而无需点击。
为此,要么用一个 DropdownGroup 包裹所有 Dropdown,要么用具有相同 group
键的 DropdownGroups 包裹它们。
Example
在下面的示例中,下面代码片段中的所有下拉菜单将共享同一个组:
<DropdownGroup>
<Dropdown>...</Dropdown>
<Dropdown>...</Dropdown>
<Dropdown>...</Dropdown>
</DropdownGroup>
而在以下代码片段中,只有第一个、第二个和第四个下拉菜单共享同一个组:
<DropdownGroup group="'my-group'">
<Dropdown>...</Dropdown>
<Dropdown>...</Dropdown>
</DropdownGroup>
<DropdownGroup group="'my-other-group'">
<Dropdown>...</Dropdown>
</DropdownGroup>
<DropdownGroup group="'my-group'">
<Dropdown>...</Dropdown>
</DropdownGroup>
笔记本电脑¶
位置¶
@web/core/notebook/notebook
描述¶
笔记本是用于在选项卡界面中显示多个页面的。选项卡可以位于元素顶部以水平方式显示,也可以位于左侧以垂直布局显示。
有两种方法可以定义您的笔记本页面进行实例化,一种是使用 slot
,另一种是通过传递专用的 props
。
可以通过 isDisabled
属性禁用页面,该属性可以直接设置在插槽节点上,或者在页面声明中设置,如果 Notebook 使用作为 props 提供的 pages
。一旦禁用,相应的标签页将变为灰色并设置为非活动状态。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选。允许在不可见选项卡内部的元素之间进行锚点导航。 |
|
|
可选。设置在组件根部的类名。 |
|
|
可选。默认显示的页面 |
|
|
optional. 标签页中使用的图标列表。 |
|
|
可选。选项卡方向是 |
|
|
可选项。页面更改后执行的回调函数。 |
|
|
可选。包含从模板填充的 |
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开始,但分页器显示 |
|
|
页面大小。 |
|
|
页面可以达到的元素总数。 |
|
|
当分页器修改页面时调用的函数。此函数可以是异步的,当此函数执行时,分页器不能被编辑。 |
|
|
允许点击当前页面进行编辑(默认为 |
|
|
在前一页按钮上绑定访问键 |
位置¶
@web/core/select_menu/select_menu
描述¶
当您希望实现的功能超出原生 select
元素时,可以使用此组件。您可以定义自己的选项模板,允许在选项之间进行搜索,或将它们分组到子部分中。
注解
优先使用原生的 HTML select
元素,因为它默认提供了无障碍功能,并且在移动设备上具有更好的用户界面。该组件旨在用于更复杂的用例,以克服原生元素的限制。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选。要在下拉菜单中显示的 |
|
|
可选。设置在 SelectMenu 组件根元素上的类名。 |
|
|
可选。包含要在下拉菜单中显示的 |
|
|
optional. 启用多选。当启用多选时,选中的值会以 标签 的形式显示在 SelectMenu 输入框中。 |
|
|
可选的。设置在切换按钮上的类名。 |
|
|
可选。选中的值是否可以取消选择。 |
|
|
optional. 下拉菜单中是否显示搜索框。 |
|
|
可选。显示为搜索框占位符的文本。 |
|
|
optional. 当前选中的值。它可以是任何类型的值。 |
|
|
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 用于多选时,value
属性必须是一个包含所选选项值的 Array
。
对于更高级的用例,您可以使用 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>
`;
位置¶
@web/core/tags_list/tags_list
描述¶
该组件可以以圆角药丸的形式显示标签列表。这些标签可以简单地列出一些值,也可以编辑,允许删除项目。可以通过 itemsVisible
属性限制显示的项目数量。如果列表超过此限制,则会在最后一个标签旁边以圆圈形式显示额外项目的数量。
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
optional. 标签是否显示为徽章。 |
|
|
optional. 是否显示带有文本的标签。 |
|
|
optional. 列表中可见标签的限制。 |
|
|
提供给组件的 |
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" />`;
根据赋予每个标签的属性,它们的外观和行为将有所不同。