Javascript 參考手冊?

本文介紹了Odoo的Javascript框架。這個框架在代碼行數方面并不是一個大型應用程序,但它非常通用,因為它基本上是一個將聲明性接口描述轉換為能夠與數據庫中的每個模型和記錄進行交互的實時應用程序的機器。甚至可以使用Web客戶端修改Web客戶端的界面。

概覽?

Javascript框架旨在處理三種主要用例:

  • Web客戶端:這是私有Web應用程序,用戶可以查看和編輯業務數據。這是一個單頁應用程序(頁面不會重新加載,只有在需要時才從服務器獲取新數據)。

  • 網站:這是Odoo的公共部分。它允許未經身份驗證的用戶瀏覽一些內容,購物或執行許多操作,作為客戶端。這是一個經典的網站:各種路由與控制器以及一些JavaScript使其正常工作。

  • 銷售點( point of sale ):這是銷售點的界面。它是一個專門的單頁應用程序。

一些 JavaScript 代碼在這三種用例中是共用的,并且被捆綁在一起(請參見下面的資產部分)。本文檔將主要關注 Web 客戶端設計。

Web客戶端?

單頁應用程序?

簡而言之, webClient * ,即 * WebClient 的實例,是整個用戶界面的根組件。它的責任是協調所有不同的子組件,并提供服務,如rpcs、本地存儲等。

在運行時,Web客戶端是一個單頁應用程序。每次用戶執行操作時,它不需要從服務器請求完整的頁面。相反,它只請求所需內容,然后替換/更新視圖。此外,它管理URL:它與Web客戶端狀態保持同步。

這意味著當用戶使用Odoo時,Web客戶端類(和操作管理器)實際上會創建和銷毀許多子組件。狀態非常動態,每個小部件都可能隨時被銷毀。

Web客戶端JS代碼概述?

在這里,我們對 web/static/src/js 插件中的 Web 客戶端代碼進行了非??焖俚母攀?。請注意,這并不是詳盡無遺的。我們只涵蓋了最重要的文件/文件夾。

  • boot.js :這是定義模塊系統的文件。它需要首先被加載。

  • core/ : 這是一組較低級別的構建塊。特別地,它包含了類系統、小部件系統、并發實用工具和許多其他類/函數。

  • chrome/ : 在這個文件夾中,我們有大多數組成用戶界面的大型小部件。

  • chrome/abstract_web_client.jschrome/web_client.js :這兩個文件一起定義了 WebClient 小部件,它是 Web 客戶端的根小部件。

  • chrome/action_manager.js :這是將操作轉換為小部件的代碼(例如看板或表單視圖)

  • chrome/search_X.js 所有這些文件定義了搜索視圖(它不是從Web客戶端的角度來看的視圖,只是從服務器的角度來看的視圖)

  • fields :所有主視圖字段小部件在此定義

  • 視圖 :這是視圖所在的位置

如果文件未加載/更新該怎么辦?

有很多不同的原因可能導致文件無法正確加載。以下是您可以嘗試解決問題的一些方法:

  • 一旦服務器啟動,它就不知道資產文件是否已被修改。因此,您可以簡單地重新啟動服務器以重新生成資產。

  • 檢查控制臺(通常使用F12打開的開發工具)以確保沒有明顯的錯誤

  • 嘗試在文件開頭(在任何模塊定義之前)添加 console.log() ,這樣您就可以看到文件是否已加載

  • 當處于任何調試模式時,調試管理器菜單(bug 圖標)中有一個選項,可以強制服務器更新其資產文件。

  • 使用 debug=assets 模式。這將實際繞過資產包(請注意,這并不能真正解決問題。服務器仍然使用過時的資產包)

  • 最后,對于開發人員來說,最方便的方法是使用 –dev=all 選項啟動服務器。這將激活文件監視器選項,當需要時會自動使資產失效。請注意,如果操作系統是Windows,則其效果不是很好。

  • 記得刷新頁面!

  • 或者可能是為了保存您的代碼文件…

注解

一旦資產文件被重新創建,您需要刷新頁面,重新加載正確的文件(如果不起作用,則文件可能被緩存)。

加載 JavaScript 代碼?

通常將大型應用程序分解為較小的文件,這些文件需要連接在一起。某些文件可能需要使用在另一個文件中定義的代碼的某些部分。有兩種在文件之間共享代碼的方法:

  • 使用全局作用域( window 對象)來讀寫某些對象或函數的引用。

  • 使用一個模塊系統,為每個模塊提供導出或導入值的方式,并確保它們以正確的順序加載。

雖然在全局范圍內工作是可能的,但這會帶來許多問題:

很難確保在全局范圍內直接完成的工作不會暴露實現細節。

  • 依賴關系是隱式的,導致加載順序脆弱且不可靠。

  • 缺乏對執行的洞察意味著無法使用各種優化(例如延遲和異步加載)。

  • 模塊系統有助于解決這些問題:因為模塊指定了它們的依賴關系,模塊系統可以確保必要的加載順序得到尊重,因為模塊可以精確地指定它們的導出,所以它們泄漏實現細節的可能性較小。

對于大多數Odoo代碼,我們希望使用模塊系統。由于Odoo中資產的工作方式(特別是每個安裝的Odoo插件都可以修改包含在捆綁包中的文件列表),Odoo必須在瀏覽器端解析模塊。為此,Odoo提供了一個小型模塊系統,如下所述(請參見: Odoo 模塊系統 )。

然而,Odoo也提供了對本地javascript模塊的支持(參見 本地 JavaScript 模塊 )。這些模塊將被服務器簡單地轉換為Odoo模塊。鼓勵將所有javascript代碼編寫為本地模塊,以獲得更好的IDE集成。在未來,Odoo模塊系統應被視為實現細節,而不是編寫javascript代碼的主要方式。

注解

本地 JavaScript 模塊是定義 JavaScript 代碼的主要方式。

類系統?

Odoo在ECMAScript 6類可用之前開發。在ECMAScript 5中,定義類的標準方式是定義一個函數并在其原型對象上添加方法。這很好,但是當我們想要使用繼承、混入時,它略微復雜。

出于這些原因,Odoo決定使用自己的類系統,受John Resig啟發?;A類位于 web.Class * ,在文件 * class.js 中。

注解

請注意,自定義類系統不應用于創建新代碼。它將在某個時候被棄用,然后被刪除。新類應使用標準的ES6類系統。

創建子類?

讓我們討論如何創建類。主要機制是使用 extend 方法(這或多或少相當于ES6類中的 extend )。

var Class = require('web.Class');

var Animal = Class.extend({
    init: function () {
        this.x = 0;
        this.hunger = 0;
    },
    move: function () {
        this.x = this.x + 1;
        this.hunger = this.hunger + 1;
    },
    eat: function () {
        this.hunger = 0;
    },
});

在這個例子中, init 函數是構造函數。當創建一個實例時,它將被調用。使用 new 關鍵字來創建一個實例。

繼承?

繼承現有類非常方便。只需在超類上使用 extend * 方法即可完成。當調用方法時,框架會秘密重新綁定一個特殊方法: * _super * ,綁定到當前調用的方法。這使我們可以在需要調用父方法時使用 * this._super 。

var Animal = require('web.Animal');

var Dog = Animal.extend({
    move: function () {
        this.bark();
        this._super.apply(this, arguments);
    },
    bark: function () {
        console.log('woof');
    },
});

var dog = new Dog();
dog.move()

混入?

odoo類系統不支持多重繼承,但在需要共享某些行為的情況下,我們有一個mixin系統: extend 方法實際上可以接受任意數量的參數,并將它們組合在新類中。

var Animal = require('web.Animal');
var DanceMixin = {
    dance: function () {
        console.log('dancing...');
    },
};

var Hamster = Animal.extend(DanceMixin, {
    sleep: function () {
        console.log('sleeping');
    },
});

在這個例子中, Hamster 類是 Animal 的子類,但它也混合了 DanceMixin。

修改現有類?

雖然不常見,但有時我們需要 就地 修改另一個類。目標是擁有一種機制來更改類和所有未來/現有實例。這是通過使用 include 方法完成的:

var Hamster = require('web.Hamster');

Hamster.include({
    sleep: function () {
        this._super.apply(this, arguments);
        console.log('zzzz');
    },
});

這顯然是一項危險的操作,應該小心進行。但是,由于Odoo的結構方式,有時在一個插件中需要修改另一個插件中定義的小部件/類的行為。請注意,它將修改類的所有實例,即使它們已經被創建。

小部件?

Widget 類是用戶界面的一個重要構建塊。幾乎所有用戶界面都在小部件的控制下。Widget 類在 web.Widget 模塊中定義,在 widget.js 中。

簡而言之,Widget 類提供的功能包括:

  • 小部件之間的父/子關系( PropertiesMixin

  • 具有安全功能的廣泛生命周期管理(例如,在銷毀父級小部件期間自動銷毀子級小部件)

  • 使用 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 已經被正確加載(模板文件已經在模塊清單的資產中正確定義: 'assets': {'web.assets_qweb': [...]} ,請參見 assets )。模板內容如下:

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

這個示例小部件可以按照以下方式使用:

// Create the instance
var counter = new Counter(this, 4);
// Render and insert into DOM
counter.appendTo(".some-div");

此示例演示了 Widget 類的一些特性,包括事件系統、模板系統和帶有初始 parent 參數的構造函數。

小部件生命周期?

像許多組件系統一樣,小部件類具有明確定義的生命周期。通常的生命周期如下:首先調用 init * ,然后調用 * willStart * ,然后進行渲染,然后調用 * start * ,最后調用 * destroy 。

Widget.init(parent)?

這是構造函數。init方法應該初始化小部件的基本狀態。它是同步的,可以被覆蓋以從小部件的創建者/父級獲取更多參數。

參數
  • parent (Widget()) – 新小部件的父級,用于處理自動銷毀和事件傳播??梢詾?null ,表示小部件沒有父級。

Widget.willStart()?

當小部件被創建并在被附加到DOM的過程中,框架將調用此方法一次。 willStart 方法是一個鉤子,應該返回一個promise。JS框架將等待此promise完成,然后再移動到渲染步驟。請注意,在此時,小部件沒有DOM根元素。 willStart 鉤子主要用于執行一些異步工作,例如從服務器獲取數據。

[Rendering]()?

這一步是由框架自動完成的??蚣軙z查小部件上是否定義了模板鍵。如果是這樣,則會使用渲染上下文中的 widget 鍵將該模板與小部件一起呈現(請參見上面的示例:我們在QWeb模板中使用 widget.count * 讀取小部件的值)。如果沒有定義模板,則會讀取 * tagName 鍵并創建相應的DOM元素。渲染完成后,我們將結果設置為小部件的$el屬性。在此之后,我們會自動綁定在事件和custom_events鍵中的所有事件。

Widget.start()?

當渲染完成后,框架會自動調用 start 方法。這對于執行一些專門的后渲染工作非常有用。例如,設置一個庫。

必須返回一個 Promise 來指示其工作何時完成。

返回

承諾

Widget.destroy()?

這總是小部件生命周期的最后一步。當小部件被銷毀時,我們基本上執行所有必要的清理操作:從組件樹中刪除小部件,解除所有事件綁定,…

當小部件的父級被銷毀時自動調用,如果小部件沒有父級或者它被移除但其父級仍然存在,則必須顯式調用。

請注意,willStart和start方法不一定會被調用。一個小部件可以被創建(init方法將被調用),然后被銷毀(destroy方法)而從未被附加到DOM中。如果是這種情況,willStart和start甚至都不會被調用。

小部件 API?

Widget.tagName?

如果小部件沒有定義模板,則使用。默認為 div ,將用作標記名稱來創建DOM元素,以設置為小部件的DOM根??梢允褂靡韵聦傩赃M一步自定義此生成的DOM根:

Widget.id?

用于在生成的DOM根元素上生成“id”屬性。請注意,這很少需要,如果小部件可以被多次使用,則可能不是一個好主意。

Widget.className?

用于在生成的 DOM 根元素上生成 class 屬性。請注意,它實際上可以包含多個 CSS 類: ‘some-class other-class’

Widget.attributes?

屬性名到屬性值的映射(對象字面量)。每個這樣的鍵值對都將被設置為生成的DOM根元素的DOM屬性。

Widget.el?

將原始DOM元素設置為小部件的根(僅在啟動生命周期方法后可用)

Widget.$el?

jQuery包裝器,包裝了 el 。(僅在啟動生命周期方法后可用)

Widget.template?

應該設置為 QWeb 模板 的名稱。如果設置了,模板將在小部件初始化后但在啟動前進行渲染。由模板生成的根元素將被設置為小部件的 DOM 根。

Widget.xmlDependencies?

需要在小部件渲染之前加載的 XML 文件路徑列表。這不會導致已經加載的任何內容再次加載。當您想要懶加載模板或者想要在網站和 Web 客戶端界面之間共享小部件時,這非常有用。

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

事件是將事件選擇器(事件名稱和可選的 CSS 選擇器,由空格分隔)映射到回調函數的過程?;卣{函數可以是小部件方法的名稱或函數對象。在任何情況下, this 都將被設置為小部件:

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

選擇器用于jQuery的事件委托,回調函數僅在與選擇器匹配的DOM根節點的后代中觸發。如果省略選擇器(僅指定事件名稱),則事件將直接設置在小部件的DOM根節點上。

注意:不建議使用內聯函數,未來可能會將其刪除。

Widget.custom_events?

這與 events 屬性幾乎相同,但鍵是任意字符串。它們表示由某些子小部件觸發的業務事件。當事件被觸發時,它將“冒泡”到小部件樹中(有關更多詳細信息,請參見組件通信部分)。

Widget.isDestroyed()?
返回

如果小部件正在被銷毀或已被銷毀,則為 true ,否則為 false

Widget.$(selector)?

將指定的 CSS 選擇器應用于小部件的 DOM 根節點:

this.$(selector);

與以下功能相同:

this.$el.find(selector);
參數
  • selector (String()) – CSS 選擇器

返回

jQuery 對象

注解

這個輔助方法類似于 Backbone.View.$

Widget.setElement(element)?

將小部件的 DOM 根元素重置為提供的元素,還處理重置 DOM 根元素的各種別名以及取消設置和重新設置委托事件。

參數
  • element (Element()) – 一個 DOM 元素或 jQuery 對象,用于設置小部件的 DOM 根節點

將小部件插入DOM中?

Widget.appendTo(element)?

渲染小部件并將其插入目標的最后一個子元素中,使用 .appendTo()

Widget.prependTo(element)?

渲染小部件并將其作為目標的第一個子元素插入,使用 .prependTo()

Widget.insertAfter(element)?

渲染小部件并將其插入目標的前一個同級元素,使用 .insertAfter()

Widget.insertBefore(element)?

渲染小部件并將其插入目標的下一個兄弟節點,使用 .insertBefore()

所有這些方法都接受與相應的 jQuery 方法相同的參數(CSS 選擇器、DOM 節點或 jQuery 對象)。它們都返回一個 Promise,并負責三個任務:

  • 通過 renderElement() 渲染小部件的根元素

  • 使用與之匹配的任何 jQuery 方法將小部件的根元素插入 DOM 中

  • 啟動小部件,并返回啟動結果

小部件指南?

  • 應避免使用標識符( id 屬性)。在通用應用程序和模塊中, id 限制了組件的可重用性,并傾向于使代碼更加脆弱。大多數情況下,它們可以被替換為無、類或保留對 DOM 節點或 jQuery 元素的引用。

    如果絕對必要(因為第三方庫需要),則應該使用 _.uniqueId() 部分生成 id ,例如:

    this.id = _.uniqueId('my-widget-');
    
  • 避免使用可預測/常見的CSS類名。例如”content”或”navigation”等類名可能匹配所需的含義/語義,但很可能其他開發人員也有相同的需求,從而創建命名沖突和意外行為。通用類名應該以它們所屬的組件的名稱為前綴(創建”非正式”命名空間,就像在C或Objective-C中一樣)。

  • 應避免使用全局選擇器。因為一個組件可能在同一頁中使用多次(例如Odoo中的儀表板),查詢應該限制在給定組件的范圍內。未經過濾的選擇器,如 $(selector)document.querySelectorAll(selector) 通常會導致意外或不正確的行為。Odoo Web的 Widget() 有一個提供其DOM根節點的屬性( $el ),以及一個直接選擇節點的快捷方式( $() )。

  • 更一般地說,永遠不要假設你的組件擁有或控制除了自己的個人 $el 之外的任何東西(因此,避免使用對父部件的引用)

  • HTML模板/渲染應該使用QWeb,除非絕對微不足道。

  • 所有交互式組件(顯示信息或攔截DOM事件的組件)必須繼承自 Widget() ,并正確實現和使用其API和生命周期。

  • 請確保在使用 $el 之前等待啟動完成,例如:

    var Widget = require('web.Widget');
    
    var AlmostCorrectWidget = Widget.extend({
        start: function () {
            this.$el.hasClass(....) // in theory, $el is already set, but you don't know what the parent will do with it, better call super first
            return this._super.apply(arguments);
        },
    });
    
    var IncorrectWidget = Widget.extend({
        start: function () {
            this._super.apply(arguments); // the parent promise is lost, nobody will wait for the start of this widget
            this.$el.hasClass(....)
        },
    });
    
    var CorrectWidget = Widget.extend({
        start: function () {
            var self = this;
            return this._super.apply(arguments).then(function() {
                self.$el.hasClass(....) // this works, no promise is lost and the code executes in a controlled order: first super, then our code.
            });
        },
    });
    

QWeb模板引擎?

Web客戶端使用 QWeb模板 模板引擎來渲染小部件(除非它們覆蓋了 renderElement 方法以執行其他操作)。Qweb JS 模板引擎基于 XML,并且與 Python 實現大多兼容。

現在,讓我們解釋一下模板是如何加載的。每當Web客戶端啟動時,都會向 /web/webclient/qweb 路由發出 rpc 請求。然后,服務器將返回在每個已安裝模塊的數據文件中定義的所有模板列表。正確的文件在每個模塊清單的 web.assets_qweb 條目中列出。還可以通過調用相應的“bundle”查詢參數來懶加載另一個包的模板。

Web客戶端將在加載完模板列表后才開始啟動第一個小部件。

這種機制對我們的需求非常有效,但有時候,我們想要延遲加載一個模板。例如,假設我們有一個很少使用的小部件。在這種情況下,也許我們更愿意不在主文件中加載它的模板,以使Web客戶端稍微輕一些。在這種情況下,我們可以使用小部件的 xmlDependencies 鍵:

var Widget = require('web.Widget');

var Counter = Widget.extend({
    template: 'some.template',
    xmlDependencies: ['/myaddon/path/to/my/file.xml'],

    ...

});

通過這樣做, Counter 小部件將在其 willStart 方法中加載 xmlDependencies 文件,因此在執行渲染時模板將準備就緒。

事件系統?

Odoo目前支持兩種事件系統:一個簡單的系統,允許添加監聽器和觸發事件,以及一個更完整的系統,還可以使事件“冒泡”。

這兩個事件系統都是在 mixins.js 文件中的 EventDispatcherMixin 中實現的。這個 mixin 被包含在 Widget 類中。

基礎事件系統?

這個事件系統是歷史上第一個。它實現了一個簡單的總線模式。我們有4個主要方法:

  • on : 用于在事件上注冊監聽器。

  • off :用于移除事件監聽器。

  • once : 這用于注冊一個只會被調用一次的監聽器。

  • trigger : 觸發一個事件。這將導致每個監聽器被調用。

以下是如何使用此事件系統的示例:

var Widget = require('web.Widget');
var Counter = require('myModule.Counter');

var MyWidget = Widget.extend({
    start: function () {
        this.counter = new Counter(this);
        this.counter.on('valuechange', this, this._onValueChange);
        var def = this.counter.appendTo(this.$el);
        return Promise.all([def, this._super.apply(this, arguments)]);
    },
    _onValueChange: function (val) {
        // do something with val
    },
});

// in Counter widget, we need to call the trigger method:

... this.trigger('valuechange', someValue);

警告

不建議使用此事件系統,我們計劃使用擴展事件系統中的 trigger_up * 方法替換每個 * trigger 方法

擴展事件系統?

自定義事件小部件是一種更高級的系統,它模仿了DOM事件API。每當觸發事件時,它都會沿著組件樹“冒泡”,直到達到根小部件或被停止。

  • trigger_up : 這個方法將創建一個小的 OdooEvent 并在組件樹中分發它。請注意,它將從觸發事件的組件開始

  • custom_events : 這是 event 字典的等效項,但用于 Odoo 事件。

OdooEvent類非常簡單。它有三個公共屬性: target * (觸發事件的小部件), * name * (事件名稱)和 * data * (有效負載)。它還有兩個方法: * stopPropagation * 和 * is_stopped 。

可以更新前面的示例以使用自定義事件系統:

var Widget = require('web.Widget');
var Counter = require('myModule.Counter');

var MyWidget = Widget.extend({
    custom_events: {
        valuechange: '_onValueChange'
    },
    start: function () {
        this.counter = new Counter(this);
        var def = this.counter.appendTo(this.$el);
        return Promise.all([def, this._super.apply(this, arguments)]);
    },
    _onValueChange: function(event) {
        // do something with event.data.val
    },
});

// in Counter widget, we need to call the trigger_up method:

... this.trigger_up('valuechange', {value: someValue});

注冊表?

在Odoo生態系統中,常見的需求是從外部擴展/更改基礎系統的行為(通過安裝應用程序,即不同的模塊)。例如,可能需要在某些視圖中添加新的小部件類型。在這種情況下,以及許多其他情況下,通常的過程是創建所需的組件,然后將其添加到注冊表中(注冊步驟),以使Web客戶端的其余部分知道其存在。

系統中有幾個可用的注冊表:

字段注冊表(由 :js web.field_registry 導出)

字段注冊表包含所有已知的字段小部件。每當視圖(通常是表單或列表/看板)需要字段小部件時,它會在這里查找。典型的用例如下:

var fieldRegistry = require('web.field_registry');

var FieldPad = ...;

fieldRegistry.add('pad', FieldPad);

請注意,每個值都應該是 AbstractField 的子類。

查看注冊表

此注冊表包含所有已知于 Web 客戶端(尤其是視圖管理器)的 JS 視圖。此注冊表的每個值都應該是 AbstractView 的子類。

動作注冊表

我們在這個注冊表中跟蹤所有客戶端操作。這是操作管理器在需要創建客戶端操作時查找的地方。在版本11中,每個值應該只是 Widget 的子類。然而,在版本12中,這些值需要是 AbstractAction 。

小部件之間的通信?

組件之間有許多通信方式。

從父組件到子組件

這是一個簡單的情況。父部件可以直接調用其子部件的方法:

this.someWidget.update(someInfo);
從小部件到其父級/某個祖先

在這種情況下,小部件的工作僅是通知其環境發生了某些事情。由于我們不希望小部件引用其父級(這將使小部件與其父級的實現耦合),通常的做法是通過使用 trigger_up 方法觸發事件,該事件將在組件樹中向上冒泡:

this.trigger_up('open_record', { record: record, id: id});

此事件將在小部件上觸發,然后會冒泡并最終被某個上游小部件捕獲:

var SomeAncestor = Widget.extend({
    custom_events: {
        'open_record': '_onOpenRecord',
    },
    _onOpenRecord: function (event) {
        var record = event.data.record;
        var id = event.data.id;
        // do something with the event.
    },
});
跨組件

通過使用總線可以實現跨組件通信。這不是首選的通信形式,因為它的缺點是使代碼更難維護。但是,它具有解耦組件的優點。在這種情況下,只需在總線上觸發和監聽事件即可。例如:

  // in WidgetA
  var core = require('web.core');

  var WidgetA = Widget.extend({
      ...
      start: function () {
          core.bus.on('barcode_scanned', this, this._onBarcodeScanned);
      },
  });

  // in WidgetB
  var WidgetB = Widget.extend({
      ...
      someFunction: function (barcode) {
          core.bus.trigger('barcode_scanned', barcode);
      },
  });

In this example, we use the bus exported by *web.core*, but this is not
required. A bus could be created for a specific purpose.

服務?

在11.0版本中,我們引入了 服務 的概念。主要思想是為子組件提供一種受控的訪問環境的方式,以使框架具有足夠的控制,并且可以進行測試。

服務系統圍繞著三個概念:服務、服務提供者和小部件。它的工作方式是小部件觸發(使用 trigger_up )事件,這些事件向上冒泡到服務提供者,服務提供者將請求服務執行任務,然后可能返回答案。

服務?

服務是 AbstractService 類的一個實例。它基本上只有一個名稱和一些方法。它的工作是執行一些工作,通常是依賴于環境的某些東西。

例如,我們有 ajax 服務(用于執行 rpc), localStorage (與瀏覽器本地存儲交互)和許多其他服務。

這里是一個簡化的例子,展示了如何實現ajax服務:

var AbstractService = require('web.AbstractService');

var AjaxService = AbstractService.extend({
    name: 'ajax',
    rpc: function (...) {
        return ...;
    },
});

此服務名為’ajax’,定義了一個方法, rpc 。

服務提供商?

為了使服務正常工作,需要準備一個服務提供者來分派自定義事件。在 后端 (Web客戶端)中,這是由主Web客戶端實例完成的。請注意,服務提供者的代碼來自 ServiceProviderMixin 。

小部件?

小部件是請求服務的部分。為了做到這一點,它只需觸發一個事件 call_service * (通常使用助手函數 * call )。此事件將冒泡并將意圖傳達給系統的其余部分。

實際上,有些函數被頻繁調用,我們有一些輔助函數使它們更容易使用。例如, _rpc 方法是一個幫助程序,可以幫助進行 RPC 調用。

var SomeWidget = Widget.extend({
    _getActivityModelViewID: function (model) {
        return this._rpc({
            model: model,
            method: 'get_activity_view_id'
        });
    },
});

警告

如果一個小部件被銷毀,它將從主組件樹中分離出來,并且將沒有父級。在這種情況下,事件將不會冒泡,這意味著工作將不會完成。這通常正是我們從銷毀的小部件中想要的。

遠程過程調用(RPC)?

rpc 功能由 ajax 服務提供。但大多數人可能只與 _rpc 幫助程序交互。

在使用Odoo時通常有兩種用例:一種是需要調用(Python)模型上的方法(這通過控制器 call_kw 進行),另一種是需要直接調用控制器(在某些路由上可用)。

  • 在 Python 模型上調用方法:

return this._rpc({
    model: 'some.model',
    method: 'some_method',
    args: [some, args],
});
  • 直接調用控制器:

return this._rpc({
    route: '/some/route/',
    params: { some: kwargs},
});

通知?

Odoo 框架有一種標準的方式向用戶傳達各種信息:通知,這些通知顯示在用戶界面的右上角。

有兩種類型的通知:

  • notification : 用于顯示一些反饋信息。例如,當用戶取消訂閱頻道時。

  • warning : 用于顯示一些重要/緊急信息。通常用于系統中的大多數(可恢復的)錯誤。

此外,通知還可以用于向用戶提問,而不會干擾其工作流程。想象一下通過VOIP接收到的電話:可以顯示一個帶有兩個按鈕“接受”和“拒絕”的粘性通知。

通知系統?

Odoo的通知系統由以下組件設計而成:

  • 一個 通知 小部件:這是一個簡單的小部件,旨在創建并顯示所需的信息

  • a NotificationService : 一個服務,其職責是在請求完成時創建和銷毀通知(使用自定義事件)。請注意,Web 客戶端是服務提供者。

  • 客戶端動作 display_notification :這允許從 Python 觸發通知的顯示(例如,在用戶單擊對象類型按鈕時調用的方法中)。

  • ServiceMixin 中的輔助函數: displayNotification

顯示通知?

顯示通知的最常見方法是使用來自 ServiceMixin 的方法:

  • displayNotification(options) * :

    顯示具有以下 選項 的通知:

    • title : 字符串,可選。這將作為標題顯示在頂部。

    • subtitle : 字符串,可選。這將顯示在頂部作為副標題。

    • message : 字符串,可選。通知的內容。如果需要,請不要忘記通過標記函數轉義您的消息。

    • sticky : 布爾值,可選(默認為 false)。如果為 true,則通知將一直保留,直到用戶將其關閉。否則,通知將在短時間后自動關閉。

    • type : 字符串,可選(默認為’warning’)。確定通知的樣式??赡艿闹担骸痠nfo’,’success’,’warning’,’danger’,’’。

    • className : 字符串,可選。這是一個 CSS 類名,將自動添加到通知中。盡管不建議使用,但這可能對樣式有用。

以下是兩個使用這些方法的示例:

// note that we call _t on the text to make sure it is properly translated.
this.displayNotification({
    title: _t("Success"),
    message: _t("Your signature request has been sent.")
});
this.displayNotification({
    title: _t("Error"),
    message: _t("Filter name is required."),
    type: 'danger',
});

這里是一個 Python 示例:

# note that we call _(string) on the text to make sure it is properly translated.
def show_notification(self):
    return {
        'type': 'ir.actions.client',
        'tag': 'display_notification',
        'params': {
            'title': _('Success'),
            'message': _('Your signature request has been sent.'),
            'sticky': False,
        }
    }

系統托盤?

系統托盤是界面菜單欄的右側,Web客戶端在此處顯示一些小部件,例如消息菜單。

當 SystrayMenu 由菜單創建時,它將查找所有已注冊的小部件,并將它們添加為子小部件到適當的位置。

目前沒有專門的 API 用于系統托盤小部件。它們應該是簡單的小部件,并且可以像其他小部件一樣使用 trigger_up 方法與其環境進行通信。

添加新的系統托盤項目?

沒有系統托盤注冊表。添加小部件的正確方法是將其添加到類變量SystrayMenu.items中。

var SystrayMenu = require('web.SystrayMenu');

var MySystrayWidget = Widget.extend({
    ...
});

SystrayMenu.Items.push(MySystrayWidget);

排序?

在將小部件添加到自己之前,系統托盤菜單將按序列屬性對項目進行排序。如果原型中不存在該屬性,則默認使用50。因此,要將系統托盤項目定位到右側,可以設置一個非常高的序列號(反之,設置一個較低的數字將其放在左側)。

MySystrayWidget.prototype.sequence = 100;

翻譯管理?

有些翻譯是在服務器端完成的(基本上是由服務器呈現或處理的所有文本字符串),但是靜態文件中有需要翻譯的字符串。目前的工作方式如下:

  • 每個可翻譯的字符串都標記有特殊的函數 _t (在 JS 模塊 web.core 中可用)

  • 這些字符串用于服務器生成正確的 PO 文件

  • 每當 Web 客戶端被加載時,它會調用路由 /web/webclient/translations ,該路由返回所有可翻譯術語的列表

  • 在運行時,每當調用函數 _t 時,它將按順序在此列表中查找翻譯,如果找不到則返回原始字符串。

請注意,有關翻譯的更多詳細信息,從服務器的角度來看,請參閱文檔: 翻譯模塊 。

在 JavaScript 中,翻譯有兩個重要的函數: _t_lt 。它們的區別在于 _lt 是惰性求值的。

var core = require('web.core');

var _t = core._t;
var _lt = core._lt;

var SomeWidget = Widget.extend({
    exampleString: _lt('this should be translated'),
    ...
    someMethod: function () {
        var str = _t('some text');
        ...
    },
});

在這個例子中, _lt 是必要的,因為翻譯在模塊加載時還沒有準備好。

請注意,翻譯函數需要一些注意。傳遞的參數字符串不應該是動態的。

會話?

網頁客戶端提供了一個特定的模塊,其中包含一些與用戶當前 會話 相關的信息。一些值得注意的鍵包括

  • uid: 當前用戶ID(作為 res.users 的ID)

  • user_name:用戶名稱,字符串類型

  • 用戶上下文(用戶ID、語言和時區)

  • partner_id: 當前用戶關聯的合作伙伴的ID

  • db:當前正在使用的數據庫的名稱

向會話添加信息?

當加載/web路由時,服務器將在模板的腳本標記中注入一些會話信息。這些信息將從模型 ir.http * 的方法 * session_info 中讀取。因此,如果想要添加特定的信息,可以通過覆蓋session_info方法并將其添加到字典中來實現。

from odoo import models
from odoo.http import request


class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    def session_info(self):
        result = super(IrHttp, self).session_info()
        result['some_key'] = get_some_value_from_db()
        return result

現在,可以通過在會話中讀取它來在 JavaScript 中獲取該值:

var session = require('web.session');
var myValue = session.some_key;
...

請注意,此機制旨在減少 Web 客戶端準備所需的通信量。它更適用于計算成本較低的數據(慢速 session_info 調用將延遲每個 Web 客戶端的加載),以及在初始化過程的早期需要的數據。

視圖?

“view”這個詞有多個含義。本節是關于視圖的javascript代碼設計,而不是 arch 結構或其他任何內容的結構。

2017年,Odoo使用了新的架構替換了以前的視圖代碼。主要需要將渲染邏輯與模型邏輯分離。

現在,視圖(在通用意義上)由4個部分描述:視圖、控制器、渲染器和模型。這4個部分的API在AbstractView、AbstractController、AbstractRenderer和AbstractModel類中描述。

View Controller Renderer Model
  • 視圖是工廠。它的工作是獲取一組字段、arch、上下文和其他一些參數,然后構建一個控制器/渲染器/模型三元組。

    視圖的作用是正確設置MVC模式的每個部分,并提供正確的信息。通常,它必須處理arch字符串并提取每個視圖其他部分所需的數據。

    請注意,視圖是一個類,而不是一個小部件。一旦它的工作完成,它就可以被丟棄。

  • 渲染器的工作是代表在DOM元素中查看的數據。每個視圖可以以不同的方式呈現數據。此外,它應該監聽適當的用戶操作,并在必要時通知其父級(控制器)。

    渲染器是MVC模式中的V。

  • 模型:它的工作是獲取和保存視圖的狀態。通常,它以某種方式表示數據庫中的一組記錄。模型是“業務數據”的所有者。它是MVC模式中的M。

  • 控制器:它的工作是協調渲染器和模型。此外,它是Web客戶端的主要入口點。例如,當用戶在搜索視圖中更改某些內容時,控制器的 update 方法將被調用,并提供相應的信息。

    它是MVC模式中的C。

注解

視圖的JS代碼被設計為可在視圖管理器/操作管理器的上下文之外使用。它們可以在客戶端操作中使用,或者可以在公共網站上顯示(需要對資源進行一些處理)。

字段小部件?

網頁客戶端體驗的重要部分是編輯和創建數據。大部分工作都是通過字段小部件完成的,它們了解字段類型以及特定細節,如何顯示和編輯值。

抽象字段?

AbstractField 類是視圖中所有小部件的基類,適用于所有支持它們的視圖(目前:表單、列表、看板)。

v11字段小部件與以前版本之間存在許多差異。讓我們提到最重要的幾個:

  • 小部件在所有視圖之間共享(好吧,表單/列表/看板)。不再需要復制實現。請注意,可以通過在視圖注冊表中以視圖名稱為前綴來為視圖創建特定版本的小部件: list.many2one * 將優先于 * many2one 。

  • 小部件不再是字段值的所有者。它們只代表數據并與視圖的其余部分通信。

  • 小部件不再需要能夠在編輯模式和只讀模式之間切換?,F在,當需要進行此類更改時,小部件將被銷毀并重新呈現。這不是問題,因為它們無論如何都不擁有自己的值。

  • 字段小部件可以在視圖之外使用。它們的API略微有些笨拙,但它們被設計為獨立的。

裝飾?

與列表視圖一樣,字段小部件具有簡單的裝飾支持。裝飾的目的是為了簡單地指定文本顏色,取決于記錄的當前狀態。例如,

<field name="state" decoration-danger="amount &lt; 10000"/>

有效的裝飾名稱為:

  • 裝飾-bf

  • 裝飾-IT

  • 危險裝飾

  • 裝飾信息

  • 裝飾-柔和

  • 主要裝飾

  • 成功修飾

  • 警告裝飾

每個修飾符 decoration-X 將被映射到一個 CSS 類 text-X ,這是一個標準的 Bootstrap CSS 類(除了 text-ittext-bf ,它們由 Odoo 處理,分別對應斜體和粗體)。請注意,修飾符屬性的值應該是一個有效的 Python 表達式,它將在記錄作為評估上下文時進行評估。

非關系型字段?

我們在此記錄所有默認提供的非關系字段,沒有特定的順序。

整數 (FieldInteger)

這是整數類型字段的默認字段類型。

  • 支持的字段類型: 整數

    選項:

    • type:設置輸入類型(默認為 text * ,可以設置為 * number

    在編輯模式下,該字段呈現為一個輸入框,HTML 屬性類型設置為 number (因此用戶可以受益于本地支持,特別是在移動設備上)。在這種情況下,默認格式化被禁用以避免不兼容。

    <field name="int_value" options='{"type": "number"}'/>
    
    • step: 當用戶點擊按鈕時,將步長設置為向上和向下的值(僅適用于數字類型的輸入,默認為1)

    <field name="int_value" options='{"type": "number", "step": 100}'/>
    
    • 格式:是否需要格式化數字(默認為 true)

    默認情況下,數字按照本地化參數進行格式化。

    此選項將防止字段值被格式化。

    <field name="int_value" options='{"format": false}'/>
    
浮點數 (FieldFloat)

這是 float 類型字段的默認字段類型。

  • 支持的字段類型: 浮點數

屬性:

  • 數字:顯示精度

<field name="factor" digits="[42,5]"/>

選項:

  • type:設置輸入類型(默認為 text * ,可以設置為 * number

在編輯模式下,該字段呈現為一個輸入框,HTML 屬性類型設置為 number (因此用戶可以受益于本地支持,特別是在移動設備上)。在這種情況下,默認格式化被禁用以避免不兼容。

<field name="int_value" options='{"type": "number"}'/>
  • 步驟:當用戶點擊按鈕時,將步驟設置為向上和向下的值

    (僅適用于數字類型的輸入,默認為1)

<field name="int_value" options='{"type": "number", "step": 0.1}'/>
  • 格式:是否需要格式化數字(默認為 true)

默認情況下,數字按照本地化參數進行格式化。

此選項將防止字段值被格式化。

<field name="int_value" options='{"format": false}'/>
浮點時間 (FieldFloatTime)

該小部件的目標是正確顯示表示時間間隔(以小時為單位)的浮點值。例如,0.5應格式化為0:30,或4.75對應于4:45。

  • 支持的字段類型: 浮點數

float_factor(FieldFloatFactor)

該小部件旨在使用其選項中給定的因子正確顯示轉換后的浮點值。因此,例如,在數據庫中保存的值為0.5,因子為3,則小部件值應格式化為1.5。

  • 支持的字段類型: 浮點數

float_toggle (浮點數切換字段)

此小部件的目的是用一個包含一系列可能值(在選項中給出)的按鈕替換輸入字段。每次單擊都允許用戶在范圍內循環。這里的目的是將字段值限制為預定義的選擇。此外,該小部件支持因子轉換,就像 float_factor 小部件一樣(范圍值應該是轉換的結果)。

  • 支持的字段類型: 浮點數

<field name="days_to_close" widget="float_toggle" options='{"factor": 2, "range": [0, 4, 8]}'/>
布爾值(FieldBoolean)

這是 boolean 類型字段的默認字段類型。

  • 支持的字段類型: 布爾值

字符 (FieldChar)

這是 char 類型字段的默認字段類型。

  • 支持的字段類型: char

日期(FieldDate)

這是 日期 類型字段的默認字段類型。請注意,它也適用于日期時間字段。它在格式化日期時使用會話時區。

  • 支持的字段類型: 日期 , 日期時間

選項:

  • datepicker: datepicker_小部件的額外設置。

<field name="datefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
日期時間(FieldDateTime)

這是 datetime 類型字段的默認字段類型。

  • 支持的字段類型: 日期 , 日期時間

選項:

  • datepicker: datepicker_小部件的額外設置。

<field name="datetimefield" options='{"datepicker": {"daysOfWeekDisabled": [0, 6]}}'/>
日期范圍 (FieldDateRange)

此小部件允許用戶在單個選擇器中選擇開始和結束日期。

  • 支持的字段類型: 日期 , 日期時間

選項:

  • related_start_date:應用于結束日期字段以獲取開始日期值,該值用于在選擇器中顯示范圍。

  • related_end_date:應用于開始日期字段以獲取用于在選擇器中顯示范圍的結束日期值。

  • picker_options:選擇器的額外設置。

<field name="start_date" widget="daterange" options='{"related_end_date": "end_date"}'/>
remaining_days (剩余天數)

此小部件可用于日期和日期時間字段。在只讀模式下,它會顯示字段值與今天之間的差值(以天為單位)。該小部件旨在用于信息目的:因此在編輯模式下不能修改該值。

  • 支持的字段類型: 日期 , 日期時間

貨幣 (FieldMonetary)

這是用于“貨幣”類型字段的默認字段類型。它用于顯示貨幣。如果在選項中給出了貨幣字段,則將使用該字段,否則將回退到默認貨幣(在會話中)。

  • 支持的字段類型: 貨幣 , 浮點數

選項:

  • currency_field:另一個字段名稱,應該是貨幣的many2one類型。

<field name="value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
文本 (FieldText)

這是 文本 類型字段的默認字段類型。

  • 支持的字段類型: 文本

句柄 (HandleWidget)

該字段的作用是作為一個 句柄 顯示,并允許通過拖放來重新排序各個記錄。

警告

必須在記錄排序的字段上指定。

警告

在同一個列表中有多個帶有句柄小部件的字段是不支持的。

  • 支持的字段類型: 整數

電子郵件 (FieldEmail)

該字段顯示電子郵件地址。使用它的主要原因是在只讀模式下,它會呈現為帶有正確 href 的錨標簽。

  • 支持的字段類型: char

電話 (FieldPhone)

此字段顯示電話號碼。使用它的主要原因是它以只讀模式呈現為帶有正確 href 的錨標簽,但僅在某些情況下:我們只想在設備可以撥打此特定號碼時使其可點擊。

  • 支持的字段類型: char

網址 (UrlWidget)

該字段以只讀模式顯示URL。使用它的主要原因是它被呈現為帶有正確CSS類和href的錨標簽。

此外,錨點標簽的文本可以使用 text 屬性進行自定義(它不會改變 href 值)。

<field name="foo" widget="url" text="Some URL"/>

選項:

  • website_path: (默認值:false) 默認情況下,如果沒有設置此選項,小部件會強制(如果尚未)將href值以http://開頭,除非將此選項設置為true,從而允許重定向到數據庫自己的網站。

  • 支持的字段類型: char

域 (FieldDomain)

“Domain”字段允許用戶通過樹形界面構建技術前綴域,并實時查看所選記錄。在調試模式下,還有一個輸入框,可以直接輸入前綴字符域(或構建樹形界面不允許的高級域)。

請注意,此功能僅限于“靜態”域(無動態表達式或訪問上下文變量)。

  • 支持的字段類型: char

鏈接按鈕(LinkButton)

LinkButton小部件實際上只是顯示一個帶有圖標和文本值作為內容的span。鏈接是可點擊的,將使用其值作為URL在新的瀏覽器窗口中打開。

  • 支持的字段類型: char

圖片 (FieldBinaryImage)

此小部件用于將二進制值表示為圖像。在某些情況下,服務器返回“bin_size”而不是實際圖像(bin_size是表示文件大小的字符串,例如6.5kb)。在這種情況下,小部件將創建一個具有源屬性的圖像,該屬性對應于服務器上的圖像。

  • 支持的字段類型: 二進制

選項:

  • preview_image: 如果圖像僅作為 ‘bin_size’ 加載,則此選項可用于通知 Web 客戶端默認字段名不是當前字段的名稱,而是另一個字段的名稱。

  • accepted_file_extensions: 用戶可以從文件輸入對話框中選擇的文件擴展名(默認值為 image/* )(參見:<input type=”file”/> 上的 accept 屬性)

<field name="image" widget='image' options='{"preview_image":"image_128"}'/>
二進制(FieldBinaryFile)

通用小部件,用于保存/下載二進制文件。

  • 支持的字段類型: 二進制

選項:

  • accepted_file_extensions:用戶可以從文件輸入對話框中選擇的文件擴展名(參見:<input type=”file”/>上的 accept 屬性)

屬性:

  • 文件名:保存二進制文件將會丟失其文件名,因為它只保存二進制值。文件名可以保存在另一個字段中。為此,應將屬性文件名設置為視圖中存在的字段。

<field name="datas" filename="datas_fname"/>
優先級(PriorityWidget)

此小部件呈現為一組星星,允許用戶單擊它來選擇值或不選擇。例如,這對于標記任務為高優先級非常有用。

請注意,此小部件也可以在“只讀”模式下工作,這是不尋常的。

  • 支持的字段類型: 選擇

attachment_image (附件圖片)

用于 many2one 字段的圖像小部件。如果字段已設置,則此小部件將以正確的 src url 渲染為圖像。此小部件在編輯或只讀模式下沒有不同的行為,僅用于查看圖像。

  • 支持的字段類型: many2one

<field name="displayed_image_id" widget="attachment_image"/>
image_selection(圖像選擇)

允許用戶通過點擊圖像來選擇一個值。

  • 支持的字段類型: 選擇

選項:一個字典,將選擇值映射到一個對象,該對象具有圖像的URL( image_link * )和預覽圖像( * preview_link )。

請注意,此選項不可選!

<field name="external_report_layout" widget="image_selection" options="{
    'background': {
        'image_link': '/base/static/img/preview_background.png',
        'preview_link': '/base/static/pdf/preview_background.pdf'
    },
    'standard': {
        'image_link': '/base/static/img/preview_standard.png',
        'preview_link': '/base/static/pdf/preview_standard.pdf'
    }
}"/>
label_selection (標簽選擇)

此小部件呈現一個簡單的不可編輯標簽。它僅用于顯示信息,而不是編輯信息。

  • 支持的字段類型: 選擇

選項:

  • classes: 一個從選擇值到 CSS 類的映射

<field name="state" widget="label_selection" options="{
    'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger'}
}"/>
state_selection (StateSelectionWidget)

這是一個專門的選擇小部件。它假設記錄在視圖中有一些硬編碼的字段: stage_id * , * legend_normal * , * legend_blocked * , * legend_done 。這主要用于在項目中顯示和更改任務的狀態,并在下拉菜單中顯示其他信息。

  • 支持的字段類型: 選擇

<field name="kanban_state" widget="state_selection"/>
kanban_state_selection (狀態選擇小部件)

這個小部件與state_selection完全相同

  • 支持的字段類型: 選擇

list.state_selection (列表狀態選擇小部件)

在列表視圖中,默認情況下,state_selection小部件會在圖標旁邊顯示標簽。

  • 支持的字段類型: 選擇

選項:

  • hide_label: 隱藏圖標旁邊的標簽

<field name="kanban_state" widget="state_selection" options="{'hide_label': True}"/>
boolean_favorite(收藏小部件)

此小部件根據布爾值顯示為空星形或實星形。請注意,它也可以在只讀模式下進行編輯。

  • 支持的字段類型: 布爾值

布爾按鈕 (FieldBooleanButton)

布爾按鈕小部件旨在用于表單視圖中的狀態按鈕。其目標是顯示一個漂亮的按鈕,顯示布爾字段的當前狀態(例如,“激活”),并允許用戶在單擊它時更改該字段。

請注意,它也可以在只讀模式下編輯。

  • 支持的字段類型: 布爾值

選項:

  • 術語:可以是“active”、“archive”、“close”或具有鍵值“string_true”、“string_false”、“hover_true”和“hover_false”的自定義映射

<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
boolean_toggle (布爾開關)

顯示一個切換開關來表示布爾值。這是FieldBoolean的子字段,主要用于具有不同的外觀。

statinfo(狀態信息)

此小部件旨在在“統計”按鈕中表示統計信息。它基本上只是一個帶有數字的標簽。

  • 支持的字段類型: 整數,浮點數

選項:

  • label_field: 如果提供了該參數,小部件將使用 label_field 的值作為文本。

<button name="%(act_payslip_lines)d"
    icon="fa-money"
    type="action">
    <field name="payslip_count" widget="statinfo"
        string="Payslip"
        options="{'label_field': 'label_tasks'}"/>
</button>
百分比餅圖 (FieldPercentPie)

此小部件旨在在“統計”按鈕中表示統計信息。這類似于statinfo小部件,但信息以 餅圖 (從空到滿)的形式呈現。請注意,該值被解釋為百分比(介于0和100之間的數字)。

  • 支持的字段類型: 整數,浮點數

<field name="replied_ratio" string="Replied" widget="percentpie"/>
進度條 (FieldProgressBar)

將一個值表示為進度條(從0到某個值)

  • 支持的字段類型: 整數,浮點數

選項:

  • editable: 布爾值,表示該值是否可編輯

  • current_value: 從視圖中獲取必須存在的字段的當前值

  • max_value: 從視圖中必須存在的字段獲取 max_value

  • edit_max_value: 布爾值,如果最大值可編輯

  • 標題:顯示在頂部的欄的標題 –> 不翻譯,請使用參數(而不是選項)”title”

<field name="absence_of_today" widget="progressbar"
    options="{'current_value': 'absence_of_today', 'max_value': 'total_employee', 'editable': false}"/>
toggle_button(FieldToggleBoolean)

此小部件旨在用于布爾字段。它切換按鈕,切換綠色子彈/灰色子彈。它還根據值和一些選項設置工具提示。

  • 支持的字段類型: 布爾值

選項:

  • active:當布爾值為真時應設置的工具提示字符串

  • 未激活:當布爾值為假時應設置的工具提示

<field name="payslip_status" widget="toggle_button"
    options='{"active": "Reported in last payslips", "inactive": "To Report in Payslip"}'
/>
dashboard_graph(日志儀表板圖表)

這是一種更專業的小部件,用于顯示代表一組數據的圖形。例如,它在會計儀表板看板視圖中使用。

它假設該字段是一組數據的JSON序列化。

  • 支持的字段類型: char

屬性

  • 圖表類型:字符串,可以是’line’或’bar’

<field name="dashboard_graph_data"
    widget="dashboard_graph"
    graph_type="line"/>
ace(AceEditor)

此小部件旨在用于文本字段。它提供了用于編輯XML和Python的Ace編輯器。

  • 支持的字段類型: char,text

  • 徽章(FieldBadge)

    在 Bootstrap 徽章標簽中顯示數值。

    • 支持的字段類型: char * , * selection * , * many2one

    默認情況下,徽章具有淺灰色的背景,但可以通過使用decoration-X機制進行自定義。例如,在給定條件下顯示紅色徽章:

    <field name="foo" widget="badge" decoration-danger="state == 'cancel'"/>
    

關聯字段?

class web.relational_fields.FieldSelection()?

支持的字段類型: 選擇

web.relational_fields.FieldSelection.placeholder?

一個字符串,用于在未選擇任何值時顯示一些信息

<field name="tax_id" widget="selection" placeholder="Select a tax"/>
單選框 (FieldRadio)

這是 FielSelection 的子字段,但專門用于將所有有效選擇顯示為單選按鈕。

請注意,如果在many2one記錄上使用,則會執行更多的rpc以獲取相關記錄的name_gets。

  • 支持的字段類型: 選擇,many2one

選項:

  • 水平:如果為真,則單選按鈕將水平顯示。

<field name="recommended_activity_type_id" widget="radio"
    options="{'horizontal':true}"/>
selection_badge (字段選擇徽章)

這是FieldSelection的子字段,但專門用于將所有有效選擇顯示為矩形徽章。

  • 支持的字段類型: 選擇,many2one

<field name="recommended_activity_type_id" widget="selection_badge"/>
多對一 (FieldMany2One)

多對一字段的默認小部件。

  • 支持的字段類型: many2one

屬性:

  • can_create: 允許創建相關記錄(優先于 no_create 選項)

  • can_write: 允許編輯相關記錄(默認值:true)

選項:

  • quick_create: 允許快速創建相關記錄(默認值:true)

  • no_create: 防止創建相關記錄 - 隱藏“創建“xxx””和“創建和編輯…”下拉菜單項(默認值:false)

  • no_quick_create: 防止快速創建相關記錄 - 隱藏“創建“xxx””下拉菜單項(默認值:false)

  • no_create_edit: 隱藏“創建和編輯…”下拉菜單項(默認值:false)

  • create_name_field: 當創建相關記錄時,如果設置了此選項,則 create_name_field * 的值將填充為輸入值(默認值: * name

  • always_reload: boolean, 默認為false。如果為true,則小部件將始終執行額外的name_get以獲取其名稱值。這用于覆蓋name_get方法的情況(請不要這樣做)

  • no_open: 布爾值,默認為 false。如果設置為 true,則 many2one 在只讀模式下單擊記錄時不會重定向。

<field name="currency_id" options="{'no_create': True, 'no_open': True}"/>
list.many2one(列表字段Many2One)

在列表視圖中,many2one 字段的默認小部件。

用于列表視圖的many2one字段的特化。主要原因是我們需要將many2one字段(只讀模式下)呈現為文本,這不允許打開相關記錄。

  • 支持的字段類型: many2one

many2one_barcode(FieldMany2OneBarcode)

對于many2one字段的小部件允許從移動設備(Android/iOS)打開相機掃描條形碼。

特殊化的many2one字段,允許用戶使用本地相機掃描條形碼。然后使用name_search搜索此值。

如果設置了此小部件并且用戶未使用移動應用程序,則會回退到常規many2one(FieldMany2One)

  • 支持的字段類型: many2one

many2one_avatar(Many2OneAvatar)

此小部件僅支持指向繼承自’image.mixin’的模型的many2one字段。在只讀模式下,它會在顯示名稱旁邊顯示相關記錄的圖像。請注意,在這種情況下,顯示名稱不是可點擊的鏈接。在編輯模式下,它的行為與常規的many2one完全相同。

  • 支持的字段類型: many2one

many2one_avatar_user(Many2OneAvatarUser)

此小部件是Many2OneAvatar的特化版本。當單擊頭像時,我們會打開與相應用戶的聊天窗口。此小部件只能設置在指向’res.users’模型的many2one字段上。

  • 支持的字段類型: many2one (指向’res.users’)

many2one_avatar_employee (員工頭像選擇框)

與 Many2OneAvatarUser 相同,但用于指向 ‘hr.employee’ 的 many2one 字段。

  • 支持的字段類型: many2one (指向 ‘hr.employee’)

kanban.many2one(KanbanFieldMany2One)

在看板視圖中,用于many2one字段的默認小部件。我們需要在看板視圖中禁用所有編輯。

  • 支持的字段類型: many2one

多對多 (FieldMany2Many)

多對多字段的默認小部件。

  • 支持的字段類型: many2many

屬性:

  • 模式:字符串,顯示默認視圖

  • 域:將數據限制在特定的域中

選項:

  • create_text: 允許自定義添加新記錄時顯示的文本

  • link: 確定是否可以向關系添加記錄的域 (默認值:True)。

  • unlink: 確定是否可以從關系中刪除記錄的域(默認值:True)。

many2many_binary(多文件Many2Many二進制字段)

此小部件幫助用戶同時上傳或刪除一個或多個文件。

請注意,此小部件僅適用于模型“ir.attachment”。

  • 支持的字段類型: many2many

選項:

  • accepted_file_extensions:用戶可以從文件輸入對話框中選擇的文件擴展名(參見:<input type=”file”/>上的 accept 屬性)

many2many_tags(FieldMany2ManyTags)

將many2many顯示為標簽列表。

  • 支持的字段類型: many2many

選項:

  • create:域,用于確定是否可以創建新標簽(默認值:True)。

<field name="category_id" widget="many2many_tags" options="{'create': [['some_other_field', '>', 24]]}"/>
  • color_field: 數字字段的名稱,應該在視圖中存在。顏色將根據其值選擇。

<field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/>
  • no_edit_color: 設置為 True 以刪除更改標簽顏色的可能性(默認值:False)。

<field name="category_id" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': True}"/>
form.many2many_tags(多對多標簽表單字段)

特殊化的many2many_tags小部件,用于表單視圖。它有一些額外的代碼,允許編輯標簽的顏色。

  • 支持的字段類型: many2many

kanban.many2many_tags(KanbanFieldMany2ManyTags)

為看板視圖專門定制的many2many_tags小部件。

  • 支持的字段類型: many2many

many2many_checkboxes(多對多復選框字段)

該字段顯示一個復選框列表,允許用戶選擇其中的一部分選項。請注意,顯示的值數量限制為100個。這個限制是不可定制的。它只是允許處理極端情況,當該小部件錯誤地設置在一個具有巨大comodel的字段上時。在這些情況下,列表視圖更為適當,因為它允許分頁和過濾。

  • 支持的字段類型: many2many

一對多(FieldOne2Many)

一對多字段的默認小部件。

通常在子列表視圖或子看板視圖中顯示數據。

  • 支持的字段類型: one2many

選項:

  • create: 域,用于確定是否可以創建相關記錄(默認值:True)。

  • delete:域,確定是否可以刪除相關記錄(默認值:True)。

<field name="turtles" options="{'create': [['some_other_field', '>', 24]]}"/>
  • create_text:用于自定義“添加”標簽/文本的字符串。

<field name="turtles" options="{\'create_text\': \'Add turtle\'}"/>
狀態欄(FieldStatus)

這是一個非常專業的小部件,用于表單視圖。它是許多表單頂部的條形,表示流程,并允許選擇特定狀態。

  • 支持的字段類型: 選擇,many2one

引用 (FieldReference)

FieldReference 是一個選擇器(用于選擇模型)和 FieldMany2One(用于選擇值)的組合。它允許在任意模型上選擇記錄。

  • 支持的字段類型: char, reference

    選項:

    • model_field: 字段名,類型為 FieldMany2One(‘ir.model’),包含可選擇的記錄的模型。

      當設置了此選項時,FieldReference 的選擇部分不會被顯示。

小部件?

week_days (星期幾)

該字段顯示一個星期中每天的復選框列表,每天一個復選框,允許用戶選擇其中的一個子集。

<widget name="week_days"/>

客戶端操作?

客戶端操作的概念是將自定義小部件集成到Web客戶端界面中,就像 act_window_action 一樣。當您需要一個與現有視圖或特定模型不太相關的組件時,這非常有用。例如,討論應用程序實際上是一個客戶端操作。

客戶端操作是一個術語,其含義因上下文而異:

  • 從服務器的角度來看,它是一個 ir_action 模型的記錄,具有類型為char的 tag 字段

  • 從 Web 客戶端的角度來看,它是一個小部件,繼承自 AbstractAction 類,并應該在操作注冊表中使用相應的鍵(從字段 char)進行注冊。

每當菜單項與客戶端操作相關聯時,打開它將簡單地從服務器獲取操作定義,然后查找其操作注冊表以獲取適當鍵處的小部件定義,最后,它將實例化并將小部件附加到DOM中的適當位置。

添加客戶端操作?

客戶端操作是控制菜單欄下方屏幕部分的小部件。如果需要,它可以有控制面板。定義客戶端操作可以分為兩步:實現新小部件并在操作注冊表中注冊該小部件。

實現新的客戶端操作。

通過創建一個小部件來完成:

var AbstractAction = require('web.AbstractAction');

var ClientAction = AbstractAction.extend({
    hasControlPanel: true,
    ...
});
注冊客戶端操作:

通常情況下,我們需要讓Web客戶端了解客戶端操作與實際類之間的映射關系:

var core = require('web.core');

core.action_registry.add('my-custom-action', ClientAction);

然后,要在Web客戶端中使用客戶端操作,我們需要創建一個客戶端操作記錄(模型 ir.actions.client 的記錄),并具有適當的 tag 屬性:

<record id="my_client_action" model="ir.actions.client">
    <field name="name">Some Name</field>
    <field name="tag">my-custom-action</field>
</record>

使用控制面板?

默認情況下,客戶端操作不顯示控制面板。要顯示控制面板,需要執行幾個步驟。

  • hasControlPanel * 設置為 * true 。在小部件代碼中:

    var MyClientAction = AbstractAction.extend({
        hasControlPanel: true,
        loadControlPanel: true, // default: false
        ...
    });
    

    警告

    loadControlPanel 設置為 true 時,客戶端操作將自動獲取搜索視圖或控制面板視圖的內容。在這種情況下,應該像這樣指定模型名稱:

    init: function (parent, action, options) {
        ...
        this.controlPanelParams.modelName = 'model.name';
        ...
    }
    
  • 每當需要更新控制面板時,請調用方法 updateControlPanel 。例如:

    var SomeClientAction = Widget.extend({
        hasControlPanel: true,
        ...
        start: function () {
            this._renderButtons();
            this._update_control_panel();
            ...
        },
        do_show: function () {
             ...
             this._update_control_panel();
        },
        _renderButtons: function () {
            this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
            this.$buttons.on('click', ...);
        },
        _update_control_panel: function () {
            this.updateControlPanel({
                cp_content: {
                   $buttons: this.$buttons,
                },
            });
        }
    

updateControlPanel 是自定義控制面板內容的主要方法。更多信息請參考 `control_panel_renderer.js <https://github.com/odoo/odoo/blob/16.0/addons/web/static/src/js/views/control_panel/control_panel_renderer.js#L130>`_文件。