odoo的web客戶端、后臺是員工經常使用的地方,在第九章中,我們了解了如何使用后臺提供的各種可能性,本章,我們將了解如何擴展這種可能性,其中web模塊包含了我們在使用odoo中的各種互動行為,
本章將依賴于web模塊,odoo有兩個不同的版本(社區版、企業版),社區版包含web模塊,而企業版是對web的擴展模塊web_enterprise模塊,
企業版提供了定制的手機端自適應、可搜索的選單及模塊化設計,
重要提醒
與其他Odoo版本相比,odoo14對于后端web客戶端來說有點獨特,它包含兩種管理odoo后臺GUI的框架,第一個是傳統基于小部件的框架,第二個是基于Odoo Web Library(OWL)的框架,OWL是odoo14的最新UI框架,兩者都使用QWeb模板,但是在語法及運行原理方面有一些明顯的調整,
盡管odoo14有新的框架OWL,但是odoo并沒有廣泛的使用,大多數網頁客戶端依舊使用老的框架,本章,我們將了解如何通過小部件的框架調整網頁客戶端,下一章節,我們將介紹OWL,
本章我們將創建一個用于獲取用戶輸入的小部件,我們還將從頭創建一個新視圖,讀完本章后,你將能夠在Odoo后端創建自己的UI元素,
小貼士
odoo的用戶互動依賴于JavaScript,本章中,我們假設你已經具備JavaScript、JQuery、Underscore.js和SCSS的基礎知識,
本章主要內容如下:
- 創建自定義控制元件
- 使用客戶端側的QWeb模板
- 通過RPC呼叫后端python方法
- 創建新的視圖
- 除錯用客戶端側的代碼
- 通過引導提升互動感
- 手機端js
創建自定義控制元件
正如您在第9章后端視圖中看到的,我們可以使用小部件以不同的格式顯示特定的資料,例如,我們使用widget='image'以影像的形式顯示一個二進制欄位,為了演示如何創建自己的小部件,我們將撰寫一個小部件,它允許用戶選擇一個整數欄位,但我們將以不同的方式顯示它,代替輸入框,我們將顯示一個顏色選擇器,以便我們可以選擇一個顏色號,在這里,每個數字將被映射到其相關的顏色,
準備
在本教程中,我們將使用my_library模塊和基本欄位和視圖,
步驟
我們將添加一個JavaScript檔案,其中包含小部件的邏輯,并添加一個SCSS檔案來執行一些樣式化操作,然后,我們將向books表單添加一個整數欄位,以使用我們的新小部件,執行以下步驟添加一個新的欄位小部件:
- 添加一個static/src/js/field_widget.js檔案,關于這里使用的語法,請參考《CMS網站開發》第14章中擴展CSS和JavaScript的內容:
odoo.define('my_field_widget', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
var fieldRegistry = require('web.field_registry');
...
})
- 創建widget:
var colorField = AbstractField.extend({
- 設定CSS類、根元素以及支持的欄位型別:
className: 'o_int_colorpicker',
tagName: 'span',
supportedFieldTypes: ['integer'],
- 配置js事件
events: {
'click .o_color_pill': 'clickPill',
},
- 多載建構式
init: function(){
this.totalColors = 10;
this._super.apply(this, arguments);
}
- 多載DOM元素的_renderEdit和_renderReadonly函式
_renderEdit: function(){
this.$el.empty();
for(var i=0;i<this.totalColors;i++)
{
var className = "o_color_pill o_color_" + i;
if(this.value=https://www.cnblogs.com/xushuotec/archive/2021/03/11/==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 = https://www.cnblogs.com/xushuotec/archive/2021/03/11/$target.data();
this._setValue(data.val.toString());
}
}); // close AbstractField
- 注冊widget:
fieldRegistry.add('int_color', colorField);
- 對其他組件可見:
return {
colorField: colorField,
};
}; // close 'my_field_widget' Namespace
- 添加SCSS,static/src/scss/field_widget.scss:
.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 14px/1 FontAwesome;
font-size: inherit;
color: #fff;
position: absolute;
padding: 4px;
font-size: 16px;
}
}
}
}
}
- 注冊js及scss檔案
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="assets_end" inherit_id="web.assets_ backend">
<xpath expr="." position="inside">
<script src="https://www.cnblogs.com/my_library /static/src/js/field_widget.js" type="text/javascript" />
<link href="https://www.cnblogs.com/my_library/static/src/scss/field_widget.scss" rel="stylesheet" type="text/scss" />
</xpath>
</template>
</odoo>
- 添加color欄位
color = fields.Integer()
- 在form視圖添加color欄位
<group>
<field name="date_release"/>
<field name="color" widget="int_color"/>
</group>
更新后如下圖所示:

原理
讓我們來了解下widget的生命周期:
- init(): 這是widget的建構式,是widget初始化時最先被呼叫的函式,
- willStart(): 當widget初始化之后呼叫,被添加到DOM時呼叫,可用于異步初始化widget資料,它還應該回傳一個延遲物件,可以簡單地從super()呼叫獲得該物件,我們將在后續菜譜中使用此方法,
- start(): 在widget渲染完成但尚未添加到DOM時呼叫,它對于后期渲染作業非常有用,并且應該回傳一個延遲的物件,你可以在this.$el中訪問一個已渲染的元素,
- destory(): 當widget被摧毀時呼叫,一般用于基礎的清理作業,比如事件解綁,
重要資訊
widget的基礎類是Widget(web.Widget),如果你想進一步了解該類,可在/addons/web/static/src/js/core/widget.js中查看,
步驟1,我們引入了AbstractField和fieldRegistry,
步驟2,我們創建AbstractField的擴展類colorField,通過該類,colorField將獲得AbstractField的所有屬性及方法,
步驟3,我們添加了三個屬性: className用于定義根元素的類;tagName是根元素的標簽;supportedFieldTypes代表當前widget可作用于哪些型別的field欄位,在我們的例子中,我們創建了可用于整型欄位的widget,
步驟4,我們映射了widget支持的事件,通過key是"事件的名稱 CSS選擇器",兩者之間是空格,value是函式名,所以,當事件被觸發的時候,函式將自動執行,本節,當用戶點擊了顏色圓點,將會在color欄位設定所對應的整數值,
步驟5,我們重寫了init方法,并設定了this.totalColors的值,通過該變數,決定展示的顏色圓點的個數,
步驟6,我們添加了_renderEdit和_renderReadonly函式,_renderEdit在編輯模式下呼叫,_renderReadonly在只讀模式下呼叫,在編輯模式下,我們添加了代表不同顏色的標簽,通過點擊標簽,我們可設定該欄位的值,并將添加到this.$el中,$el是widget的根元素,將被添加到form視圖,在只讀模式下,我們僅展示當前欄位代表的顏色,當前,我們通過硬編碼的方式添加了顏色圓點,下一章,我們將通過JavaScript QWeb模板渲染小圓點,注意,我們再編輯模式下使用了在init()函式中設定的tottalColors屬性值,
步驟7,我們添加了clickPill函式管理顏色圓點的點擊事件,為了設定欄位值,我們使用了_setValue方法,這個方法是AbstractField中的方法,當我們設定了欄位值,odoo將渲染widget并再次呼叫_renderEdit方法,
步驟8,在定義完widget后我們需將其注冊到web.field_registry,注意,所有視圖的widget都會通過web.field_registry查找widget,所以如果你創建一個在list視圖下展示欄位的widget,也同樣需要將其注冊到web.field_registry中,
最后,我們將widget暴露出來,以便其他的模塊也可以擴展或者繼承,
更多
web.mixins命名空間定義了一組非常有用的類mixin,本章中我們已經使用過mixin了,AbstractField繼承自Widget類,Widget繼承自兩個mixin,第一個是EventDispatcherMixin,可提供觸發事件及捕獲事件的介面,第二個是ServicesMixin,提供了RPC和動作所需的函式,
重要小貼士
當我們多載函式的時候,我們需要了解原始函式的回傳值,一個常見的BUG是忘記回傳父函式所需的物件,而引起報錯,
Widgets可用于資料驗證,通過isValid函式實作我們這方面的需求,
使用客戶端側的QWeb模板
正如以編程方式在JavaScript中創建HTML代碼是一個壞習慣一樣,您應該在客戶端JavaScript代碼中只創建最少數量的DOM元素,幸運的是,客戶端也有模板引擎可用,更幸運的是,客戶端模板引擎具有與服務器端模板相同的語法,
準備
我們將把DOM元素的創建移動到QWeb,使其更加模塊化,
步驟
我們需要將QWeb定義添加到清單中,并更改JavaScript代碼,以便我們可以使用它,請按照以下步驟啟動:
- 匯入web.core并提取qweb參考,如下代碼所示:
odoo.define('my_field_widget', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
var fieldRegistry = require('web.field_registry');
var core = require('web.core');
var qweb = core.qweb;
...
- 修改從widget繼承的_renderEdit方法,渲染元素
_renderEdit: function () {
this.$el.empty();
var pills = qweb.render('FieldColorPills', {
widget: this
});
this.$el.append(pills);
},
- 添加static/src/xml/qweb_template.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="FieldColorPills">
<t t-foreach="widget.totalColors" t-as='pill_no'>
<span t-attf- t-att-data-val="pill_no"/>
</t>
</t>
</templates>
- 注冊QWeb模板
"qweb": [ 'static/src/xml/qweb_template.xml',
],
原理
在CMS網站開發的第14章中,已經有了關于QWeb創建或修改模板基礎的全面討論,我們將在這里重點討論它的不同之處,首先,在這我們處理的是JavaScript QWeb模板的實作,相對的是服務器側python的實作,這就意味著,你不能訪問資料集及背景關系,你只可以訪問傳遞給qweb.render函式的引數,
在我們的例子中,我們將當前物件賦值給widget,這就意味著你可以操作widget的JavaScript實作,并讓模板訪問相關屬性及函式,由于我們可以訪問小部件上可用的所有屬性,我們可以通過檢查totalColors屬性來檢查模板中的值,
由于客戶端QWeb與QWeb視圖無關,因此有一種不同的機制使web客戶端知道這些模板,通過QWeb鍵將它們添加到相對于加載項根目錄的檔案名串列中的加載項清單中,
小貼士
如果不想在清單中列出QWeb模板,可以使用代碼段上的xmlDependencies鍵來延遲加載模板,對于xmlDependencies,QWeb模板僅在小部件初始化時加載,
更多
在這里使用QWeb的原因是可擴展性,這是客戶端和服務器端QWeb的第二大區別,在客戶端,不能使用XPath運算式;需要使用jQuery選擇器和操作,例如,如果我們想從另一個模塊向小部件添加用戶圖示,我們將使用以下代碼在每個小部件中添加一個圖示:
<t t-extend="FieldColorPills">
<t t-jquery="span" t-operation="prepend">
<i />
</t>
</t>
如果此處我們使用t-name屬性,那么我們將使用原始模板的副本,而不動原始模板,t-operation的可能值還有append, before, after, inner及replace,正如其名,可將t元素中的內容追加目標元素內元素的最后、目標元素的前或后、替換目標元素的內容、替換目標元素及其內容,還有t-operation='attributes',可設定目標元素的屬性值,
另一個不同之處是,客戶端QWeb中的名稱不是以模塊名稱命名的,因此您必須為模板選擇名稱,這些模板可能是安裝的所有附加組件中唯一的,這就是為什么開發人員傾向于選擇相當長的名稱,
參考
如果您想了解有關QWeb模板的更多資訊,請參閱以下幾點:
- 與Odoo的其他部分相比,客戶端QWeb引擎的錯誤訊息和處理不太方便,一個小錯誤可能并不會影響程式的運行,因此這也就加大了初學者發現問題的難度,
- 幸運的是,odoo提供了一些客戶端QWeb模板的除錯模型,我們將在 “除錯你的客戶端代碼”一節中學習,
通過RPC呼叫后端python方法
我們的widget需要從服務器查詢資料,本節,我們將在顏色圓點上顯示一個tooltip提醒,當我們滑鼠懸停在小圓點上時,將展示那個顏色相關圖書的數量,我們將通過RPC呼叫,獲取特定顏色圖書的數量,
準備
步驟
- 添加willStart函式并設定colorGroupData的值:
willStart: function(){
var self = this;
this.colorGroupData = https://www.cnblogs.com/xushuotec/archive/2021/03/11/{};
var colorDataPromise = this._rpc({
model: this.model,
method:'read_group',
domain: [],
fields: ['color'],
groupBy: ['color'],
}).then(function(result){
_.each(result, function(r){
self.colorGroupData[r.color] = r.color_count;
});
});
return Promise.all([this._super.apply(this, arguments), colorDataPromise]);
},
- 更新_renderEdit函式,并設定tooltip:
_renderEdit: function(){
this.$el.empty();
var pills = qweb.render('FieldColorPills', {widget: this});
this.$el.append(pills);
this.$el.find('[data-toggle="tooltip"]').tooltip();
},
- 更新FieldColorPills模板并添加tooltip資料:
<t t-name="FieldColorPills">
<t t-foreach="widget.totalColors" t-as='pill_no'>
<span t-attf- t-att-data-val="pill_no" data-toggle="tooltip" data-placement="top" t-attf-title="This color is used in #{widget.colorGroupData[pill_no] or 0 } books." />
</t>
</t>
更新模塊,效果如下:

原理
willStart函式在渲染之前呼叫,并回傳一個Promise物件,帶物件需在渲染開始前生成,
我們依賴于ServiceMixin類的_rpc函式進行資料呼叫,該方法可實作呼叫模型的公開函式,如search、read、write等,在我們的例子中,我們使用了read_group函式,
步驟1,我們通過_rpc呼叫了read_group函式,我們以color分組獲取每組顏色的數量,我們將color_count和color序號映射到colorGroupData中供QWeb模板使用,In the last line of the function, we resolved willStart with super and our RPC call using $.when. Because of this, rendering only occurs after the values are fetched and after any asynchronous action super that was busy earlier, has finished, too.
步驟2,初始化tooltip,
步驟3,我們通過colorGroupData設定tooltip的值,
小貼士
你可以在widget的任何地方呼叫_rpc函式,注意,這是一個異步呼叫函式,您需要正確地管理延遲物件,以獲得所需的結果,
更多
The AbstractField class comes with a couple of interesting properties, one of which we just used. In our example, we used the this.model property, which holds the name of the current model (for example, library.book). Another property is this.field, which contains roughly the output of the model's fields_get() function for the field the widget is displaying. This will give all the information related to the current field. For example, for x2x fields, the fields_get() function gives you information about the co-model or the domain. You can also use this to query the field's string, size, or whatever other property you can set on the field during model definition.
Another helpful property is nodeOptions, which contains data passed via the options attribute in the
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/270825.html
標籤:其他
上一篇:WMI簡介和Event駐留
