即使一个人,也要活得像军队一样!

Odoo13-- js之颜色选择器的使用及扩展

题目虽然是颜色选择器使用,但是本篇小黄文会涉及到玉女心经…
咳咳, 本篇技术文章涉及到一些重要的姿势,某些看官一直要求我讲一讲有关js方面的姿势。
那么本篇大概会出现自定义Widget、Tree视图的渲染等姿势。
某些看官(尤其’小萝卜头’…)就不要那么吝啬了,1分钱都不够手机流量费。

那下面就正式开始

颜色选择器的使用

Odoo 中关于颜色选择器的使用,有很多例子。这里就以产品属性为例子。
下面是定义了一个彩虹屁的属性,并在属性中设置了相关属性值
彩虹屁
在创建属性值得时候,点击颜色时,会弹出一个颜色选择器的界面。如下
颜色选择器

那么,这是如何实现的呢?进入下一个环境,剥丝抽茧,揭开神秘面纱。

揭开选择器面纱

我们查看代码,发现字段定义如下。

在py文件中:

1
2
3
html_color = fields.Char(
string='Color',
help="Here you can set a specific HTML color index (e.g. #ff0000) to display the color if the attribute type is 'Color'.")

在xml文件中:

1
<field name="html_color" attrs="{'column_invisible': [('parent.display_type', '!=', 'color')]}" widget="color"/>

由此可见,该字段是一个Char类型的字段,并且使用了一个colorWidget
因此我们回归到问题, 这个color是什么玩意儿?我能自定义吗?

color插件的定义

文件路径: odoo\addons\web\static\src\js\fields\field_registry.js
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
registry
.add('abstract', AbstractField)
.add('input', basic_fields.InputField)
.add('integer', basic_fields.FieldInteger)
.add('boolean', basic_fields.FieldBoolean)
.add('date', basic_fields.FieldDate)
.add('datetime', basic_fields.FieldDateTime)
.add('daterange', basic_fields.FieldDateRange)
.add('domain', basic_fields.FieldDomain)
.add('text', basic_fields.FieldText)
.add('list.text', basic_fields.ListFieldText)
.add('html', basic_fields.FieldText)
.add('float', basic_fields.FieldFloat)
.add('char', basic_fields.FieldChar)
.add('link_button', basic_fields.LinkButton)
.add('handle', basic_fields.HandleWidget)
.add('email', basic_fields.FieldEmail)
.add('phone', basic_fields.FieldPhone)
.add('url', basic_fields.UrlWidget)
.add('CopyClipboardText', basic_fields.TextCopyClipboard)
.add('CopyClipboardChar', basic_fields.CharCopyClipboard)
.add('image', basic_fields.FieldBinaryImage)
.add('kanban.image', basic_fields.KanbanFieldBinaryImage)
.add('binary', basic_fields.FieldBinaryFile)
.add('pdf_viewer', basic_fields.FieldPdfViewer)
.add('monetary', basic_fields.FieldMonetary)
.add('percentage', basic_fields.FieldPercentage)
.add('priority', basic_fields.PriorityWidget)
.add('attachment_image', basic_fields.AttachmentImage)
.add('label_selection', basic_fields.LabelSelection)
.add('kanban_label_selection', basic_fields.LabelSelection) // deprecated, use label_selection
.add('state_selection', basic_fields.StateSelectionWidget)
.add('kanban_state_selection', basic_fields.StateSelectionWidget) // deprecated, use state_selection
.add('boolean_favorite', basic_fields.FavoriteWidget)
.add('boolean_toggle', basic_fields.BooleanToggle)
.add('statinfo', basic_fields.StatInfo)
.add('percentpie', basic_fields.FieldPercentPie)
.add('float_time', basic_fields.FieldFloatTime)
.add('float_factor', basic_fields.FieldFloatFactor)
.add('float_toggle', basic_fields.FieldFloatToggle)
.add('progressbar', basic_fields.FieldProgressBar)
.add('toggle_button', basic_fields.FieldToggleBoolean)
.add('dashboard_graph', basic_fields.JournalDashboardGraph)
.add('ace', basic_fields.AceEditor)
.add('color', basic_fields.FieldColor);

发现是在此处,将basic_fields.FieldColor注册成为一个名为color的widget插件
继续跟踪basic_fields.FieldColor
文件路径: odoo\addons\web\static\src\js\fields\basic_fields.js
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
var FieldColor = AbstractField.extend({
template: 'FieldColor',
events: _.extend({}, AbstractField.prototype.events, {
'click .o_field_color': '_onColorClick',
}),
custom_events: _.extend({}, AbstractField.prototype.custom_events, {
'colorpicker:saved': '_onColorpickerSaved',
}),

//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------

/**
* @override
*/
getFocusableElement: function () {
return this.$('.o_field_color');
},

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* @override
* @private
*/
_render: function () {
this.$('.o_field_color').data('value', this.value)
.css('background-color', this.value)
.attr('title', this.value);
return this._super.apply(this, arguments);
},

//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------

/**
* @private
*/
_onColorClick: function () {
if (this.mode === 'edit') {
const dialog = new ColorpickerDialog(this, {
defaultColor: this.value,
noTransparency: true,
}).open();
dialog.on('closed', this, () => {
// we need to wait for the modal to execute its whole close function.
Promise.resolve().then(() => {
this.getFocusableElement().focus();
});
});
}
},

/**
* @private
* @param {OdooEvent} ev
*/
_onColorpickerSaved: function (ev) {
this._setValue(ev.data.hex);
},

/**
* @override
* @private
*/
_onKeydown: function (ev) {
if (ev.which === $.ui.keyCode.ENTER) {
ev.preventDefault();
ev.stopPropagation();
this._onColorClick(ev);
} else {
this._super.apply(this, arguments);
}
},
});

我们发现,该插件使用的模板是FieldColor。继续查看FieldColor模板定义。
文件路径: \odoo\addons\web\static\src\xml\base.xml
源码:

1
2
3
4
5
<t t-name="FieldColor">
<div>
<button class="o_field_color" t-attf-tabindex="#{widget.mode === 'edit' ? 0 : -1}"></button>
</div>
</t>

至此,我们已经扒开了color这个widget的层层伪装了。
首先是定义了一个模板FieldColor。而模板中定义了一个button按钮,并且class属性为o_field_color(这很关键)
其次在js中对AbstractField这个抽象字段插件进行了扩展。
扩展步骤:
一、引入模板 使用 template: 'FieldColor'
二、定义相关事件

1
2
3
4
5
6
7
events: _.extend({}, AbstractField.prototype.events, {
'click .o_field_color': '_onColorClick', // 为class属性为 o_field_color的元素绑定click事件
}),

custom_events: _.extend({}, AbstractField.prototype.custom_events, {
'colorpicker:saved': '_onColorpickerSaved', // 自定义事件
}),

重点, 此步骤(events)将模板中的按钮,绑定了事件。

三、重写render方法

1
2
3
4
5
6
_render: function () {
this.$('.o_field_color').data('value', this.value) // 为按钮设置值
.css('background-color', this.value) // 设置背景颜色
.attr('title', this.value); // 设置title提示
return this._super.apply(this, arguments);
},

render方法,是渲染字段的方法。在此处就重写渲染字段方法,则会被应用到前端。
小姿势:
render方法中,会根据当前单据的打开模式(编辑,只读)会分别执行两个方法。
_renderEditreadonly (重点)。以后自定义可重写这两个方法。

1
2
3
4
5
if (this.mode === 'edit') {
return this._renderEdit();
} else if (this.mode === 'readonly') {
return this._renderReadonly();
}

四、点击事件执行_onColorClick方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_onColorClick: function () {
if (this.mode === 'edit') { // 编辑模式
const dialog = new ColorpickerDialog(this, { // 创建一个颜色选择器对象,并打开
defaultColor: this.value,
noTransparency: true,
}).open();
dialog.on('closed', this, () => {
// we need to wait for the modal to execute its whole close function.
Promise.resolve().then(() => {
this.getFocusableElement().focus();
});
});
}
},

可以看到在点击事件中这是创建并打开了一个颜色选择器的对话框。

五、关闭对话框事件_onColorpickerSaved方法。
此事件是自定义事件custom_events中的

1
2
3
_onColorpickerSaved: function (ev) {
this._setValue(ev.data.hex); // 为当前元素设置值
},

到此为止,整个color插件的定义过程就是这样。

姿势就这些,那么接下来就是学以致用了。进入实战环节。

姿势实战

场景一:点击字段时候,弹出颜色选择器,给字段设置相关背景颜色。
体代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

odoo.define('web_custom_color.AbstractField', function (require) {
"use strict";

var AbstractField = require('web.AbstractField');
var ColorpickerDialog = require('web.ColorpickerDialog');
require("web.zoomodoo");

AbstractField.include({

// 定义一个点击事件
_onMyColorClick: function () {
if (this.mode === 'readonly') {
var self = this;
// 设置默认颜色,如果xml中定义了背景色则使用
var defaultColor = self.attrs.bg_color || '#fcfcfc' ;
const dialog = new ColorpickerDialog(self, { //创建颜色选择器
defaultColor: defaultColor,
noTransparency: true,
}).open();
dialog.on('colorpicker:saved', self, function (ev) { // 关闭事件
self.attrs.bg_color = ev.data.hex;
self.$el.css('background-color', ev.data.hex);
});
}
},

start: function () {
var self = this;
self.$el.on('click', self._onMyColorClick.bind(self)); // 给字段绑定事件
return this._super.apply(this, arguments);
},

_render: function () {
this.$el.css('background-color', this.attrs.bg_color || '#fcfcfc'); // 渲染背景色
return this._super.apply(this, arguments);
},

});

});

耕耘收获:
自定义字段颜色选择器

场景二: 给float类型字段,设置千分符
体代码,如下:

1
2
3
4
5
6
7
8
9
var basic_fields = require('web.basic_fields');
var field_registry = require('web.field_registry');

var FieldtoExponential = basic_fields.FieldFloat.extend({
_renderReadonly: function () {
this.$el.text(this.value.toExponential(3)); // 转换千分符
},
});
field_registry.add('toExponential', FieldtoExponential);

在xml中,给float字段添加一个widget="toExponential"

场景三: 在Tree视图中,给某些字段加上颜色区分(列区分,非行区分)。比如金额大于0的显示蓝色,小于0的显示红色。(红蓝发票)

1
2
3
4
5
6
7
8
9
...
// 各位看官自己写吧,别偷懒。
// 姿势都已经掌握了, 总要自己实践吧。
// 技巧任君发挥。
// 不然,我也可以帮你实践...
// 就是,
// 我担心,
// 颜色选择器会默认给你选择----绿色
...

以上。
先了解,再找准,再开发。
– end –

看官,别吝啬啦!

-------------本文结束感谢您的阅读-------------