odoo13学习16Web客户端开发YongL

本章中的所有代码都将依赖于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',{'class':className,'data-val':i,}));}},_renderReadonly:function(){varclassName="o_color_pillactivereadonlyo_color_"+this.value;this.$el.append($('',{'class':className,}));},7.定义我们前面提到的处理程序:

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',{'class':className,'data-val':i,}));}},_renderReadonly:function(){varclassName="o_color_pillactivereadonlyo_color_"+this.value;this.$el.append($('',{'class':className,}));},clickPill:function(ev){var$target=$(ev.currentTarget);vardata=$target.data();this._setValue(data.val.toString());}});8.不要忘记注册你的小部件:

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:

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

'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中添加一个图标:

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

另一个不同之处在于,客户端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,它包含通过

视图定义中的options属性传递的数据。这已经是JSON解析,所以您可以像访问任何对象一样访问它。有关这些属性的更多信息,请深入abstract_field.js文件。

如果您在管理异步操作方面有问题,请参考以下文档:

正如您在第10章后端视图中看到的,有不同种类的视图,如表单、列表、看板等。在这个内容中,我们将创建一个全新的视图。这个视图将显示作者列表以及他们的书籍。

对于这个内容,我们将使用上一个内容中的my_library模块。请注意,视图是非常复杂的结构,每个现有视图都有不同的目的和实现。此内容的目的是让您了解MVC模式视图以及如何创建简单视图。在这个内容中,我们将创建一个名为m2m_group的视图,其目的是在组中显示记录。为了将记录划分为不同的组,视图将使用many2many字段数据。在my_library模块中,我们有author_ids字段。在这里,我们将根据作者对图书进行分组,并以卡片的形式显示它们。

此外,我们将在控制面板中添加一个新按钮。在这个按钮的帮助下,您将能够添加这本书的新记录。我们还将在作者卡片上添加一个按钮,以便我们可以将用户重定向到另一个视图。

按照以下步骤添加一个新的视图m2m_group:

1.在ir.ui.view中添加一个新的视图类型:(\models\ir_ui_view.py)

#-*-coding:utf-8-*-fromodooimportfields,modelsclassView(models.Model):_inherit='ir.ui.view'type=fields.Selection(selection_add=[('m2m_group','M2mGroup')])2.在ir.actions.act_window.view中添加一个新的视图模式:(\models\ir_action_act_window.py)

#-*-coding:utf-8-*-fromodooimportfields,modelsclassActWindowView(models.Model):_inherit='ir.actions.act_window.view'view_mode=fields.Selection(selection_add=[('m2m_group','M2mgroup')])3.通过继承基模型添加新方法(\models\model.py)。这个方法将从JavaScript模型中调用(详见步骤4):

#-*-coding:utf-8-*-fromcollectionsimportdefaultdictfromdatetimeimportdatetimefromdateutil.relativedeltaimportrelativedeltafromodooimportapi,fields,modelsclassBase(models.AbstractModel):_inherit='base'@api.modeldefget_m2m_group_data(self,domain,m2m_field):records=self.search(domain)result_dict={}forrecordinrecords:form2m_recordinrecord[m2m_field]:ifm2m_record.idnotinresult_dict:result_dict[m2m_record.id]={'name':m2m_record.display_name,'children':[],'model':m2m_record._name}result_dict[m2m_record.id]['children'].append({'name':record.display_name,'id':record.id,})returnresult_dict4.添加新文件/static/src/js/m2m_group_model.js,并添加以下内容:

odoo.define('m2m_group.Model',function(require){'usestrict';varAbstractModel=require('web.AbstractModel');varM2mGroupModel=AbstractModel.extend({__get:function(){returnthis.data;},__load:function(params){this.modelName=params.modelName;this.domain=params.domain;this.m2m_field=params.m2m_field;returnthis._fetchData();},__reload:function(handle,params){if('domain'inparams){this.domain=params.domain;}returnthis._fetchData();},_fetchData:function(){varself=this;returnthis._rpc({model:this.modelName,method:'get_m2m_group_data',kwargs:{domain:this.domain,m2m_field:this.m2m_field}}).then(function(result){self.data=result;});},});returnM2mGroupModel;});5.添加一个新文件/static/src/js/m2m_group_controller.js,添加如下内容:

odoo.define('m2m_group.Controller',function(require){'usestrict';varAbstractController=require('web.AbstractController');varcore=require('web.core');varqweb=core.qweb;varM2mGroupController=AbstractController.extend({custom_events:_.extend({},AbstractController.prototype.custom_events,{'btn_clicked':'_onBtnClicked',}),renderButtons:function($node){if($node){this.$buttons=$(qweb.render('ViewM2mGroup.buttons'));this.$buttons.appendTo($node);this.$buttons.on('click','button',this._onAddButtonClick.bind(this));}},_onBtnClicked:function(ev){this.do_action({type:'ir.actions.act_window',name:this.title,res_model:this.modelName,views:[[false,'list'],[false,'form']],domain:ev.data.domain,});},_onAddButtonClick:function(ev){this.do_action({type:'ir.actions.act_window',name:this.title,res_model:this.modelName,views:[[false,'form']],target:'new'});},});returnM2mGroupController;});6.添加一个新文件/static/src/js/m2m_group_renderer.js,并添加以下内容:

odoo.define('m2m_group.Renderer',function(require){'usestrict';varAbstractRenderer=require('web.AbstractRenderer');varcore=require('web.core');varqweb=core.qweb;varM2mGroupRenderer=AbstractRenderer.extend({events:_.extend({},AbstractRenderer.prototype.events,{'click.o_primay_button':'_onClickButton',}),_render:function(){varself=this;this.$el.empty();this.$el.append(qweb.render('ViewM2mGroup',{'groups':this.state,}));returnthis._super.apply(this,arguments);},_onClickButton:function(ev){ev.preventDefault();vartarget=$(ev.currentTarget);vargroup_id=target.data('group');varchildren_ids=_.map(this.state[group_id].children,function(group_id){returngroup_id.id;});this.trigger_up('btn_clicked',{'domain':[['id','in',children_ids]]});}});returnM2mGroupRenderer;});7.添加一个新文件/static/src/js/m2m_group_view.js,并添加以下内容:

odoo.define('m2m_group.View',function(require){'usestrict';varAbstractView=require('web.AbstractView');varview_registry=require('web.view_registry');varM2mGroupController=require('m2m_group.Controller');varM2mGroupModel=require('m2m_group.Model');varM2mGroupRenderer=require('m2m_group.Renderer');varM2mGroupView=AbstractView.extend({display_name:'Author',icon:'fa-id-card-o',config:_.extend({},AbstractView.prototype.config,{Model:M2mGroupModel,Controller:M2mGroupController,Renderer:M2mGroupRenderer,}),viewType:'m2m_group',searchMenuTypes:['filter','favorite'],accesskey:"a",init:function(viewInfo,params){this._super.apply(this,arguments);varattrs=this.arch.attrs;if(!attrs.m2m_field){thrownewError('M2mviewhasnotdefined"m2m_field"attribute.');}//ModelParametersthis.loadParams.m2m_field=attrs.m2m_field;},});view_registry.add('m2m_group',M2mGroupView);returnM2mGroupView;});8.将视图的QWeb模板添加到/static/src/xml/qweb_template.xml文件中:

LibraryBookAuthorlibrary.book11.在bookaction中添加m2m_group:

LibraryBookslibrary.bookformtree,m2m_group,form更新my_library模块以打开图书视图,然后从视图切换器打开我们刚刚添加的新视图。这看起来如下:

Odoo视图非常容易使用,非常灵活。然而,通常情况下,简单和灵活的东西背后的实现是复杂的。这与OdooJavaScript视图是相同的情况:它们很容易使用,但是实现起来很复杂。它由许多组件组成,比如model、renderer、controller、view、QWeb模板等等。在下一节中,我们已经为视图添加了所有必需的组件,并且还为library.book模型使用了一个新视图。如果您不想手动添加所有内容,可以从本书GitHub存储库中的示例文件中获取一个模块。

在步骤1和步骤2中,我们在ir.ui.view和ir.actions.act_window.view中注册了一个新的视图类型,称为m2m_group。

在步骤3中,我们在基础(base)中添加了get_m2m_group_data方法。在基础中添加此方法将使该方法在每个模型中可用。这个方法将通过从JavaScript视图的RPC调用来调用。视图将传递两个参数—domain和m2m_field。在域参数中,域的值将是由搜索视图域和操作域组合生成的域。m2m_field是我们要根据它对记录进行分组的字段名。这个字段将在视图定义中设置。

在接下来的几个步骤中,我们添加了形成视图所需的JavaScript文件。一个OdooJavaScript视图由view、model、renderer和controller组成。在Odoo代码库中,view这个词具有历史意义,所以model,view,controller(MVC)变成了model,renderer,controller(MRC)。通常,视图设置model、renderer和controller,并设置MVC层次结构,使其看起来类似于以下内容:

让我们看看Model、Renderer、Controller和View的角色。Model、Renderer、Controller和View的抽象版本拥有形成视图所需的所有基本内容。因此,在我们的示例中,我们已经通过继承创建了model、renderer、controller和view。

下面是一个关于创建视图的不同部分的深入解释:

Model:Model的作用是保存视图的状态。它向服务器发送一个获取数据的RPC请求,然后将数据传递给控制器controller和呈现器renderer。然后重写load_和reload方法。当视图被初始化时,它调用load()方法来获取数据,当搜索条件改变时,视图需要一个新的状态,然后调用reload()方法。在我们的例子中,我们有创建公共_fetchData()方法来对数据进行RPC调用。注意,我们使用了步骤3中添加的get_m2m_group_data方法。控制器将调用get()方法以获取模型的状态。

Controller:Controller的角色是管理Model和Renderer之间的协调。当Renderer中出现action时,它将该信息传递给controller并执行action相应的行动。有时,它也会调用模型中的一些方法。除此之外,它还管理控制面板中的按钮。在我们的示例中,我们添加了一个按钮来添加新记录。为此,我们必须重写AbstractController的renderButtons()方法。我们还注册了custom_events,以便当单击作者卡中的一个按钮时,renderer将触发事件到controller,使其执行action。

Renderer:Renderer的作用是管理视图的DOM元素。每个视图都可以以不同的方式呈现数据。在呈现器中,您可以在一个状态变量中获得模型的状态。它调用render()方法的呈现。在我们的示例中,我们呈现了ViewM2mGroupQWeb模板的当前状态,以显示我们的视图。我们还映射了JavaScript事件来执行用户操作。在这个配制中,我们为的卡片按钮绑定了click事件。单击authorcard按钮后,它将向控制器触发btn_clicked事件,并为该作者打开图书列表。

注意:events和custom_events是不同的。Events是正常的JavaScript事件,而custom_events事件是来自OdooJavaScript框架。自定义事件可以通过trigger_up方法调用。

View:Renderer的作用是获取构建视图所需的所有基本东西,比如一组fields、一个context、一个Viewarch和一些其他参数。之后,视图将初始化controlle_r、renderer和model三元组。它将在MVC层次结构中设置它们。通常,它设置model、view和controller中需要的参数。在我们的示例中,我们希望m2m_field名称在模型中获得适当的分组数据,因此我们在其中设置了模型参数。同样,可以使用this.controllerParams和this.rendererParams来设置contr_oller和render_er中的参数

在第8步中,我们为视图和控制面板按钮添加了一个QWeb模板。要了解更多关于QWeb模板的信息,请参考本章中的使用客户端QWeb模板内容。

Odoo视图有大量的方法用于不同的目的;我们在本节中讨论了最重要的一个。如果你想了解更多关于视图的信息,你可以通过/addons/web/static/src/js/views/目录进一步了解它们。这个目录还包括抽象model、controller、renderer和view的代码。

在步骤9中,我们在资源中添加了JavaScript文件。最后,在最后两个步骤中,我们为book.library模型添加了一个视图定义。在步骤10中,我们为视图使用了标记,并且我们还传递了m2m_field属性作为选项。它将被传递给模型以从服务器获取数据

如果不想引入新的视图类型,而只想修改视图中的一些内容,则可以在视图上使用js_class。例如,如果我们想要一个类似于我们创建的看板视图的视图,那么我们可以扩展它如下:

varCustomRenderer=KanbanRenderer.extend({...});varCustomRendererModel=KanbanModel.extend({...});varCustomRendererController=KanbanController.extend({...});varCustomDashboardView=KanbanView.extend({config:_.extend({},KanbanView.prototype.config,{Model:CustomDashboardModel,Renderer:CustomDashboardRenderer,Controller:CustomDashboardController,}),});varviewRegistry=require('web.view_registry');viewRegistry.add('my_custom_view',CustomDashboardView);然后我们可以使用js_class的看板视图(注意,服务器仍然认为这是一个看板视图):

.........调试客户端代码为了调试服务器端代码,本书包含了一个完整的章节,即第8章,调试。对于客户端部分,您将在本内容中入门。

此内容实际上并不依赖于特定的代码,但如果您希望能够准确地重现所发生的事情,请获取上一个内容的代码。

使调试客户端脚本变得困难的是web客户端严重依赖于jQuery的异步事件。由于断点会使执行暂停,因此在调试时很有可能不会发生由计时问题引起的错误。我们稍后会讨论一些策略:

1.对于客户端调试,您需要使用资产激活调试模式。如果你不知道如何激活调试模式的资产,激活Odoo开发工具的秘诀从第1章,安装Odoo开发环境。

2.在你感兴趣的JavaScript函数中,调用debugger:

debugger;

console.log(“I'minfunctionXcurrent”);

4.如果你想在模板渲染过程中调试,可以从QWeb调用调试器:

;

所有这些都依赖于浏览器提供适当的调试功能。虽然所有主流浏览器都能做到这一点,但出于演示目的,我们在这里只讨论Chromium。要使用调试工具,请点击右上方的菜单按钮,选择更多工具|开发工具:

当调试器打开时,你应该看到类似下面的截图:

在这里,您可以在单独的选项卡中访问许多不同的工具。在前面的屏幕截图中,当前活动的选项卡是JavaScript调试器,我们在第31行中通过单击行号设置断点。每次我们的小部件获取用户列表时,执行应该在这一行停止,调试器将允许您检查变量或更改它们的值。在右边的观察列表中,您还可以调用函数来测试它们的效果,而不必连续地保存脚本文件并重新加载页面。

当您打开开发人员工具时,我们前面描述的调试器语句将具有相同的行为。然后,执行将停止,浏览器将切换到Sources选项卡,打开有问题的文件,并突出显示调试器语句所在的行。

前面的两种日志记录可能会在Console选项卡中结束。无论如何,这是出现问题时您应该检查的第一个选项卡,因为如果一些JavaScript代码由于语法错误或类似的基本问题而根本没有加载,您将在那里看到一条错误消息,解释发生了什么。

使用Elements选项卡检查浏览器当前显示的页面的DOM表示。当您熟悉现有小部件生成的HTML代码时,这将是很有帮助的,而且它还允许您处理类和CSS属性。这是测试布局变化的一个很好的资源。

在开发一个大型应用程序之后,向最终用户解释软件流是至关重要的。Odoo框架包括一个内置的tourmanager。有了这个tourmanager,您可以指导最终用户学习特定的流程。在这个内容中,我们将创建一个旅行,这样我们就可以在图书馆中创建一本书。

我们将使用上一个菜谱中的my_library模块。漫游只显示在没有演示数据的数据库中,因此如果您使用的数据库有演示数据,请为内容创建一个没有演示数据的新数据库。

要向图书馆添加游览,请遵循以下步骤:

1.添加一个新的/static/src/js/my_library_tour.js文件,代码如下:

odoo.define('my_library.tour',function(require){"usestrict";varcore=require('web.core');vartour=require('web_tour.tour');var_t=core._t;tour.register('library_tour',{url:"/web",rainbowManMessage:_t("Congrats,youhavelistedabook."),sequence:5,},[tour.stepUtils.showAppsMenuItem(),{trigger:'.o_app[data-menu-xmlid="my_library.library_base_menu"]',content:_t('ManagebooksandauthorsinLibraryapp.'),position:'right'},{trigger:'.o_list_button_add',content:_t("Let'screatenewbook."),position:'bottom'},{trigger:'input[name="name"]',extra_trigger:'.o_form_editable',content:_t('Setthebooktitle'),position:'right',},{trigger:'.o_form_button_save',content:_t('Savethisbookrecord'),position:'bottom',}]);});2.在后端资产中添加tourJavaScript文件:

更新模块和打开Odoo后端。此时,您将看到旅程,如下面的截图所示:

tour管理器在web.tour_tour名称空间下可用。在第一步中,我们导入了web.tour_tour。然后,我们可以使用register()函数添加一个新的tour。我们使用library_tour名称注册了游览,并传递了该游览将在其上运行的URL。

下一个参数是这些游览步骤的列表。一个巡回步骤需要三个值。触发器用于选择应该在其上显示tour的元素。这是一个JavaScript选择器。我们使用菜单的外部XMLID,因为它在DOM中可用。

第一步,tour_STEPS.SHOW_APPS_MENU_ITEM是主菜单指南中预定义的步骤。下一个键是内容,当用户将鼠标悬停在tour拖放上时将显示该内容。我们使用_t()函数是因为我们希望翻译字符串,而position键用于决定tourdrop的位置。可能的值包括top、right、left或bottom。

导览改善了用户的入职体验,并管理了集成测试。当您在内部以测试模式运行Odoo时,它也会运行漫游,如果漫游没有完成,则会导致测试用例失败。

Odoov10介绍了Odoo移动应用。它提供了一些小的实用程序来执行移动操作,如震动手机,显示吐司信息,扫描二维码,等等。

我们将使用前一个库中的my_library模块。当我们从移动应用程序中更改颜色字段的值时,我们将向您展示吐司。

警告:Odoo手机应用程序只支持企业版,所以如果你没有企业版,你就不能测试它。

按照以下步骤在Odoo手机应用程序中显示toast:

1.在field_widget.js中导入web_mobile.rpc:

varmobile=require('web_mobile.core');

2.修改clickPill方法,当用户从移动设备改变颜色时显示吐司:

clickPill:function(ev){var$target=$(ev.currentTarget);vardata=$target.data();this._setValue(data.val.toString());if(mobile.methods.showToast){mobile.methods.showToast({'message':'Colorchanged'});}}

更新模块,在手机app中打开library.book模型的表单视图,改变颜色后会看到toast,如下图所示:

THE END
1.AndroidButterknife在library模块中的使用问题当项目中有多module时,在使用Butterknife的时候会发现在library模块中使用会出问题。当library模块中的页面通过butterknife找id的时候,就会报错。 如图,testbmodule模块plugin为library,也就是一个library的模块,然后看模块中的一个页面。 当使用butterknife插件找控件的时候就会报红线。 https://blog.csdn.net/lianwa88/article/details/80021623
2.在图书馆的翻译是:Inthelibrary中文翻译英文意思,翻译英语在图书馆 青云英语翻译 请在下面的文本框内输入文字,然后点击开始翻译按钮进行翻译,如果您看不到结果,请重新翻译! 翻译结果1翻译结果2翻译结果3翻译结果4翻译结果5 翻译结果1复制译文编辑译文朗读译文返回顶部 In the library 翻译结果2复制译文编辑译文朗读译文返回顶部http://eyu.zaixian-fanyi.com/fan_yi_8387880
3.在图书馆英语怎么写什么意思?在图书馆.. 翻译 原文(简体中文): 在图书馆更多:https://www.bmcx.com/ 翻译结果(英语)1: In the library更多:https://www.bmcx.com/ 翻译结果(英语)2: In the library更多:https://www.bmcx.com/ 翻译结果(英语)3: In the library更多:https://www.bmcx.com/https://fanyi.bmcx.com/q5300yah0ot37__fanyi/
4.在图书馆用英语怎么说图书馆的英文1、在图书馆用英语是in the library。 2、in表地点时,指在某一立体空间范围内。 3、library的基本意思是“图书馆,藏书室 ”,也可指某图书馆或某人的“藏书”,还可指“收藏的录音、唱片或影片等”。library有时也可用在其他名词前作定语。 4、例句:Designs for the new sports hall are on display in thehttps://edu.iask.sina.com.cn/jy/3cslyxAaSO7.html
5.→library在世界语。字典英语library在世界语: 1. biblioteko Nia lerneja biblioteko ja estas malgranda sed nova. Hodia? mi estis supozata studi en la biblioteko, sed mi veki?is ?irka? tagmezo. Kie estas la biblioteko? La biblioteko situas dekstre. https://zh.vocapp.com/dictionary/en/eo/library
6.jqueryjavascriptlibraryjQuery: The Write Less, Do More, JavaScript Libraryhttps://jquery.com/
7.FPGA之道(22)VHDL基本程序框架51CTO博客内容一,指元件声明,即component语法,VHDL与其他语言类似,并不是习惯一个“函数”就完成了所有的功能,所以,在绝大多数时候也是需要通过调用若干个独立的小规模的元件,把它们有机的组合到一起,进而实现一个规模比较复杂的设计。那么什么是元件,元件又保存在什么地方呢?还记得我们之前在library里面提到的work库么,我们利https://blog.51cto.com/u_15338162/3555912
8.studiolibrary在MAYA里加载不出来直接爆红求助问答studiolibrary在MAYA里加载不出来直接爆红 import studiolibrary studiolibrary.main() # UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal, at line 14, in "D:\MaYa\Maya2018\bin\maya.exe"http://www.wmiao.com/t-45414-1-1.html
9.AndroidDataBinding在librarymodule中遇到错误及解决办法Library module 生成 class 在library module 下启用 Data Binding 很简单,跟 application module 一样,加上: 1 2 3 4 5 android { dataBinding { enabled =true } } 对应生成的 binding 类会在 manifest 里面指定的 package name 下的 databinding 包下。 https://www.jb51.net/article/108689.htm
10.Android组件化过程中ButterKnife在Library中的使用? 在初步的完成了封装后,发现了一个问题,就是我把原来在单一项目框架下的源码转移到了组件化框架下的子业务框架下,说白了就是在组件化开启的前提下,把源码从App转移到了Library下,从理论上来说,应该不会有什么问题,但是历史实践告诉我们,理论往往是靠不住的,在转移的过程中,我就遇到了ButterKnife造成的问题。https://www.jianshu.com/p/a7c94907baaf
11.技术干货如何在Library中使用/依赖mPaaS?技术干货 | 如何在 Library 中使用/依赖 mPaaS? 简介:在使用 mPaaS 框架过程中,有时需要复用模块。复用时需要按照使用 Module 依赖的方式添加模块。 使用场景 在使用 mPaaS 框架过程中,有时需要复用模块。复用时需要按照使用 Module 依赖的方式添加模块。本文以将复用 mPaaS 扫码组件的 Module 为例进行说明。https://developer.aliyun.com/article/783788
12.全球最大的盗版电子书网站被查封我很怀念它两千多年过后,一场规模更大、影响更广的「焚书运动」在互联网上出现。 最近,号称是世界上最大的电子图书馆 Z-Library 一夜之间被封禁,书迷们一如往常打开熟悉的网站,看到的却只有一张吓人的 FBI 警告图。 收藏了千万本图书的 Z-Library,一个字节都没给书迷留下。 https://i.ifeng.com/c/8KrJ3vTNyrw
13.ASP.NETCore适用于.NET的开源Web框架NET 在 GitHub 上是开放源代码的,已收到 100,000 次贡献,并已有 3,700 家公司参与。 加入.NET 社区 Azure 上的免费托管 使用Microsoft Azure 可免费获取 10 个 ASP.NET Core 网站。 还可以部署到任何主要云平台、自己的 Linux 或 Windows 服务器,或多个托管提供程序其中之一。 https://asp.net/
14.lsplibrary下载的东西在哪儿lsplibrary下载路径介绍这是一款可以高清看图的软件,很多用户不知道它的具体下载路径,下面小编为大家带来lsplibrary下载路径介绍,感兴趣的小伙伴一起了解一下吧。 lsplibrary下载的东西在哪儿 系统目录下的library一直在。Mac上资源库文件library是默认隐藏的,在访达中无法直接打开。可通过option快捷键打开,步骤如下。https://www.duotegame.com/mgl/60493.html
15.java.library.path属性在代码中设置不生效问题根据上面的提示信息在VM参数处通过-Djava.library.path将加载路径指定到自己的lib目录后,程序可以正常启动。 这种方式不是太好,因为要手动的去指定虚拟机参数,于是想通过System类的setProperty函数来在代码中动态的改变一下java.library.path的值。 使用 System.setProperty("java.library.path", "./lib"); https://www.iteye.com/blog/daimojingdeyu-238039
16.技术干货如何在Library中使用/依赖mPaaS?技术干货 | 如何在 Library 中使用/依赖 mPaaS?,程序员大本营,技术文章内容聚合第一站。https://www.pianshen.com/article/69932626484/
17.科技信息研究所两名教师在《LibraryHiTech》发表两篇论文科技信息研究所教师王婧怡博士和苏文成博士的两篇研究论文正式发表在SSCI期刊《图书馆高技术》(Q2)第40卷第1期上。 王婧怡博士的论文题目为:四维空间下高校图书馆服务质量感知与评价方法的定量表征。合作者为袁润教授和石宏伟教授。本文旨在更准确、动态地评价高校图书馆的服务质量,提高图书馆的服务效率。本文实现了图书https://lib.ujs.edu.cn/info/1130/11378.htm
18.HomeClinicalTrials.govClinicalTrials.gov ClinicalTrials.gov is a place to learn about clinical studies from around the world. Focus Your Search(all filters optional) Expert Search Study Status All studies Recruiting and not yet recruiting studies More Filters Eligibility Criteriahttps://clinicaltrials.gov/
19.Injectiondoesnotworkinlibraryprojects·Issue#100I am having problem with ButterKnife injections on library projects. Whenever i try any view injection such as: @InjectView(R.id.content_frame) protected FrameLayout contentFrame; I get a compile time error: "error: attribute value must be constant" on the "R.id.content_frame". This occurshttps://github.com/JakeWharton/butterknife/issues/100
20.就是inthelibrarylibrary就是in和at在表示在哪个地点的时候傻傻In 和 at 表示地点时的区别在于 in 表示在某个范围或物理空间内,而 at 表示在某个特定的地方。例如,可以说 I am in the library(我在图书馆里),表示我在图书馆的范围内;也可以说 I am at the library(我在图书馆),表示我在图书馆这个特定的地方。实际应用中根据具体的语境选用,也可以查查词典。祝同学https://www.hujiang.com/c/k_4947035/
21.aParticularBookintheLibrary在图书馆里寻找某一本书Finding a particular book in the library is quite easy.You simply go to the author catalogue.You will find ahttps://www.tingclass.net/show-9733-358147-1.html
22.管理TableauDesktop授權使用情況在/Library/Preferences 中建立一個 com.tableau.ReportingServer.plist 檔案,將 Server 項設定為想要 Tableau Desktop 執行個體向其報告的伺服器的地址,並將 scheduleReportInterval 項設定為 Tableau Desktop 向伺服器報告應採用的頻率(以秒為單位)。 範例: 一個.plist 檔案,該檔設定為每四小時將資訊傳送到兩個伺服http://help.tableau.com/current/desktopdeploy/zh-tw/desktop_deploy_reporting_admin.htm
23.科学网—R语言在Tomcat中遇到java.library.path问题在tomcat调用R语言引擎,遇到类似于下面的错误: Cannot find JRI native library! Please make sure that the JRI native library is in a directory listed in java.library.path. java.lang.UnsatisfiedLinkError: no jri in java.library.path at java.lang.ClassLoader.loadLibrary(Unknown Source) https://blog.sciencenet.cn/blog-111906-773992.html
24.小米手机如何在国内轻松访问岛国强大网站Javlibrary.doc小米手机如何在国内轻松访问岛国强大网站Javlibrary今天我们说说怎么在Ipad平板打开jav的方法下面上教程:打开手机主菜单,选择“设置”,然后选择“无线和网络” 2.选择“VPN”3.选择“添加VPN”选择pptp类型。名称可自定义填写。ppp加密不选。填写服务器地址(联系外游VPN客服领取),点击“确定” 4.点击刚创建的VPN连接登https://www.renrendoc.com/paper/234855463.html
25.PSoCCreator教程:如何在工程中创建LibraryProject赛普拉斯PSoCCreator教程,包括时钟、生成组件等内容,例如添加API模板、设置组件参数、创建符号、添加Library Dpendency,创建电路图等。 声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站https://www.elecfans.com/d/1237154.html