odoo12學習之javascript


本文來源: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中插入小部件的根元素
      匹配的方法
    • 啟動小部件並返回啟動結果
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM