Owl components¶
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 组件¶
The Owl documentation already documents in detail the Owl framework, so this page will only provide Odoo specific information. But first, let us see how we can make a simple component in 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 框架提供了一套通用组件,可以在一些常见情况下重复使用,例如下拉菜单、复选框或日期选择器。本页面解释了如何使用这些通用组件。
技术名称 |
简短描述 |
---|---|
一个滑动组件,用于在触摸滑动时执行操作 |
|
一个简单的复选框组件,旁边带有标签 |
|
可供选择的颜色列表 |
|
全功能下拉菜单 |
|
一个使用选项卡导航页面的组件 |
|
一个用于处理分页的小组件 |
|
a dropdown component to choose between different options |
|
a list of tags displayed in rounded pills |
动作轮播¶
位置¶
@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
, which is the callableFunction
serving as a callback. Once the swipe has been completed in the given direction, that action is performed.
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
描述¶
The Dropdown lets you show a menu with a list of items when a toggle is clicked on. They can be combined with DropdownItems to invoke callbacks and close the menu when items are selected.
Dropdowns are surprisingly complicated components, the list of features they provide is as follow:
点击时切换项目列表
点击外部关闭
Call a function when items are selected
当选择一个项目时,可选择关闭项目列表
SIY:自己设计样式
支持多级子菜单下拉框
可配置的热键,用于打开/关闭下拉菜单或选择下拉菜单项
键盘导航(箭头、Tab、Shift+Tab、Home、End、Enter 和 Escape)
每当页面滚动或调整大小时重新定位自身
智能地选择它应该打开的方向(从右到左的方向会自动处理)。
直接兄弟下拉菜单:当一个打开时,悬停时切换其他菜单
要正确使用 <Dropdown/>
组件,您需要填充两个 OWL slots :
default
slot: it contains the toggle elements of your dropdown. By default, click events will be attached to this element to open and close the dropdown.content
slot: it contains the elements of the dropdown menu itself and is rendered inside a popover. Although it is not mandatory, you can put someDropdownItem
inside this slot, the dropdown will automatically close when these items are selected.
<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 Props¶
名称 |
类型 |
描述 |
---|---|---|
|
|
Optional classname added to the dropdown’s menu |
|
|
Optional, if true, disables the dropdown so the user is not able to open it anymore. (default: |
|
|
Optional list of items to be displayed as DropdownItems inside the dropdown’s menu |
|
|
Optionally defines the desired menu opening position. RTL direction is automatically applied. Should be a valid usePosition hook position. (default: |
|
|
Optional function called just before opening. May be asynchronous. |
|
|
Optional function called just after opening. |
|
|
Optional function called after opening or closing (gives a boolean as single argument that represents whether the dropdown is open or not). |
|
|
Optional object with |
|
|
Optional, when true, the Dropdown component will not add click event listeners to the toggler. This allows for more control as when to open the dropdown. (This should be used in tandem with the |
|
|
Optionally overrides the navigation options of the dropdown, (see |
|
|
Optional, if true, keeps the Dropdown’s menu at the same position while the mouse is hovering it, creating a better UX when the menu’s content changes. |
|
|
Optional, allows to get a ref of the dropdown’s menu, (expects a function returned from |
DropdownItem Props¶
名称 |
类型 |
描述 |
---|---|---|
|
|
Optional value added to the root span classname (supports both strings and OWL classname object notation). |
|
|
Optional function called when the dropdown item is selected. |
|
|
Optional, controls which parent dropdown should close when the item is selected:
|
|
|
Optional object representing attributes that are added to the root element. |
重要
When writing custom css for you components, do not forget that the menu elements are not next to the toggle
but inside the overlay container, at the bottom of the document. Thus, use the menuClass
and class
props to more
easily write your selectors. (This DOM magic let us avoid lots of z-index issues.)
Nested Dropdown¶
Dropdown can be nested, to do this simply put new Dropdown components inside other dropdown’s content slot. When the parent dropdown is open, child dropdowns will open automatically on hover.
By default, selecting a DropdownItem will close the whole Dropdown tree.
Example
This example shows how one could make a nested File dropdown menu, with submenus for the New sub elements.
<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>
In the example bellow, we recursively call a template to display a tree-like structure.
<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>
Controlled Dropdown¶
If needed, you can also open or close the dropdown using code. To do this you must use the useDropdownState
hook along
with the state
prop. useDropdownState
returns an object that has an open
and a close
method (as well as an isOpen
getter).
Give the object to the state
prop of the dropdown you want to control and calling the respective functions should now open and
close your dropdown.
You can also set manual
to true
if you don’t want the default click handlers to be added on the toggle.
Example
The following example shows a dropdown that opens automatically when mounted and only has a 50% chance of closing when clicking on the button inside.
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¶
Location: @web/core/dropdown/dropdown_group
You can use the DropdownGroup component to make Dropdowns share a common group, this means that when one of these Dropdown is open, the others will automatically open themselves on mouse hover, without the need for a click.
To do this, either surround all the Dropdowns with a single DropdownGroup or surround them with
DropdownGroups with the same group
key.
Example
In the example bellow, all dropdown in the snippet bellow will share the same group:
<DropdownGroup>
<Dropdown>...</Dropdown>
<Dropdown>...</Dropdown>
<Dropdown>...</Dropdown>
</DropdownGroup>
Whereas in the following snippet, only the first, second and fourth dropdown share the same group:
<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
。
A page can be disabled with the isDisabled
attribute, set directly on the slot node, or
in the page declaration, if the Notebook is used with the pages
given as props. Once disabled,
the corresponding tab is greyed out and set as inactive as well.
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
可选。允许在不可见选项卡内部的元素之间进行锚点导航。 |
|
|
可选。设置在组件根部的类名。 |
|
|
可选。默认显示的页面 |
|
|
optional. List of icons used in the tabs. |
|
|
可选。选项卡方向是 |
|
|
可选项。页面更改后执行的回调函数。 |
|
|
可选。包含从模板填充的 |
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开始,但分页器显示 |
|
|
页面大小。 |
|
|
页面可以达到的元素总数。 |
|
|
当分页器修改页面时调用的函数。此函数可以是异步的,当此函数执行时,分页器不能被编辑。 |
|
|
允许点击当前页面进行编辑(默认为 |
|
|
Binds access key |
位置¶
@web/core/select_menu/select_menu
描述¶
This component can be used when you want to do more than using the native select
element. You can define your own option template, allowing to search
between your options, or group them in subsections.
注解
Prefer the native HTML select
element, as it provides by default accessibility features, and has a better user interface on mobile devices.
This component is designed to be used for more complex use cases, to overcome limitations of the native element.
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
optional. List of |
|
|
optional. Classname set on the root of the SelectMenu component. |
|
|
optional. List of |
|
|
optional. Enable multiple selections. When multiple selection is enabled, selected values are displayed as tag’s in the SelectMenu input. |
|
|
optional. classname set on the toggler button. |
|
|
optional. Whether the selected value can be unselected. |
|
|
optional. Whether a search box is visible in the dropdown. |
|
|
optional. Text displayed as the search box placeholder. |
|
|
optional. Current selected value. It can be from any kind of type. |
|
|
optional. Callback executed when an option is chosen. |
The shape of a choice
is the following:
value
is actual value of the choice. It is usually a technical string, but can be fromany
type.
label
is the displayed text associated with the option. This one is usually a more friendly and translatedstring
.
The shape of a group
is the following:
choices
is the list ofchoice
’s to display for this group.
label
is the displayed text associated with the group. This is astring
displayed at the top of the group.
Example
In the following example, the SelectMenu will display four choices. One of them is displayed on top of the options, since no groups are associated with it, but the other ones are separated by the label of their group.
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'"
/>
`;
You can also customize the appearance of the toggler and set a custom template for the choices, using the appropriate component slot
’s.
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>
`;
When SelectMenu is used with multiple selection, the value
props must be an Array
containing the values of the selected choices.
For more advanced use cases, you can customize the bottom area of the dropdown, using the bottomArea
slot. Here, we choose to display
a button with the corresponding value set in the search input.
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
描述¶
This component can display a list of tags in rounded pills. Those tags can either simply list a few values, or can be editable, allowing the removal of items.
It can be possible to limit the number of displayed items using the itemsVisible
props. If the list is longer than this limit, the number of additional items is
shown in a circle next to the last tag.
属性¶
名称 |
类型 |
描述 |
---|---|---|
|
|
optional. Whether the tag is displayed as a badge. |
|
|
optional. Whether the tag is displayed with a text or not. |
|
|
optional. Limit of visible tags in the list. |
|
|
list of |
The shape of a tag
is the following:
colorIndex
is an optional color id.
icon
is an optional icon displayed just before the displayed text.
id
is a unique identifier for the tag.
img
is an optional image displayed in a circle, just before the displayed text.
onClick
is an optional callback that can be given to the element. This allows the parent element to handle any functionality depending on the tag clicked.
onDelete
is an optional callback that can be given to the element. This makes the removal of the item from the list of tags possible, and must be handled by the parent element.
text
is the displayedstring
associated with the tag.
Example
In the next example, a TagsList component is used to display multiple tags. It’s at the developer to handle from the parent what would happen when the tag is pressed, or when the delete button is clicked.
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" />`;
Depending the attributes given to each tag, their appearance and behavior will differ.