本文来源:https://www.jianshu.com/p/1a47fac01077

Odoo12 Javascript 参考指南

 

本文介绍了odoo javascript框架。从代码行的角度来看,这个框架不是一个大的应用程序,但它是非常通用的,因为它基本上是一个将声明性接口描述转换为活动应用程序的机器,能够与数据库中的每个模型和记录交互。甚至可以使用Web客户端修改Web客户端的接口。

概览

这个Javascript框架主要设计用于三个地方使用:

  • web客户端:这是一个私有的web应用,可以在其中查看和编辑业务数据。这是一个单页应用程序(永远不会重新加载该页,只在需要时从服务器提取新数据)。
  • 网站:这是Odoo的公共部分。它允许身份不明的用户作为客户端浏览某些内容、购物或执行许多操作。这是一个经典的网站:各种各样的带有控制器的路由和共同协作的Javascript代码。
  • POS:这是销售点的接口。它是一个特定的但也应用程序。

Web客户端

单页应用

简而言之,webclient实例是整个用户界面的根组件。它的职责是协调所有的子组件,并提供服务,如RPC、本地存储等等。

在运行时,Web客户端是单页应用程序。每次用户执行操作时,它不需要从服务器请求整页。相反,它只请求它所需要的,然后替换/更新视图。此外,它还管理URL:它与Web客户机状态保持同步。

这意味着,当用户在处理odoo时,Web客户机类(和动作管理器)实际上创建并销毁了许多子组件。状态是高度动态的,每个小部件都可以随时销毁。

Web客户端JS代码概览

这里,我们在web/static/src/js插件中快速概述了web客户机代码。注意,这是故意不详尽的,我们只涉及最重要的文件/文件夹。

  • boot.js : 这是定义模块系统的文件,它需要首先加载。
  • core/ : 这是较低级别的构建基块的集合。值得注意的是,它包含类系统、小部件系统、并发实用程序和许多其他类/函数。
  • chorm/ :在这个文件夹中,我们有大多数大的小部件,它们构成了大部分用户界面。
  • chrome/abstract_web_client.js and chrome/web_client.js : 这些文件一起定义了WebClient小部件(widget),它是Web客户机的根小部件(wideget)。
  • chrome/action_manager.js : 这是将动作(action)转换为小部件(widget)(例如看板或表单视图)的代码。
  • chrome/search_X.js : 所有这些文件定义了搜索视图(它不是Web客户机视图中的视图,仅从服务器视)
  • fields : 这里定义了所有主要字段视图小部件(widget)
  • views : 这是视图所在的位置

资源管理

在Odoo中管理资源并不像在其他应用程序中那样简单。其中一个原因是,在其中一些情况中我们有各种各样的状态,但不是所有的资源都是必需的。例如,Web客户端、销售点、网站甚至移动应用程序的需求是不同的。此外,有些资源可能很大,但很少需要。在这种情况下,我们有时希望它们被懒惰地加载。

主要思想是我们用XML定义一组包。捆绑包在这里定义为一组文件(javascript、css、scss)。在odoo中,最重要的包在addons/web/views/webclient_templates.xml文件中定义。看起来是这样的:

<template id="web.assets_common" name="Common Assets (used in backend interface and website)">
<link rel="stylesheet" type="text/css" href="/web/static/lib/jquery.ui/jquery-ui.css"/>
...
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
...
</template>

然后,可以使用t-call-assets指令将捆绑包中的文件插入到模板中:

<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_common" t-css="false"/>

下面是当服务器使用以下指令呈现模板时发生的情况:

  • 包中描述的所有SCSS文件都编译为CSS文件。名为file.scss的文件将编译在名为file.scss.css的文件中。
  • 如果我们在debug=assets模式
       * t-js属性设置为false的t-call-assets指令将替换为指向css文件的样式表标记列表。
       * t-css属性设置为false的t-call-assets指令将替换为指向JS文件的脚本标记列表。
  • 如果我们不在debug=assets模式
       * CSS文件将被连接并缩小,然后拆分为不超过4096个规则的文件(以绕过IE9的旧限制)。然后,我们根据需要生成尽可能多的样式表标签
       * JS文件被连接并缩小,然后生成一个脚本标记。

请注意,资源文件是缓存的,因此从理论上讲,浏览器应该只加载它们一次。

主包

当odoo服务器启动时,它检查包中每个文件的时间戳,如果需要,它将创建/重新创建相应的包。

以下是大多数开发人员需要知道的一些重要包:

  • web.assets_common : 此包包含Web客户端、网站以及销售点(POS)所共有的大多数资源。这应该包含用于Odoo框架的较低级别的构建块。注意,它包含boot.js文件,它定义了odoo模块系统。
  • web.assets_backend :这个包包含特定于Web客户端的代码(特别是Web客户端/动作管理器/视图)
  • web.assets_frontend :这个包是关于所有特定于公共网站的:电子商务、论坛、博客、事件管理…

在一个资源包里添加文件

将位于addons/web中的文件添加到bundle的正确方法很简单:只需将脚本或样式表标记添加到文件webclient_templates.xml中的bundle即可。但是当我们使用不同的插件(addon)时,我们需要从该插件添加一个文件。在这种情况下,应分三步进行:

  1. 添加一个 assets.xml 文件到views/文件夹
  2. 添加字符'views/assets.xml' 到manifest文件的键'data'的值里
  3. 创建所需包的继承视图,并使用xpath表达式添加文件。例如:
<template id="assets_backend" name="helpdesk assets" inherit_id="web.assets_backend">
<xpath expr="//script[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/helpdesk/static/src/scss/helpdesk.scss"/>
<script type="text/javascript" src="/helpdesk/static/src/js/helpdesk_dashboard.js"></script>
</xpath>
</template>

请注意,当用户加载odoo web客户端时,包中的所有文件都会立即加载。这意味着每次通过网络传输文件(浏览器缓存处于活动状态时除外)。在某些情况下,最好使用Lazyload的一些资产。例如,如果一个小部件需要一个大的库,而这个小部件不是体验的核心部分,那么在实际创建小部件时,最好只加载库。widget类实际上已经为这个用例内置了支持。(查阅QWeb模板引擎部分)

如果文件没有加载/更新应该怎么办

文件可能无法正确加载有许多不同的原因。您可以尝试以下几点来解决此问题:

  • 一旦服务器启动,它就不知道资源文件是否已被修改。因此,您可以简单地重新启动服务器来重新生成资源。
  • 检查控制台(在开发工具中,通常用F12打开),确保没有明显的错误
  • 尝试在文件的开头添加console.log(在任何模块定义之前),这样您就可以查看文件是否已加载。
  • 在用户界面中,在调试模式下(在此处插入链接到调试模式),有一个选项可以强制服务器更新其资源文件。
  • 使用debug=assets模式。这实际上会绕过资源包(请注意,它实际上并不能解决问题,服务器仍然使用过时的包)
  • 最后,对于开发人员来说,最方便的方法是使用--dev=all选项启动服务器。这将激活文件监视程序选项,必要时将自动使资源无效。请注意,如果操作系统是Windows,它就不能很好地工作。
  • 记住刷新页面!
  • 或者保存代码文件…

Javascript模块系统

一旦我们能够将我们的javascript文件加载到浏览器中,我们就需要确保以正确的顺序加载它们。为了实现这一点,odoo定义了一个小模块系统(位于addons/web/static/src/js/boot.js文件中,需要首先加载该文件)。

在AMD的启发下,odoo模块系统通过在全局odoo对象上定义函数define来工作。然后我们通过调用该函数来定义每个javascript模块。在odoo框架中,模块是一段将尽快执行的代码。它有一个名称,可能还有一些依赖项。当它的依赖项被加载时,模块也将被加载。模块的值就是定义模块的函数的返回值。

一个例子,看起来像这样:

// in file a.js
odoo.define('module.A', function (require) {
"use strict"; var A = ...; return A;
}); // in file b.js
odoo.define('module.B', function (require) {
"use strict"; var A = require('module.A'); var B = ...; // something that involves A return B;
});

定义模块的另一种方法是在第二个参数中明确地给出依赖项列表。

odoo.define('module.Something', ['module.A', 'module.B'], function (require) {
"use strict"; var A = require('module.A');
var B = require('module.B'); // some code
});

如果某些依赖项丢失/未就绪,那么模块将不会被加载。几秒钟后控制台中将出现警告。

请注意,不支持循环依赖项。这是有道理的,但这意味着需要谨慎。

定义一个模块

odoo.define 方法给了三个参数:

  • moduleName: javascript模块的名称。它应该是一个唯一的字符串。惯例是在odoo插件(addon)的名字后面加上一个具体的描述。例如,“web.widget”描述在web插件中定义的模块,该模块导出一个widget类(因为第一个字母大写)。
    如果名称不唯一,将引发异常并显示在控制台中
  • dependencies : 第二个参数是可选的。如果给定,它应该是一个字符串列表,每个字符串对应一个JavaScript模块。这描述了在执行模块之前需要加载的依赖项。如果这里没有明确地给出依赖项,那么模块系统将通过调用ToString从函数中提取它们,然后使用regexp查找所有Require语句。
  • 最后一个参数是定义模块的函数。它的返回值是模块的值,可以传递给其他需要它的模块。注意,异步模块有一个小的异常,请参见下一节。
    如果发生错误,将在控制台中记录(在调试模式下):
  • Missing dependencies: 这些模块不会出现在页面中。可能是javascript文件不在页面中或模块名称错误
  • Failed Modules : 一个Javascript错误被检测到
  • Rejected modules :块返回拒绝的延迟。它(及其相关模块)未加载。
  • Rejected linked modules: 依赖被拒绝模块的模块
  • Non loaded modules : 模块依赖了一个缺失/失败的模块

异步模块

模块可能需要在准备就绪之前执行一些工作。例如,它可以做一个RPC来加载一些数据。在这种情况下,模块只需返回一个deferred(promise)。在这种情况下,模块系统只需等待deferred完成,然后注册模块。

odoo.define('module.Something', ['web.ajax'], function (require) {
"use strict"; var ajax = require('web.ajax'); return ajax.rpc(...).then(function (result) {
// some code here
return something;
});
});

最好的练习

  • 记住模块名的约定:插件名加上模块名后缀
  • 在模块顶部声明所有依赖项。此外,它们应该按模块名称的字母顺序排序。这样更容易理解您的模块。
  • 在末尾声明所有导出的值
  • 尽量避免从一个模块导出过多的内容。通常最好在一个(小/更小)模块中简单地导出一件事情。
  • 异步模块可以用来简化一些用例。例如,web.dom_ready模块返回一个deferred ,当dom实际就绪时,这个deferred 将被解决。因此,另一个需要dom的模块可以在某个地方简单地有一个require(“web.dom_ready”)语句,并且只有当dom准备好时才会执行代码。
  • 尽量避免在一个文件中定义多个模块。这在短期内可能很方便,但实际上很难维护。

例子1

odoo.define('my_field_widget',function(require){
"user strict"

// 此方法:三个参数:1、A自定义:唯一的。2、是一个列表:可以不写。3、function(require)固定写法
//odoo.define("A",[],function (require) {
// "user strict"
//});

var AbstractField = require('web.AbstractField');
var fieldRegistry = require('web.field_registry'); // 通过扩展AbstractField来扩展widget
var colorField = AbstractField.extend({
// 设置css类,根元素标记和支持的字段类型
className:'o_int_colorpicker',
tagName:'span',
supportedFieldTypes: ['integer'], // #获取js事件
events:{
'click.o_color_pill':'clickPill',
}, // 继承init,进行一些初始化
init:function () {
this.totalColors=10;
this._super.apply(this,arguments);
}, // 继承_renderEdit、_renderReadonly以设置Dom元素
// #编辑状态下操作
_renderEdit:function () {
this.$el.empty();
for (var i=0; i<this.totalColors;i++){
var className = "o_color_pill o_color_" + i;
if (this.value===i){
className += 'active'
}
this.$el.append($('<span>',{
'class': className,
'data-val': i,
}));
}
}, // 只读
_renderReadonly:function () {
var className = "o_color_pill active readonly o_color_"+ this.value;
this.$el.append($('<span>',{
'class':className,
}));
},
// 处理程序
clickPill:function(ev){
var $target = $(ev.currentTarget);
var data = $target.data();
this._setValue(data.val.toString());
}
}); // 注册你的widget
fieldRegistry.add('int_color',colorField); // 使其可以用于其他附加
return {
colorField:colorField,
}; });
.o_int_colorpicker {
.o_color_pill {
display: inline-block;
height: 25px;
width: 25px;
margin: 4px;
border-radius: 25px;
position: relative;
@for $size from 1 through length($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: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
color: #fff;
position: absolute;
padding: 4px;
font-size: 16px;
}
}
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<odoo> <template id="assets_end" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script src="/xxx/static/src/js/field_widget.js" type="text/javascript" />
<link href="/xxx/static/src/scss/field_widget.scss" rel="stylesheet" type="text/scss" />
</xpath>
</template> </odoo>
   <field name="color" widget="int_color"/>

小部件(Widget)

widget类实际上是用户界面的一个重要构建块。几乎用户界面中的所有内容都在小部件(widget)的控制之下。widget类在widget.js中的module web.widget中定义。
简而言之,widget类提供的特性包括:

  • 小部件之间的父/子关系(PropertiesMixin)
  • **具有安全功能的广泛生命周期管理 **(e.g. 在销毁父级期间自动销毁子窗口小部件)
  • 自动渲染qweb模板
  • 帮助与外部环境交互的各种实用功能。
    一个计数的小部件例子:

var Widget = require('web.Widget'); var Counter = Widget.extend({
template: 'some.template',
events: {
'click button': '_onClick',
},
init: function (parent, value) {
this._super(parent);
this.count = value;
},
_onClick: function () {
this.count++;
this.$('.val').text(this.count);
},
});

对于本例,假设模板some.template(并且正确加载:模板位于一个文件中,该文件在模块清单中的qweb键中正确定义)如下:

<div t-name="some.template">
<span class="val"><t t-esc="widget.count"/></span>
<button>Increment</button>
</div>

这个例子说明了小部件类的一些特性,包括事件系统、模板系统、带有初始父参数的构造函数。

小部件的生命周期

与许多组件系统一样,widget类有一个定义良好的生命周期。通常的生命周期如下:调用init,然后willStart,然后rendering,然后start,最后destroy。

Widget.init(parent)
这是构造函数。init方法应该初始化小部件的基本状态。它是同步的,可以被重写以从小部件的创建者/父对象获取更多参数。

Arguments : parent(Widget())–新的widget的父级,用于处理自动销毁和事件传播。对于没有父级的小部件,可以为null

Widget.willStart()
当一个小部件被创建并被附加到DOM的过程中,框架将调用这个方法一次。willstart方法是一个钩子,它应该返回一个deferred。JS框架将等待这个deferred完成,然后再继续渲染步骤。注意,此时小部件没有dom根元素。willstart钩子主要用于执行一些异步工作,例如从服务器获取数据。

[Rendering]()
此步骤由框架自动完成。框架会检查小部件上是否定义了template键。如果定义了,那么它将在呈现上下文中使用绑定到小部件的widget键呈现该模板(请参见上面的示例:我们在QWeb模板中使用widget.count来读取小部件的值)。如果没有定义模板,则读取 tagName 键并创建相应的DOM元素。渲染完成后,我们将结果设置为小部件的$el属性。在此之后,我们将自动绑定events和custom_events键中的所有事件。

Widget.start()
渲染完成后,框架将自动调用Start方法。这对于执行一些特殊的后期渲染工作很有用。例如,设置库。
必须返回deferred以指示其工作何时完成。

Returns: deferred 对象

Widget.destroy()
这始终是小部件生命周期中的最后一步。当小部件被销毁时,我们基本上执行所有必要的清理操作:从组件树中删除小部件,取消绑定所有事件,…
当小部件的父级被销毁时自动调用,如果小部件没有父级,或者如果它被删除但父级仍然存在,则必须显式调用。

请注意,不必调用willstart和start方法。可以创建一个小部件(将调用init方法),然后销毁(destroy方法),而不需要附加到DOM。如果是这种情况,将不会调用will start和start。

 

Widget API

Widget.tagName
如果小部件没有定义模板,则使用。默认为DIV,将用作标记名来创建要设置为小部件的dom根的dom元素。可以使用以下属性进一步自定义生成的dom根目录:

Widget.id
用于在生成的dom根上生成id属性。请注意,这是很少需要的,如果一个小部件可以多次使用,这可能不是一个好主意。

Widget.className
用于在生成的dom根上生成class属性。请注意,它实际上可以包含多个css类:“some-class other-class”

Widget.attributes
属性名到属性值的映射(对象文本)。这些k:v对中的每一个都将被设置为生成的dom根上的dom属性。

Widget.el
将原始dom元素设置为小部件的根(仅在start lifecyle方法之后可用)

Widget.$el
jquery封装的el,(仅在Start Lifecyle方法之后可用)

Widget.template
应设置为QWeb模板的名称。如果设置了,模板将在小部件初始化之后但在其启动之前呈现。模板生成的根元素将被设置为小部件的dom根。

Widget.xmlDependencies
呈现小部件之前需要加载的XML文件的路径列表。这不会导致加载已加载的任何内容。如果您想延迟加载模板,或者想要在网站和Web客户机界面之间共享一个小部件,这很有用。

var EditorMenuBar = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
...

Widget.events
事件是事件选择器(由空格分隔的事件名称和可选CSS选择器)到回调的映射。回调可以是小部件方法或函数对象的名称。在这两种情况下,这都将设置为小部件:

    'click p.oe_some_class a': 'some_method',
'change input': function (e) {
e.stopPropagation();
}
},

选择器用于jquery的事件委托,回调只对与选择器匹配的dom根的后代触发。如果选择器被省略(只指定了一个事件名),那么事件将直接设置在小部件的dom根上。
注意:不鼓励使用内联函数,将来可能会删除它。

Widget.custom_events

returns: true 如果小部件正在或者已经被销毁,否则false

Widget.$(selector)
将指定为参数的CSS选择器应用于小部件的dom根:
this.$(selector);
功能上与以下相同:
this.$el.find(selector);

arguments: selector(string)-CSS选择器
return:jQuery 对象

这个助手方法类似于Backbone.View.$

Widget.setElement(element)
将小部件的dom根重新设置为提供的元素,还处理重新设置dom根的各种别名以及取消设置和重新设置委托事件。

arguments: element(Element) -一个DOM元素或者jQuery对象设置为小部件的根DOM

在DOM中插入一个小部件

Widget.appendTo(element)
渲染小部件并将它作为子元素插入到目标元素后面,使用.appentTo()

Widget.prependTo(element)
渲染小部件并将它作为子元素插入到目标元素前面,使用.prependTo()

Widget.insertAfter(element)
渲染小部件并将它作为目标元素的前一个同级插入,使用.insertAfter()

Widget.insertBefore(element)
渲染小部件并将其作为目标的后一个同级插入,使用.insertBefore()

所有这些方法都接受相应jquery方法接受的任何内容(css选择器、dom节点或jquery对象)。他们都会返回一个 deferred,并承担三个任务:

    • 通过以下方式呈现小部件的根元素:
      renderElement()
    • 使用jquery在DOM中插入小部件的根元素
      匹配的方法
    • 启动小部件并返回启动结果
       

odoo12学习之javascript的更多相关文章

  1. HTML 学习笔记 JavaScript(面向对象)

    现在让我们继续跟着大神的脚步前进 学习一下JavaScript中的面向对象的思想,其实作为一个iOS开发者,对面向对象还是比较熟悉的,但是昨晚看了一下Js中的面向对象,妈蛋 一脸萌比啊.还好有大神.让 ...

  2. JavaScript学习13 JavaScript中的继承

    JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript"> //继承第一种方式 ...

  3. 前端之JavaScript第一天学习(1)-JavaScript 简介

    javaScript 是世界上最流行的编程语言. 这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. JavaScript 是脚本语言 JavaSc ...

  4. Javascript学习2 - Javascript中的表达式和运算符

    原文:Javascript学习2 - Javascript中的表达式和运算符 Javascript中的运算符与C/C++中的运算符相似,但有几处不同的地方,相对于C/C++,也增加了几个不同的运算符, ...

  5. Javascript学习1 - Javascript中的类型对象

    原文:Javascript学习1 - Javascript中的类型对象 1.1关于Numbers对象. 常用的方法:number.toString() 不用具体介绍,把数字转换为字符串,相应的还有一个 ...

  6. 学习现代 JavaScript 编程的最佳教程

    天天编码 , 版权所有丨本文标题:0.0 学习现代 JavaScript 编程的最佳教程 转载请保留页面地址:http://www.tiantianbianma.com/the-modern-java ...

  7. 学习笔记---Javascript事件Event、IE浏览器下的拖拽效果

    学习笔记---Javascript事件Event.IE浏览器下的拖拽效果     1. 关于event常用属性有returnValue(是否允许事件处理继续进行, false为停止继续操作).srcE ...

  8. 5月31日 python学习总结 JavaScript概述

    JavaScript概述 ECMAScript和JavaScript的关系 1996年11月,JavaScript的创造者--Netscape公司,决定将JavaScript提交给国际标准化组织ECM ...

  9. HTML 学习笔记 JavaScript (prototype)

    原博地址:http://www.cnblogs.com/dolphinX/p/3286177.html 原博客的作者是一个非常牛逼的前端大神,我作为一个初学者,在此借助大神的博客进行自己的学习.在这里 ...

随机推荐

  1. NX二次开发-获取面的外围边和孔槽边

    函数: UF_MODL_ask_face_loops()  获取面的所有封闭边组合(多组edge) UF_MODL_ask_loop_list_count() 获取loop的数量(面上孔.槽的数量+1 ...

  2. Linux命令大全之挂载命令

    理解:Linux挂载相当于Windows分配盘符 1.查询系统中已挂载的设备 mount 2.设置自动挂载 编辑文件/etc/fstab,把文件写入就可以启动自动挂载了, 注:一般不把光盘写入,如果写 ...

  3. HTTP头部POST表单详解

    2 POST /hello/checkUser.html?opt=xxx HTTP/1.1 方法的声明,Get,Post,Delete等 3 Accept: */* 4 Referer: http:/ ...

  4. vue项目使用Echarts制作项目工期甘特图

    目录 1,前言 2,布局和数据部分 3,制作甘特图 1,前言 项目迭代过程中,碰上一个需求,要求用甘特图的方式显示项目的工期进度,开完会我赶紧搜索一下甘特图是啥东东,大概了解之后,做出了如下样式 Ec ...

  5. Duilib的双缓冲实现,附带GDI、WTL的双缓冲实现

    前言: 闪烁问题,之前的经验是使用双缓冲,借此机会,把双缓冲的研究心得总结下. 双缓冲的含义: 缓冲这个词,相信大家都不陌生,Cache.主要是为了解决上下游(或者模块.或者系统)等性能不匹配问题.如 ...

  6. ECMAScript 2021 正式确认

    ECMAScript 2021 主要包含内容: ECMAScript 2021 于2021年6月22日获得 ECMA International 的批准.ECMAScript 是标准化的 JavaSc ...

  7. js笔记12

    1.元素的属性 div.attributes是所有标签属性构成的数组集合 div.classList是所有class名构成的数组集合 在classList的原型链上可以看到add()和remove() ...

  8. 让你发布的nuget包支持源代码调试

    前情概要 在不久的从前(也还是要以年为单位哈), 我们如果需要调试第三方代码, 或者框架代码很麻烦. 需要配置symbols, 匹配原始代码路径等. 为此, MS推出了 Source Link 功能, ...

  9. 在idea的控制台中中文显示为乱码

    显示乱码的原因不一定相同 我目前解决方法: -Dfile.encoding=UTF-8

  10. yield生成器demo代码

    <?phpfunction get_val1($n = 10000*100){ $arr = []; for($i = 0; $i <= $n;$i++){ $arr[] = $i; if ...