diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js
index cebfed606fd..8ae7362b75f 100644
--- a/addons/web/static/src/js/fields/field_registry.js
+++ b/addons/web/static/src/js/fields/field_registry.js
@@ -60,6 +60,7 @@ registry
registry
.add('selection', relational_fields.FieldSelection)
.add('radio', relational_fields.FieldRadio)
+ .add('selection_badge', relational_fields.FieldSelectionBadge)
.add('many2one', relational_fields.FieldMany2One)
.add('list.many2one', relational_fields.ListFieldMany2One)
.add('kanban.many2one', relational_fields.KanbanFieldMany2One)
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 62cc965cc15..c86411b6cd9 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -2301,6 +2301,75 @@ var FieldRadio = FieldSelection.extend({
},
});
+
+var FieldSelectionBadge = FieldSelection.extend({
+ template: null,
+ className: 'o_field_selection_badge',
+ tagName: 'span',
+ specialData: "_fetchSpecialMany2ones",
+ events: _.extend({}, AbstractField.prototype.events, {
+ 'click span.o_selection_badge': '_onBadgeClicked',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @override
+ */
+ _renderEdit: function () {
+ this.currentValue = this.value;
+
+ if (this.field.type === 'many2one') {
+ this.currentValue = this.value && this.value.data.id;
+ }
+ this.$el.empty();
+ this.$el.html(qweb.render('FieldSelectionBadge', {'values': this.values, 'current_value': this.currentValue}));
+ },
+ /**
+ * Sets the possible field values. If the field is a many2one, those values
+ * may change during the life cycle of the widget if the domain change (an
+ * onchange may change the domain).
+ *
+ * @private
+ * @override
+ */
+ _setValues: function () {
+ // Note: We can make abstract widget for common code in radio and selection badge
+ if (this.field.type === 'selection') {
+ this.values = this.field.selection || [];
+ } else if (this.field.type === 'many2one') {
+ this.values = _.map(this.record.specialData[this.name], function (val) {
+ return [val.id, val.display_name];
+ });
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onBadgeClicked: function (event) {
+ var index = $(event.target).data('index');
+ var value = this.values[index];
+ if (value[0] !== this.currentValue) {
+ if (this.field.type === 'many2one') {
+ this._setValue({id: value[0], display_name: value[1]});
+ } else {
+ this._setValue(value[0]);
+ }
+ } else {
+ this._setValue(false);
+ }
+ },
+});
+
/**
* The FieldReference is a combination of a select (for the model) and
* a FieldMany2one for its value.
@@ -2461,6 +2530,7 @@ return {
KanbanFieldMany2ManyTags: KanbanFieldMany2ManyTags,
FieldRadio: FieldRadio,
+ FieldSelectionBadge: FieldSelectionBadge,
FieldSelection: FieldSelection,
FieldStatus: FieldStatus,
diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss
index 3de64e866ac..0fd69037b0b 100644
--- a/addons/web/static/src/scss/fields.scss
+++ b/addons/web/static/src/scss/fields.scss
@@ -229,6 +229,24 @@
cursor: ns-resize;
}
+ &.o_field_selection_badge {
+ .o_selection_badge {
+ display: inline-block;
+ margin: 0px 0px 4px 4px;
+ padding: 1px 6px;
+ color: $o-main-color-muted;
+ border: 1px solid $o-color-silver-dark;
+ cursor: pointer;
+ &.active {
+ color: $o-brand-optional;
+ border-color: $o-brand-optional;
+ }
+ &:hover:not(.active) {
+ color: darken($o-color-silver-dark, 30%);
+ border-color: darken($o-color-silver-dark, 30%);
+ }
+ }
+ }
// Radio buttons
&.o_field_radio {
.o_radio_input {
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 0debc65dc54..458e15dd2be 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -792,6 +792,13 @@
+
+
+
+
+
+
+
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 9e1ca77a05c..913ca896b7c 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -11382,6 +11382,79 @@ QUnit.module('relational_fields', {
form.destroy();
});
+
+ QUnit.module('FieldSelectionBadge');
+
+ QUnit.test('FieldSelectionBadge widget on a many2one in a new record', function (assert) {
+ assert.expect(6);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ });
+
+ assert.ok(form.$('span.o_selection_badge').length, "should have rendered outer div");
+ assert.strictEqual(form.$('span.o_selection_badge').length, 2, "should have 2 possible choices");
+ assert.ok(form.$('span.o_selection_badge:contains(xphone)').length, "one of them should be xphone");
+ assert.strictEqual(form.$('span.active').length, 0, "none of the input should be checked");
+
+ $("span.o_selection_badge:first").click();
+
+ assert.strictEqual(form.$('span.active').length, 1, "one of the input should be checked");
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ var newRecord = _.last(this.data.partner.records);
+ assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value");
+ form.destroy();
+ });
+
+ QUnit.test('FieldSelectionBadge widget on a selection in a new record', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ });
+
+ assert.ok(form.$('span.o_selection_badge').length, "should have rendered outer div");
+ assert.strictEqual(form.$('span.o_selection_badge').length, 2, "should have 2 possible choices");
+ assert.ok(form.$('span.o_selection_badge:contains(Red)').length, "one of them should be Red");
+
+ // click on 2nd option
+ form.$("span.o_selection_badge").eq(1).click();
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ var newRecord = _.last(this.data.partner.records);
+ assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value");
+ form.destroy();
+ });
+
+ QUnit.test('FieldSelectionBadge widget on a selection in a readonly mode', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ });
+
+ assert.strictEqual(form.$('span.o_readonly_modifier').length, 1, "should have 1 possible value in readonly mode");
+ form.destroy();
+ });
+
QUnit.module('FieldMany2ManyCheckBoxes');
QUnit.test('widget many2many_checkboxes', function (assert) {
diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst
index 0e9c89f611c..ef41944e389 100644
--- a/doc/reference/javascript_reference.rst
+++ b/doc/reference/javascript_reference.rst
@@ -1756,6 +1756,16 @@ Relational fields
+- selection_badge (FieldSelectionBadge)
+ This is a subfield of FieldSelection, but specialized to display all the
+ valid choices as rectangular badges.
+
+ - Supported field types: *selection, many2one*
+
+ .. code-block:: xml
+
+
+
- many2one (FieldMany2One)
Default widget for many2one fields.