构建接口扩展(Building Interface Extensions)

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

  要创建有Odoo的网站,请参见建立网站;要添加业务功能或扩展Odoo的现有业务系统,请参见构建模块

警告:
该指南需要以下知识:
Javascript 、jQuery、Underscore.js
同时也需要安装 Odoo 和 Git。

  一个简单的模型

  让我们从一个简单的Odoo模块开始,它包含基本的web组件配置,并让我们测试web框架。
  示例模块可以在线下载,可以使用以下命令下载:

$ git clone http://github.com/odoo/petstore

  这将在您执行命令的地方创建一个petstore文件夹。然后需要将该文件夹添加到Odoo的addons路径中,创建一个新的数据库并安装oepetstore模块。

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

oepetstore
|-- images
| |-- alligator.jpg
| |-- ball.jpg
| |-- crazy_circle.jpg
| |-- fish.jpg
| `-- mice.jpg
|-- __init__.py
|-- oepetstore.message_of_the_day.csv
|-- __manifest__.py
|-- petstore_data.xml
|-- petstore.py
|-- petstore.xml
`-- static
`-- src
|-- css
| `-- petstore.css
|-- js
| `-- petstore.js
`-- xml
`-- petstore.xml

  模块已经包含了各种服务器定制。稍后我们将回到这些内容,现在让我们关注与web相关的内容,在静态文件夹(static)中。

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

  

oepetstore/static/css/petstore.css

  目前为空,将为宠物店(pet store)内容保留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;
var QWeb = instance.web.qweb; local.HomePage = instance.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
}); instance.web.client_actions.add(
'petstore.homepage', 'instance.oepetstore.HomePage');
}

  它只在浏览器的控制台打印一个小消息。

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

  

警告
所有的JavaScript文件都被连接和缩小以提高应用程序的加载时间。
其中一个缺点是,随着单个文件的消失,调试变得更加困难,而且代码的可读性也大大降低。可以通过启用“开发者模式”来禁用此过程:
登录到您的Odoo实例(默认用户admin密码admin)打开用户菜单(在Odoo屏幕的右上角)并选择Odoo,然后激活开发者模式:

  Odoo JavaScript单元

  Javascript没有内置模块。因此,在不同文件中定义的变量都会混合在一起,并可能发生冲突。这引发了各种模块模式,用于构建干净的名称空间并限制命名冲突的风险。 Odoo框架使用一种这样的模式来定义Web插件中的模块,以便命名空间代码和正确地命令其加载。

oepetstore/static/js/petstore.js

  文件中包含一个模块声明,代码如下:

openerp.oepetstore = function(instance, local) {
local.xxx = ...;
}

  在Odoo网站中,模块被声明为在全局odoo(请改成openerp)变量上设置的函数。该函数的名称必须与模块名称(在这里为oeststore)相同,以便框架可以找到它,并自动初始化它。

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

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

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

  类

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

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

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

var MyClass = instance.web.Class.extend({
say_hello: function() {
console.log("hello");
},
});

  extend()方法需要一个描述新类的内容(方法和静态属性)的字典。在这种情况下,它只会有一个不带参数的say_hello方法。

  类使用new运算符实例化:

var my_object = new MyClass();
my_object.say_hello();
// print "hello" in the console

  实例的属性可以通过以下方式 this 访问:

var MyClass = instance.web.Class.extend({
say_hello: function() {
console.log("hello", this.name);
},
}); var my_object = new MyClass();
my_object.name = "Bob";
my_object.say_hello();
// print "hello Bob" in the console

  通过定义init()方法,类可以提供初始化程序来执行实例的初始设置。初始化程序接收使用新运算符时传递的参数:

var MyClass = instance.web.Class.extend({
init: function(name) {
this.name = name;
},
say_hello: function() {
console.log("hello", this.name);
},
}); var my_object = new MyClass("Bob");
my_object.say_hello();
// print "hello Bob" in the console

  也可以通过在父类上调用extend()来创建现有(使用定义的)类的子类,如同子类Class()所做的那样:

var MySpanishClass = MyClass.extend({
say_hello: function() {
console.log("hola", this.name);
},
}); var my_object = new MySpanishClass("Bob");
my_object.say_hello();
// print "hola Bob" in the console

  当使用继承覆盖方法时,可以使用this._super()调用原始方法:

var MySpanishClass = MyClass.extend({
say_hello: function() { //已覆盖的方法
this._super(); //调用父类中的原始方法,即“hello 。。。”
console.log("translation in Spanish: hola", this.name);
},
}); var my_object = new MySpanishClass("Bob");
my_object.say_hello();
// print "hello Bob \n translation in Spanish: hola Bob" in the console

  警告

  _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基础

  Odoo web 客户端捆绑了jQuery以实现简单的DOM操作。它比标准的W3C DOM2更有用,并且提供了更好的API,但不足以构成复杂的应用程序,导致难以维护。 很像面向对象的桌面UI工具包(例如Qt,Cocoa或GTK),Odoo Web使特定组件负责页面的各个部分。在Odoo网站中,这些组件的基础是Widget()类,它是专门处理页面部分并显示用户信息的组件。

  您的第一个Widget

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

local.HomePage = instance.Widget.extend({
start: function() {
console.log("pet store home page loaded");
},
});

  它扩展了Widget()并重载了标准方法start(),它与之前的MyClass很像,现在做的很少。

   该行在文件末尾:

instance.web.client_actions.add(
'petstore.homepage', 'instance.oepetstore.HomePage');

  将我们的widget注册为客户端操作。客户端操作将在稍后解释,现在这只是当我们选择 Pet Store ‣ Pet Store ‣ Home Page 菜单时,可以调用和显示我们的窗口小部件。

  警告

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

  显示内容

  widget有很多方法和功能,但基础很简单:
  设置一个widget;
  格式化widget的数据;
    显示widget。
  HomePage 的widget已经有了一个start()方法。该方法是常规widget生命周期的一部分,并在widget插入页面后自动调用。我们可以使用它来显示一些内容。
  所有widget都有一个$ el,代表它们负责的页面部分(作为jQuery对象)。应该在那里插入widget内容。默认情况下,$ el是一个空的<div>元素。
  如果用户没有内容(或没有特定的样式给它一个大小),那么<div>元素通常对用户是不可见的,这就是为什么当HomePage启动时没有任何内容显示在页面上。
  让我们使用jQuery向小部件的根元素添加一些内容:
local.HomePage = instance.Widget.extend({
start: function() {
this.$el.append("<div>Hello dear Odoo user!</div>");
},
});

  当您打开 Pet Store ‣ Pet Store ‣ Home Page时,此消息将显示。

  注意

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

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

local.GreetingsWidget = instance.Widget.extend({
start: function() {
this.$el.append("<div>We are so happy to see you again in this menu!</div>");
},
});

  现在我们可以使用GreetingsWidget的appendTo()方法将我们的GreetingsWidget添加到主页:

local.HomePage = instance.Widget.extend({
start: function() {
this.$el.append("<div>Hello dear Odoo user!</div>");
var greeting = new local.GreetingsWidget(this);
return greeting.appendTo(this.$el);
},
});

  HomePage首先将其自己的内容添加到其DOM根目录;

  HomePage然后实例化GreetingsWidget ;

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

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

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

local.HomePage = instance.Widget.extend({
className: 'oe_petstore_homepage',
...
});
local.GreetingsWidget = instance.Widget.extend({
className: 'oe_petstore_greetings',
...
});

  如果您可以找到DOM的相关部分(右键单击文本然后检查元素),它应该如下所示:

<div class="oe_petstore_homepage">
<div>Hello dear Odoo user!</div>
<div class="oe_petstore_greetings">
<div>We are so happy to see you again in this menu!</div>
</div>
</div>

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

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

  最后,注意GreetingsWidget实例的<div class =“oe_petstore_greetings”>元素位于代表HomePage实例的<div class =“oe_petstore_homepage”>中,这是因为我们追加了该元素。

  Widget的父类和子类

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

new local.GreetingsWidget(this);  //括号内对象是指greetingswidget实例化后归谁所有。

  第一个参数是 this,在这种情况下是一个HomePage实例。这告诉小部件被创建,其他小部件是其父项。

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

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

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

  

local.GreetingsWidget = instance.Widget.extend({
start: function() {
console.log(this.getParent().$el );
// will print "div.oe_petstore_homepage" in the console
},
});

  getChildren() 可以用来获取其子女的名单:

local.HomePage = instance.Widget.extend({
start: function() {
var greeting = new local.GreetingsWidget(this);
greeting.appendTo(this.$el);
console.log(this.getChildren()[0].$el);
// will print "div.oe_petstore_greetings" in the console
},
});

  当重写小部件的init()方法时,将父项传递给this._super()调用是非常重要的,否则关系将无法正确设置:

local.GreetingsWidget = instance.Widget.extend({
init: function(parent, name) {
this._super(parent);
this.name = name;
},
});

  最后,如果小部件没有父项(例如,因为它是应用程序的根小部件),则可以将null作为父项提供:

new local.GreetingsWidget(null);

  销毁Widget

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

greeting.destroy();

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

  危险

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

  QWeb模板引擎

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

this.$el.append("<div>Hello dear Odoo user!</div>");

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

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

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

  • 它在JavaScript中完全实现并在浏览器中呈现;
  • 每个模板文件(XML文件)都包含多个模板;
  • 它在Odoo Web的Widget()中有特殊的支持,虽然它可以在Odoo的Web客户端之外使用(并且可以在不依赖于QWeb的情况下使用Widget())。

  注意

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

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

  使用QWeb

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

  oepetstore/static/src/xml/petstore.xml

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="HomePageTemplate">

<div style="background-color: red;">This is some simple HTML</div>
</t>
</templates>
  现在我们可以在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() {
...
},
});

  尽管结果看起来相似,但这些用法之间有两点区别:

  • 在第二个版本中,模板在调用start()之前就被渲染了;
  • 在第一个版本中,模板的内容被添加到小部件的根元素,而在第二个版本中,模板的根元素被直接设置为小部件的根元素。这就是为什么“greetings”子窗口小部件也会出现红色背景。

  警告

  模板应该有一个非t根元素,特别是如果它们被设置为一个小部件的模板。如果有多个“根元素”,结果是未定义的(通常只有第一个根元素将被使用,其他元素将被忽略)。

  QWeb上下文

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

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

QWeb.render("HomePageTemplate", {name: "Klaus"});

  将模板修改为:

<t t-name="HomePageTemplate">
<div>Hello <t t-esc="name"/></div>
</t>

  最终结果为:

<div>Hello Klaus</div>

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

<t t-name="HomePageTemplate">
<div>Hello <t t-esc="widget.name"/></div>
</t>
local.HomePage = instance.Widget.extend({
template: "HomePageTemplate",
init: function(parent) {
this._super(parent);
this.name = "Mordecai";
},
start: function() {
},
});

  结果为:

<div>Hello Mordecai</div>

  模板声明

  我们已经看到了如何渲染QWeb模板,现在让我们看看模板本身的语法。

  QWeb模板由常规XML和QWeb指令组成。 QWeb指令声明了以t-开头的XML属性。

  最基本的指令是t-name,用于在模板文件中声明新模板:

<templates>
<t t-name="HomePageTemplate">
<div>This is some simple HTML</div>
</t>
</templates>

  t-name采用被定义模板的名称,并声明可以使用QWeb.render()来调用它。它只能在模板文件的顶层使用。

  Escaping(文本输出)

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

<div>Hello <t t-esc="name"/></div>

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

<div><t t-esc="3+5"/></div>

  或方法调用:

<div><t t-esc="name.toUpperCase()"/></div>

  输出HTML

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

<div><t t-raw="name.link('http://www.baidu.com')"/></div>  <!-- 产生一个超链接,指向百度-->

  危险

  t-raw不得用于用户提供的任何可能包含非转义内容的数据,因为这会导致跨站脚本漏洞。

  条件语句

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

<div>
<t t-if="true == true">
true is true
</t>
<t t-if="true == false">
true is not true
</t>
</div>

  注意

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

  迭代

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

<div>
<t t-foreach="names" t-as="name">
<div>
Hello <t t-esc="name"/>
</div>
</t>
</div>

  注意

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

  定义属性

  QWeb提供了两个相关的指令来定义计算属性:t-att-name和t-attf-name。无论哪种情况,name都是要创建的属性的名称(例如t-att-id在渲染后定义属性id)。

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

<div>
Input your name:
<input type="text" t-att-value="defaultName"/>
</div>

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

<div t-attf-class="container {{ left ? 'text-left' : '' }} {{ extra_class }}">
insert content here
</div>

  调用其他模板

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

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

<t t-name="A">
<div class="i-am-a">
<t t-call="B"/>
</div>
</t>
<t t-name="B">
<div class="i-am-b"/>
</t>

  渲染A模板将导致:

<div class="i-am-a">
<div class="i-am-b"/>
</div>

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

  了解关于QWeb的更多信息

  练习:在Widgets中使用QWeb

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

  • product_names应该是一个字符串数组,每个字符串都是一个产品的名称 颜色是包含CSS颜色格式的颜色的字符串(即:#000000表示黑色)。
  • 小部件应该将给定的产品名称一个显示在另一个下面,每个显示在一个单独的框中,背景颜色为颜色值和边框。
  • 你应该使用QWeb来呈现HTML。任何必要的CSS应该在oepetstore / static / src / css / petstore.css中。 在HomePage中使用小部件,并有六种产品。
odoo.oepetstore = function(instance, local) {
var _t = instance.web._t,
_lt = instance.web._lt;
var QWeb = instance.web.qweb; local.HomePage = instance.Widget.extend({
start: function() {
var products = new local.ProductsWidget(
this, ["cpu", "mouse", "keyboard", "graphic card", "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部分,因此没有全局选择的原因。

  更容易的DOM事件绑定

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

local.MyWidget = instance.Widget.extend({
start: function() {
var self = this;
this.$(".my_button").click(function() {
self.button_clicked();
});
},
button_clicked: function() {
..
},
});

  虽然这有效,但它有一些问题:

  • 它比较冗长
  • 它不支持在运行时替换小部件的根元素,因为绑定仅在start()运行时执行(在小部件初始化期间)
  • 它需要处理这个绑定问题

  小部件因此提供了通过事件绑定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) { /* code here */ }
}

  或对象上方法的名称(请参见上面的示例)。

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

  小部件事件和属性

  事件

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

local.ConfirmWidget = instance.Widget.extend({
events: {
'click button.ok_button': function () {
this.trigger('user_chose', true);
},
'click button.cancel_button': function () {
this.trigger('user_chose', false);
}
},
start: function() {
this.$el.append("<div>Are you sure you want to perform this action?</div>" +
"<button class='ok_button'>Ok</button>" +
"<button class='cancel_button'>Cancel</button>");
},
});

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

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

local.HomePage = instance.Widget.extend({
start: function() {
var widget = new local.ConfirmWidget(this);
widget.on("user_chose", this, this.user_chose);
widget.appendTo(this.$el);
},
user_chose: function(confirm) {
if (confirm) {
console.log("The user agreed to continue");
} else {
console.log("The user refused to continue");
}
},
});

  on()绑定一个函数,当event_name标识的事件发生时被调用。 func参数是要调用的函数,object是该函数与方法相关的对象。绑定函数将被调用trigger()(如果有的话)的附加参数。例:

start: function() {
var widget = ...
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);
// will print "1 2 3"
}

  提示:

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

【原创】Odoo开发文档学习之:构建接口扩展(Building Interface Extensions)(边Google翻译边学习)的更多相关文章

  1. 【原创】Odoo开发文档学习之:ORM API接口(ORM API)(边Google翻译边学习)

    官方ORM API开发文档:https://www.odoo.com/documentation/10.0/reference/orm.html Recordsets(记录集) New in vers ...

  2. Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)

    在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现..   例子相关博文:Androi ...

  3. Android 滑动界面实现---Scroller类别 从源代码和开发文档了解(让你的移动布局)

    在android学习,行动互动是软件的重要组成部分,其中Scroller是提供了拖动效果的类,在网上.比方说一些Launcher实现滑屏都能够通过这个类去实现.. 样例相关博文:Android 仿 窗 ...

  4. WSTMart开发文档

    WSTMart开发文档页面   PC版   开源版 授权版   序言   WSTMart安装协议   WSTMart电商系统安装   商城前台安装操作指南   用户中心指南   商家中心操作指南   ...

  5. Android官方开发文档Training系列课程中文版:目录

    Android官方开发文档Training系列课程中文版:目录   引言 在翻译了一篇安卓的官方文档之后,我觉得应该做一件事情,就是把安卓的整篇训练课程全部翻译成英文,供国内的开发者使用,尤其是入门开 ...

  6. .NET6使用DOCFX自动生成开发文档

    本文内容来自我写的开源电子书<WoW C#>,现在正在编写中,可以去WOW-Csharp/学习路径总结.md at master · sogeisetsu/WOW-Csharp (gith ...

  7. 在线API,桌面版,jquery,css,Android中文开发文档,JScript,SQL掌用实例

    学习帮助文档大全 jquery,css,Android中文开发文档,JScript,SQL掌用实例 http://api.jq-school.com/

  8. 工具(5): 极简开发文档编写(How-to)

    缘起 一个合格的可维护项目,必须要有足够的文档,因此一个项目开发到一定阶段后需要适当的编写文档.项目的类型多种多样,有许多项目属于内部项目,例如一个内部的开发引擎,或者一个本身就是面向开发者的项目. ...

  9. 微信小程序 开发文档

    官方开发文档: 小程序公众平台 小程序开发者指南 小程序开发者文档 学习资源: 微信:官方入门教程 微信:WeUI 是一套同微信原生视觉体验一致的基础样式库 微信:微信小程序示例 视频: 学堂在线:学 ...

随机推荐

  1. nfs 笔记

    问题:客户端在nfs文件目录下读写文件提示Permission denied: 解决方法: 修改/etc/exports 中 文件共享方式为 no_root_squash no_root_squash ...

  2. aop的概念以及 cglib-nodep-2.1_3.jar第三方jia包动态代理使用

    引入 cglib-nodep-2.1_3.ja包 cglib产生的代理类是目标类的子类 定义接口,让切面都继承它,方便加入到动态代理方法 的那个类中使用 在SalaryInterceptor类中使用  ...

  3. 2018 Multi-University Training Contest 4 Problem B. Harvest of Apples 【莫队+排列组合+逆元预处理技巧】

    任意门:http://acm.hdu.edu.cn/showproblem.php?pid=6333 Problem B. Harvest of Apples Time Limit: 4000/200 ...

  4. vue - 简单实例(vue-router + webpack + vuex)

    分享 + 实践  基于公司部分产品技术栈转型使用vue,部分同事需要学习一下,快速上手,那么我很荣幸的成为了给大家分享vue技术栈的‘ ’导师‘,在这里我分享一下: 讲解大纲为:(我是有一份PPT的, ...

  5. 使用 PHP Curl 做数据中转

    流程 收集头部信息 收集请求数据 转换头部信息为 CURL 头部请求格式 使用 Curl 进行转发 收集 HTTP 头信息 function getAllHeaders() { $headers = ...

  6. UIScrollView的常用属性

    UIScrollView的常用属性

  7. EF Core 2.1 中的 Eager loading、Explicit loading和LazyLoading (转自MSDN)

    Entity Framework Core allows you to use the navigation properties in your model to load related enti ...

  8. 在windows service中启动类型“Automatic” 和 “Automatic (Delayed start)” 有何不同?

    问题: When installing Windows services there are two options for automatically starting a Windows serv ...

  9. private、protected、public和internal的区别

    private是完全私有的,只有在类自己里面可以调用,在类的外部和子类都不能调用,子类也不能继承父类的private的属性和方法. protected虽然可以被外界看到,但外界却不能调用,只有自己及自 ...

  10. Maven项目改为spring boot项目的方法

    目录树 新建Maven项目及步骤 修改方法 启动测试 新建Maven项目及步骤 我这里是从创建开始讲,使用的工具是Idea2017版本.如果是已经创建了Maven,想改为spring boot项目的请 ...