odoo13学习---16 Web客户端开发
2020-12-24 22:28
标签:介绍 最小数 get png 意义 try table dashboard 自己的 介绍 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,像这样: 怎么做呢? 我们将添加一个包含小部件逻辑的JavaScript文件和一个SCSS文件来进行一些样式化。然后,我们将在books表单上添加一个整数字段来使用我们的新小部件。按照以下步骤添加一个新的字段小部件: 1. 添加一个静态/src/js/field_widget.js文件。这里使用的语法,请参考第15章,CMS网站开发中的扩展CSS和JavaScript。 2. 在field_widget.js文件中通过扩展AbstractField创建小部件: 3.为扩展的小部件设置CSS类、根元素标签和支持的字段类型: 4. 为扩展的小部件捕获一些JavaScript事件: 5. 重写init来做一些初始化: 6. 覆盖_renderEdit和_renderReadonly来设置DOM元素: 7. 定义我们前面提到的处理程序: 8. 不要忘记注册你的小部件: 9. 使它可用于其他附加组件: 10. 在静态/src/ SCSS /field_widget.scss中添加一些SCSS: 11. 在views/templates.xml的后端资源中注册两个文件: 12. 最后,在library_book模型中添加颜色整数字段: 13. 在书的表单视图中添加颜色字段,并添加widget="int_color": 更新模块以应用更改。更新完成后,打开图书的表单视图,你会看到颜色选择器,如下图所示:
它是如何工作的… 为了让您理解我们的示例,让我们通过查看widget的组件来回顾一下它的生命周期: init():这是小部件构造函数。它用于初始化目的。初始化小部件时,首先调用此方法。 willStart():这个方法在小部件初始化和添加到DOM中的过程中被调用。它用于将异步数据初始化到小部件中。它还应该返回一个延迟对象,该对象可以简单地从super()调用中获得。我们将在后面的内容中使用这个方法。 start():该方法在小部件完成呈现后调用,但还没有添加到DOM中。它对于post呈现作业非常有用,应该返回一个延迟对象。您可以访问this.$el中呈现的元素 destroy():在销毁小部件时调用此方法。它主要用于基本的清理操作,如事件解绑定。 Widget的基本基类是Widget(由web.Widget定义)。如果你想深入研究它,你可以在/addons/web/static/src/js/core/widget.js学习。 在步骤1中,我们导入了AbstractField和fieldRegistry。 在第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函数来实现这个方面的定制。 正如用JavaScript编程创建HTML代码是一个坏习惯一样,您应该只在客户端JavaScript代码中创建最小数量的DOM元素。幸运的是,客户端也有模板引擎可用,更幸运的是,客户端模板引擎具有与服务器端模板相同的语法。 准备 对于这个内容,我们将使用上一个内容中的my_library模块。我们将通过将DOM元素创建移动到QWeb来使其更加模块化。 怎么做呢? 我们需要在清单中添加QWeb定义,并更改JavaScript代码以便使用它。按照以下步骤开始: 1. 导入web.core并将qweb引用提取到一个变量中,如下代码所示: 2. 将_renderEdit函数改为简单地呈现元素(继承自widget): 3.将模板文件添加到static/src/xml/qweb_template.xml: 4.在你的manifest中注册QWeb文件: 现在,对于其他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-name属性,那么我们将对原始模板进行复制,并且不动那个模板。t-operation其他可能的属性值:append, before, after, inner, 和 replace,导致t元素的内容是通过添加附加到匹配的元素的内容,把匹配的元素之前或之后通过之前或之后,通过内部替换匹配的元素的内容,通过替换或取代完整的元素。还有t-operation= ‘ attributes‘,它允许您在匹配的元素上设置属性,遵循与服务器端QWeb相同的规则。 另一个不同之处在于,客户端QWeb中的名称不是由模块名称命名的,因此您必须为模板选择名称,而这些名称可能是您安装的所有外接程序中唯一的,这就是开发人员倾向于选择较长的名称的原因。 另请参阅 如欲了解更多有关Qweb模版的资料,请参阅以下要点: 与Odoo的其他部分相比,客户端QWeb引擎的错误消息和处理不太方便。一个小错误通常意味着什么都没有发生,初学者很难从那里继续下去。 幸运的是,有一些客户端QWeb模板的调试语句将在本章后面的调试客户端代码内容中描述。 您的小部件迟早需要从服务器查找一些数据。在这个内容中,我们将在颜色药片上添加一个工具提示。当用户将光标悬停在color pill元素上时,工具提示将显示与该颜色相关的书籍数量。 我们将对服务器进行RPC调用,以获取与特定颜色相关联的数据的图书计数。 准备 对于这个内容,我们将使用上一个内容中的my_library模块。 怎么做呢? 执行以下步骤,对服务器进行RPC调用,并在工具提示中显示结果: 1. 在RPC调用中添加willStart方法并设置colorGroupData: 2. 更新_renderEdit并设置药丸的引导工具提示: 3.更新FieldColorPills药丸模板并添加工具提示数据:在本章中,我们将介绍以下内容:
创建自定义小部件
...
‘depends‘: [‘base‘, ‘web‘],
...
odoo.define(‘my_field_widget‘, function (require) {
"use strict";
var AbstractField = require(‘web.AbstractField‘);
var fieldRegistry = require(‘web.field_registry‘);
......var colorField = AbstractField.extend({
......var colorField = AbstractField.extend({
className: ‘o_int_colorpicker‘,
tagName: ‘span‘,
supportedFieldTypes: [‘integer‘],
......var colorField = AbstractField.extend({
className: ‘o_int_colorpicker‘,
tagName: ‘span‘,
supportedFieldTypes: [‘integer‘],
events: {
‘click .o_color_pill‘: ‘clickPill‘,
},
......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);
},
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,
}));
},
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());
}
});
fieldRegistry.add(‘int_color‘, colorField);
return {
colorField: colorField,
};
});// closing ‘my_field_widget‘ namespace
.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;
}
}
}
}
}
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>
color = fields.Integer()
...
group>
field name="date_release"/>
field name="color" widget="int_color"/>
group>
...
使用客户端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;
...
_renderEdit: function () {
this.$el.empty();
var pills = qweb.render(‘FieldColorPills‘, {widget:this});
this.$el.append(pills);
},
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> ‘qweb‘: [
‘static/src/xml/qweb_template.xml‘
]
t t-extend="FieldColorPills">
t t-jquery="span" t-operation="prepend">
i class="fa fa-user" />
t>
t>
对服务器进行RPC调用
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);
},
_renderEdit: function () {
this.$el.empty();
var pills = qweb.render(‘FieldColorPills‘, {widget: this});
this.$el.append(pills);
this.$el.find(‘[data-toggle="tooltip"]‘).tooltip();
},
xml version="1.0" encoding="UTF-8"?>
templates>
t t-name="FieldColorPills">
t t-foreach="widget.tot
下一篇:index.jsp
文章标题:odoo13学习---16 Web客户端开发
文章链接:http://soscw.com/index.php/essay/38016.html