Javascript 参考手册

本文介绍了Odoo的Javascript框架。这个框架在代码行数方面并不是一个大型应用程序,但它非常通用,因为它基本上是一个将声明性接口描述转换为能够与数据库中的每个模型和记录进行交互的实时应用程序的机器。甚至可以使用Web客户端修改Web客户端的界面。

概览

Javascript框架旨在处理三种主要用例:

  • the web client: 这是私有的网络应用程序,可以查看和编辑业务数据。这是一个单页应用程序(页面从不重新加载,只有在需要时才从服务器获取新数据)

  • the website: this is the public part of Odoo. It allows an unidentified user to browse some content, to shop or to perform many actions, as a client. This is a classical website: various routes with controllers and some javascript to make it work.

  • the point of sale: this is the interface for the point of sale. It is a specialized single page application.

一些 JavaScript 代码在这三种用例中是共用的,并且被捆绑在一起(请参见下面的资产部分)。本文档将主要关注 Web 客户端设计。

Web客户端

单页应用程序

简而言之, webClientWebClient 的实例是整个用户界面的根组件。它的责任是协调所有各种子组件,并提供服务,如 rpcs、本地存储等。

在运行时,Web客户端是一个单页应用程序。每次用户执行操作时,它不需要从服务器请求完整的页面。相反,它只请求所需内容,然后替换/更新视图。此外,它管理URL:它与Web客户端状态保持同步。

这意味着当用户使用Odoo时,Web客户端类(和操作管理器)实际上会创建和销毁许多子组件。状态非常动态,每个小部件都可能随时被销毁。

Web客户端JS代码概述

在这里,我们对 web/static/src/js 插件中的 Web 客户端代码进行了非常快速的概述。请注意,这并不是详尽无遗的。我们只涵盖了最重要的文件/文件夹。

  • boot.js: 这是定义模块系统的文件。它需要首先加载。

  • core/:这是一个低级构建块的集合。特别地,它包含了类系统、小部件系统、并发工具和许多其他类/函数。

  • chrome/:在这个文件夹中,我们有大多数构成用户界面的大型小部件。

  • chrome/abstract_web_client.jschrome/web_client.js:一起,这些文件定义了 WebClient 小部件,它是 Web 客户端的根小部件。

  • chrome/action_manager.js:这是将动作转换为小部件的代码(例如看板或表单视图)

  • chrome/search_X.js 所有这些文件定义了搜索视图(从Web客户端的角度来看,它不是一个视图,只是从服务器的角度来看)

  • fields:所有主视图字段小部件在此定义

  • 视图: 这是视图所在的位置

如果文件未加载/更新该怎么办

有很多不同的原因可能导致文件无法正确加载。以下是您可以尝试解决问题的一些方法:

  • 一旦服务器启动,它就不知道资产文件是否已被修改。因此,您可以简单地重新启动服务器以重新生成资产。

  • 检查控制台(通常使用F12打开的开发工具)以确保没有明显的错误

  • 尝试在文件开头(在任何模块定义之前)添加 console.log() ,这样您就可以看到文件是否已加载

  • 当处于任何调试模式时,调试管理器菜单(bug 图标)中有一个选项,可以强制服务器更新其资产文件。

  • 使用 debug=assets 模式。这将实际绕过资产包(请注意,这并不能真正解决问题。服务器仍然使用过时的资产包)

  • 最后,对于开发人员来说,最方便的方法是使用 –dev=all 选项启动服务器。这将激活文件监视器选项,当需要时会自动使资产失效。请注意,如果操作系统是Windows,则其效果不是很好。

  • 记得刷新页面!

  • 或者可能是为了保存您的代码文件…

注解

一旦资产文件被重新创建,您需要刷新页面,重新加载正确的文件(如果不起作用,则文件可能被缓存)。

加载 JavaScript 代码

通常将大型应用程序分解为较小的文件,这些文件需要连接在一起。某些文件可能需要使用在另一个文件中定义的代码的某些部分。有两种在文件之间共享代码的方法:

  • 使用全局作用域( window 对象)来读写某些对象或函数的引用。

  • 使用一个模块系统,为每个模块提供导出或导入值的方式,并确保它们以正确的顺序加载。

虽然在全局范围内工作是可能的,但这会带来许多问题:

很难确保在全局范围内直接完成的工作不会暴露实现细节。

  • 依赖关系是隐式的,导致加载顺序脆弱且不可靠。

  • 缺乏对执行的洞察意味着无法使用各种优化(例如延迟和异步加载)。

  • 模块系统有助于解决这些问题:因为模块指定了它们的依赖关系,模块系统可以确保必要的加载顺序得到尊重,因为模块可以精确地指定它们的导出,所以它们泄漏实现细节的可能性较小。

对于大多数Odoo代码,我们希望使用一个模块系统。由于Odoo中的资源工作方式(特别是每个安装的Odoo插件都可以修改包含在捆绑包中的文件列表),Odoo必须在浏览器端解析模块。为此,Odoo提供了一个小型模块系统,如下所述(参见: Odoo 模块系统 )。

然而,Odoo也支持原生的javascript模块(参见 本地 JavaScript 模块)。这些模块将简单地由服务器转换为Odoo模块。鼓励将所有javascript代码编写为原生模块,以获得更好的IDE集成。在将来,Odoo模块系统应被视为实现细节,而不是编写javascript代码的主要方式。

注解

本地 JavaScript 模块是定义 JavaScript 代码的主要方式。

类系统

Odoo在ECMAScript 6类可用之前开发。在ECMAScript 5中,定义类的标准方式是定义一个函数并在其原型对象上添加方法。这很好,但是当我们想要使用继承、混入时,它略微复杂。

出于这些原因,Odoo决定使用自己的类系统,受到John Resig的启发。基类位于 web.Class ,在文件 class.js 中。

注解

请注意,自定义类系统不应用于创建新代码。它将在某个时候被弃用,然后被删除。新类应使用标准的ES6类系统。

创建子类

让我们讨论如何创建类。主要机制是使用 extend 方法(这或多或少相当于ES6类中的 extend )。

var Class = require('web.Class');

var Animal = Class.extend({
    init: function () {
        this.x = 0;
        this.hunger = 0;
    },
    move: function () {
        this.x = this.x + 1;
        this.hunger = this.hunger + 1;
    },
    eat: function () {
        this.hunger = 0;
    },
});

在这个例子中, init 函数是构造函数。当创建一个实例时,它将被调用。使用 new 关键字来创建一个实例。

继承

方便的是,能够继承一个现有的类。这可以通过在超类上使用 extend 方法来简单地完成。当调用一个方法时,框架会秘密地重新绑定一个特殊的方法: _super 绑定到当前调用的方法。这使我们能够在需要调用父方法时使用 this._super

var Animal = require('web.Animal');

var Dog = Animal.extend({
    move: function () {
        this.bark();
        this._super.apply(this, arguments);
    },
    bark: function () {
        console.log('woof');
    },
});

var dog = new Dog();
dog.move()

混入

odoo类系统不支持多重继承,但在需要共享某些行为的情况下,我们有一个mixin系统: extend 方法实际上可以接受任意数量的参数,并将它们组合在新类中。

var Animal = require('web.Animal');
var DanceMixin = {
    dance: function () {
        console.log('dancing...');
    },
};

var Hamster = Animal.extend(DanceMixin, {
    sleep: function () {
        console.log('sleeping');
    },
});

在这个例子中, Hamster 类是 Animal 的子类,但它也混合了 DanceMixin。

修改现有类

这并不常见,但有时我们需要 原地修改 另一个类。目标是拥有一种机制来改变一个类和所有未来/现有的实例。这是通过使用 include 方法来实现的:

var Hamster = require('web.Hamster');

Hamster.include({
    sleep: function () {
        this._super.apply(this, arguments);
        console.log('zzzz');
    },
});

这显然是一项危险的操作,应该小心进行。但是,由于Odoo的结构方式,有时在一个插件中需要修改另一个插件中定义的小部件/类的行为。请注意,它将修改类的所有实例,即使它们已经被创建。

小部件

Widget 类是用户界面的一个非常重要的构建块。几乎所有的用户界面都在小部件的控制之下。Widget 类在 web.Widget 模块中定义,在 widget.js 中。

简而言之,Widget 类提供的功能包括:

  • 小部件之间的父/子关系 (PropertiesMixin)

  • 具有安全功能的广泛生命周期管理(例如,在销毁父级小部件期间自动销毁子级小部件)

  • 使用 qweb 进行自动渲染

  • 各种实用函数,帮助与外部环境交互。

这是一个基本计数器小部件的示例:

var Widget = require('web.Widget');

var Counter = Widget.extend({
    template: 'some.template',
    events: {
        'click button': '_onClick',
    },
    init: function (parent, value) {
        this._super(parent);
        this.count = value;
    },
    _onClick: function () {
        this.count++;
        this.$('.val').text(this.count);
    },
});

对于这个例子,假设模板 *some.template*(并且已经正确加载:模板位于一个文件中,该文件已在模块清单的资产中正确定义 'assets': {'web.assets_qweb': [...]},请参见 assets。)如下所示:

<div t-name="some.template">
    <span class="val"><t t-esc="widget.count"/></span>
    <button>Increment</button>
</div>

这个示例小部件可以按照以下方式使用:

// Create the instance
var counter = new Counter(this, 4);
// Render and insert into DOM
counter.appendTo(".some-div");

此示例演示了 Widget 类的一些特性,包括事件系统、模板系统和带有初始 parent 参数的构造函数。

小部件生命周期

像许多组件系统一样,小部件类具有明确定义的生命周期。通常的生命周期如下: init 被调用,然后是 willStart ,然后进行渲染,然后是 start ,最后是 destroy

Widget.init(parent)

这是构造函数。init方法应该初始化小部件的基本状态。它是同步的,可以被覆盖以从小部件的创建者/父级获取更多参数。

参数
  • parent (Widget()) – 新小部件的父级,用于处理自动销毁和事件传播。可以为 null ,表示小部件没有父级。

Widget.willStart()

当小部件被创建并在被附加到DOM的过程中,框架将调用此方法一次。 willStart 方法是一个钩子,应该返回一个promise。JS框架将等待此promise完成,然后再移动到渲染步骤。请注意,在此时,小部件没有DOM根元素。 willStart 钩子主要用于执行一些异步工作,例如从服务器获取数据。

[Rendering]()

这一步是由框架自动完成的。发生的情况是,框架会检查小部件上是否定义了模板键。如果是这样,它将使用 widget 键绑定到渲染上下文中的小部件来渲染该模板(参见上面的示例:我们在 QWeb 模板中使用 widget.count 读取小部件的值)。如果没有定义模板,则读取 tagName 键并创建相应的 DOM 元素。渲染完成后,我们将结果设置为小部件的 $el 属性。在此之后,我们会自动绑定 events 和 custom_events 键中的所有事件。

Widget.start()

当渲染完成后,框架会自动调用 start 方法。这对于执行一些专门的后渲染工作非常有用。例如,设置一个库。

必须返回一个 Promise 来指示其工作何时完成。

返回

承诺

Widget.destroy()

这总是小部件生命周期的最后一步。当小部件被销毁时,我们基本上执行所有必要的清理操作:从组件树中删除小部件,解除所有事件绑定,…

当小部件的父级被销毁时自动调用,如果小部件没有父级或者它被移除但其父级仍然存在,则必须显式调用。

注意,willStart和start方法不一定会被调用。一个小部件可以被创建(*init*方法会被调用),然后被销毁(*destroy*方法),而不必将其附加到DOM中。如果是这种情况,willStart和start甚至都不会被调用。

小部件 API

Widget.tagName

如果小部件没有定义模板,则使用。默认为 div,将用作标签名称来创建DOM元素,以设置为小部件的DOM根。可以使用以下属性进一步自定义生成的DOM根:

Widget.id

用于在生成的DOM根元素上生成 id 属性。请注意,这很少需要,并且如果小部件可以多次使用,则可能不是一个好主意。

Widget.className

用于在生成的DOM根元素上生成 class 属性。请注意,它实际上可以包含多个CSS类: ‘some-class other-class’

Widget.attributes

属性名到属性值的映射(对象字面量)。每个这样的键值对都将被设置为生成的DOM根元素的DOM属性。

Widget.el

将原始DOM元素设置为小部件的根(仅在启动生命周期方法后可用)

Widget.$el

jQuery 封装 el。 (只有在启动生命周期方法之后才可用)

Widget.template

应该设置为 QWeb 模板 的名称。如果设置了,模板将在小部件初始化之后但在启动之前进行渲染。模板生成的根元素将被设置为小部件的 DOM 根元素。

Widget.xmlDependencies

需要在小部件渲染之前加载的 XML 文件路径列表。这不会导致已经加载的任何内容再次加载。当您想要懒加载模板或者想要在网站和 Web 客户端界面之间共享小部件时,这非常有用。

var EditorMenuBar = Widget.extend({
    xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
    ...
Widget.events

事件是将事件选择器(事件名称和可选的 CSS 选择器,由空格分隔)映射到回调函数的过程。回调函数可以是小部件方法的名称或函数对象。在任何情况下, this 都将被设置为小部件:

events: {
    'click p.oe_some_class a': 'some_method',
    'change input': function (e) {
        e.stopPropagation();
    }
},

选择器用于jQuery的事件委托,回调函数仅在与选择器匹配的DOM根节点的后代中触发。如果省略选择器(仅指定事件名称),则事件将直接设置在小部件的DOM根节点上。

注意:不建议使用内联函数,未来可能会将其删除。

Widget.custom_events

这与 events 属性几乎相同,但键是任意字符串。它们表示由某些子小部件触发的业务事件。当事件被触发时,它将“冒泡”到小部件树中(有关更多详细信息,请参见组件通信部分)。

Widget.isDestroyed()
返回

true 如果小部件正在被销毁或已经被销毁,false 否则

Widget.$(selector)

将指定的 CSS 选择器应用于小部件的 DOM 根节点:

this.$(selector);

与以下功能相同:

this.$el.find(selector);
参数
  • selector (String()) – CSS 选择器

返回

jQuery 对象

注解

这个辅助方法与 Backbone.View.$ 类似

Widget.setElement(element)

将小部件的 DOM 根元素重置为提供的元素,还处理重置 DOM 根元素的各种别名以及取消设置和重新设置委托事件。

参数
  • element (Element()) – 一个 DOM 元素或 jQuery 对象,用于设置小部件的 DOM 根节点

将小部件插入DOM中

Widget.appendTo(element)

渲染小部件并将其插入目标的最后一个子元素中,使用 .appendTo()

Widget.prependTo(element)

渲染小部件并将其作为目标的第一个子元素插入,使用 .prependTo()

Widget.insertAfter(element)

渲染小部件并将其插入目标的前一个同级元素,使用 .insertAfter()

Widget.insertBefore(element)

渲染小部件并将其插入目标的下一个兄弟节点,使用 .insertBefore()

所有这些方法都接受与相应的 jQuery 方法相同的参数(CSS 选择器、DOM 节点或 jQuery 对象)。它们都返回一个 Promise,并负责三个任务:

  • 通过 renderElement() 渲染小部件的根元素

  • 使用与之匹配的任何 jQuery 方法将小部件的根元素插入 DOM 中

  • 启动小部件,并返回启动结果

小部件指南

  • 应避免使用标识符(id 属性)。在通用应用程序和模块中,id 限制了组件的可重用性,并且往往使代码更加脆弱。大多数情况下,它们可以被替换为空值、类或保留对 DOM 节点或 jQuery 元素的引用。

    如果绝对必要(因为第三方库需要),则应该使用 _.uniqueId() 部分生成 id ,例如:

    this.id = _.uniqueId('my-widget-');
    
  • 避免使用可预测/常见的CSS类名。例如”content”或”navigation”等类名可能匹配所需的含义/语义,但很可能其他开发人员也有相同的需求,从而创建命名冲突和意外行为。通用类名应该以它们所属的组件的名称为前缀(创建”非正式”命名空间,就像在C或Objective-C中一样)。

  • 应避免使用全局选择器。因为一个组件可能在单个页面中使用多次(Odoo中的一个例子是仪表板),查询应限制在给定组件的范围内。未经过滤的选择,如 $(selector)document.querySelectorAll(selector) 通常会导致意外或不正确的行为。Odoo Web 的 Widget() 有一个提供其 DOM 根节点的属性($el),以及一个直接选择节点的快捷方式($())。

  • 更一般地说,永远不要假设你的组件拥有或控制除了它自己的个人 $el 之外的任何东西(因此,避免使用对父部件的引用)

  • HTML模板/渲染应该使用QWeb,除非绝对微不足道。

  • 所有交互式组件(显示信息到屏幕或拦截DOM事件的组件)必须继承自 Widget() 并正确实现和使用其API和生命周期。

  • 请确保在使用 $el 之前等待启动完成,例如:

    var Widget = require('web.Widget');
    
    var AlmostCorrectWidget = Widget.extend({
        start: function () {
            this.$el.hasClass(....) // in theory, $el is already set, but you don't know what the parent will do with it, better call super first
            return this._super.apply(arguments);
        },
    });
    
    var IncorrectWidget = Widget.extend({
        start: function () {
            this._super.apply(arguments); // the parent promise is lost, nobody will wait for the start of this widget
            this.$el.hasClass(....)
        },
    });
    
    var CorrectWidget = Widget.extend({
        start: function () {
            var self = this;
            return this._super.apply(arguments).then(function() {
                self.$el.hasClass(....) // this works, no promise is lost and the code executes in a controlled order: first super, then our code.
            });
        },
    });
    

QWeb模板引擎

Web 客户端使用 QWeb模板 模板引擎来渲染小部件 (除非它们覆盖 renderElement 方法以执行其他操作)。Qweb JS 模板引擎基于 XML,并且与 Python 实现大部分兼容。

现在,让我们解释一下模板是如何加载的。每当Web客户端启动时,都会向 /web/webclient/qweb 路由发出 rpc 请求。然后,服务器将返回在每个已安装模块的数据文件中定义的所有模板列表。正确的文件在每个模块清单的 web.assets_qweb 条目中列出。还可以通过调用相应的“bundle”查询参数来懒加载另一个包的模板。

Web客户端将在加载完模板列表后才开始启动第一个小部件。

这种机制对我们的需求非常有效,但有时候,我们想要延迟加载一个模板。例如,假设我们有一个很少使用的小部件。在这种情况下,也许我们更愿意不在主文件中加载它的模板,以使Web客户端稍微轻一些。在这种情况下,我们可以使用小部件的 xmlDependencies 键:

var Widget = require('web.Widget');

var Counter = Widget.extend({
    template: 'some.template',
    xmlDependencies: ['/myaddon/path/to/my/file.xml'],

    ...

});

通过这样做, Counter 小部件将在其 willStart 方法中加载 xmlDependencies 文件,因此在执行渲染时模板将准备就绪。

事件系统

Odoo目前支持两种事件系统:一个简单的系统,允许添加监听器和触发事件,以及一个更完整的系统,还可以使事件“冒泡”。

这两个事件系统都是在 EventDispatcherMixin 中实现的,在 mixins.js 文件中。这个 mixin 被包含在 Widget 类中。

基础事件系统

这个事件系统是历史上第一个。它实现了一个简单的总线模式。我们有4个主要方法:

  • on:这用于在事件上注册监听器。

  • off:用于移除事件监听器。

  • once: 这用于注册一个只会被调用一次的监听器。

  • trigger: 触发一个事件。这将导致每个监听器被调用。

以下是如何使用此事件系统的示例:

var Widget = require('web.Widget');
var Counter = require('myModule.Counter');

var MyWidget = Widget.extend({
    start: function () {
        this.counter = new Counter(this);
        this.counter.on('valuechange', this, this._onValueChange);
        var def = this.counter.appendTo(this.$el);
        return Promise.all([def, this._super.apply(this, arguments)]);
    },
    _onValueChange: function (val) {
        // do something with val
    },
});

// in Counter widget, we need to call the trigger method:

... this.trigger('valuechange', someValue);

警告

不鼓励使用此事件系统,我们计划用扩展事件系统中的 trigger_up 方法替换每个 trigger 方法

扩展事件系统

自定义事件小部件是一种更高级的系统,它模仿了DOM事件API。每当触发事件时,它都会沿着组件树“冒泡”,直到达到根小部件或被停止。

  • trigger_up:这是一个方法,它将创建一个小的*OdooEvent*并将其分发到组件树中。请注意,它将从触发事件的组件开始

  • custom_events:这是 event 字典的等效项,但用于 odoo 事件。

OdooEvent 类非常简单。它有三个公共属性: target (触发事件的小部件)、 name (事件名称)和 data (有效负载)。它还有两个方法: stopPropagationis_stopped

可以更新前面的示例以使用自定义事件系统:

var Widget = require('web.Widget');
var Counter = require('myModule.Counter');

var MyWidget = Widget.extend({
    custom_events: {
        valuechange: '_onValueChange'
    },
    start: function () {
        this.counter = new Counter(this);
        var def = this.counter.appendTo(this.$el);
        return Promise.all([def, this._super.apply(this, arguments)]);
    },
    _onValueChange: function(event) {
        // do something with event.data.val
    },
});

// in Counter widget, we need to call the trigger_up method:

... this.trigger_up('valuechange', {value: someValue});

注册表

在Odoo生态系统中,常见的需求是从外部扩展/更改基础系统的行为(通过安装应用程序,即不同的模块)。例如,可能需要在某些视图中添加新的小部件类型。在这种情况下,以及许多其他情况下,通常的过程是创建所需的组件,然后将其添加到注册表中(注册步骤),以使Web客户端的其余部分知道其存在。

系统中有几个可用的注册表:

字段注册表(由 web.field_registry 导出)

字段注册表包含所有已知的字段小部件。每当视图(通常是表单或列表/看板)需要字段小部件时,它会在这里查找。典型的用例如下:

var fieldRegistry = require('web.field_registry');

var FieldPad = ...;

fieldRegistry.add('pad', FieldPad);

请注意,每个值都应该是 AbstractField 的子类

查看注册表

此注册表包含所有已知的JS视图,供Web客户端(尤其是视图管理器)使用。此注册表的每个值都应该是 AbstractView 的子类。

动作注册表

我们在这个注册表中跟踪所有客户端操作。每当操作管理器需要创建客户端操作时,它会在这里查找。在版本11中,每个值应该只是 Widget 的子类。然而,在版本12中,值必须是 AbstractAction

小部件之间的通信

组件之间有许多通信方式。

从父组件到子组件

这是一个简单的情况。父部件可以直接调用其子部件的方法:

this.someWidget.update(someInfo);
从小部件到其父级/某个祖先

在这种情况下,小部件的工作仅是通知其环境发生了某些事情。由于我们不希望小部件引用其父级(这将使小部件与其父级的实现耦合),通常的做法是通过使用 trigger_up 方法触发事件,该事件将在组件树中向上冒泡:

this.trigger_up('open_record', { record: record, id: id});

此事件将在小部件上触发,然后会冒泡并最终被某个上游小部件捕获:

var SomeAncestor = Widget.extend({
    custom_events: {
        'open_record': '_onOpenRecord',
    },
    _onOpenRecord: function (event) {
        var record = event.data.record;
        var id = event.data.id;
        // do something with the event.
    },
});
跨组件

通过使用总线可以实现跨组件通信。这不是首选的通信形式,因为它的缺点是使代码更难维护。但是,它具有解耦组件的优点。在这种情况下,只需在总线上触发和监听事件即可。例如:

  // in WidgetA
  var core = require('web.core');

  var WidgetA = Widget.extend({
      ...
      start: function () {
          core.bus.on('barcode_scanned', this, this._onBarcodeScanned);
      },
  });

  // in WidgetB
  var WidgetB = Widget.extend({
      ...
      someFunction: function (barcode) {
          core.bus.trigger('barcode_scanned', barcode);
      },
  });

In this example, we use the bus exported by *web.core*, but this is not
required. A bus could be created for a specific purpose.

服务

在11.0版本中,我们引入了 service 的概念。主要思想是为子组件提供一种受控的方式来访问它们的环境,这种方式允许框架有足够的控制,并且是可测试的。

服务系统围绕三个概念进行组织:服务、服务提供者和小部件。它的工作方式是小部件触发(使用 trigger_up )事件,这些事件向上冒泡到服务提供者,服务提供者将要求服务执行任务,然后可能返回一个答案。

服务

服务是 AbstractService 类的一个实例。它基本上只有一个名称和一些方法。它的工作是执行一些工作,通常是依赖于环境的某些东西。

例如,我们有 ajax 服务(用于执行 rpc), localStorage (与浏览器本地存储交互)和许多其他服务。

这里是一个简化的例子,展示了如何实现ajax服务:

var AbstractService = require('web.AbstractService');

var AjaxService = AbstractService.extend({
    name: 'ajax',
    rpc: function (...) {
        return ...;
    },
});

此服务名为 ‘ajax’,定义了一个方法, rpc

服务提供商

为了使服务正常工作,我们需要一个准备好分派自定义事件的服务提供者。在 后端 (Web客户端)中,这是由主Web客户端实例完成的。请注意,服务提供者的代码来自 ServiceProviderMixin

小部件

小部件是请求服务的部分。为了做到这一点,它只需触发一个事件 call_service*(通常使用辅助函数 *call)。这个事件将冒泡并将意图传达给系统的其他部分。

实际上,有些函数被频繁调用,我们有一些辅助函数使它们更容易使用。例如, _rpc 方法是一个帮助程序,可以帮助进行 RPC 调用。

var SomeWidget = Widget.extend({
    _getActivityModelViewID: function (model) {
        return this._rpc({
            model: model,
            method: 'get_activity_view_id'
        });
    },
});

警告

如果一个小部件被销毁,它将从主组件树中分离出来,并且将没有父级。在这种情况下,事件将不会冒泡,这意味着工作将不会完成。这通常正是我们从销毁的小部件中想要的。

远程过程调用(RPC)

rpc 功能由 ajax 服务提供。但大多数人可能只与 _rpc 帮助程序交互。

通常在使用Odoo时有两种情况:一个可能需要在(Python)模型上调用一个方法(这通过一个控制器 call_kw 进行),或者一个可能需要直接调用一个控制器(在某些路由上可用)。

  • 在 Python 模型上调用方法:

return this._rpc({
    model: 'some.model',
    method: 'some_method',
    args: [some, args],
});
  • 直接调用控制器:

return this._rpc({
    route: '/some/route/',
    params: { some: kwargs},
});

通知

Odoo 框架有一种标准的方式向用户传达各种信息:通知,这些通知显示在用户界面的右上角。

有两种类型的通知:

  • notification:用于显示一些反馈信息。例如,每当用户取消订阅频道时。

  • 警告:用于显示一些重要/紧急的信息。通常用于系统中的大多数(可恢复的)错误。

此外,通知还可以用于向用户提问,而不会干扰其工作流程。想象一下通过VOIP接收到的电话:可以显示一个带有两个按钮“接受”和“拒绝”的粘性通知。

通知系统

Odoo的通知系统由以下组件设计而成:

  • 一个 通知 小部件:这是一个简单的小部件,旨在创建并显示所需的信息

  • a NotificationService:一个负责在请求完成时(使用自定义事件)创建和销毁通知的服务。请注意,Web 客户端是一个服务提供者。

  • a client action display_notification: this allows to trigger the display of a notification from python (e.g. in the method called when the user clicked on a button of type object).

  • ServiceMixin 中的一个辅助函数: displayNotification

显示通知

显示通知的最常见方法是使用来自 ServiceMixin 的方法:

  • displayNotification(options):

    显示具有以下 选项 的通知:

    • title: 字符串,可选。这将显示在顶部作为标题。

    • subtitle: 字符串,可选。这将显示在顶部作为副标题。

    • message: 字符串,可选。通知的内容。如果需要,请不要忘记通过标记函数转义您的消息。

    • sticky: 布尔值,可选(默认为 false)。如果为 true,则通知将一直保持显示,直到用户关闭它。否则,通知将在短时间后自动关闭。

    • type: string, optional (default ‘warning’). 确定通知的样式。可能的值:’info’,’success’,’warning’,’danger’,’’.

    • className: 字符串,可选。这是一个 CSS 类名,将自动添加到通知中。尽管不鼓励使用,但这对于样式化目的可能很有用。

以下是两个使用这些方法的示例:

// note that we call _t on the text to make sure it is properly translated.
this.displayNotification({
    title: _t("Success"),
    message: _t("Your signature request has been sent.")
});
this.displayNotification({
    title: _t("Error"),
    message: _t("Filter name is required."),
    type: 'danger',
});

这里是一个 Python 示例:

# note that we call _(string) on the text to make sure it is properly translated.
def show_notification(self):
    return {
        'type': 'ir.actions.client',
        'tag': 'display_notification',
        'params': {
            'title': _('Success'),
            'message': _('Your signature request has been sent.'),
            'sticky': False,
        }
    }

系统托盘

系统托盘是界面菜单栏的右侧,Web客户端在此处显示一些小部件,例如消息菜单。

当 SystrayMenu 由菜单创建时,它将查找所有已注册的小部件,并将它们添加为子小部件到适当的位置。

目前没有专门的 API 用于系统托盘小部件。它们应该是简单的小部件,并且可以像其他小部件一样使用 trigger_up 方法与其环境进行通信。

添加新的系统托盘项目

没有系统托盘注册表。添加小部件的正确方法是将其添加到类变量SystrayMenu.items中。

var SystrayMenu = require('web.SystrayMenu');

var MySystrayWidget = Widget.extend({
    ...
});

SystrayMenu.Items.push(MySystrayWidget);

排序

在将小部件添加到自己之前,系统托盘菜单将按序列属性对项目进行排序。如果原型中不存在该属性,则默认使用50。因此,要将系统托盘项目定位到右侧,可以设置一个非常高的序列号(反之,设置一个较低的数字将其放在左侧)。

MySystrayWidget.prototype.sequence = 100;

翻译管理

有些翻译是在服务器端完成的(基本上是由服务器呈现或处理的所有文本字符串),但是静态文件中有需要翻译的字符串。目前的工作方式如下:

  • 每个可翻译的字符串都标记有特殊函数 _t*(在 JS 模块 *web.core 中可用)

  • 这些字符串用于服务器生成正确的 PO 文件

  • 每当网络客户端被加载时,它会调用路由 /web/webclient/translations,该路由返回所有可翻译术语的列表

  • 在运行时,每当调用函数 _t 时,它将按顺序在此列表中查找翻译,如果找不到则返回原始字符串。

请注意,有关翻译的更详细解释,从服务器的角度来看,请参阅文档 翻译模块

在javascript中,有两个重要的翻译函数: _t_lt 。区别在于 _lt 是惰性评估的。

var core = require('web.core');

var _t = core._t;
var _lt = core._lt;

var SomeWidget = Widget.extend({
    exampleString: _lt('this should be translated'),
    ...
    someMethod: function () {
        var str = _t('some text');
        ...
    },
});

在这个例子中, _lt 是必要的,因为翻译在模块加载时还没有准备好。

请注意,翻译函数需要一些注意。传递的参数字符串不应该是动态的。

会话

有一个特定的模块由Web客户端提供,其中包含一些特定于用户当前 会话 的信息。一些值得注意的键是

  • uid:当前用户ID(其ID作为 res.users

  • user_name:用户名称,字符串类型

  • 用户上下文(用户ID、语言和时区)

  • partner_id: 当前用户关联的合作伙伴的ID

  • db:当前正在使用的数据库的名称

向会话添加信息

当加载/web路由时,服务器将在模板的脚本标签中注入一些会话信息。这些信息将从模型 ir.http 的方法 session_info 中读取。因此,如果想要添加特定的信息,可以通过重写session_info方法并将其添加到字典中来实现。

from odoo import models
from odoo.http import request


class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    def session_info(self):
        result = super(IrHttp, self).session_info()
        result['some_key'] = get_some_value_from_db()
        return result

现在,可以通过在会话中读取它来在 JavaScript 中获取该值:

var session = require('web.session');
var myValue = session.some_key;
...

请注意,此机制旨在减少 Web 客户端准备所需的通信量。它更适用于计算成本较低的数据(慢速 session_info 调用将延迟每个 Web 客户端的加载),以及在初始化过程的早期需要的数据。

视图

“view”这个词有多个含义。本节是关于视图的javascript代码设计,而不是 arch 结构或其他任何内容的结构。

2017年,Odoo使用了新的架构替换了以前的视图代码。主要需要将渲染逻辑与模型逻辑分离。

现在,视图(在通用意义上)由4个部分描述:视图、控制器、渲染器和模型。这4个部分的API在AbstractView、AbstractController、AbstractRenderer和AbstractModel类中描述。

View Controller Renderer Model
  • 视图是工厂。它的工作是获取一组字段、arch、上下文和其他一些参数,然后构建一个控制器/渲染器/模型三元组。

    视图的作用是正确设置MVC模式的每个部分,并提供正确的信息。通常,它必须处理arch字符串并提取每个视图其他部分所需的数据。

    请注意,视图是一个类,而不是一个小部件。一旦它的工作完成,它就可以被丢弃。

  • 渲染器的工作是代表在DOM元素中查看的数据。每个视图可以以不同的方式呈现数据。此外,它应该监听适当的用户操作,并在必要时通知其父级(控制器)。

    渲染器是MVC模式中的V。

  • 模型:它的工作是获取和保存视图的状态。通常,它以某种方式表示数据库中的一组记录。模型是“业务数据”的所有者。它是MVC模式中的M。

  • 控制器:它的工作是协调渲染器和模型。此外,它是Web客户端的主要入口点。例如,当用户在搜索视图中更改某些内容时,控制器的 update 方法将被调用,并提供相应的信息。

    它是MVC模式中的C。

注解

视图的JS代码被设计为可在视图管理器/操作管理器的上下文之外使用。它们可以在客户端操作中使用,或者可以在公共网站上显示(需要对资源进行一些处理)。

字段小部件

网页客户端体验的重要部分是编辑和创建数据。大部分工作都是通过字段小部件完成的,它们了解字段类型以及特定细节,如何显示和编辑值。

抽象字段

The AbstractField class is the base class for all widgets in a view, for all views that support them (currently: Form, List, Kanban).

v11字段小部件与以前版本之间存在许多差异。让我们提到最重要的几个:

  • 小部件在所有视图之间共享(好吧,表单/列表/看板)。不再需要复制实现。请注意,可以为视图创建特殊版本的小部件,方法是在视图注册表中使用视图名称作为前缀: list.many2one 将优先选择于 many2one

  • 小部件不再是字段值的所有者。它们只代表数据并与视图的其余部分通信。

  • 小部件不再需要能够在编辑模式和只读模式之间切换。现在,当需要进行此类更改时,小部件将被销毁并重新呈现。这不是问题,因为它们无论如何都不拥有自己的值。

  • 字段小部件可以在视图之外使用。它们的API略微有些笨拙,但它们被设计为独立的。

装饰

与列表视图一样,字段小部件具有简单的装饰支持。装饰的目的是为了简单地指定文本颜色,取决于记录的当前状态。例如,

<field name="state" decoration-danger="amount &lt; 10000"/>

有效的装饰名称为:

  • 装饰-bf

  • 装饰-IT

  • 危险装饰

  • 装饰信息

  • 装饰-柔和

  • 主要装饰

  • 成功修饰

  • 警告装饰

每个修饰符 decoration-X 将被映射到一个 css 类 text-X,这是一个标准的 bootstrap css 类(除了 text-ittext-bf,它们由 odoo 处理并分别对应斜体和粗体)。请注意,修饰符属性的值应该是一个有效的 Python 表达式,它将在记录作为评估上下文时进行评估。

非关系型字段

我们在此记录所有默认提供的非关系字段,没有特定的顺序。

整数 (FieldInteger)

这是 integer 类型字段的默认字段类型。

  • 支持的字段类型: integer

    选项:

    • type: 设置输入类型(默认为 text,可以设置为 number

    在编辑模式下,该字段呈现为一个输入框,HTML 属性类型设置为 number (因此用户可以受益于本地支持,特别是在移动设备上)。在这种情况下,默认格式化被禁用以避免不兼容。

    <field name="int_value" options='{"type": "number"}'/>
    
    • step: 当用户点击按钮时,将步长设置为向上和向下的值(仅适用于数字类型的输入,默认为1)

    <field name="int_value" options='{"type": "number", "step": 100}'/>
    
    • 格式:是否需要格式化数字(默认为 true)

    默认情况下,数字按照本地化参数进行格式化。

    此选项将防止字段值被格式化。

    <field name="int_value" options='{"format": false}'/>
    
浮点数 (FieldFloat)

这是 float 类型字段的默认字段类型。

  • 支持的字段类型: float

属性:

  • 数字:显示精度

<field name="factor" digits="[42,5]"/>

选项:

  • type: 设置输入类型(默认为 text,可以设置为 number

在编辑模式下,该字段呈现为一个输入框,HTML 属性类型设置为 number (因此用户可以受益于本地支持,特别是在移动设备上)。在这种情况下,默认格式化被禁用以避免不兼容。

<field name="int_value" options='{"type": "number"}'/>
  • 步骤:当用户点击按钮时,将步骤设置为向上和向下的值

    (仅适用于数字类型的输入,默认为1)

<field name="int_value" options='{"type": "number", "step": 0.1}'/>
  • 格式:是否需要格式化数字(默认为 true)

默认情况下,数字按照本地化参数进行格式化。

此选项将防止字段值被格式化。

<field name="int_value" options='{"format": false}'/>
浮点时间 (FieldFloatTime)

该小部件的目标是正确显示表示时间间隔(以小时为单位)的浮点值。例如,0.5应格式化为0:30,或4.75对应于4:45。

  • 支持的字段类型: float

float_factor(FieldFloatFactor)

该小部件旨在使用其选项中给定的因子正确显示转换后的浮点值。因此,例如,在数据库中保存的值为0.5,因子为3,则小部件值应格式化为1.5。

  • 支持的字段类型: float

float_toggle (浮点数切换字段)

此小部件的目的是用一个包含一系列可能值(在选项中给出)的按钮替换输入字段。每次单击都允许用户在范围内循环。这里的目的是将字段值限制为预定义的选择。此外,该小部件支持因子转换,就像 float_factor 小部件一样(范围值应该是转换的结果)。

  • 支持的字段类型: float

<field name="days_to_close" widget="float_toggle" options='{"factor": 2, "range": [0, 4, 8]}'/>
布尔值(FieldBoolean)

这是 boolean 类型字段的默认字段类型。

  • 支持的字段类型: boolean

字符 (FieldChar)

这是 char 类型字段的默认字段类型。

  • 支持的字段类型: char

日期(FieldDate)

这是 日期 类型字段的默认字段类型。请注意,它也适用于日期时间字段。在格式化日期时,它使用会话时区。

  • 支持的字段类型: datedatetime

选项:

  • datepicker: datepicker_小部件的额外设置。

<field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
日期时间(FieldDateTime)

这是 datetime 类型字段的默认字段类型。

  • 支持的字段类型: datedatetime

选项:

  • datepicker: datepicker_小部件的额外设置。

<field name="datetimefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
日期范围 (FieldDateRange)

此小部件允许用户在单个选择器中选择开始和结束日期。

  • 支持的字段类型: datedatetime

选项:

  • related_start_date:应用于结束日期字段以获取开始日期值,该值用于在选择器中显示范围。

  • related_end_date:应用于开始日期字段以获取用于在选择器中显示范围的结束日期值。

  • picker_options:选择器的额外设置。

<field name="start_date" widget="daterange" options='{"related_end_date": "end_date"}'/>
remaining_days (剩余天数)

此小部件可用于日期和日期时间字段。在只读模式下,它会显示字段值与今天之间的差值(以天为单位)。该小部件旨在用于信息目的:因此在编辑模式下不能修改该值。

  • 支持的字段类型: datedatetime

货币 (FieldMonetary)

这是用于“货币”类型字段的默认字段类型。它用于显示货币。如果在选项中给出了货币字段,则将使用该字段,否则将回退到默认货币(在会话中)。

  • 支持的字段类型: monetaryfloat

选项:

  • currency_field:另一个字段名称,应该是货币的many2one类型。

<field name="value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
文本 (FieldText)

这是 text 类型字段的默认字段类型。

  • 支持的字段类型: text

句柄 (HandleWidget)

这个字段的作用是作为一个 句柄 显示,并允许通过拖放来重新排序各个记录。

警告

必须在记录排序的字段上指定。

警告

在同一个列表中有多个带有句柄小部件的字段是不支持的。

  • 支持的字段类型: integer

电子邮件 (FieldEmail)

该字段显示电子邮件地址。使用它的主要原因是在只读模式下,它会呈现为带有正确 href 的锚标签。

  • 支持的字段类型: char

电话 (FieldPhone)

此字段显示电话号码。使用它的主要原因是它以只读模式呈现为带有正确 href 的锚标签,但仅在某些情况下:我们只想在设备可以拨打此特定号码时使其可点击。

  • 支持的字段类型: char

网址 (UrlWidget)

该字段以只读模式显示URL。使用它的主要原因是它被呈现为带有正确CSS类和href的锚标签。

此外,锚点标签的文本可以使用 text 属性进行自定义(它不会改变 href 值)。

<field name="foo" widget="url" text="Some URL"/>

选项:

  • website_path: (默认值:false) 默认情况下,如果没有设置此选项,小部件会强制(如果尚未)将href值以http://开头,除非将此选项设置为true,从而允许重定向到数据库自己的网站。

  • 支持的字段类型: char

域 (FieldDomain)

“Domain”字段允许用户通过树形界面构建技术前缀域,并实时查看所选记录。在调试模式下,还有一个输入框,可以直接输入前缀字符域(或构建树形界面不允许的高级域)。

请注意,此功能仅限于“静态”域(无动态表达式或访问上下文变量)。

  • 支持的字段类型: char

链接按钮(LinkButton)

LinkButton小部件实际上只是显示一个带有图标和文本值作为内容的span。链接是可点击的,将使用其值作为URL在新的浏览器窗口中打开。

  • 支持的字段类型: char

图片 (FieldBinaryImage)

此小部件用于将二进制值表示为图像。在某些情况下,服务器返回“bin_size”而不是实际图像(bin_size是表示文件大小的字符串,例如6.5kb)。在这种情况下,小部件将创建一个具有源属性的图像,该属性对应于服务器上的图像。

  • 支持的字段类型: binary

选项:

  • preview_image: 如果图像仅作为 ‘bin_size’ 加载,则此选项可用于通知 Web 客户端默认字段名不是当前字段的名称,而是另一个字段的名称。

  • accepted_file_extensions: 用户可以从文件输入对话框中选择的文件扩展名(默认值为 image/*)(参见:accept 属性 <input type="file"/>

<field name="image" widget='image' options='{"preview_image":"image_128"}'/>
二进制(FieldBinaryFile)

通用小部件,用于保存/下载二进制文件。

  • 支持的字段类型: binary

选项:

  • accepted_file_extensions:用户可以从文件输入对话框中选择的文件扩展名(参见:<input type=”file”/>上的 accept 属性)

属性:

  • 文件名:保存二进制文件将会丢失其文件名,因为它只保存二进制值。文件名可以保存在另一个字段中。为此,应将属性文件名设置为视图中存在的字段。

<field name="datas" filename="datas_fname"/>
优先级(PriorityWidget)

此小部件呈现为一组星星,允许用户单击它来选择值或不选择。例如,这对于标记任务为高优先级非常有用。

请注意,此小部件也可以在“只读”模式下工作,这是不寻常的。

  • 支持的字段类型: selection

attachment_image (附件图片)

用于 many2one 字段的图像小部件。如果字段已设置,则此小部件将以正确的 src url 渲染为图像。此小部件在编辑或只读模式下没有不同的行为,仅用于查看图像。

  • 支持的字段类型: many2one

<field name="displayed_image_id" widget="attachment_image"/>
image_selection(图像选择)

允许用户通过点击图像来选择一个值。

  • 支持的字段类型: selection

选项:一个字典,将选择值映射到一个对象,该对象具有图像的URL(image_link)和预览图像(preview_link)。

请注意,此选项不可选!

<field name="external_report_layout" widget="image_selection" options="{
    'background': {
        'image_link': '/base/static/img/preview_background.png',
        'preview_link': '/base/static/pdf/preview_background.pdf'
    },
    'standard': {
        'image_link': '/base/static/img/preview_standard.png',
        'preview_link': '/base/static/pdf/preview_standard.pdf'
    }
}"/>
label_selection (标签选择)

此小部件呈现一个简单的不可编辑标签。它仅用于显示信息,而不是编辑信息。

  • 支持的字段类型: selection

选项:

  • classes: 一个从选择值到 CSS 类的映射

<field name="state" widget="label_selection" options="{
    'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger'}
}"/>
state_selection (StateSelectionWidget)

这是一个专门的选择小部件。它假设记录在视图中有一些硬编码的字段: stage_idlegend_normallegend_blockedlegend_done 。这主要用于显示和更改项目中任务的状态,并在下拉菜单中显示附加信息。

  • 支持的字段类型: selection

<field name="kanban_state" widget="state_selection"/>
kanban_state_selection (状态选择小部件)

这个小部件与state_selection完全相同

  • 支持的字段类型: selection

list.state_selection (列表状态选择小部件)

在列表视图中,默认情况下,state_selection小部件会在图标旁边显示标签。

  • 支持的字段类型: selection

选项:

  • hide_label: 隐藏图标旁边的标签

<field name="kanban_state" widget="state_selection" options="{'hide_label': True}"/>
boolean_favorite(收藏小部件)

此小部件根据布尔值显示为空星形或实星形。请注意,它也可以在只读模式下进行编辑。

  • 支持的字段类型: boolean

布尔按钮 (FieldBooleanButton)

布尔按钮小部件旨在用于表单视图中的状态按钮。其目标是显示一个漂亮的按钮,显示布尔字段的当前状态(例如,“激活”),并允许用户在单击它时更改该字段。

请注意,它也可以在只读模式下编辑。

  • 支持的字段类型: boolean

选项:

  • 术语:可以是’active’、’archive’、’close’或具有键 string_truestring_falsehover_truehover_false 的自定义映射

<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
boolean_toggle (布尔开关)

显示一个切换开关来表示布尔值。这是FieldBoolean的子字段,主要用于具有不同的外观。

statinfo(状态信息)

这个小部件用于在 stat按钮 中表示统计信息。它基本上只是一个带有数字的标签。

  • 支持的字段类型: 整数,浮点数

选项:

  • label_field: 如果提供了该参数,小部件将使用 label_field 的值作为文本。

<button name="%(act_payslip_lines)d"
    icon="fa-money"
    type="action">
    <field name="payslip_count" widget="statinfo"
        string="Payslip"
        options="{'label_field': 'label_tasks'}"/>
</button>
百分比饼图 (FieldPercentPie)

此小部件用于在 统计按钮 中表示统计信息。这类似于statinfo小部件,但信息以 饼图 (从空到满)的形式表示。请注意,该值被解释为百分比(介于0和100之间的数字)。

  • 支持的字段类型: 整数,浮点数

<field name="replied_ratio" string="Replied" widget="percentpie"/>
进度条 (FieldProgressBar)

将一个值表示为进度条(从0到某个值)

  • 支持的字段类型: 整数,浮点数

选项:

  • editable: 布尔值,表示该值是否可编辑

  • current_value: 从视图中获取必须存在的字段的当前值

  • max_value: 从视图中必须存在的字段获取 max_value

  • edit_max_value: 布尔值,如果最大值可编辑

  • 标题:显示在顶部的栏的标题 –> 不翻译,请使用参数(而不是选项)”title”

<field name="absence_of_today" widget="progressbar"
    options="{'current_value': 'absence_of_today', 'max_value': 'total_employee', 'editable': false}"/>
toggle_button(FieldToggleBoolean)

此小部件旨在用于布尔字段。它切换按钮,切换绿色子弹/灰色子弹。它还根据值和一些选项设置工具提示。

  • 支持的字段类型: boolean

选项:

  • active:当布尔值为真时应设置的工具提示字符串

  • 未激活:当布尔值为假时应设置的工具提示

<field name="payslip_status" widget="toggle_button"
    options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}'
/>
dashboard_graph(日志仪表板图表)

这是一种更专业的小部件,用于显示代表一组数据的图形。例如,它在会计仪表板看板视图中使用。

它假设该字段是一组数据的JSON序列化。

  • 支持的字段类型: char

属性

  • 图表类型:字符串,可以是’line’或’bar’

<field name="dashboard_graph_data"
    widget="dashboard_graph"
    graph_type="line"/>
ace (AceEditor)

此小部件旨在用于文本字段。它提供了用于编辑XML和Python的Ace编辑器。

  • 支持的字段类型: char, text

  • 徽章(FieldBadge)

    在 Bootstrap 徽章标签中显示数值。

    • 支持的字段类型: charselectionmany2one

    默认情况下,徽章具有浅灰色的背景,但可以通过使用decoration-X机制进行自定义。例如,在给定条件下显示红色徽章:

    <field name="foo" widget="badge" decoration-danger="state == 'cancel'"/>
    

关联字段

class web.relational_fields.FieldSelection()

支持的字段类型: selection

web.relational_fields.FieldSelection.placeholder

一个字符串,用于在未选择任何值时显示一些信息

<field name="tax_id" widget="selection" placeholder="Select a tax"/>
单选框 (FieldRadio)

这是 FielSelection 的子字段,但专门用于将所有有效选择显示为单选按钮。

请注意,如果在many2one记录上使用,则会执行更多的rpc以获取相关记录的name_gets。

  • 支持的字段类型: selection, many2one

选项:

  • 水平:如果为真,则单选按钮将水平显示。

<field name="recommended_activity_type_id" widget="radio"
    options="{'horizontal':true}"/>
selection_badge (字段选择徽章)

这是FieldSelection的子字段,但专门用于将所有有效选择显示为矩形徽章。

  • 支持的字段类型: selection, many2one

<field name="recommended_activity_type_id" widget="selection_badge"/>
多对一 (FieldMany2One)

多对一字段的默认小部件。

  • 支持的字段类型: many2one

属性:

  • can_create: 允许创建相关记录(优先于 no_create 选项)

  • can_write: 允许编辑相关记录(默认值:true)

选项:

  • quick_create: 允许快速创建相关记录(默认值:true)

  • no_create: 防止创建相关记录 - 隐藏“创建“xxx””和“创建和编辑…”下拉菜单项(默认值:false)

  • no_quick_create: 防止快速创建相关记录 - 隐藏“创建“xxx””下拉菜单项(默认值:false)

  • no_create_edit: 隐藏“创建和编辑…”下拉菜单项(默认值:false)

  • create_name_field:在创建相关记录时,如果设置了此选项, create_name_field 的值将被填充为输入值(默认值: name

  • always_reload: boolean, 默认为false。如果为true,则小部件将始终执行额外的name_get以获取其名称值。这用于覆盖name_get方法的情况(请不要这样做)

  • no_open: 布尔值,默认为 false。如果设置为 true,则 many2one 在只读模式下单击记录时不会重定向。

<field name="currency_id" options="{'no_create': True, 'no_open': True}"/>
list.many2one(列表字段Many2One)

在列表视图中,many2one 字段的默认小部件。

用于列表视图的many2one字段的特化。主要原因是我们需要将many2one字段(只读模式下)呈现为文本,这不允许打开相关记录。

  • 支持的字段类型: many2one

many2one_barcode(FieldMany2OneBarcode)

对于many2one字段的小部件允许从移动设备(Android/iOS)打开相机扫描条形码。

特殊化的many2one字段,允许用户使用本地相机扫描条形码。然后使用name_search搜索此值。

如果设置了此小部件并且用户未使用移动应用程序,则会回退到常规many2one(FieldMany2One)

  • 支持的字段类型: many2one

many2one_avatar(Many2OneAvatar)

此小部件仅支持指向继承自’image.mixin’的模型的many2one字段。在只读模式下,它会在显示名称旁边显示相关记录的图像。请注意,在这种情况下,显示名称不是可点击的链接。在编辑模式下,它的行为与常规的many2one完全相同。

  • 支持的字段类型: many2one

many2one_avatar_user(Many2OneAvatarUser)

此小部件是Many2OneAvatar的特化版本。当单击头像时,我们会打开与相应用户的聊天窗口。此小部件只能设置在指向’res.users’模型的many2one字段上。

  • 支持的字段类型: many2one (指向’res.users’)

many2one_avatar_employee (员工头像选择框)

与 Many2OneAvatarUser 相同,但用于指向 ‘hr.employee’ 的 many2one 字段。

  • 支持的字段类型: many2one (指向 ‘hr.employee’)

kanban.many2one(KanbanFieldMany2One)

在看板视图中,用于many2one字段的默认小部件。我们需要在看板视图中禁用所有编辑。

  • 支持的字段类型: many2one

多对多 (FieldMany2Many)

多对多字段的默认小部件。

  • 支持的字段类型: many2many

属性:

  • 模式:字符串,显示默认视图

  • 域:将数据限制在特定的域中

选项:

  • create_text: 允许自定义添加新记录时显示的文本

  • link: 确定是否可以向关系添加记录的域 (默认值:True)。

  • unlink: 确定是否可以从关系中删除记录的域(默认值:True)。

many2many_binary(多文件Many2Many二进制字段)

此小部件帮助用户同时上传或删除一个或多个文件。

请注意,此小部件仅适用于模型“ir.attachment”。

  • 支持的字段类型: many2many

选项:

  • accepted_file_extensions:用户可以从文件输入对话框中选择的文件扩展名(参见:<input type=”file”/>上的 accept 属性)

many2many_tags(FieldMany2ManyTags)

将many2many显示为标签列表。

  • 支持的字段类型: many2many

选项:

  • create:域,用于确定是否可以创建新标签(默认值:True)。

<field name="category_id" widget="many2many_tags" options="{'create': [['some_other_field', '>', 24]]}"/>
  • color_field: 数字字段的名称,应该在视图中存在。颜色将根据其值选择。

<field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/>
  • no_edit_color: 设置为 True 以删除更改标签颜色的可能性(默认值:False)。

<field name="category_id" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': True}"/>
form.many2many_tags(多对多标签表单字段)

特殊化的many2many_tags小部件,用于表单视图。它有一些额外的代码,允许编辑标签的颜色。

  • 支持的字段类型: many2many

kanban.many2many_tags(KanbanFieldMany2ManyTags)

为看板视图专门定制的many2many_tags小部件。

  • 支持的字段类型: many2many

many2many_checkboxes(多对多复选框字段)

该字段显示一个复选框列表,允许用户选择其中的一部分选项。请注意,显示的值数量限制为100个。这个限制是不可定制的。它只是允许处理极端情况,当该小部件错误地设置在一个具有巨大comodel的字段上时。在这些情况下,列表视图更为适当,因为它允许分页和过滤。

  • 支持的字段类型: many2many

一对多(FieldOne2Many)

一对多字段的默认小部件。

通常在子列表视图或子看板视图中显示数据。

  • 支持的字段类型: one2many

选项:

  • create: 域,用于确定是否可以创建相关记录(默认值:True)。

  • delete:域,确定是否可以删除相关记录(默认值:True)。

<field name="turtles" options="{'create': [['some_other_field', '>', 24]]}"/>
  • create_text:用于自定义“添加”标签/文本的字符串。

<field name="turtles" options="{\'create_text\': \'Add turtle\'}"/>
状态栏(FieldStatus)

这是一个非常专业的小部件,用于表单视图。它是许多表单顶部的条形,表示流程,并允许选择特定状态。

  • 支持的字段类型: selection, many2one

引用 (FieldReference)

FieldReference 是一个选择器(用于选择模型)和 FieldMany2One(用于选择值)的组合。它允许在任意模型上选择记录。

  • 支持的字段类型: char, reference

    选项:

    • model_field: 字段名,类型为 FieldMany2One(‘ir.model’),包含可选择的记录的模型。

      当设置了此选项时,FieldReference 的选择部分不会被显示。

小部件

week_days (星期几)

该字段显示一个星期中每天的复选框列表,每天一个复选框,允许用户选择其中的一个子集。

<widget name="week_days"/>

客户端操作

客户端操作的概念是一个自定义的小部件,它被集成到Web客户端界面中,就像一个 act_window_action。这在你需要一个与现有视图或特定模型没有紧密联系的组件时非常有用。例如,讨论应用实际上是一个客户端操作。

客户端操作是一个术语,其含义因上下文而异:

  • 从服务器的角度来看,它是一个模型 ir_action 的记录,具有一个类型为 char 的字段 tag

  • 从 Web 客户端的角度来看,它是一个小部件,继承自 AbstractAction 类,并应该在操作注册表中使用相应的键(从字段 char)进行注册。

每当菜单项与客户端操作相关联时,打开它将简单地从服务器获取操作定义,然后查找其操作注册表以获取适当键处的小部件定义,最后,它将实例化并将小部件附加到DOM中的适当位置。

添加客户端操作

客户端操作是控制菜单栏下方屏幕部分的小部件。如果需要,它可以有控制面板。定义客户端操作可以分为两步:实现新小部件并在操作注册表中注册该小部件。

实现新的客户端操作。

通过创建一个小部件来完成:

var AbstractAction = require('web.AbstractAction');

var ClientAction = AbstractAction.extend({
    hasControlPanel: true,
    ...
});
注册客户端操作:

通常情况下,我们需要让Web客户端了解客户端操作与实际类之间的映射关系:

var core = require('web.core');

core.action_registry.add('my-custom-action', ClientAction);

然后,在Web客户端中使用客户端操作,我们需要创建一个客户端操作记录(一个 ir.actions.client 模型的记录),并设置正确的 tag 属性:

<record id="my_client_action" model="ir.actions.client">
    <field name="name">Some Name</field>
    <field name="tag">my-custom-action</field>
</record>

使用控制面板

默认情况下,客户端操作不显示控制面板。要显示控制面板,需要执行几个步骤。

  • hasControlPanel 设置为 true。在小部件代码中:

    var MyClientAction = AbstractAction.extend({
        hasControlPanel: true,
        loadControlPanel: true, // default: false
        ...
    });
    

    警告

    loadControlPanel 设置为 true 时,客户端操作将自动获取搜索视图或控制面板视图的内容。在这种情况下,应该像这样指定模型名称:

    init: function (parent, action, options) {
        ...
        this.controlPanelParams.modelName = 'model.name';
        ...
    }
    
  • 每当需要更新控制面板时,请调用方法 updateControlPanel 。例如:

    var SomeClientAction = Widget.extend({
        hasControlPanel: true,
        ...
        start: function () {
            this._renderButtons();
            this._update_control_panel();
            ...
        },
        do_show: function () {
             ...
             this._update_control_panel();
        },
        _renderButtons: function () {
            this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
            this.$buttons.on('click', ...);
        },
        _update_control_panel: function () {
            this.updateControlPanel({
                cp_content: {
                   $buttons: this.$buttons,
                },
            });
        }
    

The updateControlPanel is the main method to customize the content in the control panel. For more information, look into the control_panel_renderer.js file.