主頁 > 企業開發 > 【odoo14】第十五章、網站客戶端開發

【odoo14】第十五章、網站客戶端開發

2021-04-01 20:11:10 企業開發

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的基礎知識,

本章主要內容如下:

  1. 創建自定義控制元件
  2. 使用客戶端側的QWeb模板
  3. 通過RPC呼叫后端python方法
  4. 創建新的視圖
  5. 除錯用客戶端側的代碼
  6. 通過引導提升互動感
  7. 手機端js

創建自定義控制元件

正如您在第9章后端視圖中看到的,我們可以使用小部件以不同的格式顯示特定的資料,例如,我們使用widget='image'以影像的形式顯示一個二進制欄位,為了演示如何創建自己的小部件,我們將撰寫一個小部件,它允許用戶選擇一個整數欄位,但我們將以不同的方式顯示它,代替輸入框,我們將顯示一個顏色選擇器,以便我們可以選擇一個顏色號,在這里,每個數字將被映射到其相關的顏色,

準備

在本教程中,我們將使用my_library模塊和基本欄位和視圖,

步驟

我們將添加一個JavaScript檔案,其中包含小部件的邏輯,并添加一個SCSS檔案來執行一些樣式化操作,然后,我們將向books表單添加一個整數欄位,以使用我們的新小部件,執行以下步驟添加一個新的欄位小部件:

  1. 添加一個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');
	...
})
  1. 創建widget:
var colorField = AbstractField.extend({
  1. 設定CSS類、根元素以及支持的欄位型別:
className: 'o_int_colorpicker', 
tagName: 'span', 
supportedFieldTypes: ['integer'],
  1. 配置js事件
events: {
	'click .o_color_pill': 'clickPill',
},
  1. 多載建構式
init: function(){
	this.totalColors = 10;
	this._super.apply(this, arguments);
}
  1. 多載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,
	}));
},
  1. 添加處理函式
	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
  1. 注冊widget:
fieldRegistry.add('int_color', colorField);
  1. 對其他組件可見:
return {
	colorField: colorField,
};
}; // close 'my_field_widget' Namespace
  1. 添加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;
                }
            }
        }
    }
}
  1. 注冊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>
  1. 添加color欄位
color = fields.Integer()
  1. 在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代碼,以便我們可以使用它,請按照以下步驟啟動:

  1. 匯入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;
...
  1. 修改從widget繼承的_renderEdit方法,渲染元素
_renderEdit: function () {
    this.$el.empty();
    var pills = qweb.render('FieldColorPills', {
        widget: this
    });
    this.$el.append(pills);
},
  1. 添加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>
  1. 注冊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呼叫,獲取特定顏色圖書的數量,

準備

步驟

  1. 添加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]);
},
  1. 更新_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();	
},
  1. 更新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

view definition. This is already JSON parsed, so you can
access it like any object. For more information on such properties, dig further into the abstract_field.js file.

參考

如果在管理異步操作方面存在問題,請參考以下檔案:

  • Odoo's RPC returns JavaScript's native Promise object. You will get requested data once Promise is resolved. You can learn more about Promise here: https:// developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Global_Objects/Promise.

創建新的視圖

本節,我們將創建一種新的視圖,用于展示作者及其圖書資訊,

準備

本節,我們使用之前的my_library模塊,因為視圖有很復雜的結構,每個視圖都有其實作的目標,本節的目標是讓你理解MVC模型下的視圖及如何創建簡單的視圖,我們將創建m2m_group的視圖,目標是以組的形式展示記錄,為了將記錄分配到不同的組,視圖使用的是many2many的資料,在my_library模塊中,我們有author_ids欄位,我們將以作者分組并展示圖書,
此外,我們將在控制區新增一個按鈕,可允許我們新增圖書記錄,同時,我們也會在作者的卡片上新增一個按鈕,用于重定向到另一個視圖,

步驟

  1. 添加新的視圖型別
class View(models.Model):
	_inherit = 'ir.ui.view'

	type = fields.Selection(selection_add=[('m2m_group', 'M2m Group')])
  1. 新建視圖模式
class ActWindowView(models.Model):
	_inherit = 'ir.actions.act_window.view'

	view_mode = fields.Selection(selection_add=[('m2m_group', 'M2m group')], ondelete={'m2m_group': 'cascade'})
  1. 繼承base模型實作新的方法,該方法將從JavaScript呼叫
class Base(models.AbstractModel):
	_inherit = 'base'

	@api.model
	def get_m2m_group_data(self, domain, m2m_field):
		records = self.search(domain)
		result_dict = {}
		for record in records:
			for m2m_record in record[m2m_field]:
				if m2m_record.id not in result_dict:
					result_dict[m2m_record.id] = {
                        'name': m2m_record.display_name,
                        'children': [],
                        'model': m2m_record._name
	                }
                result_dict[m2m_record.id]['children'].append({
                    'name': record.display_name,
                    'id': record.id
                })
        return result_dict
  1. 添加/static/src/js/m2m_group_model.js
odoo.define('m2m_group.Model', function(require){
    'use strict';
    var AbstractModel = require('web.AbstractModel');

    var M2mGroupModel = AbstractModel.extend({
        __get: function(){
            return this.data;
        },
        __load: function(params){
            this.modelName = params.modelName;
            this.domain = params.domain;
            this.m2m_field = params.m2m_field;
            return this._fetchData();
        },
        __reload: function (handle, params){
            if ('domain' in params){
                this.domain = params.domain;
            }
        },
        _fetchData: function(){
            var self = this;
            return this._rpc({
                model: this.modelName,
                method: 'get_m2m_group_data',
                kwargs: {
                    domain: this.domain,
                    m2m_field: this.m2m_field
                }
            }).then(function(result){
                self.data = https://www.cnblogs.com/xushuotec/archive/2021/03/11/result;
            })
        },
    });
    return M2mGroupModel;
})
  1. /static/src/js/m2m_group_controller.js
odoo.define('m2m_group.Controller', function(require) {
    'use strict';
    
    var AbstractController = require('web.AbstractController');
    var core = require('web.core');
    var qweb = core.qweb;

    var M2mGroupController = AbstractController.extend({
        custom_events: _.extend({}, AbstractController.prototype.custom_events, {
            'btn_clicked': '_onBtnClicked',
        }),
        renderButtons: function($node){
            if($node){
                this.$buttons = $(qweb.render('ViewM2mGroup.buttons'));
                this.$buttons.appendTo($node);
                this.$buttons.on('click', 'button', this._onAddButtonClick.bind(this));
            }
        },
        _onBtnClicked: function(ev){
            this.do_action({
                type: 'ir.actions.act_window',
                name: this.title,
                res_model: this.modelName,
                views: [[false, 'list'], [false, 'form']],
                domain: ev.data.domain
            });
        },
        _onAddButtonClick: function(ev){
            this.do_action({
                type: 'ir.actions.act_window',
                name: this.title,
                res_model: this.modelName,
                views: [[false, 'form']],
                target: 'new'
            });
        },
    });
    return M2mGroupController;
});
  1. 添加/static/src/js/m2m_group_renderer.js
odoo.define('m2m_group.Renderer', function(require){
    'use strict';
    
    var AbstractRenderer = require('web.AbstractRenderer');
    var core = require('web.core');
    var qweb = core.qweb;

    var M2mGroupRenderer = AbstractRenderer.extend({
        events: _.extend({}, AbstractRenderer.prototype.events, {
            'click .o_primay_button': '_onClickButton',
        }),
        _render: function(){
            var self = this;
            this.$el.empty();
            this.$el.append(qweb.render('ViewM2mGroup', {
                'groups': this.state,
            }));
            return this._super.apply(this, arguments);
        },
        _onClickButton: function(ev){
            ev.preventDefault();
            var target = $(ev.currentTarget);
            var group_id = target.data('group');
            var children_ids = _.map(this.state[group_id].children, function(group_id){
                return group_id.id;
            });
            this.trigger_up('btn_clicked', {
                'domain': [['id', 'in', children_ids]]
            });
        }
    });
	return M2mGroupRenderer;
});
  1. 添加/static/src/js/m2m_group_view.js
odoo.define('m2m_group.View', function(require){
    'use strict';

    var AbstractView = require('web.AbstractView');
    var view_registry = require('web.view_registry');
    var M2mGroupController = require('m2m_group.Controller');
    var M2mGroupModel = require('m2m_group.Model');
    var M2mGroupRenderer = require('m2m_group.Renderer');

    var M2mGroupView = AbstractView.extend({
        display_name: 'Author',
        icon: 'fa-id-card-o',
        config: _.extend({}, AbstractView.prototype.config, {
            Model: M2mGroupModel,
            Controller: M2mGroupController,
            Renderer: M2mGroupRenderer,
        }),
        viewType: 'm2m_group',
        searchMenuTypes: ['filter', 'favorite'],
        accesskey: "a",
        init: function(viewInfo, params){
            this._super.apply(this, arguments);
            var attrs = this.arch.attrs;

            if(!attrs.m2m_field){
                throw new Error('M2m view has not define "m2m_field" attribute.');
            }

            // Model Parameters
            this.loadParams.m2m_field = attrs.m2m_field;
        },
    });
    view_registry.add('m2m_group', M2mGroupView);

    return M2mGroupView;
})
  1. 添加/static/src/xml/qweb_template.xml
<t t-name="ViewM2mGroup">
    <div >
        <div t-foreach="groups" t-as="group" >

            <t t-set="group_data" t-value="https://www.cnblogs.com/xushuotec/archive/2021/03/11/groups[group]"/>
            <div >
                <img  t-attf-src="https://www.cnblogs.com/web/image/#{group_data.model}/#{group}/image_512"/>
                <div >
                    <h5 >
                        <t t-esc="group_data['name']"/>
                    </h5>

                </div>
                <ul >
                    <t t-foreach="group_data['children']" t-as="child">
                        <li >
                            <i />
                            <t t-esc="child.name"/>
                        </li>
                    </t>
                </ul>
                <div >
                    <a href="https://www.cnblogs.com/xushuotec/archive/2021/03/11/#"  t-att-data- group="group">View books</a>
                </div>
            </div>
        </div>
    </div>
</t>
<div t-name="ViewM2mGroup.buttons">
    <button type="button" >
        Add Record
    </button>
</div>
  1. 添加所有的js檔案
...
<script type="text/javascript" src="https://www.cnblogs.com/my_library/static/ src/js/m2m_group_view.js" />
<script type="text/javascript" src="https://www.cnblogs.com/my_library/static/ src/js/m2m_group_model.js" />
<script type="text/javascript" src="https://www.cnblogs.com/my_library/static/ src/js/m2m_group_controller.js" />
<script type="text/javascript" src="https://www.cnblogs.com/my_library/static/ src/js/m2m_group_renderer.js" />
...
  1. 最后,添加新視圖實體
<record id="library_book_view_author" model="ir. ui.view">
    <field name="name">Library Book Author</field>
    <field name="model">library.book</field>
    <field name="arch" type="xml">
        <m2m_group m2m_field="author_ids" color_ field="color">
        </m2m_group>
    </field>
</record>
  1. 添加動作
...
<field name="view_mode">tree,m2m_group,form</field> 
...

更新視圖后如下:

重要資訊
Odoo視圖非常易于使用,并且非常靈活,然而,通常情況下,簡單和靈活的事物都有復雜的實作,odoojavascript視圖也是如此:它們易于使用,但實作起來很復雜,它們由許多組件組成,包括模型、渲染器、控制器、視圖和QWeb模板,在下一節中,我們為視圖添加了所有必需的組件,并為library.book模型,如果不想手動添加所有內容,請從本書的GitHub存盤庫中的示例檔案中獲取一個模塊,

原理

步驟1、2,我們在ir.ui.view和ir.actions_act_window.view注冊了名為m2m_group的新視圖,
步驟3,在base中我們新增了名為get_m2m_group_data的方法,在base中新增可滿足任何模型中呼叫的需求,該函式將在JavaScript的RPC中呼叫,視圖會有兩個引數,domain和m2m_field,在domain中,domain的值是搜索視圖domain及action domain的組合,m2m_field是我們計劃分組的元素,將在視圖中定義,
后面幾步,我們新增了JavaScript檔案,Odoo中的JavaScript視圖是由view,model,renderer,及controller構成,由于歷史原因,單次view在odoo中有其特殊的含義,因此,傳統意義上的model、view、controller(MVC)變成了model、renderer、controller(MRC),view與model、renderer、controller的關系如下:

Model、Renderer、Controller、View是組成視圖的基本元素,

  • Model: model是視圖的狀態載體,負責發送RPC請求資料,并傳遞資料給controller及renderer,我們重寫了__load和__reload方法,當視圖在初始化的將呼叫__load()獲取資料,當搜索條件變化或視圖有一個新的狀態的時候,__reload()將被呼叫,在我們的案例中,我們創建了用于RPC請求資料的_fetchData()方法,可實作呼叫服務器側模型的方法get_m2m_group_data(步驟3中定義),__get()是在controller中呼叫獲取模型狀態,
  • Controller: Controller負責將Model和Renderer串起來,當Renderer中的action被觸發的時候,它將傳遞相關資訊給controller并執行該action,有時,它也將呼叫Model中的方法,此外,它也負責管理控制面板上的按鈕,在我們的例子中,我們創建了添加記錄的按鈕,為了實作此目標,我們需重寫AbstractController中的renderButtons()函式,我們也需要注冊custom_events,以便在點擊作者卡片上的按鈕時,能夠觸發相關動作(點擊button->觸發事件->controll捕獲事件->回應),
  • Renderer: renderer負責管理DOM元素,每種視圖都有其獨特的渲染方式,在渲染器中,你可以獲取Model的狀態,并通過render()渲染視圖,在我們的例子中,我們渲染了ViewM2mGroup QWeb模板,并把JavaScript事件與動作進行關聯,本節,我們系結了卡片按鈕的點擊事件,當用戶點擊了按鈕,將觸發btn_clicked事件,并打開該作者的圖書串列,

重要貼士
events和custom_events是不同的,events是JavaScript常規的事件,而custom_events源自odoo的JavaScript的框架,用戶自定義事件可通過trigger_up觸發,

  • View: View負責用于獲取構成視圖的基本元素,比如fields、context、view arch以及其他的元素,然后,View將初始化controller、renderer、model,通常,View還將為model、view、controller準備相關引數,在我們的例子中,Model是以m2m_field的值進行分組,因此我們設定了model的引數,同樣,this.controllerParams和this.rendererParams將用于controller及renderer,

步驟8,我們添加了QWeb模板用于視圖及控制面板,關于QWeb模板更多的內容,可學習本章中“使用用戶側QWeb模板”一節,

重要資訊
Odoo視圖有各種各樣的函式,我們在本章中學了最常用的一些函式,如果你想要學習更多的內容,可查看/addons/web/static/src/js/views目錄,該目錄也包含abstract model、controller、renderer及view的內容,

步驟9,添加JavaScript檔案,
最后,最后兩步,我們添加了book.library模型的視圖,步驟10,我們使用<m2m_group>標簽及m2m_field屬性,這將用于從服務器獲取資料,

更多

如果你只是想對現有視圖進行調整,你可以使用js_class,比如,如果我們想要創建一個類似于kanban的視圖,可如下操作:

var CustomDashboardRenderer = KanbanRenderer.extend({...});
var CustomDashboardModel = KanbanModel.extend({...});
var CustomDashboardController = KanbanController.extend({...});
var CustomDashboardView = KanbanView.extend({
	config: _.extend({}, KanbanView.prototype.config, {
		Model: CustomDashboardModel,
		Renderer: CustomDashRenderer,
		Controller: CustomDashboardController,
	}),
});

var viewRegistry = require('web.view_registry');
viewRegistry.add('my_custom_view', CustomDashboardView);

然后我們呼叫帶有js_class屬性的kanban視圖:

...
<field name="arch" type="xml">
	<kanban js_>
		...
	</kanban>
</field>
...

除錯用客戶端側的代碼

對于服務器側的代碼除錯,我們在第七章進行了詳細介紹,本節,我們將對客戶端側代碼除錯進行說明,

準備

步驟

除錯客戶端側代碼之所以麻煩是因為web客戶端嚴重依賴于Jquery的異步事件,因為斷點會中斷代碼執行,因此很有可能由于時間問題引起的BUG并不會被觸發,

  1. 對于客戶端除錯,您需要使用資產激活除錯模式,如果您不知道如何使用資產激活除錯模式,請閱讀第1章“安裝Odoo開發環境”中的“激活Odoo開發工具”配方,
  2. 在你感興趣的JavaScript函式中,呼叫除錯器:
debugger;
  1. 如果你有計時問題,可以通過JavaScript函式登錄控制臺:
console.log("I'm in function X currently");
  1. 如果你想在模板渲染程序中進行除錯,可以從QWeb呼叫除錯器:
<t t-debug="" />
  1. 您也可以使用QWeb登錄控制臺,如下所示:
<t t-log="myvalue" />

所有這一切都依賴于您的瀏覽器提供適當的除錯功能,雖然所有的主流瀏覽器都能做到這一點,但為了便于演示,我們在這里只討論Chromium,為了能夠使用除錯工具,點擊右上角的選單按鈕并選擇更多工具|開發人員工具:

原理

當除錯器打開時,你應該會看到類似下面的截圖:

在這里,您可以在不同的選項卡中訪問許多不同的工具,前面螢屏截圖中當前活動的選項卡是JavaScript除錯器,我們通過單擊行號在第31行中設定斷點,每當我們的小部件獲取用戶串列時,執行應該在這一行停止,除錯器將允許您檢查變數或更改它們的值,在右側的觀察串列中,您還可以呼叫函式來嘗試它們的效果,而不必不斷保存腳本檔案并重新加載頁面,
一旦打開了開發人員工具,我們前面描述的除錯器陳述句的行為將是相同的,然后,執行將停止,瀏覽器將切換到Sources選項卡,打開有問題的檔案,并突出顯示debugger陳述句所在的行,
前面提到的兩種日志記錄的可能性將在控制臺選項卡中結束,這是您在任何情況下都應該檢查的第一個選項卡,因為如果一些JavaScript代碼由于語法錯誤或類似的基本問題而根本無法加載,那么您將看到一條錯誤訊息,解釋發生了什么,

更多

使用Elements選項卡檢查瀏覽器當前顯示的頁面的DOM表示,在熟悉現有小部件生成的HTML代碼時,這將被證明是有幫助的,而且一般來說,它還允許您使用類和CSS屬性,這是測驗布局變化的一個很好的資源,
Network選項卡提供了當前頁面發出的請求的概覽,以及它花費了多長時間,在除錯緩慢的頁面加載時,這很有幫助,因為在Network選項卡中,您通常會找到請求的詳細資訊,如果您選擇了一個請求,您可以檢查傳遞給服務器的有效負載和回傳的結果,這有助于您找出客戶端意外行為的原因,您還將看到請求的狀態碼(例如,404),以防由于檔案名拼寫錯誤而找不到資源,

通過引導提升互動感

在開發了一個大型的應用程式之后,向最終用戶解釋軟體流程是至關重要的,Odoo框架包括一個內置的向導管理器,有了這個導覽器,您就可以通過學習特定流程來指導最終用戶,在此菜譜中,我們將創建一個向導來引導用戶創建一本書,

準備

步驟

  1. 添加/static/src/js/my_library_tour.js檔案
odoo.define('my_library.tour', function (require) {
    "use strict";
    var core = require('web.core');
    var tour = require('web_tour.tour');
    var _t = core._t;
    tour.register('library_tour', {
        url: "/web",
        rainbowManMessage: _t("Congrats, you have listed a book."),
        sequence: 5,
    }, [tour.stepUtils.showAppsMenuItem(), {
        trigger: '.o_app[data-menu-xmlid="my_library. library_base_menu"]',
        content: _t('Manage books and authors in <b>Library app</b>.'),
        position: 'right'
    }, {
        trigger: '.o_list_button_add',
        content: _t("Let's create new book."),
        position: 'bottom'
    }, {
        trigger: 'input[name="name"]',
        extra_trigger: '.o_form_editable',
        content: _t('Set the book title'),
        position: 'right',
    }, {
        trigger: '.o_form_button_save',
        content: _t('Save this book record'),
        position: 'bottom',
    }]);
});
  1. 將js檔案添加到后臺資源
<script type="text/javascript" src="https://www.cnblogs.com/my_library/static/ src/js/my_library_tour.js" />

更新模塊如下:

原理

向導管理器在web_tour.tour命名空間中,
步驟1,我們引入了web_tour.tour,隨后我們通過registry()添加了向導,我們注冊了名為library_tour的向導并配置URL(向導的作用URL),
下一個引數是這些向導步驟的串列,一個瀏覽步驟需要三個值,觸發器用于選擇應該在其上顯示游覽的元素,這是一個JavaScript選擇器,我們使用選單的XML ID,因為它在DOM中可用,
第一步tour. steputils . showappsmenuitem()是tour中為主選單預定義的步驟,下一個鍵是內容,當用戶將滑鼠懸停在tour drop上時將顯示內容,我們使用_t()函式是因為我們想要轉換字串,而position鍵用于決定行程洗掉的位置,可能的值包括top、right、left或bottom,

重要資訊
這些向導可改善了用戶體驗,以及管理集成測驗,當您在內部以測驗模式運行Odoo時,它也會運行向導,但是如果向導未完成,將導致測驗用例失敗,

手機端js(企業版可用)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/270825.html

標籤:其他

上一篇:WMI簡介和Event駐留

下一篇:【odoo14】第十八章、自動化測驗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more