Odoo Javascript 參考指南

本文介紹了odoo javascript框架汪茧。從代碼行的角度來看,這個框架不是一個大的應(yīng)用程序蝶锋,但它是非常通用的,因為它基本上是一個將聲明性接口描述轉(zhuǎn)換為活動應(yīng)用程序的機器什往,能夠與數(shù)據(jù)庫中的每個模型和記錄交互扳缕。甚至可以使用Web客戶端修改Web客戶端的接口。

這里有一個有用的html版本的文檔:Javascript API

概覽

這個Javascript框架主要設(shè)計用于三個地方使用:

  • web客戶端:這是一個私有的web應(yīng)用别威,可以在其中查看和編輯業(yè)務(wù)數(shù)據(jù)躯舔。這是一個單頁應(yīng)用程序(永遠不會重新加載該頁,只在需要時從服務(wù)器提取新數(shù)據(jù))省古。
  • 網(wǎng)站:這是Odoo的公共部分粥庄。它允許身份不明的用戶作為客戶端瀏覽某些內(nèi)容、購物或執(zhí)行許多操作豺妓。這是一個經(jīng)典的網(wǎng)站:各種各樣的帶有控制器的路由和共同協(xié)作的Javascript代碼惜互。
  • POS:這是銷售點的接口布讹。它是一個特定的但也應(yīng)用程序。

Web客戶端

單頁應(yīng)用

簡而言之训堆,webclient實例是整個用戶界面的根組件描验。它的職責(zé)是協(xié)調(diào)所有的子組件,并提供服務(wù)坑鱼,如RPC膘流、本地存儲等等。

在運行時鲁沥,Web客戶端是單頁應(yīng)用程序呼股。每次用戶執(zhí)行操作時,它不需要從服務(wù)器請求整頁画恰。相反彭谁,它只請求它所需要的,然后替換/更新視圖阐枣。此外马靠,它還管理URL:它與Web客戶機狀態(tài)保持同步。

這意味著蔼两,當(dāng)用戶在處理odoo時甩鳄,Web客戶機類(和動作管理器)實際上創(chuàng)建并銷毀了許多子組件。狀態(tài)是高度動態(tài)的额划,每個小部件都可以隨時銷毀俊戳。

Web客戶端JS代碼概覽

這里,我們在web/static/src/js插件中快速概述了web客戶機代碼抑胎。注意,這是故意不詳盡的铭拧,我們只涉及最重要的文件/文件夾。

  • boot.js : 這是定義模塊系統(tǒng)的文件,它需要首先加載恃锉。
  • core/ : 這是較低級別的構(gòu)建基塊的集合破托。值得注意的是土砂,它包含類系統(tǒng)谜洽、小部件系統(tǒng)褥琐、并發(fā)實用程序和許多其他類/函數(shù)敌呈。
  • chorm/ :在這個文件夾中,我們有大多數(shù)大的小部件析显,它們構(gòu)成了大部分用戶界面谷异。
  • chrome/abstract_web_client.js and chrome/web_client.js : 這些文件一起定義了WebClient小部件(widget)歹嘹,它是Web客戶機的根小部件(wideget)尺上。
  • chrome/action_manager.js : 這是將動作(action)轉(zhuǎn)換為小部件(widget)(例如看板或表單視圖)的代碼怎抛。
  • chrome/search_X.js : 所有這些文件定義了搜索視圖(它不是Web客戶機視圖中的視圖马绝,僅從服務(wù)器視圖)
  • fields : 這里定義了所有主要字段視圖小部件(widget)
  • views : 這是視圖所在的位置

資源管理

在Odoo中管理資源并不像在其他應(yīng)用程序中那樣簡單。其中一個原因是耙饰,在其中一些情況中我們有各種各樣的狀態(tài)苟跪,但不是所有的資源都是必需的。例如元暴,Web客戶端茉盏、銷售點鸠姨、網(wǎng)站甚至移動應(yīng)用程序的需求是不同的讶迁。此外,有些資源可能很大祟峦,但很少需要搀愧。在這種情況下咱筛,我們有時希望它們被懶惰地加載迅箩。

主要思想是我們用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"/>

下面是當(dāng)服務(wù)器使用以下指令呈現(xiàn)模板時發(fā)生的情況:

  • 包中描述的所有SCSS文件都編譯為CSS文件。名為file.scss的文件將編譯在名為file.scss.css的文件中尘喝。
  • 如果我們在debug=assets模式
    ?? * t-js屬性設(shè)置為false的t-call-assets指令將替換為指向css文件的樣式表標記列表朽褪。
    ?? * t-css屬性設(shè)置為false的t-call-assets指令將替換為指向JS文件的腳本標記列表缔赠。
  • 如果我們不在debug=assets模式
    ?? * CSS文件將被連接并縮小嗤堰,然后拆分為不超過4096個規(guī)則的文件(以繞過IE9的舊限制)踢匣。然后,我們根據(jù)需要生成盡可能多的樣式表標簽
    ?? * JS文件被連接并縮小输莺,然后生成一個腳本標記模闲。

請注意尸折,資源文件是緩存的,因此從理論上講亮航,瀏覽器應(yīng)該只加載它們一次缴淋。

主包

當(dāng)odoo服務(wù)器啟動時重抖,它檢查包中每個文件的時間戳,如果需要恨统,它將創(chuàng)建/重新創(chuàng)建相應(yīng)的包畜埋。

以下是大多數(shù)開發(fā)人員需要知道的一些重要包:

  • web.assets_common : 此包包含Web客戶端、網(wǎng)站以及銷售點(POS)所共有的大多數(shù)資源狞玛。這應(yīng)該包含用于Odoo框架的較低級別的構(gòu)建塊。注意硬鞍,它包含boot.js文件固该,它定義了odoo模塊系統(tǒng)怔匣。
  • web.assets_backend :這個包包含特定于Web客戶端的代碼(特別是Web客戶端/動作管理器/視圖)
  • web.assets_frontend :這個包是關(guān)于所有特定于公共網(wǎng)站的:電子商務(wù)每瞒、論壇剿骨、博客浓利、事件管理…

在一個資源包里添加文件

將位于addons/web中的文件添加到bundle的正確方法很簡單:只需將腳本或樣式表標記添加到文件webclient_templates.xml中的bundle即可。但是當(dāng)我們使用不同的插件(addon)時羽资,我們需要從該插件添加一個文件屠升。在這種情況下,應(yīng)分三步進行:

  1. 添加一個 assets.xml 文件到views/文件夾
  2. 添加字符'views/assets.xml' 到manifest文件的鍵'data'的值里
  3. 創(chuàng)建所需包的繼承視圖脏答,并使用xpath表達式添加文件。例如:
<template id="assets_backend" name="helpdesk assets" inherit_id="web.assets_backend">
    <xpath expr="http://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>

請注意黄绩,當(dāng)用戶加載odoo web客戶端時筑煮,包中的所有文件都會立即加載真仲。這意味著每次通過網(wǎng)絡(luò)傳輸文件(瀏覽器緩存處于活動狀態(tài)時除外)。在某些情況下,最好使用Lazyload的一些資產(chǎn)墓懂。例如,如果一個小部件需要一個大的庫榜跌,而這個小部件不是體驗的核心部分,那么在實際創(chuàng)建小部件時础浮,最好只加載庫豆同。widget類實際上已經(jīng)為這個用例內(nèi)置了支持。(查閱QWeb模板引擎部分)

如果文件沒有加載/更新應(yīng)該怎么辦

文件可能無法正確加載有許多不同的原因鸭廷。您可以嘗試以下幾點來解決此問題:

  • 一旦服務(wù)器啟動,它就不知道資源文件是否已被修改佛吓。因此淤刃,您可以簡單地重新啟動服務(wù)器來重新生成資源。
  • 檢查控制臺(在開發(fā)工具中铝侵,通常用F12打開)咪鲜,確保沒有明顯的錯誤
  • 嘗試在文件的開頭添加console.log(在任何模塊定義之前),這樣您就可以查看文件是否已加載享郊。
  • 在用戶界面中,在調(diào)試模式下(在此處插入鏈接到調(diào)試模式)温自,有一個選項可以強制服務(wù)器更新其資源文件。
  • 使用debug=assets模式馆里。這實際上會繞過資源包(請注意,它實際上并不能解決問題营密,服務(wù)器仍然使用過時的包)
  • 最后纷捞,對于開發(fā)人員來說,最方便的方法是使用--dev=all選項啟動服務(wù)器糜值。這將激活文件監(jiān)視程序選項,必要時將自動使資源無效。請注意累贤,如果操作系統(tǒng)是Windows硼被,它就不能很好地工作检访。
  • 記住刷新頁面脆贵!
  • 或者保存代碼文件…

重新創(chuàng)建資源文件后,需要刷新頁面,重新加載正確的文件(如果不起作用系吭,文件可能被緩存了)沃缘。

Javascript模塊系統(tǒng)

一旦我們能夠?qū)⑽覀兊膉avascript文件加載到瀏覽器中,我們就需要確保以正確的順序加載它們峰档。為了實現(xiàn)這一點,odoo定義了一個小模塊系統(tǒng)(位于addons/web/static/src/js/boot.js文件中欢顷,需要首先加載該文件)捉蚤。

在AMD的啟發(fā)下抬驴,odoo模塊系統(tǒng)通過在全局odoo對象上定義函數(shù)define來工作。然后我們通過調(diào)用該函數(shù)來定義每個javascript模塊缆巧。在odoo框架中布持,模塊是一段將盡快執(zhí)行的代碼。它有一個名稱题暖,可能還有一些依賴項。當(dāng)它的依賴項被加載時捉超,模塊也將被加載胧卤。模塊的值就是定義模塊的函數(shù)的返回值。

一個例子拼岳,看起來像這樣:

// 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;
});

定義模塊的另一種方法是在第二個參數(shù)中明確地給出依賴項列表枝誊。

odoo.define('module.Something', ['module.A', 'module.B'], function (require) {
    "use strict";

    var A = require('module.A');
    var B = require('module.B');

    // some code
});

如果某些依賴項丟失/未就緒,那么模塊將不會被加載惜纸。幾秒鐘后控制臺中將出現(xiàn)警告叶撒。

請注意,不支持循環(huán)依賴項堪簿。這是有道理的痊乾,但這意味著需要謹慎。

定義一個模塊

odoo.define 方法給了三個參數(shù):

  • moduleName: javascript模塊的名稱椭更。它應(yīng)該是一個唯一的字符串哪审。慣例是在odoo插件(addon)的名字后面加上一個具體的描述。例如虑瀑,“web.widget”描述在web插件中定義的模塊湿滓,該模塊導(dǎo)出一個widget類(因為第一個字母大寫)滴须。
    如果名稱不唯一,將引發(fā)異常并顯示在控制臺中
  • dependencies : 第二個參數(shù)是可選的叽奥。如果給定扔水,它應(yīng)該是一個字符串列表,每個字符串對應(yīng)一個JavaScript模塊朝氓。這描述了在執(zhí)行模塊之前需要加載的依賴項魔市。如果這里沒有明確地給出依賴項,那么模塊系統(tǒng)將通過調(diào)用ToString從函數(shù)中提取它們赵哲,然后使用regexp查找所有Require語句待德。
  • 最后一個參數(shù)是定義模塊的函數(shù)。它的返回值是模塊的值枫夺,可以傳遞給其他需要它的模塊将宪。注意,異步模塊有一個小的異常橡庞,請參見下一節(jié)较坛。
    如果發(fā)生錯誤,將在控制臺中記錄(在調(diào)試模式下):
  • Missing dependencies: 這些模塊不會出現(xiàn)在頁面中扒最〕笄冢可能是javascript文件不在頁面中或模塊名稱錯誤
  • Failed Modules : 一個Javascript錯誤被檢測到
  • Rejected modules :塊返回拒絕的延遲。它(及其相關(guān)模塊)未加載扼倘。
  • Rejected linked modules: 依賴被拒絕模塊的模塊
  • Non loaded modules : 模塊依賴了一個缺失/失敗的模塊

異步模塊

模塊可能需要在準備就緒之前執(zhí)行一些工作色迂。例如掉蔬,它可以做一個RPC來加載一些數(shù)據(jù)。在這種情況下宏邮,模塊只需返回一個deferred(promise)颜曾。在這種情況下纠拔,模塊系統(tǒng)只需等待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;
    });
});

最好的練習(xí)

  • 記住模塊名的約定:插件名加上模塊名后綴
  • 在模塊頂部聲明所有依賴項稠诲。此外,它們應(yīng)該按模塊名稱的字母順序排序诡曙。這樣更容易理解您的模塊臀叙。
  • 在末尾聲明所有導(dǎo)出的值
  • 盡量避免從一個模塊導(dǎo)出過多的內(nèi)容。通常最好在一個(小/更屑勐薄)模塊中簡單地導(dǎo)出一件事情劝萤。
  • 異步模塊可以用來簡化一些用例。例如慎璧,web.dom_ready模塊返回一個deferred 床嫌,當(dāng)dom實際就緒時跨释,這個deferred 將被解決。因此厌处,另一個需要dom的模塊可以在某個地方簡單地有一個require(“web.dom_ready”)語句鳖谈,并且只有當(dāng)dom準備好時才會執(zhí)行代碼。
  • 盡量避免在一個文件中定義多個模塊阔涉。這在短期內(nèi)可能很方便缆娃,但實際上很難維護。

類系統(tǒng)

Odoo是在ECMAScript 6類可用之前開發(fā)的瑰排。在ECMAScript 5中龄恋,定義類的標準方法是定義一個函數(shù)并在其原型對象上添加方法。這很好凶伙,但是當(dāng)我們想要使用繼承郭毕、混合時,它稍微復(fù)雜一些函荣。
出于這些原因显押,Odoo決定使用自己的類系統(tǒng),這是受到約翰·雷西格的啟發(fā)傻挂〕吮基類位于web.class文件class.js中。

創(chuàng)建一個子類

讓我們討論如何創(chuàng)建類金拒。主要機制是使用extend方法(這或多或少相當(dāng)于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函數(shù)是構(gòu)造函數(shù)绪抛。它將在創(chuàng)建實例時調(diào)用资铡。通過使用new關(guān)鍵字創(chuàng)建實例。

繼承

可以方便地繼承現(xiàn)有的類幢码。這只需在超類上使用extend方法即可完成笤休。當(dāng)調(diào)用一個方法時,框架會秘密地將一個特殊的方法_super重新綁定到當(dāng)前調(diào)用的方法中症副。這允許我們在需要調(diào)用父方法時使用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類系統(tǒng)不支持多重繼承,但是對于那些需要共享某些行為的情況贞铣,我們有一個混合系統(tǒng):extend方法實際上可以接受任意數(shù)量的參數(shù)闹啦,并將它們組合到新的類中。

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

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

在這個例子中辕坝,Hamter 類是Animal的子類窍奋,但是它也混合了DanceMixin.

修改現(xiàn)有的類

這并不常見,但有時我們需要在適當(dāng)?shù)奈恢眯薷牧硪粋€類。目標是有一個機制來改變一個類和所有未來/現(xiàn)在的實例费变。這是通過使用include方法完成的:

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

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

這顯然是一個危險的操作摧扇,應(yīng)該小心操作。但是挚歧,按照odoo的結(jié)構(gòu)扛稽,有時需要在一個插件中修改在另一個插件中定義的widget/class的行為。請注意滑负,它將修改類的所有實例在张,即使它們已經(jīng)創(chuàng)建。

小部件(Widget)

widget類實際上是用戶界面的一個重要構(gòu)建塊矮慕。幾乎用戶界面中的所有內(nèi)容都在小部件(widget)的控制之下帮匾。widget類在widget.js中的module web.widget中定義。
簡而言之痴鳄,widget類提供的特性包括:

  • 小部件之間的父/子關(guān)系(PropertiesMixin)
  • **具有安全功能的廣泛生命周期管理 **(e.g. 在銷毀父級期間自動銷毀子窗口小部件)
  • 自動渲染qweb模板
  • 幫助與外部環(huán)境交互的各種實用功能瘟斜。
    一個計數(shù)的小部件例子:
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);
    },
});

對于本例,假設(shè)模板some.template(并且正確加載:模板位于一個文件中痪寻,該文件在模塊清單中的qweb鍵中正確定義)如下:

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

這個例子說明了小部件類的一些特性螺句,包括事件系統(tǒng)、模板系統(tǒng)橡类、帶有初始父參數(shù)的構(gòu)造函數(shù)蛇尚。

小部件的生命周期

與許多組件系統(tǒng)一樣,widget類有一個定義良好的生命周期顾画。通常的生命周期如下:調(diào)用init取劫,然后willStart,然后rendering研侣,然后start谱邪,最后destroy。

Widget.init(parent)
這是構(gòu)造函數(shù)义辕。init方法應(yīng)該初始化小部件的基本狀態(tài)虾标。它是同步的寓盗,可以被重寫以從小部件的創(chuàng)建者/父對象獲取更多參數(shù)灌砖。

Arguments : parent(Widget())–新的widget的父級,用于處理自動銷毀和事件傳播傀蚌。對于沒有父級的小部件基显,可以為null

Widget.willStart()
當(dāng)一個小部件被創(chuàng)建并被附加到DOM的過程中善炫,框架將調(diào)用這個方法一次撩幽。willstart方法是一個鉤子,它應(yīng)該返回一個deferred。JS框架將等待這個deferred完成窜醉,然后再繼續(xù)渲染步驟宪萄。注意,此時小部件沒有dom根元素榨惰。willstart鉤子主要用于執(zhí)行一些異步工作拜英,例如從服務(wù)器獲取數(shù)據(jù)。

[Rendering]()
此步驟由框架自動完成琅催【有祝框架會檢查小部件上是否定義了template鍵。如果定義了藤抡,那么它將在呈現(xiàn)上下文中使用綁定到小部件的widget鍵呈現(xiàn)該模板(請參見上面的示例:我們在QWeb模板中使用widget.count來讀取小部件的值)侠碧。如果沒有定義模板,則讀取 tagName 鍵并創(chuàng)建相應(yīng)的DOM元素缠黍。渲染完成后弄兜,我們將結(jié)果設(shè)置為小部件的$el屬性。在此之后瓷式,我們將自動綁定events和custom_events鍵中的所有事件挨队。

Widget.start()
渲染完成后,框架將自動調(diào)用Start方法蒿往。這對于執(zhí)行一些特殊的后期渲染工作很有用盛垦。例如,設(shè)置庫瓤漏。
必須返回deferred以指示其工作何時完成腾夯。

Returns: deferred 對象

Widget.destroy()
這始終是小部件生命周期中的最后一步。當(dāng)小部件被銷毀時蔬充,我們基本上執(zhí)行所有必要的清理操作:從組件樹中刪除小部件蝶俱,取消綁定所有事件,…
當(dāng)小部件的父級被銷毀時自動調(diào)用饥漫,如果小部件沒有父級榨呆,或者如果它被刪除但父級仍然存在,則必須顯式調(diào)用庸队。

請注意积蜻,不必調(diào)用willstart和start方法〕瓜可以創(chuàng)建一個小部件(將調(diào)用init方法)竿拆,然后銷毀(destroy方法),而不需要附加到DOM宾尚。如果是這種情況丙笋,將不會調(diào)用will start和start谢澈。

Widget API

Widget.tagName
如果小部件沒有定義模板,則使用御板。默認為DIV锥忿,將用作標記名來創(chuàng)建要設(shè)置為小部件的dom根的dom元素〉±撸可以使用以下屬性進一步自定義生成的dom根目錄:

Widget.id
用于在生成的dom根上生成id屬性缎谷。請注意,這是很少需要的灶似,如果一個小部件可以多次使用列林,這可能不是一個好主意。

Widget.className
用于在生成的dom根上生成class屬性酪惭。請注意希痴,它實際上可以包含多個css類:“some-class other-class”

Widget.attributes
屬性名到屬性值的映射(對象文本)。這些k:v對中的每一個都將被設(shè)置為生成的dom根上的dom屬性春感。

Widget.el
將原始dom元素設(shè)置為小部件的根(僅在start lifecyle方法之后可用)

Widget.$el
jquery封裝的el砌创,(僅在Start Lifecyle方法之后可用)

Widget.template
應(yīng)設(shè)置為QWeb模板的名稱。如果設(shè)置了鲫懒,模板將在小部件初始化之后但在其啟動之前呈現(xiàn)嫩实。模板生成的根元素將被設(shè)置為小部件的dom根。

Widget.xmlDependencies
呈現(xiàn)小部件之前需要加載的XML文件的路徑列表窥岩。這不會導(dǎo)致加載已加載的任何內(nèi)容甲献。如果您想延遲加載模板,或者想要在網(wǎng)站和Web客戶機界面之間共享一個小部件颂翼,這很有用晃洒。

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

Widget.events
事件是事件選擇器(由空格分隔的事件名稱和可選CSS選擇器)到回調(diào)的映射‰Γ回調(diào)可以是小部件方法或函數(shù)對象的名稱球及。在這兩種情況下,這都將設(shè)置為小部件:

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

選擇器用于jquery的事件委托呻疹,回調(diào)只對與選擇器匹配的dom根的后代觸發(fā)吃引。如果選擇器被省略(只指定了一個事件名),那么事件將直接設(shè)置在小部件的dom根上刽锤。
注意:不鼓勵使用內(nèi)聯(lián)函數(shù)镊尺,將來可能會刪除它。

Widget.custom_events

returns: true 如果小部件正在或者已經(jīng)被銷毀姑蓝,否則false

Widget.$(selector)
將指定為參數(shù)的CSS選擇器應(yīng)用于小部件的dom根:
this.$(selector);
功能上與以下相同:
this.$el.find(selector);

arguments: selector(string)-CSS選擇器
return:jQuery 對象

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

Widget.setElement(element)
將小部件的dom根重新設(shè)置為提供的元素鹅心,還處理重新設(shè)置dom根的各種別名以及取消設(shè)置和重新設(shè)置委托事件。

arguments: element(Element) -一個DOM元素或者jQuery對象設(shè)置為小部件的根DOM

在DOM中插入一個小部件

Widget.appendTo(element)
渲染小部件并將它作為子元素插入到目標元素后面纺荧,使用.appentTo()

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

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

Widget.insertBefore(element)
渲染小部件并將其作為目標的后一個同級插入宙暇,使用.insertBefore()

所有這些方法都接受相應(yīng)jquery方法接受的任何內(nèi)容(css選擇器输枯、dom節(jié)點或jquery對象)。他們都會返回一個 deferred占贫,并承擔(dān)三個任務(wù):

  • 通過以下方式呈現(xiàn)小部件的根元素:
    renderElement()
  • 使用jquery在DOM中插入小部件的根元素
    匹配的方法
  • 啟動小部件并返回啟動結(jié)果

小部件指南

  • 在一般應(yīng)用和模塊中中桃熄,標識 (id 屬性)應(yīng)該避免使用,ID限制了組件的可重用性型奥,并使代碼更加脆弱瞳收。大多數(shù)情況下,它們可以替換為Nothing厢汹、Classes或保留對dom節(jié)點或jquery元素的引用螟深。
    如果ID是絕對必要的(因為第三方庫需要一個),則應(yīng)使用_.uniqueId()部分生成ID烫葬,例如:
    this.id = _.uniqueId('my-widget-');
  • 避免使用可預(yù)測/通用的CSS類名界弧。類名稱(如“content”或“navigation”)可能與所需的含義/語義匹配,但其他開發(fā)人員可能也有相同的需求搭综,從而造成命名沖突和意外行為垢箕。通用類名的前綴應(yīng)該是它們所屬組件的名稱(創(chuàng)建“非正式”名稱空間,就像在C或Objective-C中那樣)兑巾。
  • 應(yīng)避免使用全局選擇器条获。因為一個組件可以在一個頁面中多次使用(ODoo中的一個例子是儀表板),所以查詢應(yīng)該限制在給定組件的范圍內(nèi)蒋歌。未篩選的選擇(如$(selector)document.querySelectorAll(selector))通常會導(dǎo)致意外或錯誤的行為月匣。odoo web的widget()有一個提供dom根(el)的屬性,以及直接選擇節(jié)點的快捷方式(_().)
  • 更一般地說奋姿,不要假設(shè)您的組件擁有或控制任何超出其個人$el的東西(因此锄开,避免使用對父部件的引用)。
  • HTML模板/渲染應(yīng)該使用QWeb称诗,除非非常簡單萍悴。
  • 所有交互組件(向屏幕顯示信息或截取DOM事件的組件)必須繼承自widget(),并正確實現(xiàn)和使用其API和生命周期寓免。

Qweb模板引擎

Web客戶端使用QWeb模板引擎來呈現(xiàn)小部件(除非它們重寫renderelement方法來執(zhí)行其他操作)癣诱。QWebJS模板引擎基于XML,主要與Python實現(xiàn)兼容袜香。

現(xiàn)在撕予,讓我們解釋如何加載模板。每當(dāng)Web客戶端啟動時蜈首,都會對/web/web client/qweb路由進行RPC实抡。然后欠母,服務(wù)器將返回在每個已安裝模塊的數(shù)據(jù)文件中定義的所有模板的列表。正確的文件列在每個模塊清單的QWeb條目中吆寨。

在啟動第一個小部件之前赏淌,Web客戶機將等待加載該模板列表。

這個機制可以很好地滿足我們的需求啄清,但有時我們希望懶加載模板六水。例如,假設(shè)我們有一個很少使用的小部件辣卒。在這種情況下掷贾,我們可能不希望將其模板加載到主文件中,以便使Web客戶機稍微輕一些荣茫。在這種情況下想帅,我們可以使用小部件的xmlpendencies鍵:

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

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

    ...

});

有了這個,計數(shù)器小部件將以willstart方法加載xmlpendencies文件计露,這樣在執(zhí)行呈現(xiàn)時模板就可以準備好了博脑。

事件系統(tǒng)

目前,odoo支持兩個事件系統(tǒng):一個允許添加偵聽器和觸發(fā)事件的簡單系統(tǒng)票罐,以及一個更完整的系統(tǒng)叉趣,它還可以使事件“冒泡”。

這兩個事件系統(tǒng)都在文件mixins.js的eventspatchemixin中實現(xiàn)该押。這個mixin包含在widget類中疗杉。

基礎(chǔ)事件系統(tǒng)

這是歷史上第一個事件系統(tǒng)。它實現(xiàn)了一個簡單的總線模式蚕礼。我們有4種主要方法:

  • on : 在一個事件上注冊監(jiān)聽器
  • off: 移除事件的監(jiān)聽器
  • once: 注冊一個只使用一次的監(jiān)聽器
  • trigger:跟蹤一個事件烟具。這會調(diào)用所有監(jiān)聽器。
    一下是一個怎么使用事件系統(tǒng)的例子:
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 $.when(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);

\color{#FFC125}{警告}
不鼓勵使用此事件系統(tǒng)奠蹬,我們計劃用擴展事件系統(tǒng)中的trigger-up方法替換每個trigger方法朝聋。

擴展的事件系統(tǒng)

自定義事件小部件是一個更高級的系統(tǒng),它模擬DOM事件API囤躁。每當(dāng)一個事件被觸發(fā)時冀痕,它將“冒泡”組件樹,直到它到達根小部件狸演,或者停止言蛇。

  • trigger_up:這是一種方法,它將創(chuàng)建一個小的odooEvent并將其分派到組件樹中宵距。請注意腊尚,它將從觸發(fā)事件的組件開始
  • custom_events:這相當(dāng)于事件字典,但是對于odoo事件來說满哪。
    OdoEvent類非常簡單婿斥。它有三個公共屬性:target(觸發(fā)事件的小部件)劝篷、name(事件名稱)和data(有效負載)。它還有兩種方法:stopPropagation 和 is_stopped.受扳。
    上一個示例可以更新為使用自定義事件系統(tǒng):
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 $.when(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生態(tài)系統(tǒng)的一個常見需求是從外部擴展/更改基本系統(tǒng)的行為(通過安裝應(yīng)用程序携龟,即不同的模塊)兔跌。例如勘高,可能需要在某些視圖中添加新的小部件類型。在這種情況下坟桅,以及其他許多情況下华望,通常的過程是創(chuàng)建所需的組件,然后將其添加到注冊表(注冊步驟)仅乓,以使Web客戶機的其余部分知道它的存在赖舟。
一下是一些在系統(tǒng)中可用的注冊:

  • 字段注冊表(由“web.field_registry”導(dǎo)出)。字段注冊表包含Web客戶端已知的所有字段小部件夸楣。每當(dāng)視圖(通常是表單或列表/看板)需要字段小部件時宾抓,這就是它將要查找的地方。典型的用例如下所示:
var fieldRegistry = require('web.field_registry');

var FieldPad = ...;

fieldRegistry.add('pad', FieldPad);

注意豫喧,每個值都應(yīng)該是AbstractField的子類

  • 視圖注冊表:此注冊表包含Web客戶端已知的所有JS視圖

(尤其是視圖管理器)石洗。此注冊表的每個值都應(yīng)該是AbstractView的子類

  • 動作注冊表:我們跟蹤此注冊表中的所有客戶端動作。這個是動作管理器在需要創(chuàng)建客戶端操作時查找的位置紧显。在版本11中讲衫,每個值應(yīng)該只是小部件的一個子類。但是孵班,在版本12中涉兽,值必須是abstractAction。

小部件之間的通信

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

  • 從父級到它的子級
    一個簡單的例子篙程。父不見可以簡單的調(diào)用子部件方法:
    this.someWidget.update(someInfo);

  • 從一個小部件到它的父/某個祖先
    在這種情況下枷畏,小部件的工作只是通知其環(huán)境發(fā)生了什么事情。由于我們不希望小部件具有對其父部件的引用(這將使小部件與其父部件的實現(xiàn)相結(jié)合)虱饿,因此繼續(xù)操作的最佳方法通常是使用觸發(fā)器trigger_up方法觸發(fā)一個事件拥诡,該事件將冒泡到組件樹中:
    this.trigger_up('open_record', { record: record, id: id});
    此事件將在小部件上觸發(fā),然后將冒泡并最終被某些上游小部件捕獲:

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.
    },
});
  • 交叉組件
    通過總線可以實現(xiàn)跨組件通信郭厌。這不是首選的通信形式袋倔,因為它有使代碼難以維護的缺點。但是折柠,它具有分離組件的優(yōu)勢宾娜。在這種情況下,這只是通過觸發(fā)和監(jiān)聽總線上的事件來完成的扇售。例如:
// 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);
    },
});

在本例中前塔,我們使用web.core導(dǎo)出的總線嚣艇,但這不是必需的』可以為特定目的創(chuàng)建總線食零。

服務(wù)services

在11.0版中,我們引入了服務(wù)的概念寂屏。主要的想法是給子組件一種受控制的方式來訪問它們的環(huán)境贰谣,這種方式允許框架進行足夠的控制,并且是可測試的迁霎。
服務(wù)系統(tǒng)圍繞三個理念進行組織:services吱抚、service providers和widget。它的工作方式是小部件觸發(fā)(使用trigger_up)事件考廉,這些事件冒泡到服務(wù)提供者秘豹,服務(wù)提供者將要求服務(wù)執(zhí)行任務(wù),然后可能返回一個答案昌粤。

服務(wù)service

服務(wù)是AbstractService類的實例既绕。它基本上只有一個名稱和一些方法。它的工作是執(zhí)行一些工作涮坐,通常是一些依賴于環(huán)境的工作凄贩。
例如,我們有Ajax服務(wù)(任務(wù)是執(zhí)行RPC)膊升、本地存儲(與瀏覽器本地存儲交互)和許多其他服務(wù)怎炊。
以下是有關(guān)如何實現(xiàn)Ajax服務(wù)的簡化示例:

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

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

這個服務(wù)被叫做‘a(chǎn)jax’而且定義了一個方法,rpc.

服務(wù)提供者Service Provider

為了使服務(wù)正常工作廓译,有必要讓一個服務(wù)提供者準備好分派定制事件评肆。在后端(Web客戶端),這是由主Web客戶端實例完成的非区。請注意瓜挽,服務(wù)提供程序的代碼來自ServiceProviderMin。

部件widget

小部件是請求服務(wù)的部分征绸。為了做到這一點久橙,它只需觸發(fā)一個事件調(diào)用服務(wù)(通常通過使用helper函數(shù)調(diào)用)。此事件將冒泡并將意圖傳達給系統(tǒng)的其余部分管怠。
在實踐中淆衷,有些函數(shù)被頻繁地調(diào)用,以至于我們有一些助手函數(shù)使它們更容易使用渤弛。例如祝拯,_rpc方法是幫助生成rpc的助手。

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

\color{#FFC125}{警告}
如果一個小部件被銷毀,它將從主組件樹中分離出來佳头,并且沒有父組件鹰贵。在這種情況下,事件不會冒泡康嘉,這意味著工作不會完成碉输。這通常正是我們從一個被破壞的小部件中想要的。

RPCs

RPC功能由Ajax服務(wù)提供亭珍。但大多數(shù)人可能只會與_rpc助手進行交互敷钾。
在處理odoo時,通常有兩個用例:一個需要在(python)模型上調(diào)用方法(這需要通過控制器call_kw)块蚌,或者一個需要直接調(diào)用控制器(在某些路由上可用)膘格。

  • 在python模型中調(diào)用方法
return this._rpc({
    model: 'some.model',
    method: 'some_method',
    args: [some, args],
});
  • 直接調(diào)用控制器
return this._rpc({
    route: '/some/route/',
    params: { some: kwargs},
});

通知

odoo框架有一種標準的方式來向用戶傳遞各種信息:通知峭范,它顯示在用戶界面的右上角。
通知有兩種類型:

  • notification: 有助于顯示一些反饋瘪贱。例如纱控,每當(dāng)用戶取消訂閱某個頻道時。
  • warning:用于顯示一些重要/緊急信息菜秦。通常是系統(tǒng)中的大多數(shù)(可恢復(fù)的)錯誤甜害。

此外,通知還可以用于向用戶詢問問題球昨,而不會干擾其工作流尔店。想象一下,通過VoIP接收到的一個電話呼叫:可以顯示一個帶有兩個按鈕的粘性通知:接受和拒絕主慰。

通知系統(tǒng)

Odoo中的通知系統(tǒng)設(shè)計有以下組件:

  • a Notification widget:這是一個簡單的小部件嚣州,用于創(chuàng)建和顯示所需的信息。
  • a NotificationService:一種服務(wù)共螺,其職責(zé)是在請求完成時(使用custom_event)創(chuàng)建和銷毀通知该肴。請注意,Web客戶端是一個服務(wù)提供者藐不。
  • ServiceMixin中的兩個助手功能:do_notify和do_warn

顯示通知

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

  • do_notify(title, message, sticky, className):
    顯示一個通知類型的通知:
    ?? * title:string. 將會在頂部顯示為標題
    ?? * message:string. 通知的內(nèi)容
    ?? * sticky : boolean,optional. 如果為真匀哄,這個通知將會一直保留直到用戶解除。否則雏蛮,通知將會在一段很短的時間之后自動關(guān)閉涎嚼。
    ?? * calssname:string,optional.這是一個css類的名字,將會自動添加到通知中挑秉。這對樣式有用法梯,即使不鼓勵使用它。

  • do_warn(title, message, sticky, className):
    顯示一個警告類型的通知衷模。
    ?? * title:string.將會在頂部顯示為標題
    ?? * message:string.通知的內(nèi)容
    ?? * sticky : boolean,optional. 如果為真鹊汛,這個通知將會一直保留直到用戶解除蒲赂。否則,通知將會在一段很短的時間之后自動關(guān)閉刁憋。
    ?? * calssname:string,optional.這是一個css類的名字滥嘴,將會自動添加到通知中。這對樣式有用至耻,即使不鼓勵使用它若皱。
    這里有兩個如何使用這兩個方法的例子:

// note that we call _t on the text to make sure it is properly translated.
this.do_notify(_t("Success"), _t("Your signature request has been sent."));

this.do_warn(_t("Error"), _t("Filter name is required."));

任務(wù)欄Systray

Systray是界面菜單欄的右側(cè)部分,Web客戶端在其中顯示一些小部件尘颓,如消息菜單走触。
當(dāng)菜單創(chuàng)建SystrayMenu時,它將查找所有已注冊的小部件疤苹,并將它們作為子小部件添加到適當(dāng)?shù)奈恢谩?br> 目前沒有針對Systray小工具的特定API互广。它們應(yīng)該是簡單的小部件,并且可以像使用trigger-up方法的其他小部件一樣與環(huán)境通信卧土。

添加一個新得任務(wù)欄項目

沒有Systray注冊表惫皱。添加小部件的正確方法是將其添加到類變量systraymenu.items中。

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

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

SystrayMenu.Items.push(MySystrayWidget);

排序Ordering

在將小部件添加到自己之前尤莺,Systray菜單將按Sequence屬性對項目進行排序旅敷。如果原型上不存在該屬性,則將使用50颤霎。因此媳谁,要將Systray項目定位在靠后,可以設(shè)置一個非常高的序列號(反之友酱,將其放在靠前的是一個較低的序列號)晴音。
MySystrayWidget.prototype.sequence = 100;

翻譯管理

有些翻譯是在服務(wù)器端進行的(基本上是由服務(wù)器呈現(xiàn)或處理的所有文本字符串),但是靜態(tài)文件中有需要翻譯的字符串粹污。它目前的工作方式如下:

  • 每個可翻譯字符串都帶有特殊的函數(shù)_t(可在js模塊web.core中找到)
  • 服務(wù)器使用這些字符串生成正確的PO文件段多。
  • 每當(dāng)加載Web客戶機時,它將調(diào)用route/web/web client/translations壮吩,它返回所有可翻譯術(shù)語的列表
  • 在運行時进苍,每當(dāng)調(diào)用函數(shù)時,它都會在該列表中查找以查找轉(zhuǎn)換鸭叙,如果找不到轉(zhuǎn)換觉啊,則返回它或原始字符串。

請注意沈贝,在文檔翻譯模塊中杠人,從服務(wù)器的角度對翻譯進行了更詳細的解釋。
javascript中的翻譯有兩個重要功能:_t和_lt。區(qū)別在于_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罩引。
注意各吨,翻譯功能需要注意。參數(shù)中給定的字符串不應(yīng)是動態(tài)的袁铐。

會話Session

Web客戶端提供了一個特定的模塊揭蜒,其中包含一些特定于用戶當(dāng)前會話的信息。一些顯著的鍵是:

  • uid:當(dāng)前用戶的id(來自于表 res.users 的ID)
  • user_name: 用戶的名字剔桨,字符串類型
  • 用戶上下文(context [用戶id,語言和時區(qū)])
  • partner_id: 與當(dāng)前用戶關(guān)聯(lián)的合作伙伴的ID
  • db:當(dāng)前使用的數(shù)據(jù)庫名字

添加信息到會話中

加載/web路由后屉更,服務(wù)器將在模板中插入一些會話信息和腳本標記。信息將從模型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

現(xiàn)在似舵,通過在會話中讀取該值,可以在javascript中獲取該值:

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

請注意葱峡,此機制旨在減少Web客戶端準備就緒所需的通信量。它更適合于計算成本較低的數(shù)據(jù)(緩慢的session_info調(diào)用將延遲為每個人加載Web客戶端)龙助,以及在初始化過程早期需要的數(shù)據(jù)砰奕。

視圖

“視圖”一詞有多種含義。本節(jié)是關(guān)于視圖的JavaScript代碼的設(shè)計提鸟,而不是Arch的結(jié)構(gòu)或其他任何內(nèi)容军援。
2017年,Odoo用新架構(gòu)替換了先前的視圖代碼称勋。主要需要將呈現(xiàn)邏輯與模型邏輯分開胸哥。
視圖(一般意義上)現(xiàn)在用4個部分描述:視圖、控制器赡鲜、渲染器和模型空厌。這4個部分的API在AbstractView、AbstractController银酬、AbstractRenderer和AbstractModel類中進行了描述嘲更。


視圖結(jié)構(gòu).png
  • 視圖是工廠。它的工作是獲取一組字段揩瞪、arch赋朦、上下文和其他一些參數(shù),然后構(gòu)造一個控制器/渲染器/模型三元組。
    視圖的作用是用正確的信息正確地設(shè)置MVC模式的每一部分宠哄。通常壹将,它必須處理arch字符串,并提取視圖中彼此所需的數(shù)據(jù)毛嫉。
    請注意瞭恰,視圖是一個類,而不是一個小部件狱庇。一旦它的工作完成惊畏,它就可以被丟棄。
  • 渲染器有一個作業(yè):表示在DOM元素中查看的數(shù)據(jù)密任。每個視圖都可以以不同的方式呈現(xiàn)數(shù)據(jù)颜启。此外,它應(yīng)該監(jiān)聽適當(dāng)?shù)挠脩舨僮骼嘶洌⒃诒匾獣r通知其父級(Controller)缰盏。
    渲染器是MVC模式中的V.
  • 模型:它的工作是獲取并保持視圖的狀態(tài)。通常淹遵,它以某種方式表示數(shù)據(jù)庫中的一組記錄口猜。該模型是“業(yè)務(wù)數(shù)據(jù)”的所有者。它是MVC模式中的M.
  • Controller:它的工作是協(xié)調(diào)渲染器和模型透揣。此外济炎,它是Web客戶端其余部分的主要入口點。例如辐真,當(dāng)用戶在搜索視圖中更改某些內(nèi)容時须尚,將使用適當(dāng)?shù)男畔⒄{(diào)用控制器的更新方法。
    它是MVC模式中的C.

視圖的JS代碼已設(shè)計為可在視圖管理器/操作管理器的上下文之外使用侍咱。它們可以用于客戶端操作耐床,也可以顯示在公共網(wǎng)站上(對資源進行一些處理)

字段部件

該AbstractField類是在一個視圖中的所有控件的基類,用于支持他們所有的視圖(目前為:表格楔脯,列表撩轰,看板)。
v11字段小部件與先前版本之間存在許多差異昧廷。讓我們提一下最重要的一些:

  • 小部件在所有視圖之間共享(表單/列表/看板)堪嫂。無需再復(fù)制實現(xiàn)。請注意麸粮,可以為視圖設(shè)置特定版本的窗口小部件溉苛,方法是在視圖注冊表中為視圖名稱添加前綴:list.many2one將優(yōu)先于many2one選擇。
  • 小部件不再是字段值的所有者弄诲。它們僅表示數(shù)據(jù)并與視圖的其余部分進行通信愚战。
  • 小部件不再需要能夠在編輯和只讀模式之間切換〗课ǎ現(xiàn)在,當(dāng)需要進行此類更改時寂玲,窗口小部件將被銷毀并再次重新呈現(xiàn)塔插。這不是問題,因為他們無論如何都不擁有自己的價值
  • 字段小部件可以在視圖之外使用拓哟。他們的API略顯笨拙想许,但它們的設(shè)計是獨立的。

裝飾與列表視圖一樣断序,字段小部件對裝飾具有簡單的支持流纹。裝飾的目標是有一種簡單的方法來根據(jù)記錄當(dāng)前狀態(tài)指定文本顏色。例如违诗,

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

有效的裝飾名字有:

  • decoration-bf
  • decoration-it
  • decoration-danger
  • decoration-info
  • decoration-muted
  • decoration-primary
  • decoration-success
  • decoration-warning
    每個裝飾decoration-X將映射到css類text-X漱凝,這是一個標準的bootstrap css類(text-it和text-bf除外,它們由odoo處理并分別對應(yīng)于斜體和粗體)诸迟。請注意茸炒,decoration屬性的值應(yīng)該是一個有效的python表達式,它將使用記錄作為評估上下文進行評估阵苇。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壁公,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绅项,更是在濱河造成了極大的恐慌紊册,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趁怔,死亡現(xiàn)場離奇詭異湿硝,居然都是意外死亡,警方通過查閱死者的電腦和手機润努,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來示括,“玉大人铺浇,你說我怎么就攤上這事《庀ィ” “怎么了鳍侣?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吼拥。 經(jīng)常有香客問我倚聚,道長,這世上最難降的妖魔是什么凿可? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任惑折,我火速辦了婚禮授账,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惨驶。我一直安慰自己白热,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布粗卜。 她就那樣靜靜地躺著水孩,像睡著了一般介陶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天衙猪,我揣著相機與錄音,去河邊找鬼瘪撇。 笑死形导,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砌些。 我是一名探鬼主播呜投,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼存璃!你這毒婦竟也來了仑荐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤纵东,失蹤者是張志新(化名)和其女友劉穎粘招,沒想到半個月后偎球,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袍冷,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡淌友,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年瑰抵,在試婚紗的時候發(fā)現(xiàn)自己被綠了婿崭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛球。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氯葬,到底是詐尸還是另有隱情,我是刑警寧澤闯睹,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布妄讯,位于F島的核電站躬窜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仍劈,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望组民。 院中可真熱鬧臭胜,春花似錦、人聲如沸仪壮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铆铆。三九已至薄货,卻和暖如春柄慰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背概行。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勤家,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓伤提,卻偏偏與公主長得像介汹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容