概覽
這個(gè)Javascript框架主要設(shè)計(jì)用于三個(gè)地方使用:
- web客戶端:這是一個(gè)私有的web應(yīng)用详拙,可以在其中查看和編輯業(yè)務(wù)數(shù)據(jù)屡萤。這是一個(gè)單頁應(yīng)用程序(永遠(yuǎn)不會(huì)重新加載該頁拾枣,只在需要時(shí)從服務(wù)器提取新數(shù)據(jù))。
- 網(wǎng)站:這是Odoo的公共部分腊瑟。它允許身份不明的用戶作為客戶端瀏覽某些內(nèi)容肴掷、購物或執(zhí)行許多操作。這是一個(gè)經(jīng)典的網(wǎng)站:各種各樣的帶有控制器的路由和共同協(xié)作的Javascript代碼传轰。
- POS:這是銷售點(diǎn)的接口动猬。它是一個(gè)特定的但也應(yīng)用程序作箍。
Web客戶端
單頁應(yīng)用
簡而言之,webclient實(shí)例是整個(gè)用戶界面的根組件。它的職責(zé)是協(xié)調(diào)所有的子組件努潘,并提供服務(wù)已脓,如RPC酸钦、本地存儲(chǔ)等等赞赖。
在運(yùn)行時(shí),Web客戶端是單頁應(yīng)用程序通砍。每次用戶執(zhí)行操作時(shí)玛臂,它不需要從服務(wù)器請求整頁。相反封孙,它只請求它所需要的迹冤,然后替換/更新視圖。此外虎忌,它還管理URL:它與Web客戶機(jī)狀態(tài)保持同步泡徙。
這意味著,當(dāng)用戶在處理odoo時(shí)膜蠢,Web客戶機(jī)類(和動(dòng)作管理器)實(shí)際上創(chuàng)建并銷毀了許多子組件堪藐。狀態(tài)是高度動(dòng)態(tài)的莉兰,每個(gè)小部件都可以隨時(shí)銷毀。
Web客戶端JS代碼概覽
這里礁竞,我們在web/static/src/js插件中快速概述了web客戶機(jī)代碼贮勃。注意,這是故意不詳盡的苏章,我們只涉及最重要的文件/文件夾。
- boot.js : 這是定義模塊系統(tǒng)的文件,它需要首先加載奏瞬。
- core/ : 這是較低級(jí)別的構(gòu)建基塊的集合枫绅。值得注意的是,它包含類系統(tǒng)硼端、小部件系統(tǒng)并淋、并發(fā)實(shí)用程序和許多其他類/函數(shù)。
- chorm/ :在這個(gè)文件夾中珍昨,我們有大多數(shù)大的小部件县耽,它們構(gòu)成了大部分用戶界面。
- chrome/abstract_web_client.js and chrome/web_client.js : 這些文件一起定義了WebClient小部件(widget)镣典,它是Web客戶機(jī)的根小部件(wideget)兔毙。
- chrome/action_manager.js : 這是將動(dòng)作(action)轉(zhuǎn)換為小部件(widget)(例如看板或表單視圖)的代碼。
- chrome/search_X.js : 所有這些文件定義了搜索視圖(它不是Web客戶機(jī)視圖中的視圖兄春,僅從服務(wù)器視圖)
- fields : 這里定義了所有主要字段視圖小部件(widget)
- views : 這是視圖所在的位置
資源管理
在Odoo中管理資源并不像在其他應(yīng)用程序中那樣簡單澎剥。其中一個(gè)原因是,在其中一些情況中我們有各種各樣的狀態(tài)赶舆,但不是所有的資源都是必需的哑姚。例如,Web客戶端芜茵、銷售點(diǎn)叙量、網(wǎng)站甚至移動(dòng)應(yīng)用程序的需求是不同的。此外九串,有些資源可能很大绞佩,但很少需要。在這種情況下猪钮,我們有時(shí)希望它們被懶惰地加載征炼。
主要思想是我們用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)模板時(shí)發(fā)生的情況:
- 包中描述的所有SCSS文件都編譯為CSS文件魄懂。名為file.scss的文件將編譯在名為file.scss.css的文件中沿侈。
-
如果我們在debug=assets模式:
?? * t-js屬性設(shè)置為false的t-call-assets指令將替換為指向css文件的樣式表標(biāo)記列表。
?? * t-css屬性設(shè)置為false的t-call-assets指令將替換為指向JS文件的腳本標(biāo)記列表市栗。 -
如果我們不在debug=assets模式
?? * CSS文件將被連接并縮小缀拭,然后拆分為不超過4096個(gè)規(guī)則的文件(以繞過IE9的舊限制)。然后填帽,我們根據(jù)需要生成盡可能多的樣式表標(biāo)簽
?? * JS文件被連接并縮小蛛淋,然后生成一個(gè)腳本標(biāo)記。
請注意篡腌, 資源文件是緩存的褐荷,因此從理論上講,瀏覽器應(yīng)該只加載它們一次嘹悼。
主包
當(dāng)odoo服務(wù)器啟動(dòng)時(shí)叛甫,它檢查包中每個(gè)文件的時(shí)間戳,如果需要杨伙,它將創(chuàng)建/重新創(chuàng)建相應(yīng)的包其监。
以下是大多數(shù)開發(fā)人員需要知道的一些重要包:
- web.assets_common : 此包包含Web客戶端、網(wǎng)站以及銷售點(diǎn)(POS)所共有的大多數(shù)資源限匣。這應(yīng)該包含用于Odoo框架的較低級(jí)別的構(gòu)建塊棠赛。注意,它包含boot.js文件膛腐,它定義了odoo模塊系統(tǒng)睛约。
- web.assets_backend :這個(gè)包包含特定于Web客戶端的代碼(特別是Web客戶端/動(dòng)作管理器/視圖)
- web.assets_frontend :這個(gè)包是關(guān)于所有特定于公共網(wǎng)站的:電子商務(wù)、論壇哲身、博客辩涝、事件管理…
在一個(gè)資源包里添加文件
將位于addons/web中的文件添加到bundle的正確方法很簡單:只需將腳本或樣式表標(biāo)記添加到文件webclient_templates.xml中的bundle即可。但是當(dāng)我們使用不同的插件(addon)時(shí)勘天,我們需要從該插件添加一個(gè)文件怔揩。在這種情況下,應(yīng)分三步進(jìn)行:
- 添加一個(gè) assets.xml 文件到views/文件夾
- 添加字符'views/assets.xml' 到manifest文件的鍵'data'的值里
- 創(chuàng)建所需包的繼承視圖脯丝,并使用xpath表達(dá)式添加文件商膊。例如:
<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客戶端時(shí)宠进,包中的所有文件都會(huì)立即加載晕拆。這意味著每次通過網(wǎng)絡(luò)傳輸文件(瀏覽器緩存處于活動(dòng)狀態(tài)時(shí)除外)。在某些情況下材蹬,最好使用Lazyload的一些資產(chǎn)实幕。例如吝镣,如果一個(gè)小部件需要一個(gè)大的庫,而這個(gè)小部件不是體驗(yàn)的核心部分昆庇,那么在實(shí)際創(chuàng)建小部件時(shí)末贾,最好只加載庫。widget類實(shí)際上已經(jīng)為這個(gè)用例內(nèi)置了支持整吆。(查閱QWeb模板引擎部分)
如果文件沒有加載/更新應(yīng)該怎么辦
文件可能無法正確加載有許多不同的原因拱撵。您可以嘗試以下幾點(diǎn)來解決此問題:
- 一旦服務(wù)器啟動(dòng),它就不知道資源文件是否已被修改表蝙。因此拴测,您可以簡單地重新啟動(dòng)服務(wù)器來重新生成資源。
- 檢查控制臺(tái)(在開發(fā)工具中勇哗,通常用F12打開),確保沒有明顯的錯(cuò)誤
- 嘗試在文件的開頭添加console.log(在任何模塊定義之前)寸齐,這樣您就可以查看文件是否已加載欲诺。
- 在用戶界面中,在調(diào)試模式下(在此處插入鏈接到調(diào)試模式)渺鹦,有一個(gè)選項(xiàng)可以強(qiáng)制服務(wù)器更新其資源文件扰法。
- 使用debug=assets模式。這實(shí)際上會(huì)繞過資源包(請注意毅厚,它實(shí)際上并不能解決問題塞颁,服務(wù)器仍然使用過時(shí)的包)
- 最后,對于開發(fā)人員來說吸耿,最方便的方法是使用--dev=all選項(xiàng)啟動(dòng)服務(wù)器祠锣。這將激活文件監(jiān)視程序選項(xiàng),必要時(shí)將自動(dòng)使資源無效咽安。請注意伴网,如果操作系統(tǒng)是Windows,它就不能很好地工作妆棒。
- 記住刷新頁面澡腾!
- 或者保存代碼文件…
重新創(chuàng)建資源文件后,需要刷新頁面糕珊,重新加載正確的文件(如果不起作用动分,文件可能被緩存了)。
Javascript模塊系統(tǒng)
一旦我們能夠?qū)⑽覀兊膉avascript文件加載到瀏覽器中红选,我們就需要確保以正確的順序加載它們澜公。為了實(shí)現(xiàn)這一點(diǎn),odoo定義了一個(gè)小模塊系統(tǒng)(位于addons/web/static/src/js/boot.js文件中喇肋,需要首先加載該文件)玛瘸。
在AMD的啟發(fā)下蜕青,odoo模塊系統(tǒng)通過在全局odoo對象上定義函數(shù)define來工作。然后我們通過調(diào)用該函數(shù)來定義每個(gè)javascript模塊糊渊。在odoo框架中右核,模塊是一段將盡快執(zhí)行的代碼。它有一個(gè)名稱渺绒,可能還有一些依賴項(xiàng)贺喝。當(dāng)它的依賴項(xiàng)被加載時(shí),模塊也將被加載宗兼。模塊的值就是定義模塊的函數(shù)的返回值躏鱼。
一個(gè)例子,看起來像這樣:
// 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;
});
定義模塊的另一種方法是在第二個(gè)參數(shù)中明確地給出依賴項(xiàng)列表殷绍。
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àng)丟失/未就緒染苛,那么模塊將不會(huì)被加載。幾秒鐘后控制臺(tái)中將出現(xiàn)警告主到。
請注意茶行,不支持循環(huán)依賴項(xiàng)。這是有道理的登钥,但這意味著需要謹(jǐn)慎畔师。
定義一個(gè)模塊
odoo.define 方法給了三個(gè)參數(shù):
- moduleName: javascript模塊的名稱。它應(yīng)該是一個(gè)唯一的字符串牧牢。慣例是在odoo插件(addon)的名字后面加上一個(gè)具體的描述看锉。例如,“web.widget”描述在web插件中定義的模塊塔鳍,該模塊導(dǎo)出一個(gè)widget類(因?yàn)榈谝粋€(gè)字母大寫)伯铣。
如果名稱不唯一,將引發(fā)異常并顯示在控制臺(tái)中 - dependencies : 第二個(gè)參數(shù)是可選的轮纫。如果給定懂傀,它應(yīng)該是一個(gè)字符串列表,每個(gè)字符串對應(yīng)一個(gè)JavaScript模塊蜡感。這描述了在執(zhí)行模塊之前需要加載的依賴項(xiàng)蹬蚁。如果這里沒有明確地給出依賴項(xiàng),那么模塊系統(tǒng)將通過調(diào)用ToString從函數(shù)中提取它們郑兴,然后使用regexp查找所有Require語句犀斋。
- 最后一個(gè)參數(shù)是定義模塊的函數(shù)。它的返回值是模塊的值情连,可以傳遞給其他需要它的模塊叽粹。注意,異步模塊有一個(gè)小的異常,請參見下一節(jié)虫几。
如果發(fā)生錯(cuò)誤锤灿,將在控制臺(tái)中記錄(在調(diào)試模式下): - Missing dependencies: 這些模塊不會(huì)出現(xiàn)在頁面中×玖常可能是javascript文件不在頁面中或模塊名稱錯(cuò)誤
- Failed Modules : 一個(gè)Javascript錯(cuò)誤被檢測到
- Rejected modules :塊返回拒絕的延遲但校。它(及其相關(guān)模塊)未加載。
- Rejected linked modules: 依賴被拒絕模塊的模塊
- Non loaded modules : 模塊依賴了一個(gè)缺失/失敗的模塊
異步模塊
模塊可能需要在準(zhǔn)備就緒之前執(zhí)行一些工作啡氢。例如状囱,它可以做一個(gè)RPC來加載一些數(shù)據(jù)。在這種情況下倘是,模塊只需返回一個(gè)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í)
- 記住模塊名的約定:插件名加上模塊名后綴
- 在模塊頂部聲明所有依賴項(xiàng)。此外瘤睹,它們應(yīng)該按模塊名稱的字母順序排序升敲。這樣更容易理解您的模塊。
- 在末尾聲明所有導(dǎo)出的值
- 盡量避免從一個(gè)模塊導(dǎo)出過多的內(nèi)容默蚌。通常最好在一個(gè)(小/更卸澄睢)模塊中簡單地導(dǎo)出一件事情苇羡。
- 異步模塊可以用來簡化一些用例绸吸。例如,web.dom_ready模塊返回一個(gè)deferred 设江,當(dāng)dom實(shí)際就緒時(shí)锦茁,這個(gè)deferred 將被解決。因此叉存,另一個(gè)需要dom的模塊可以在某個(gè)地方簡單地有一個(gè)require(“web.dom_ready”)語句码俩,并且只有當(dāng)dom準(zhǔn)備好時(shí)才會(huì)執(zhí)行代碼。
- 盡量避免在一個(gè)文件中定義多個(gè)模塊歼捏。這在短期內(nèi)可能很方便稿存,但實(shí)際上很難維護(hù)。
類系統(tǒng)
Odoo是在ECMAScript 6類可用之前開發(fā)的瞳秽。在ECMAScript 5中瓣履,定義類的標(biāo)準(zhǔn)方法是定義一個(gè)函數(shù)并在其原型對象上添加方法。這很好练俐,但是當(dāng)我們想要使用繼承袖迎、混合時(shí),它稍微復(fù)雜一些。
出于這些原因燕锥,Odoo決定使用自己的類系統(tǒng)辜贵,這是受到約翰·雷西格的啟發(fā)」樾危基類位于web.class文件class.js中托慨。
創(chuàng)建一個(gè)子類
讓我們討論如何創(chuàng)建類。主要機(jī)制是使用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)建實(shí)例時(shí)調(diào)用跺撼。通過使用new關(guān)鍵字創(chuàng)建實(shí)例窟感。
繼承
可以方便地繼承現(xiàn)有的類。這只需在超類上使用extend方法即可完成歉井。當(dāng)調(diào)用一個(gè)方法時(shí)柿祈,框架會(huì)秘密地將一個(gè)特殊的方法_super重新綁定到當(dāng)前調(diào)用的方法中。這允許我們在需要調(diào)用父方法時(shí)使用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)不支持多重繼承躏嚎,但是對于那些需要共享某些行為的情況,我們有一個(gè)混合系統(tǒng):extend方法實(shí)際上可以接受任意數(shù)量的參數(shù)菩貌,并將它們組合到新的類中卢佣。
var Animal = require('web.Animal');
var DanceMixin = {
dance: function () {
console.log('dancing...');
},
};
var Hamster = Animal.extend(DanceMixin, {
sleep: function () {
console.log('sleeping');
},
});
在這個(gè)例子中,Hamter 類是Animal的子類箭阶,但是它也混合了DanceMixin.
修改現(xiàn)有的類
這并不常見虚茶,但有時(shí)我們需要在適當(dāng)?shù)奈恢眯薷牧硪粋€(gè)類。目標(biāo)是有一個(gè)機(jī)制來改變一個(gè)類和所有未來/現(xiàn)在的實(shí)例仇参。這是通過使用include方法完成的:
var Hamster = require('web.Hamster');
Hamster.include({
sleep: function () {
this._super.apply(this, arguments);
console.log('zzzz');
},
});
這顯然是一個(gè)危險(xiǎn)的操作嘹叫,應(yīng)該小心操作。但是诈乒,按照odoo的結(jié)構(gòu)罩扇,有時(shí)需要在一個(gè)插件中修改在另一個(gè)插件中定義的widget/class的行為。請注意怕磨,它將修改類的所有實(shí)例喂饥,即使它們已經(jīng)創(chuàng)建。
小部件(Widget)
widget類實(shí)際上是用戶界面的一個(gè)重要構(gòu)建塊肠鲫。幾乎用戶界面中的所有內(nèi)容都在小部件(widget)的控制之下员帮。widget類在widget.js中的module web.widget中定義。
簡而言之滩届,widget類提供的特性包括:
- 小部件之間的父/子關(guān)系(PropertiesMixin)
- **具有安全功能的廣泛生命周期管理 **(e.g. 在銷毀父級(jí)期間自動(dòng)銷毀子窗口小部件)
- 自動(dòng)渲染qweb模板
- 幫助與外部環(huán)境交互的各種實(shí)用功能集侯。
一個(gè)計(jì)數(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(并且正確加載:模板位于一個(gè)文件中,該文件在模塊清單中的qweb鍵中正確定義)如下:
<div t-name="some.template">
<span class="val"><t t-esc="widget.count"/></span>
<button>Increment</button>
</div>
這個(gè)例子說明了小部件類的一些特性棠枉,包括事件系統(tǒng)浓体、模板系統(tǒng)、帶有初始父參數(shù)的構(gòu)造函數(shù)辈讶。
小部件的生命周期
與許多組件系統(tǒng)一樣命浴,widget類有一個(gè)定義良好的生命周期。通常的生命周期如下:調(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的父級(jí)录语,用于處理自動(dòng)銷毀和事件傳播倍啥。對于沒有父級(jí)的小部件,可以為null
澎埠。
Widget.willStart()
當(dāng)一個(gè)小部件被創(chuàng)建并被附加到DOM的過程中虽缕,框架將調(diào)用這個(gè)方法一次。willstart方法是一個(gè)鉤子蒲稳,它應(yīng)該返回一個(gè)deferred氮趋。JS框架將等待這個(gè)deferred完成,然后再繼續(xù)渲染步驟弟塞。注意凭峡,此時(shí)小部件沒有dom根元素拙已。willstart鉤子主要用于執(zhí)行一些異步工作决记,例如從服務(wù)器獲取數(shù)據(jù)。
[Rendering]()
此步驟由框架自動(dòng)完成倍踪∠倒框架會(huì)檢查小部件上是否定義了template鍵。如果定義了建车,那么它將在呈現(xiàn)上下文中使用綁定到小部件的widget鍵呈現(xiàn)該模板(請參見上面的示例:我們在QWeb模板中使用widget.count來讀取小部件的值)扩借。如果沒有定義模板,則讀取 tagName 鍵并創(chuàng)建相應(yīng)的DOM元素缤至。渲染完成后潮罪,我們將結(jié)果設(shè)置為小部件的$el屬性。在此之后,我們將自動(dòng)綁定events和custom_events鍵中的所有事件嫉到。
Widget.start()
渲染完成后沃暗,框架將自動(dòng)調(diào)用Start方法。這對于執(zhí)行一些特殊的后期渲染工作很有用何恶。例如孽锥,設(shè)置庫。
必須返回deferred以指示其工作何時(shí)完成细层。
Returns: deferred 對象
Widget.destroy()
這始終是小部件生命周期中的最后一步惜辑。當(dāng)小部件被銷毀時(shí),我們基本上執(zhí)行所有必要的清理操作:從組件樹中刪除小部件疫赎,取消綁定所有事件盛撑,…
當(dāng)小部件的父級(jí)被銷毀時(shí)自動(dòng)調(diào)用,如果小部件沒有父級(jí)捧搞,或者如果它被刪除但父級(jí)仍然存在撵彻,則必須顯式調(diào)用。
請注意实牡,不必調(diào)用willstart和start方法陌僵。可以創(chuàng)建一個(gè)小部件(將調(diào)用init方法)创坞,然后銷毀(destroy方法)碗短,而不需要附加到DOM。如果是這種情況题涨,將不會(huì)調(diào)用will start和start偎谁。
Widget API
Widget.tagName
如果小部件沒有定義模板,則使用纲堵。默認(rèn)為DIV巡雨,將用作標(biāo)記名來創(chuàng)建要設(shè)置為小部件的dom根的dom元素∠可以使用以下屬性進(jìn)一步自定義生成的dom根目錄:
Widget.id
用于在生成的dom根上生成id屬性铐望。請注意,這是很少需要的茂附,如果一個(gè)小部件可以多次使用正蛙,這可能不是一個(gè)好主意。
Widget.className
用于在生成的dom根上生成class屬性营曼。請注意乒验,它實(shí)際上可以包含多個(gè)css類:“some-class other-class”
Widget.attributes
屬性名到屬性值的映射(對象文本)。這些k:v對中的每一個(gè)都將被設(shè)置為生成的dom根上的dom屬性蒂阱。
Widget.el
將原始dom元素設(shè)置為小部件的根(僅在start lifecyle方法之后可用)
Widget.$el
jquery封裝的el锻全,(僅在Start Lifecyle方法之后可用)
Widget.template
應(yīng)設(shè)置為QWeb模板的名稱狂塘。如果設(shè)置了,模板將在小部件初始化之后但在其啟動(dòng)之前呈現(xiàn)鳄厌。模板生成的根元素將被設(shè)置為小部件的dom根睹耐。
Widget.xmlDependencies
呈現(xiàn)小部件之前需要加載的XML文件的路徑列表。這不會(huì)導(dǎo)致加載已加載的任何內(nèi)容部翘。如果您想延遲加載模板硝训,或者想要在網(wǎng)站和Web客戶機(jī)界面之間共享一個(gè)小部件,這很有用新思。
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ā)荸哟。如果選擇器被省略(只指定了一個(gè)事件名)假哎,那么事件將直接設(shè)置在小部件的dom根上。
注意:不鼓勵(lì)使用內(nèi)聯(lián)函數(shù)鞍历,將來可能會(huì)刪除它舵抹。
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 對象
這個(gè)助手方法類似于
Backbone.View.$
Widget.setElement(element)
將小部件的dom根重新設(shè)置為提供的元素劣砍,還處理重新設(shè)置dom根的各種別名以及取消設(shè)置和重新設(shè)置委托事件惧蛹。
arguments: element(Element) -一個(gè)DOM元素或者jQuery對象設(shè)置為小部件的根DOM
在DOM中插入一個(gè)小部件
Widget.appendTo(element)
渲染小部件并將它作為子元素插入到目標(biāo)元素后面,使用.appentTo()
Widget.prependTo(element)
渲染小部件并將它作為子元素插入到目標(biāo)元素前面刑枝,使用.prependTo()
Widget.insertAfter(element)
渲染小部件并將它作為目標(biāo)元素的前一個(gè)同級(jí)插入香嗓,使用.insertAfter()
Widget.insertBefore(element)
渲染小部件并將其作為目標(biāo)的后一個(gè)同級(jí)插入,使用.insertBefore()
所有這些方法都接受相應(yīng)jquery方法接受的任何內(nèi)容(css選擇器装畅、dom節(jié)點(diǎn)或jquery對象)靠娱。他們都會(huì)返回一個(gè) deferred践盼,并承擔(dān)三個(gè)任務(wù):
-
通過以下方式呈現(xiàn)小部件的根元素:
renderElement()
-
使用jquery在DOM中插入小部件的根元素
匹配的方法 - 啟動(dòng)小部件并返回啟動(dòng)結(jié)果
小部件指南
在一般應(yīng)用和模塊中中遣钳,標(biāo)識(shí) (id 屬性)應(yīng)該避免使用,ID限制了組件的可重用性载慈,并使代碼更加脆弱徽千。大多數(shù)情況下苫费,它們可以替換為Nothing汤锨、Classes或保留對dom節(jié)點(diǎn)或jquery元素的引用双抽。
如果ID是絕對必要的(因?yàn)榈谌綆煨枰粋€(gè)),則應(yīng)使用_.uniqueId()部分生成ID闲礼,例如:
this.id = _.uniqueId('my-widget-');
避免使用可預(yù)測/通用的CSS類名牍汹。類名稱(如“content”或“navigation”)可能與所需的含義/語義匹配铐维,但其他開發(fā)人員可能也有相同的需求,從而造成命名沖突和意外行為慎菲。通用類名的前綴應(yīng)該是它們所屬組件的名稱(創(chuàng)建“非正式”名稱空間嫁蛇,就像在C或Objective-C中那樣)。
-
應(yīng)避免使用全局選擇器露该。因?yàn)橐粋€(gè)組件可以在一個(gè)頁面中多次使用(ODoo中的一個(gè)例子是儀表板)睬棚,所以查詢應(yīng)該限制在給定組件的范圍內(nèi)。未篩選的選擇(如
$(selector)
或document.querySelectorAll(selector)
)通常會(huì)導(dǎo)致意外或錯(cuò)誤的行為解幼。odoo web的widget()有一個(gè)提供dom根([圖片上傳失敗...(image-8bb977-1661453922022)]_().)
更一般地說抑党,不要假設(shè)您的組件擁有或控制任何超出其個(gè)人$el的東西(因此,避免使用對父部件的引用)撵摆。
HTML模板/渲染應(yīng)該使用QWeb底靠,除非非常簡單。
所有交互組件(向屏幕顯示信息或截取DOM事件的組件)必須繼承自widget()特铝,并正確實(shí)現(xiàn)和使用其API和生命周期暑中。
Qweb模板引擎
Web客戶端使用QWeb模板引擎來呈現(xiàn)小部件(除非它們重寫renderelement方法來執(zhí)行其他操作)。QWebJS模板引擎基于XML鲫剿,主要與Python實(shí)現(xiàn)兼容鳄逾。
現(xiàn)在,讓我們解釋如何加載模板灵莲。每當(dāng)Web客戶端啟動(dòng)時(shí)严衬,都會(huì)對/web/web client/qweb路由進(jìn)行RPC。然后笆呆,服務(wù)器將返回在每個(gè)已安裝模塊的數(shù)據(jù)文件中定義的所有模板的列表请琳。正確的文件列在每個(gè)模塊清單的QWeb條目中。
在啟動(dòng)第一個(gè)小部件之前赠幕,Web客戶機(jī)將等待加載該模板列表俄精。
這個(gè)機(jī)制可以很好地滿足我們的需求,但有時(shí)我們希望懶加載模板榕堰。例如竖慧,假設(shè)我們有一個(gè)很少使用的小部件。在這種情況下逆屡,我們可能不希望將其模板加載到主文件中圾旨,以便使Web客戶機(jī)稍微輕一些。在這種情況下魏蔗,我們可以使用小部件的xmlpendencies鍵:
var Widget = require('web.Widget');
var Counter = Widget.extend({
template: 'some.template',
xmlDependencies: ['/myaddon/path/to/my/file.xml'],
...
});
有了這個(gè)砍的,計(jì)數(shù)器小部件將以willstart方法加載xmlpendencies文件,這樣在執(zhí)行呈現(xiàn)時(shí)模板就可以準(zhǔn)備好了莺治。
事件系統(tǒng)
目前廓鞠,odoo支持兩個(gè)事件系統(tǒng):一個(gè)允許添加偵聽器和觸發(fā)事件的簡單系統(tǒng)帚稠,以及一個(gè)更完整的系統(tǒng),它還可以使事件“冒泡”床佳。
這兩個(gè)事件系統(tǒng)都在文件mixins.js的eventspatchemixin中實(shí)現(xiàn)滋早。這個(gè)mixin包含在widget類中。
基礎(chǔ)事件系統(tǒng)
這是歷史上第一個(gè)事件系統(tǒng)砌们。它實(shí)現(xiàn)了一個(gè)簡單的總線模式杆麸。我們有4種主要方法:
- on : 在一個(gè)事件上注冊監(jiān)聽器
- off: 移除事件的監(jiān)聽器
- once: 注冊一個(gè)只使用一次的監(jiān)聽器
- trigger:跟蹤一個(gè)事件。這會(huì)調(diào)用所有監(jiān)聽器浪感。
一下是一個(gè)怎么使用事件系統(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);
[圖片上傳失敗...(image-deccf9-1661453922022)]
不鼓勵(lì)使用此事件系統(tǒng)角溃,我們計(jì)劃用擴(kuò)展事件系統(tǒng)中的trigger-up方法替換每個(gè)trigger方法。
擴(kuò)展的事件系統(tǒng)
自定義事件小部件是一個(gè)更高級(jí)的系統(tǒng)篮撑,它模擬DOM事件API减细。每當(dāng)一個(gè)事件被觸發(fā)時(shí),它將“冒泡”組件樹赢笨,直到它到達(dá)根小部件未蝌,或者停止。
- trigger_up:這是一種方法茧妒,它將創(chuàng)建一個(gè)小的odooEvent并將其分派到組件樹中萧吠。請注意,它將從觸發(fā)事件的組件開始
- custom_events:這相當(dāng)于事件字典桐筏,但是對于odoo事件來說纸型。
OdoEvent類非常簡單。它有三個(gè)公共屬性:target(觸發(fā)事件的小部件)梅忌、name(事件名稱)和data(有效負(fù)載)狰腌。它還有兩種方法:stopPropagation 和 is_stopped.。
上一個(gè)示例可以更新為使用自定義事件系統(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)的一個(gè)常見需求是從外部擴(kuò)展/更改基本系統(tǒng)的行為(通過安裝應(yīng)用程序牧氮,即不同的模塊)琼腔。例如,可能需要在某些視圖中添加新的小部件類型踱葛。在這種情況下丹莲,以及其他許多情況下,通常的過程是創(chuàng)建所需的組件尸诽,然后將其添加到注冊表(注冊步驟)甥材,以使Web客戶機(jī)的其余部分知道它的存在。
一下是一些在系統(tǒng)中可用的注冊:
- 字段注冊表(由“web.field_registry”導(dǎo)出)性含。字段注冊表包含Web客戶端已知的所有字段小部件洲赵。每當(dāng)視圖(通常是表單或列表/看板)需要字段小部件時(shí),這就是它將要查找的地方。典型的用例如下所示:
var fieldRegistry = require('web.field_registry');
var FieldPad = ...;
fieldRegistry.add('pad', FieldPad);
注意板鬓,每個(gè)值都應(yīng)該是AbstractField的子類
- 視圖注冊表:此注冊表包含Web客戶端已知的所有JS視圖
(尤其是視圖管理器)悲敷。此注冊表的每個(gè)值都應(yīng)該是AbstractView的子類
- 動(dòng)作注冊表:我們跟蹤此注冊表中的所有客戶端動(dòng)作究恤。這個(gè)是動(dòng)作管理器在需要?jiǎng)?chuàng)建客戶端操作時(shí)查找的位置俭令。在版本11中,每個(gè)值應(yīng)該只是小部件的一個(gè)子類部宿。但是抄腔,在版本12中,值必須是abstractAction理张。
小部件之間的通信
有許多組件之間的通信方式
從父級(jí)到它的子級(jí)
一個(gè)簡單的例子赫蛇。父不見可以簡單的調(diào)用子部件方法:
this.someWidget.update(someInfo);
從一個(gè)小部件到它的父/某個(gè)祖先
在這種情況下,小部件的工作只是通知其環(huán)境發(fā)生了什么事情雾叭。由于我們不希望小部件具有對其父部件的引用(這將使小部件與其父部件的實(shí)現(xiàn)相結(jié)合)悟耘,因此繼續(xù)操作的最佳方法通常是使用觸發(fā)器trigger_up方法觸發(fā)一個(gè)事件,該事件將冒泡到組件樹中:
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.
},
});
-
交叉組件
通過總線可以實(shí)現(xiàn)跨組件通信暂幼。這不是首選的通信形式,因?yàn)樗惺勾a難以維護(hù)的缺點(diǎ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)境蛹头,這種方式允許框架進(jìn)行足夠的控制顿肺,并且是可測試的。
服務(wù)系統(tǒng)圍繞三個(gè)理念進(jìn)行組織:services渣蜗、service providers和widget屠尊。它的工作方式是小部件觸發(fā)(使用trigger_up)事件,這些事件冒泡到服務(wù)提供者耕拷,服務(wù)提供者將要求服務(wù)執(zhí)行任務(wù)讼昆,然后可能返回一個(gè)答案。
服務(wù)service
服務(wù)是AbstractService類的實(shí)例骚烧。它基本上只有一個(gè)名稱和一些方法浸赫。它的工作是執(zhí)行一些工作闰围,通常是一些依賴于環(huán)境的工作。
例如既峡,我們有Ajax服務(wù)(任務(wù)是執(zhí)行RPC)羡榴、本地存儲(chǔ)(與瀏覽器本地存儲(chǔ)交互)和許多其他服務(wù)。
以下是有關(guān)如何實(shí)現(xiàn)Ajax服務(wù)的簡化示例:
var AbstractService = require('web.AbstractService');
var AjaxService = AbstractService.extend({
name: 'ajax',
rpc: function (...) {
return ...;
},
});
這個(gè)服務(wù)被叫做‘a(chǎn)jax’而且定義了一個(gè)方法运敢,rpc.
服務(wù)提供者Service Provider
為了使服務(wù)正常工作校仑,有必要讓一個(gè)服務(wù)提供者準(zhǔn)備好分派定制事件。在后端(Web客戶端)传惠,這是由主Web客戶端實(shí)例完成的迄沫。請注意,服務(wù)提供程序的代碼來自ServiceProviderMin卦方。
部件widget
小部件是請求服務(wù)的部分羊瘩。為了做到這一點(diǎn),它只需觸發(fā)一個(gè)事件調(diào)用服務(wù)(通常通過使用helper函數(shù)調(diào)用)盼砍。此事件將冒泡并將意圖傳達(dá)給系統(tǒng)的其余部分尘吗。
在實(shí)踐中,有些函數(shù)被頻繁地調(diào)用衬廷,以至于我們有一些助手函數(shù)使它們更容易使用摇予。例如,_rpc方法是幫助生成rpc的助手吗跋。
var SomeWidget = Widget.extend({
_getActivityModelViewID: function (model) {
return this._rpc({
model: model,
method: 'get_activity_view_id'
});
},
});
[圖片上傳失敗...(image-dcf544-1661453922021)]
如果一個(gè)小部件被銷毀侧戴,它將從主組件樹中分離出來,并且沒有父組件跌宛。在這種情況下酗宋,事件不會(huì)冒泡,這意味著工作不會(huì)完成疆拘。這通常正是我們從一個(gè)被破壞的小部件中想要的蜕猫。
RPCs
RPC功能由Ajax服務(wù)提供。但大多數(shù)人可能只會(huì)與_rpc助手進(jìn)行交互哎迄。
在處理odoo時(shí)回右,通常有兩個(gè)用例:一個(gè)需要在(python)模型上調(diào)用方法(這需要通過控制器call_kw),或者一個(gè)需要直接調(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框架有一種標(biāo)準(zhǔn)的方式來向用戶傳遞各種信息:通知翔烁,它顯示在用戶界面的右上角。
通知有兩種類型:
- notification: 有助于顯示一些反饋旨涝。例如蹬屹,每當(dāng)用戶取消訂閱某個(gè)頻道時(shí)。
- warning:用于顯示一些重要/緊急信息。通常是系統(tǒng)中的大多數(shù)(可恢復(fù)的)錯(cuò)誤慨默。
此外贩耐,通知還可以用于向用戶詢問問題,而不會(huì)干擾其工作流厦取。想象一下潮太,通過VoIP接收到的一個(gè)電話呼叫:可以顯示一個(gè)帶有兩個(gè)按鈕的粘性通知:接受和拒絕。
通知系統(tǒng)
Odoo中的通知系統(tǒng)設(shè)計(jì)有以下組件:
- a Notification widget:這是一個(gè)簡單的小部件蒜胖,用于創(chuàng)建和顯示所需的信息消别。
- a NotificationService:一種服務(wù)抛蚤,其職責(zé)是在請求完成時(shí)(使用custom_event)創(chuàng)建和銷毀通知台谢。請注意,Web客戶端是一個(gè)服務(wù)提供者岁经。
- ServiceMixin中的兩個(gè)助手功能:do_notify和do_warn
顯示通知
顯示通知的最常見方法是使用來自ServiceMixin的兩種方法:
-
do_notify(title, message, sticky, className):
顯示一個(gè)通知類型的通知:- title:string. 將會(huì)在頂部顯示為標(biāo)題
- message:string. 通知的內(nèi)容
- sticky : boolean,optional. 如果為真朋沮,這個(gè)通知將會(huì)一直保留直到用戶解除。否則缀壤,通知將會(huì)在一段很短的時(shí)間之后自動(dòng)關(guān)閉樊拓。
- calssname:string,optional.這是一個(gè)css類的名字,將會(huì)自動(dòng)添加到通知中塘慕。這對樣式有用筋夏,即使不鼓勵(lì)使用它。
-
do_warn(title, message, sticky, className):
顯示一個(gè)警告類型的通知图呢。- title:string.將會(huì)在頂部顯示為標(biāo)題
- message:string.通知的內(nèi)容
- sticky : boolean,optional. 如果為真条篷,這個(gè)通知將會(huì)一直保留直到用戶解除。否則蛤织,通知將會(huì)在一段很短的時(shí)間之后自動(dòng)關(guān)閉赴叹。
- calssname:string,optional.這是一個(gè)css類的名字,將會(huì)自動(dòng)添加到通知中指蚜。這對樣式有用乞巧,即使不鼓勵(lì)使用它。
這里有兩個(gè)如何使用這兩個(gè)方法的例子:
// 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時(shí)免猾,它將查找所有已注冊的小部件是辕,并將它們作為子小部件添加到適當(dāng)?shù)奈恢谩?br>
目前沒有針對Systray小工具的特定API。它們應(yīng)該是簡單的小部件掸刊,并且可以像使用trigger-up方法的其他小部件一樣與環(huán)境通信免糕。
添加一個(gè)新得任務(wù)欄項(xiàng)目
沒有Systray注冊表。添加小部件的正確方法是將其添加到類變量systraymenu.items中。
var SystrayMenu = require('web.SystrayMenu');
var MySystrayWidget = Widget.extend({
...
});
SystrayMenu.Items.push(MySystrayWidget);
排序Ordering
在將小部件添加到自己之前石窑,Systray菜單將按Sequence屬性對項(xiàng)目進(jìn)行排序牌芋。如果原型上不存在該屬性,則將使用50松逊。因此躺屁,要將Systray項(xiàng)目定位在靠后,可以設(shè)置一個(gè)非常高的序列號(hào)(反之经宏,將其放在靠前的是一個(gè)較低的序列號(hào))犀暑。
MySystrayWidget.prototype.sequence = 100;
翻譯管理
有些翻譯是在服務(wù)器端進(jìn)行的(基本上是由服務(wù)器呈現(xiàn)或處理的所有文本字符串),但是靜態(tài)文件中有需要翻譯的字符串烁兰。它目前的工作方式如下:
- 每個(gè)可翻譯字符串都帶有特殊的函數(shù)_t(可在js模塊web.core中找到)
- 服務(wù)器使用這些字符串生成正確的PO文件耐亏。
- 每當(dāng)加載Web客戶機(jī)時(shí),它將調(diào)用route/web/web client/translations沪斟,它返回所有可翻譯術(shù)語的列表
- 在運(yùn)行時(shí)广辰,每當(dāng)調(diào)用函數(shù)時(shí),它都會(huì)在該列表中查找以查找轉(zhuǎn)換主之,如果找不到轉(zhuǎn)換择吊,則返回它或原始字符串。
請注意槽奕,在文檔翻譯模塊中几睛,從服務(wù)器的角度對翻譯進(jìn)行了更詳細(xì)的解釋。
javascript中的翻譯有兩個(gè)重要功能:_t和_lt粤攒。區(qū)別在于_lt是以懶惰的方式進(jìn)行的所森。
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');
...
},
});
在本例中,由于在加載模塊時(shí)翻譯尚未準(zhǔn)備就緒琼讽,因此必須使用_lt必峰。
注意,翻譯功能需要注意钻蹬。參數(shù)中給定的字符串不應(yīng)是動(dòng)態(tài)的吼蚁。
會(huì)話Session
Web客戶端提供了一個(gè)特定的模塊,其中包含一些特定于用戶當(dāng)前會(huì)話的信息问欠。一些顯著的鍵是:
- uid:當(dāng)前用戶的id(來自于表 res.users 的ID)
- user_name: 用戶的名字肝匆,字符串類型
- 用戶上下文(context [用戶id,語言和時(shí)區(qū)])
- partner_id: 與當(dāng)前用戶關(guān)聯(lián)的合作伙伴的ID
- db:當(dāng)前使用的數(shù)據(jù)庫名字
添加信息到會(huì)話中
加載/web路由后,服務(wù)器將在模板中插入一些會(huì)話信息和腳本標(biāo)記顺献。信息將從模型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)在度硝,通過在會(huì)話中讀取該值,可以在javascript中獲取該值:
var session = require('web.session');
var myValue = session.some_key;
...
請注意寿冕,此機(jī)制旨在減少Web客戶端準(zhǔn)備就緒所需的通信量蕊程。它更適合于計(jì)算成本較低的數(shù)據(jù)(緩慢的session_info調(diào)用將延遲為每個(gè)人加載Web客戶端),以及在初始化過程早期需要的數(shù)據(jù)驼唱。
視圖
“視圖”一詞有多種含義藻茂。本節(jié)是關(guān)于視圖的JavaScript代碼的設(shè)計(jì),而不是Arch的結(jié)構(gòu)或其他任何內(nèi)容玫恳。
2017年辨赐,Odoo用新架構(gòu)替換了先前的視圖代碼。主要需要將呈現(xiàn)邏輯與模型邏輯分開京办。
視圖(一般意義上)現(xiàn)在用4個(gè)部分描述:視圖掀序、控制器、渲染器和模型臂港。這4個(gè)部分的API在AbstractView森枪、AbstractController视搏、AbstractRenderer和AbstractModel類中進(jìn)行了描述审孽。
- 視圖是工廠。它的工作是獲取一組字段浑娜、arch佑力、上下文和其他一些參數(shù),然后構(gòu)造一個(gè)控制器/渲染器/模型三元組筋遭。
視圖的作用是用正確的信息正確地設(shè)置MVC模式的每一部分打颤。通常,它必須處理arch字符串漓滔,并提取視圖中彼此所需的數(shù)據(jù)编饺。
請注意,視圖是一個(gè)類响驴,而不是一個(gè)小部件透且。一旦它的工作完成,它就可以被丟棄豁鲤。 - 渲染器有一個(gè)作業(yè):表示在DOM元素中查看的數(shù)據(jù)秽誊。每個(gè)視圖都可以以不同的方式呈現(xiàn)數(shù)據(jù)。此外琳骡,它應(yīng)該監(jiān)聽適當(dāng)?shù)挠脩舨僮鞴郏⒃诒匾獣r(shí)通知其父級(jí)(Controller)。
渲染器是MVC模式中的V. - 模型:它的工作是獲取并保持視圖的狀態(tài)楣号。通常最易,它以某種方式表示數(shù)據(jù)庫中的一組記錄怒坯。該模型是“業(yè)務(wù)數(shù)據(jù)”的所有者。它是MVC模式中的M.
- Controller:它的工作是協(xié)調(diào)渲染器和模型藻懒。此外敬肚,它是Web客戶端其余部分的主要入口點(diǎn)。例如束析,當(dāng)用戶在搜索視圖中更改某些內(nèi)容時(shí)艳馒,將使用適當(dāng)?shù)男畔⒄{(diào)用控制器的更新方法。
它是MVC模式中的C.
視圖的JS代碼已設(shè)計(jì)為可在視圖管理器/操作管理器的上下文之外使用员寇。它們可以用于客戶端操作弄慰,也可以顯示在公共網(wǎng)站上(對資源進(jìn)行一些處理)
字段部件
該AbstractField類是在一個(gè)視圖中的所有控件的基類,用于支持他們所有的視圖(目前為:表格蝶锋,列表陆爽,看板)。
v11字段小部件與先前版本之間存在許多差異扳缕。讓我們提一下最重要的一些:
- 小部件在所有視圖之間共享(表單/列表/看板)慌闭。無需再復(fù)制實(shí)現(xiàn)。請注意躯舔,可以為視圖設(shè)置特定版本的窗口小部件驴剔,方法是在視圖注冊表中為視圖名稱添加前綴:list.many2one將優(yōu)先于many2one選擇。
- 小部件不再是字段值的所有者粥庄。它們僅表示數(shù)據(jù)并與視圖的其余部分進(jìn)行通信丧失。
- 小部件不再需要能夠在編輯和只讀模式之間切換。現(xiàn)在惜互,當(dāng)需要進(jìn)行此類更改時(shí)布讹,窗口小部件將被銷毀并再次重新呈現(xiàn)。這不是問題训堆,因?yàn)樗麄儫o論如何都不擁有自己的價(jià)值
- 字段小部件可以在視圖之外使用描验。他們的API略顯笨拙,但它們的設(shè)計(jì)是獨(dú)立的坑鱼。
裝飾與列表視圖一樣膘流,字段小部件對裝飾具有簡單的支持。裝飾的目標(biāo)是有一種簡單的方法來根據(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
每個(gè)裝飾decoration-X將映射到css類text-X,這是一個(gè)標(biāo)準(zhǔn)的bootstrap css類(text-it和text-bf除外黍析,它們由odoo處理并分別對應(yīng)于斜體和粗體)卖怜。請注意,decoration屬性的值應(yīng)該是一個(gè)有效的python表達(dá)式阐枣,它將使用記錄作為評估上下文進(jìn)行評估马靠。