本章中的所有代码都将依赖于web模块。如你所知,Odoo有两个不同的版本(企业版和社区版)。Community使用web模块作为用户界面,而Enterprise版本使用Communityweb模块的扩展版本,即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){"usestrict";varAbstractField=require('web.AbstractField');varfieldRegistry=require('web.field_registry');......2.在field_widget.js文件中通过扩展AbstractField创建小部件:
varcolorField=AbstractField.extend({......3.为扩展的小部件设置CSS类、根元素标签和支持的字段类型:
varcolorField=AbstractField.extend({className:'o_int_colorpicker',tagName:'span',supportedFieldTypes:['integer'],......4.为扩展的小部件捕获一些JavaScript事件:
varcolorField=AbstractField.extend({className:'o_int_colorpicker',tagName:'span',supportedFieldTypes:['integer'],events:{'click.o_color_pill':'clickPill',},......5.重写init来做一些初始化:
varcolorField=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元素:
varcolorField=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(vari=0;i
varcolorField=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(vari=0;i
fieldRegistry.add('int_color',colorField);9.使它可用于其他附加组件:
return{colorField:colorField,};});//closing'my_field_widget'namespace10.在静态/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$sizefrom1throughlength($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:normalnormalnormal14px/1FontAwesome;font-size:inherit;color:#fff;position:absolute;padding:4px;font-size:16px;}}}}}11.在views/templates.xml的后端资源中注册两个文件:
color=fields.Integer()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,但是在下一个内容中,我们将使用一个JavaScriptQweb模板来呈现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引用提取到一个变量中,如下代码所示:
odoo.define('my_field_widget',function(require){"usestrict";varAbstractField=require('web.AbstractField');varfieldRegistry=require('web.field_registry');varcore=require('web.core');varqweb=core.qweb;...2.将_renderEdit函数改为简单地呈现元素(继承自widget):
_renderEdit:function(){this.$el.empty();varpills=qweb.render('FieldColorPills',{widget:this});this.$el.append(pills);},3.将模板文件添加到static/src/xml/qweb_template.xml:
'qweb':['static/src/xml/qweb_template.xml']现在,对于其他add-ons,更改widget使用的HTML代码要容易得多,因为它们可以简单地用通常的QWeb模式覆盖它。
在第15章“CMS网站开发”中,已经有了关于创建或修改模板的QWeb基础知识的全面讨论,我们将在这里重点讨论它的不同之处。首先,您需要认识到我们处理的是JavaScriptQWeb实现,而不是服务器端的Python实现。这意味着你不能访问浏览记录或环境;您只能访问从qweb.render函数传递的参数。
在本例中,我们通过widgetkey传递了当前对象。这意味着您应该在小部件的JavaScript代码中拥有所有的智能,并且让您的模板只访问属性,或者可能是函数。假设我们可以访问widget上可用的所有属性,我们可以通过检查totalColors属性来检查模板中的值。
在这里努力使用QWeb的原因是可扩展性,这是客户端和服务器端QWeb之间的第二大区别。在客户端,不能使用XPath表达式;您需要使用jQuery选择器和操作。例如,如果我们想在widget中添加另一个模块的用户图标,我们将使用以下代码在每个pill中添加一个图标:
另一个不同之处在于,客户端QWeb中的名称不是由模块名称命名的,因此您必须为模板选择名称,而这些名称可能是您安装的所有外接程序中唯一的,这就是开发人员倾向于选择较长的名称的原因。
另请参阅
如欲了解更多有关Qweb模版的资料,请参阅以下要点:
与Odoo的其他部分相比,客户端QWeb引擎的错误消息和处理不太方便。一个小错误通常意味着什么都没有发生,初学者很难从那里继续下去。
幸运的是,有一些客户端QWeb模板的调试语句将在本章后面的调试客户端代码内容中描述。
对于这个内容,我们将使用上一个内容中的my_library模块。
执行以下步骤,对服务器进行RPC调用,并在工具提示中显示结果:
1.在RPC调用中添加willStart方法并设置colorGroupData:
willStart:function(){varself=this;this.colorGroupData={};varcolorDataDef=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();varpills=qweb.render('FieldColorPills',{widget:this});this.$el.append(pills);this.$el.find('[data-toggle="tooltip"]').tooltip();},3.更新FieldColorPills药丸模板并添加工具提示数据:
willStart函数在呈现之前被调用,更重要的是,它返回一个延迟对象,必须在呈现开始之前解析该对象。因此,在像我们这样的情况下,我们需要在呈现发生之前运行一个异步操作,这是做这件事的正确函数。
在处理数据访问时,我们依赖于ServicesMixin类提供的_rpc函数,正如前面解释的那样。这个函数允许您调用模型上的任何公共函数,比如search、read、write、或在本例中是read_group。
在步骤1中,我们对当前模型(在我们的例子中是library.book)进行了一个RPC调用并调用了read_group方法。我们根据颜色字段对数据进行分组,因此RPC调用将返回按颜色分组的图书数据,并在color_count键中添加一个聚合。我们还在colorGroupData中映射了color_count和颜色索引,以便可以在QWeb模板中使用它。在函数的最后一行中,我们解析了将从super开始,并使用$.when调用RPC。因此,渲染只发生在值被获取之后,并且在任何异步动作super已经完成之后。
第二步没什么特别的。我们刚刚初始化了引导工具提示。
在第3步中,我们使用colorGroupData设置显示工具提示所需的属性。在willStart方法中,我们通过this.colorGroupData分配了一个颜色映射,这样您就可以通过widget.colorGroupData在QWeb模板中访问它们。这是因为我们传递了小部件引用;这是qweb.render方法。
您可以在小部件中的任何位置使用_rpc。请注意,这是一个异步调用,您需要正确地管理一个延迟对象以获得所需的结果。
另一个有用的属性是nodeOptions,它包含通过