vuvivian's blog

越努力,越幸运.

  1. 1. ### 概览
  • Web客户端
    1. 1. 单页应用
  • Web客户端JS代码概览
  • 资源管理
  • 主包
  • 在一个资源包里添加文件
  • 如果文件没有加载/更新应该怎么办
  • Javascript模块系统
  • 定义一个模块
  • 异步模块
  • 最好的练习
  • ### 概览

    这个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文件中定义。看起来是这样的:

    1
    2
    3
    4
    5
    6
    <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指令将捆绑包中的文件插入到模板中:

    1
    2
    <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表达式添加文件。例如:
    1
    2
    3
    4
    5
    6
    <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框架中,模块是一段将尽快执行的代码。它有一个名称,可能还有一些依赖项。当它的依赖项被加载时,模块也将被加载。模块的值就是定义模块的函数的返回值。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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;
    });

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

    1
    2
    3
    4
    5
    6
    7
    8
    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完成,然后注册模块。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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准备好时才会执行代码。
    • 尽量避免在一个文件中定义多个模块。这在短期内可能很方便,但实际上很难维护。
    本文最后更新于 天前,文中所描述的信息可能已发生改变