odoo13学习---16 Web客户端开发

2020-12-24 22:28

阅读:829

标签:介绍   最小数   get   png   意义   try   table   dashboard   自己的   

在本章中,我们将介绍以下内容:

  • 创建自定义小部件
  • 使用客户端QWeb模板
  • 对服务器进行RPC调用
  • 创建一个新视图
  • 调试客户端代码
  • 通过巡回演出提高新人入职能力
  • 手机应用程序的JavaScript

 

介绍

  Odoo的网络客户端,或后端,是员工花大部分时间的地方。在第10章后端视图中,你看到了如何使用后端提供的现有可能性。在这里,我们将看看如何扩展和定制这些可能性。web模块包含所有与Odoo用户界面相关的内容。

  本章中的所有代码都将依赖于web模块。如你所知,Odoo有两个不同的版本(企业版和社区版)。Community使用web模块作为用户界面,而Enterprise版本使用Community web模块的扩展版本,即web_enterprise模块。

  企业版在社区web上提供了一些额外的特性,比如移动兼容性、可搜索菜单、材料设计等等。我们将在这里开发社区版。不用担心——社区开发的模块在企业版中工作得很好,因为web_enterprise内部依赖于社区web模块,只是添加了一些特性。

  在本章中,您将学习如何创建新的字段小部件来从用户那里获取输入。我们还将从头创建一个新视图。读完这一章后,你将能够在Odoo后端创建自己的UI元素。

  注意:Odoo的用户界面严重依赖于JavaScript。在本章中,我们将假设你有JavaScript, jQuery,Underscore.js和SCSS。

创建自定义小部件

  正如你在第10章后端视图中看到的,我们可以使用小部件以不同的格式显示特定的数据。例如,我们使用widget=‘image‘将二进制字段显示为图像。为了演示如何创建自己的小部件,我们将编写一个小部件,让用户选择一个整数字段,但我们将以不同的方式显示它。我们将显示一个颜色选择器,而不是一个输入框,这样我们就可以选择一个色号。

准备

  对于这个内容,我们将使用第4章中的my_library模块,创建Odoo附加模块。在这个内容中,我们将添加一个新的需要依赖于web模块的字段小部件。确保你已经在清单文件中添加了依赖onweb,像这样:

...
depends: [base, web],
...

怎么做呢?

  我们将添加一个包含小部件逻辑的JavaScript文件和一个SCSS文件来进行一些样式化。然后,我们将在books表单上添加一个整数字段来使用我们的新小部件。按照以下步骤添加一个新的字段小部件: 

  1. 添加一个静态/src/js/field_widget.js文件。这里使用的语法,请参考第15章,CMS网站开发中的扩展CSS和JavaScript。

odoo.define(‘my_field_widget‘, function (require) {
  "use strict";
  var AbstractField = require(‘web.AbstractField‘);
  var fieldRegistry = require(‘web.field_registry‘);
......

  2. 在field_widget.js文件中通过扩展AbstractField创建小部件:

var colorField = AbstractField.extend({
......

  3.为扩展的小部件设置CSS类、根元素标签和支持的字段类型:

var colorField = AbstractField.extend({
    className: ‘o_int_colorpicker‘,
    tagName: ‘span‘,
    supportedFieldTypes: [‘integer‘],
......

  4. 为扩展的小部件捕获一些JavaScript事件:

var colorField = AbstractField.extend({
    className: ‘o_int_colorpicker‘,
    tagName: ‘span‘,
    supportedFieldTypes: [‘integer‘],
    events: {
        ‘click .o_color_pill‘: ‘clickPill‘,
    },
......

  5. 重写init来做一些初始化:

var colorField = AbstractField.extend({
    className: ‘o_int_colorpicker‘,
    tagName: ‘span‘,
    supportedFieldTypes: [‘integer‘],
    events: {
        ‘click .o_color_pill‘: ‘clickPill‘,
    },
    init: function () {
        this.totalColors = 10;
        this._super.apply(this, arguments);
    },

  6. 覆盖_renderEdit和_renderReadonly来设置DOM元素:

var colorField = AbstractField.extend({
    className: ‘o_int_colorpicker‘,
    tagName: ‘span‘,
    supportedFieldTypes: [‘integer‘],
    events: {
        ‘click .o_color_pill‘: ‘clickPill‘,
    },
    init: function () {
        this.totalColors = 10;
        this._super.apply(this, arguments);
    },
    _renderEdit: function () {
        this.$el.empty();
        for (var i = 0; i ‘, {
                ‘class‘: className,
                ‘data-val‘: i,
            }));
        }
    },
    _renderReadonly: function () {
        var className = "o_color_pill active readonly o_color_" + this.value;
        this.$el.append($(‘‘, {
            ‘class‘: className,
        }));
    },

  7. 定义我们前面提到的处理程序:

var colorField = AbstractField.extend({
    className: ‘o_int_colorpicker‘,
    tagName: ‘span‘,
    supportedFieldTypes: [‘integer‘],
    events: {
        ‘click .o_color_pill‘: ‘clickPill‘,
    },
    init: function () {
        this.totalColors = 10;
        this._super.apply(this, arguments);
    },
    _renderEdit: function () {
        this.$el.empty();
        for (var i = 0; i this.totalColors; i++ ) {
            var className = "o_color_pill o_color_" + i;
            if (this.value === i ) {
                className += ‘ active‘;
            }
            this.$el.append($(‘, {
                ‘class‘: className,
                ‘data-val‘: i,
            }));
        }
    },
    _renderReadonly: function () {
        var className = "o_color_pill active readonly o_color_" + this.value;
        this.$el.append($(‘, {
            ‘class‘: className,
        }));
    },
    clickPill: function (ev) {
        var $target = $(ev.currentTarget);
        var data = $target.data();
        this._setValue(data.val.toString());
    }

});

  8. 不要忘记注册你的小部件:

fieldRegistry.add(‘int_color‘, colorField);

  9. 使它可用于其他附加组件:

  return {
      colorField: colorField,
  };
});// closing ‘my_field_widget‘ namespace

  10. 在静态/src/ SCSS /field_widget.scss中添加一些SCSS:

.o_int_colorpicker {
    .o_color_pill {
        display: inline-block;
        height: 25px;
        width: 25px;
        margin: 4px;
        border-radius: 25px;
        position: relative;
        @for $size from 1 through length($o-colors) {
            &.o_color_#{$size - 1} {
                background-color: nth($o-colors, $size);
                &:not(.readonly):hover {
                    transform: scale(1.2);
                    transition: 0.3s;
                    cursor: pointer;
                }
                &.active:after{
                    content: "\f00c";
                    display: inline-block;
                    font: normal normal normal 14px/1 FontAwesome;
                    font-size: inherit;
                    color: #fff;
                    position: absolute;
                    padding: 4px;
                    font-size: 16px;
                }
            }
        }
    }
}

  11. 在views/templates.xml的后端资源中注册两个文件:

xml version="1.0" encoding="UTF-8"?>
odoo>

    template id="assets_end" inherit_id="web.assets_backend">
        xpath expr="." position="inside">
            script src="/my_library/static/src/js/field_widget.js" type="text/javascript" />
            link href="/my_library/static/src/scss/field_widget.scss" rel="stylesheet" type="text/scss" />
        /xpath>
    /template>

/odoo>

  12. 最后,在library_book模型中添加颜色整数字段:

color = fields.Integer()

  13. 在书的表单视图中添加颜色字段,并添加widget="int_color":

...
group>
  field name="date_release"/>
  field name="color" widget="int_color"/>
group>
...

  更新模块以应用更改。更新完成后,打开图书的表单视图,你会看到颜色选择器,如下图所示:

技术图片

它是如何工作的…

  为了让您理解我们的示例,让我们通过查看widget的组件来回顾一下它的生命周期:

  init():这是小部件构造函数。它用于初始化目的。初始化小部件时,首先调用此方法。

  willStart():这个方法在小部件初始化和添加到DOM中的过程中被调用。它用于将异步数据初始化到小部件中。它还应该返回一个延迟对象,该对象可以简单地从super()调用中获得。我们将在后面的内容中使用这个方法。

  start():该方法在小部件完成呈现后调用,但还没有添加到DOM中。它对于post呈现作业非常有用,应该返回一个延迟对象。您可以访问this.$el中呈现的元素

  destroy():在销毁小部件时调用此方法。它主要用于基本的清理操作,如事件解绑定。

  Widget的基本基类是Widget(由web.Widget定义)。如果你想深入研究它,你可以在/addons/web/static/src/js/core/widget.js学习。

  在步骤1中,我们导入了AbstractFieldfieldRegistry

  在第2步中,我们通过扩展AbstractField创建了colorField。通过这样,我们的colorField将从AbstractField获得所有属性和方法。

  在步骤3中,我们添加了三个属性—classname用于为小部件的根元素定义类,tagName用于根元素类型,而supportedFieldTypes用于决定这个widget支持哪种类型的字段。在本例中,我们希望为integer类型字段创建一个widget。

  在步骤4中,我们映射了小部件的事件。通常,key是event名称和可选的CSS选择器的组合event和CSS选择器之间用空格隔开,值将是widget方法的名称。因此,当执行event时,将自动调用指定的方法。在此内容中,当用户单击颜色时,我们希望在字段中设置integer值。为了管理click事件,我们在events键中添加了一个CSS选择器和方法名。

  在第5步中,我们覆盖了init方法并设置了this.totalColors属性的值。我们将使用这个变量来决定颜色丸的数量。我们想要显示10个颜色丸,所以我们将值设置为10。

  在第6步中,我们添加了两个方法-_renderEdit_renderReadonly顾名思义,当widget处于编辑模式时调用_renderEdit,而当widget处于只读模式时调用_renderReadonly在edit方法中,我们添加了几个标记,每个标记表示widget中的一个单独的颜色。单击标记后,我们将在字段中设置值。我们把它们加到this.$el中。这里,$el是widget的根元素,它将被添加到表单视图中在只读模式下,我们只想显示活动的颜色,因此我们通过_renderReadonly()方法添加了单个pill。现在,我们已经以硬编码的方式添加了pill,但是在下一个内容中,我们将使用一个JavaScript Qweb模板来呈现pill。注意,在编辑方法中,我们使用了totalColors属性,该属性是由init()方法设置的

  在第7步中,我们添加了clickPill处理程序方法来管理pill点击。为了设置字段值,我们使用了_setValue方法。此方法是从AbstractField类添加的。当你设置字段值时,Odoo框架会重新运行widget并再次调用_renderEdit方法,这样你就可以用更新后的值呈现widget。

  在步骤8中,在我们定义了新的widget之后,向表单widget注册表注册它是至关重要的,它位于web.field_registry中。注意,所有视图类型都会查看此注册表,因此如果您希望以另一种方式在列表视图中显示字段,您还可以在这里添加widget并在视图定义的字段上设置widget属性。

   最后,导出我们的widget类,以便其他add-ons可以扩展它或从它继承它。然后,我们在library.book模型中添加了一个名为color的新整数字段。我们还使用widget="int_color"属性在表单视图中添加了相同的字段。这将在表单中显示我们的widget,而不是默认整数widget。

 

有更多的…

 

  web.mixins名称空间定义了两个非常有用的mixin类,在开发表单widget时,您不应该错过这些类。您已经在本内容中使用了这些mixins。AbstractField是通过继承Widget类创建的,Widget类继承两个mixins。第一个是EventDispatcherMixin,它提供了一个用于附加和触发事件处理程序的简单接口。第二个是ServicesMixin,它为RPC调用和操作提供函数

  当您想要重写一个方法时,总是要研究基类,看看函数应该返回什么。bug的一个常见原因是忘记返回超级用户的deferred对象这会导致异步操作出现问题

  Widgets负责验证。使用isValid函数来实现这个方面的定制。


 

 

使用客户端QWeb模板

 

  正如用JavaScript编程创建HTML代码是一个坏习惯一样,您应该只在客户端JavaScript代码中创建最小数量的DOM元素。幸运的是,客户端也有模板引擎可用,更幸运的是,客户端模板引擎具有与服务器端模板相同的语法。

准备

  对于这个内容,我们将使用上一个内容中的my_library模块。我们将通过将DOM元素创建移动到QWeb来使其更加模块化。

怎么做呢?

  我们需要在清单中添加QWeb定义,并更改JavaScript代码以便使用它。按照以下步骤开始:

  1. 导入web.core并将qweb引用提取到一个变量中,如下代码所示:

odoo.define(‘my_field_widget‘, function (require) {
"use strict";
var AbstractField = require(‘web.AbstractField‘);
var fieldRegistry = require(‘web.field_registry‘);
var core = require(‘web.core‘);
var qweb = core.qweb;
...

  2. 将_renderEdit函数改为简单地呈现元素(继承自widget):

_renderEdit: function () {
  this.$el.empty();
  var pills = qweb.render(‘FieldColorPills‘, {widget:this});
  this.$el.append(pills);
},

  3.将模板文件添加到static/src/xml/qweb_template.xml:

xml version="1.0" encoding="UTF-8"?>
templates>
  t t-name="FieldColorPills">
    t t-foreach="widget.totalColors" t-as=‘pill_no‘>
      span t-attf-class="o_color_pill o_color_#{pill_no} #{widget.value === pill_no and ‘active‘ or ‘‘}" 
t-att-data-val="pill_no"/>     t>   t> templates>

  4.在你的manifest中注册QWeb文件:

    ‘qweb‘: [
        ‘static/src/xml/qweb_template.xml‘
    ]

  现在,对于其他add-ons,更改widget使用的HTML代码要容易得多,因为它们可以简单地用通常的QWeb模式覆盖它。

 

它是如何工作的…

 

  在第15章“CMS网站开发”中,已经有了关于创建或修改模板的QWeb基础知识的全面讨论,我们将在这里重点讨论它的不同之处。首先,您需要认识到我们处理的是JavaScript QWeb实现,而不是服务器端的Python实现。这意味着你不能访问浏览记录或环境;您只能访问从qweb.render函数传递的参数。

  在本例中,我们通过widget key传递了当前对象。这意味着您应该在小部件的JavaScript代码中拥有所有的智能,并且让您的模板只访问属性,或者可能是函数。假设我们可以访问widget上可用的所有属性,我们可以通过检查totalColors属性来检查模板中的值。

  由于客户端QWeb与QWeb views没有任何关系,所以有一种不同的机制可以让web客户机知道这些模板——通过与add-on的清单相关的文件名列表中的QWeb密钥来添加它们。

有更多的…

  在这里努力使用QWeb的原因是可扩展性,这是客户端和服务器端QWeb之间的第二大区别。在客户端,不能使用XPath表达式;您需要使用jQuery选择器和操作例如,如果我们想在widget中添加另一个模块的用户图标,我们将使用以下代码在每个pill中添加一个图标:

t t-extend="FieldColorPills">
  t t-jquery="span" t-operation="prepend">
    i class="fa fa-user" />
  t>
t>

  如果我们在这里也提供了一个t-name属性,那么我们将对原始模板进行复制,并且不动那个模板。t-operation其他可能的属性值:append, before, after, inner, 和 replace,导致t元素的内容是通过添加附加到匹配的元素的内容,把匹配的元素之前或之后通过之前或之后,通过内部替换匹配的元素的内容,通过替换或取代完整的元素。还有t-operation= ‘ attributes‘,它允许您在匹配的元素上设置属性,遵循与服务器端QWeb相同的规则。 

  另一个不同之处在于,客户端QWeb中的名称不是由模块名称命名的,因此您必须为模板选择名称,而这些名称可能是您安装的所有外接程序中唯一的,这就是开发人员倾向于选择较长的名称的原因。

另请参阅

  如欲了解更多有关Qweb模版的资料,请参阅以下要点:

  与Odoo的其他部分相比,客户端QWeb引擎的错误消息和处理不太方便。一个小错误通常意味着什么都没有发生,初学者很难从那里继续下去。

  幸运的是,有一些客户端QWeb模板的调试语句将在本章后面的调试客户端代码内容中描述。


 

 对服务器进行RPC调用

  您的小部件迟早需要从服务器查找一些数据。在这个内容中,我们将在颜色药片上添加一个工具提示。当用户将光标悬停在color pill元素上时,工具提示将显示与该颜色相关的书籍数量。

我们将对服务器进行RPC调用,以获取与特定颜色相关联的数据的图书计数。

准备

  对于这个内容,我们将使用上一个内容中的my_library模块。

怎么做呢?

  执行以下步骤,对服务器进行RPC调用,并在工具提示中显示结果:

  1. 在RPC调用中添加willStart方法并设置colorGroupData:  

 willStart: function () {
        var self = this;
        this.colorGroupData = {};
        var colorDataDef = this._rpc({
            model: this.model,
            method: ‘read_group‘,
            domain: [],
            fields: [‘color‘],
            groupBy: [‘color‘],
        }).then(function (result) {
            _.each(result, function (r) {
                self.colorGroupData[r.color] = r.color_count;
            });
        });
        return $.when(this._super.apply(this, arguments), colorDataDef);
    },

  2. 更新_renderEdit并设置药丸的引导工具提示:

 _renderEdit: function () {
        this.$el.empty();
        var pills = qweb.render(‘FieldColorPills‘, {widget: this});
        this.$el.append(pills);
        this.$el.find(‘[data-toggle="tooltip"]‘).tooltip();
    },

  3.更新FieldColorPills药丸模板并添加工具提示数据:

xml version="1.0" encoding="UTF-8"?>
templates>
    t t-name="FieldColorPills">
        t t-foreach="widget.tot


评论


亲,登录后才可以留言!