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

时间:2022-10-09 20:17:22

  构建接口扩展(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应用程序感兴趣的平台。