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.