原创Odoo开发文档学习之:构建接口扩展(BuildingInterfaceExtensions)(边Google翻译边学习)A·DONG

本指南是关于为Odoo的web客户创建模块。

警告:该指南需要以下知识:Javascript、jQuery、Underscore.js同时也需要安装Odoo和Git。一个简单的模型让我们从一个简单的Odoo模块开始,它包含基本的web组件配置,并让我们测试web框架。示例模块可以在线下载,可以使用以下命令下载:

如果您浏览petstore文件夹,您应该看到以下内容:

在Odoo模块的“web”端中使用的文件必须放置在静态文件夹中,这样它们就可以在web浏览器中使用,而浏览器之外的文件也不能被浏览器获取。src/css、src/js和src/xml子文件夹是常规的,并不是绝对必要的。

oepetstore/static/css/petstore.css目前为空,将为宠物店(petstore)内容保留CSS。

oepetstore/static/xml/petstore.xml大部分也是空的,将保存QWeb模板。

oepetstore/static/js/petstore.js最重要(也是最有趣的)部分,包含javascript应用程序的逻辑(或者至少是它的web浏览器端)。它现在应该是:

openerp.oepetstore=function(instance,local){//特别注意:红色部分在开发文档中10.0版本中用odoo关键字,但是测试时无法通过,必须是openerp,估计是尚未完全支持odoo关键字var_t=instance.web._t,_lt=instance.web._lt;varQWeb=instance.web.qweb;local.HomePage=instance.Widget.extend({start:function(){console.log("petstorehomepageloaded");},});instance.web.client_actions.add('petstore.homepage','instance.oepetstore.HomePage');}它只在浏览器的控制台打印一个小消息。

静态文件夹中的文件,需要在模块中定义,以便正确加载它们。src/xml中的所有内容都在__manifest.__中定义。在petstore.xml或类似的文件中定义或引用src/css和src/js的内容。

当Web客户端加载你的模块时,它会调用根函数并提供两个参数:

第一个参数(instance)是OdooWeb客户端的当前实例,它允许访问由Odoo(网络服务)定义的各种功能以及由内核或其他模块定义的对象。

第二个参数(local)是您自己的本地名称空间,由Web客户端自动创建。应该可以从模块外部访问的对象和变量(无论是因为OdooWeb客户端需要调用它们,还是因为其他人可能想要定制它们)应该在该名称空间内设置。

就像模块一样,并且与大多数面向对象的语言相反,JavaScript不会构建在classes中,尽管它提供了大致相同(如果是较低级别和更详细的)机制。

为了简单和开发人员友好,Odooweb提供了一个基于JohnResig的简单JavaScript继承的类系统。

通过调用odoo.web.Class()的extend()方法来定义新的类:

varMyClass=instance.web.Class.extend({say_hello:function(){console.log("hello");},});extend()方法需要一个描述新类的内容(方法和静态属性)的字典。在这种情况下,它只会有一个不带参数的say_hello方法。

类使用new运算符实例化:

varmy_object=newMyClass();my_object.say_hello();//print"hello"intheconsole实例的属性可以通过以下方式this访问:

varMyClass=instance.web.Class.extend({say_hello:function(){console.log("hello",this.name);},});varmy_object=newMyClass();my_object.name="Bob";my_object.say_hello();//print"helloBob"intheconsole通过定义init()方法,类可以提供初始化程序来执行实例的初始设置。初始化程序接收使用新运算符时传递的参数:

varMyClass=instance.web.Class.extend({init:function(name){this.name=name;},say_hello:function(){console.log("hello",this.name);},});varmy_object=newMyClass("Bob");my_object.say_hello();//print"helloBob"intheconsole也可以通过在父类上调用extend()来创建现有(使用定义的)类的子类,如同子类Class()所做的那样:

varMySpanishClass=MyClass.extend({say_hello:function(){console.log("hola",this.name);},});varmy_object=newMySpanishClass("Bob");my_object.say_hello();//print"holaBob"intheconsole当使用继承覆盖方法时,可以使用this._super()调用原始方法:

varMySpanishClass=MyClass.extend({say_hello:function(){//已覆盖的方法this._super();//调用父类中的原始方法,即“hello。。。”console.log("translationinSpanish:hola",this.name);},});varmy_object=newMySpanishClass("Bob");my_object.say_hello();//print"helloBob\ntranslationinSpanish:holaBob"intheconsole警告_super不是一个标准的方法,它被设置为当前继承链中的一个方法(如果有的话)。它只在方法调用的同步部分中定义,用于异步处理程序(在网络调用或setTimeout回调之后)应该保留对其值的引用,因此不应通过以下方式访问它:

//以下调用会产生错误say_hello:function(){setTimeout(function(){this._super();}.bind(this),0);}//以下方式正确say_hello:function(){//不能忘记.bind()var_super=this._super.bind(this);setTimeout(function(){_super();}.bind(this),0);}Widgets基础Odooweb客户端捆绑了jQuery以实现简单的DOM操作。它比标准的W3CDOM2更有用,并且提供了更好的API,但不足以构成复杂的应用程序,导致难以维护。很像面向对象的桌面UI工具包(例如Qt,Cocoa或GTK),OdooWeb使特定组件负责页面的各个部分。在Odoo网站中,这些组件的基础是Widget()类,它是专门处理页面部分并显示用户信息的组件。

初始演示模块已经提供了一个基本的widget:

local.HomePage=instance.Widget.extend({start:function(){console.log("petstorehomepageloaded");},});它扩展了Widget()并重载了标准方法start(),它与之前的MyClass很像,现在做的很少。

该行在文件末尾:

instance.web.client_actions.add('petstore.homepage','instance.oepetstore.HomePage');将我们的widget注册为客户端操作。客户端操作将在稍后解释,现在这只是当我们选择PetStorePetStoreHomePage菜单时,可以调用和显示我们的窗口小部件。

警告

由于该组件将从我们的模块外部调用,Web客户端需要其“完全限定(规范)”名称,而不是任意名称。

local.HomePage=instance.Widget.extend({start:function(){this.$el.append("

HellodearOdoouser!
");},});当您打开PetStorePetStoreHomePage时,此消息将显示。

注意

要刷新OdooWeb中加载的JavaScript代码,您需要重新加载页面(升级一下模块)。没有必要重新启动Odoo服务器。

HomePageWidget由OdooWeb使用并自动管理。要学习如何从头开始使用Widget,我们来创建一个新Widget:

local.GreetingsWidget=instance.Widget.extend({start:function(){this.$el.append("

Wearesohappytoseeyouagaininthismenu!
");},});现在我们可以使用GreetingsWidget的appendTo()方法将我们的GreetingsWidget添加到主页:

local.HomePage=instance.Widget.extend({start:function(){this.$el.append("

HellodearOdoouser!
");vargreeting=newlocal.GreetingsWidget(this);returngreeting.appendTo(this.$el);},});HomePage首先将其自己的内容添加到其DOM根目录;

HomePage然后实例化GreetingsWidget;

最后,它告诉GreetingsWidget将自己的部分插入到GreetingsWidget中。

当调用appendTo()方法时,它会要求小部件(widget,以下将的小部件就是widget)将自身插入指定位置并显示其内容。在调用appendTo()期间,将调用start()方法。

要查看显示界面下发生了什么,我们将使用浏览器的DOMExplorer。但首先让我们稍微修改我们的小部件,以便通过向它们的根元素添加一个类来更轻松地找到它们的位置:

HellodearOdoouser!
Wearesohappytoseeyouagaininthismenu!
它清楚地显示了由Widget()自动创建的两个
元素,因为我们在它们上面添加了一些类。

我们也可以看到我们自己添加的两个消息控制器。

最后,注意GreetingsWidget实例的元素位于代表HomePage实例的中,这是因为我们追加了该元素。

在上一部分中,我们使用以下语法实例化了一个小部件:

newlocal.GreetingsWidget(this);//括号内对象是指greetingswidget实例化后归谁所有。第一个参数是this,在这种情况下是一个HomePage实例。这告诉小部件被创建,其他小部件是其父项。

正如我们所看到的,小部件通常由另一个小部件插入到DOM中,并在其他小部件的根元素内插入。这意味着大多数小部件是另一个小部件的“部分”,并代表它存在。我们将容器称为父项,并将包含的小部件称为子项。

由于技术和概念上的多重原因,小部件有必要知道谁是其父类以及谁是子类。

getParent()可以用来获取小部件的父级:

local.GreetingsWidget=instance.Widget.extend({start:function(){console.log(this.getParent().$el);//willprint"div.oe_petstore_homepage"intheconsole},});getChildren()可以用来获取其子女的名单:

local.HomePage=instance.Widget.extend({start:function(){vargreeting=newlocal.GreetingsWidget(this);greeting.appendTo(this.$el);console.log(this.getChildren()[0].$el);//willprint"div.oe_petstore_greetings"intheconsole},});当重写小部件的init()方法时,将父项传递给this._super()调用是非常重要的,否则关系将无法正确设置:

local.GreetingsWidget=instance.Widget.extend({init:function(parent,name){this._super(parent);this.name=name;},});最后,如果小部件没有父项(例如,因为它是应用程序的根小部件),则可以将null作为父项提供:

newlocal.GreetingsWidget(null);销毁Widget如果您可以向用户显示内容,则应该也可以将其删除。这是通过destroy()方法完成的:

greeting.destroy();当一个小部件被销毁时,它将首先对其所有子项调用destroy()。然后它从DOM中删除自己。如果你已经在init()或start()中设置了永久结构,必须明确清除它们(因为垃圾回收器不会处理它们),你可以重写destroy()。

危险

当覆盖destroy()时,必须始终调用_super(),否则即使没有显示错误,小部件及其子项也没有正确清理,从而可能会发生内存泄漏和“意想不到的事件”。

在上一节中,我们通过直接操作(并添加)DOM来将内容添加到我们的小部件:

this.$el.append("

HellodearOdoouser!
");这允许生成和显示任何类型的内容,但在生成大量DOM时会很难处理(大量重复,引用问题......)。

与许多其他环境一样,Odoo的解决方案是使用模板引擎。Odoo的模板引擎被称为QWeb。

QWeb是一种基于XML的模板语言,与Genshi,Thymeleaf或Facelets类似。它具有以下特点:

使用QWeb代替现有的JavaScript模板引擎的原理是预先存在的(第三方)模板的可扩展性,就像Odoo视图一样。

大多数JavaScript模板引擎是基于文本的,这排除了容易的结构可扩展性,其中基于XML的模板引擎可以通过使用例如通用数据库XPath或CSS以及树型变更DSL(甚至只是XSLT)。这种灵活性和可扩展性是Odoo的核心特征,丢失它被认为是不可接受的。

首先让我们在几乎空白的地方定义一个简单的QWeb模板,在以下文件进行操作:

oepetstore/static/src/xml/petstore.xml

ThisissomesimpleHTML

现在我们可以在HomePage小部件中使用这个模板。使用页面顶部定义的QWeb加载器变量,我们可以调用XML文件中定义的模板:local.HomePage=instance.Widget.extend({start:function(){this.$el.append(QWeb.render("HomePageTemplate"));},});QWeb.render()查找指定的模板,将其呈现为一个字符串并返回结果。

但是,因为Widget()对QWeb有特殊的集成,所以模板可以通过它的模板属性直接设置在Widget上:

local.HomePage=instance.Widget.extend({template:"HomePageTemplate",start:function(){...},});尽管结果看起来相似,但这些用法之间有两点区别:

QWeb模板可以被赋予数据并且可以包含基本的显示逻辑。

对于显式调用QWeb.render(),模板数据作为第二个参数传递:

QWeb.render("HomePageTemplate",{name:"Klaus"});将模板修改为:

Hello
最终结果为:

HelloKlaus
当使用Widget()的集成时,不可能为模板提供额外的数据。该模板将被赋予一个单一的窗口小部件上下文变量,引用在start()被调用之前被渲染的窗口小部件(窗口小部件的状态基本上是由init()设置的):

Hello
local.HomePage=instance.Widget.extend({template:"HomePageTemplate",init:function(parent){this._super(parent);this.name="Mordecai";},start:function(){},});结果为:

t-esc指令可用于输出文本:

Hello
它需要一个经过评估的Javascript表达式,然后表达式的结果被HTML转义并插入到文档中。由于它是一个表达式,因此可以像上面那样仅提供一个变量名称,或者像计算这样的更复杂的表达式:

或方法调用:

输出HTML要在呈现的页面中注入HTML,请使用t-raw。像t-esc一样,它以一个任意的Javascript表达式作为参数,但它不执行HTML转义步骤。

QWeb可以使用t-if的条件块。该指令采用任意表达式,如果表达式为falsy(false,null,0或空字符串),则整个块将被抑制,否则将显示该表达式。

trueistruetrueisnottrue
注意QWeb没有“else”结构,如果原始条件反转,则使用第二个t。如果它是复杂或昂贵的表达式,您可能需要将条件存储在局部变量中。

要在列表上迭代,请使用t-foreach和t-as。t-foreach需要一个表达式返回一个列表来迭代t-因为在迭代过程中需要一个变量名来绑定到每个项目。

Hello
注意t-foreach也可以用于数字和对象(字典)。

t-att-接受一个javascript表达式,其结果被设置为属性的值,如果计算该属性的所有值,则它是非常有用的:

Inputyourname:
t-attf-采用格式字符串。格式字符串是带有插值块的文本文本,插值块是{{和}}之间的javascript表达式,它将被表达式的结果替换。对于部分文字和部分计算的属性(如类),这是最有用的:

insertcontenthere

调用其他模板模板可以拆分成子模板(为了简单,可维护性,可重用性或避免过多的标记嵌套)。

这是通过使用t-call指令完成的,该指令采用要呈现的模板的名称:

渲染A模板将导致:

子模板继承其调用者的渲染上下文。

在Widgets创建一个构件除了parent:product_names和color之外还有两个参数的构件。

odoo.oepetstore=function(instance,local){var_t=instance.web._t,_lt=instance.web._lt;varQWeb=instance.web.qweb;local.HomePage=instance.Widget.extend({start:function(){varproducts=newlocal.ProductsWidget(this,["cpu","mouse","keyboard","graphiccard","screen"],"#00FF00");products.appendTo(this.$el);},});local.ProductsWidget=instance.Widget.extend({template:"ProductsWidget",init:function(parent,products,color){this._super(parent);this.products=products;this.color=color;},});instance.web.client_actions.add('petstore.homepage','instance.oepetstore.HomePage');}

小部件的jQuery选择器

在窗口小部件中选择DOM元素可以通过调用窗口小部件的DOM根目录上的find()方法来执行:

this.$el.find("input.my_input")...但是由于这是一种常见的操作,Widget()通过$()方法提供了一个等效的快捷方式:

local.MyWidget=instance.Widget.extend({start:function(){this.$("input.my_input")...},});警告全局jQuery函数$()应该永远不会被使用(不是this.$()),除非它是绝对必要的:对一个小部件的根进行选择的范围是小部件,对本地来说是本地的,但是使用$()的选择对于页面/应用程序是全局的,并且可以匹配部分其他小部件和视图,导致奇怪或危险的副作用。由于小部件通常只应用于其拥有的DOM部分,因此没有全局选择的原因。

我们以前使用常规jQuery事件处理程序(例如,.click()或.change())在窗口小部件元素上绑定了DOM事件:

local.MyWidget=instance.Widget.extend({start:function(){varself=this;this.$(".my_button").click(function(){self.button_clicked();});},button_clicked:function(){..},});虽然这有效,但它有一些问题:

小部件因此提供了通过事件绑定DOM事件的捷径:

local.MyWidget=instance.Widget.extend({events:{"click.my_button":"button_clicked",},button_clicked:function(){..}});event是事件触发时调用的函数或方法的对象(映射):

关键是一个事件名称,可能使用CSS选择器进行优化,在这种情况下,只有当事件发生在选定的子元素上时,函数或方法才会运行:点击将处理小部件内的所有点击,但单击.my_button将只处理点击含有my_button类的元素。

该值是触发事件时要执行的操作。

它也可以这样描述:

events:{'click':function(e){/*codehere*/}}或对象上方法的名称(请参见上面的示例)。

无论哪种情况,这都是小部件实例,并且处理程序被赋予一个参数,即事件的jQuery事件对象。

小部件提供了一个事件系统(与上面描述的DOM/jQuery事件系统分开):一个小部件可以触发自身的事件,其他小部件(或其本身)可以绑定自己并监听这些事件:

local.ConfirmWidget=instance.Widget.extend({events:{'clickbutton.ok_button':function(){this.trigger('user_chose',true);},'clickbutton.cancel_button':function(){this.trigger('user_chose',false);}},start:function(){this.$el.append("

Areyousureyouwanttoperformthisaction
"+"Ok"+"Cancel");},});trigger()将触发事件的名称作为其第一个(必需)参数,任何其他参数都视为事件数据并直接传递给侦听器。

然后,我们可以设置一个父事件来实例化我们的通用小部件,并使用on()来监听user_chose事件:

start:function(){varwidget=...widget.on("my_event",this,this.my_event_triggered);widget.trigger("my_event",1,2,3);},my_event_triggered:function(a,b,c){console.log(a,b,c);//willprint"123"}提示:

触发其他小部件上的事件通常是一个坏主意。该规则的主要例外是odoo.web.bus,它专门用于广播任何小部件可能对整个OdooWeb应用程序感兴趣的平台。

THE END
1.开宠物店需要具备什么条件:2025开设宠物店的秘诀:你需要了解的...在宠物行业持续蓬勃发展的今天,越来越多的人考虑开设自己的宠物店。然而,成功开设一家宠物店并不仅仅是提供食物和玩具那么简单。为了确保你的宠物店能够吸引顾客并实现盈利,有几个关键条件需要了解和准备。本文将为你详细解析2025年开设宠物店所需具备的重要条件。 http://azww9w.jiuyu8.com/post/23811.html
2.宠物店连锁加盟宠物店概念 特指提供宠物出售,宠物用品售卖,宠物洗护服务的一体化商店。宠物在人们日常生活中的地位逐渐升高,这就带动了整个宠物行业的快速成长,在中国宠物市场中,宠物用品和宠物服装已经成为社会中的一股时尚潮流。在其迅速崛起的同时,宠物用品市场也同样面临着来自国内外市场的各种压力和挑战。那么中国应该采取什么样的...https://www.jiamengfei.com/xm/110070
3.大学生宠物店创业计划书20241121.docx大学生宠物店创业计划书(精选10篇)在社会发展不断提速的今天,创业计划书的使用频率呈上升趋势,一份优秀的创业计划书往往会使创业者达到事半功倍的效果。我们该怎么拟定创业计划书呢?下面是小编为大家整理的大学生宠物店创业计划书,希望能够帮助到大家。目前市场上的宠物店林林总总,风格不一。有专门给宠物提供各种...https://www.renrendoc.com/paper/362112091.html
1.宠物狗店的秘密是什么在繁忙的都市中,人们往往因为工作和生活压力而忽略了与家中的宠物共度时光。然而,有一类商店却成为了城市居民寻找慰藉与快乐的地方——宠物狗店。这不仅仅是一家普通的商店,它承载着更多的情感和责任,是一个充满爱心与关怀的地方。 1. 宠物狗店背后的故事 ...https://www.gurotsr.cn/chong-wu-gou/597874.html
2.旺盛的爱与忠诚探索宠物狗店背后的故事宠物狗店的诞生与发展 宠物狗店作为一种新的商业模式,其兴起可以追溯到90年代末期,当时人们开始越来越重视家庭生活,希望在家中拥有自己的宠物。随着社会经济的发展,越来越多的人能够负担起养dog费用,从而推动了宠物狗店这一行业的迅速增长。 宠物狗店经营策略分析 ...https://www.lynecx.cn/chong-wu-xun-lian/531315.html
3.交易商标“宠概念”详解“宠概念”作为这一领域中的重要组成部分,不仅涵盖了宠物食品、用品等实物产品,还包括了宠物美容、寄养等一系列服务项目。而在激烈的市场竞争中,如何有效地通过商标来提升品牌形象,增加产品的市场辨识度,成为了众多商家关注的重点。本文将围绕“宠概念”下的商标交易进行探讨,旨在为相关企业提供有价值的参考。https://www.jingbiaow.com/news/17108.html
4.猫垃圾箱的特写.填满猫粪托盘.宠物店概念.宠物产品.股票视频...猫垃圾箱的特写. 填满猫粪托盘. 宠物店概念. 宠物产品. 作者引用附注 ID269787667 ?Roman Tyshchenko | Dreamstime.com 图像关键字 吸收体 敌意 蓝色 棚车 采购 猫 干净 特写镜头 伴随 发运 国内 干燥 似猫 补白 卫生学 卫生 查出 小猫 废弃物 ...https://cn.dreamstime.com/%E7%8C%AB%E5%9E%83%E5%9C%BE%E7%AE%B1%E7%9A%84%E7%89%B9%E5%86%99-%E5%A1%AB%E6%BB%A1%E7%8C%AB%E7%B2%AA%E6%89%98%E7%9B%98-%E5%AE%A0%E7%89%A9%E5%BA%97%E6%A6%82%E5%BF%B5-%E5%AE%A0%E7%89%A9%E4%BA%A7%E5%93%81-video269787667