From 1d6e4948d2e8534379b6815e931a6eaa58955e18 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 29 Apr 2020 07:21:34 +0000 Subject: [PATCH] [IMP] web: add BadgeField component This new field component displays the field's value inside a bootstrap pill badge. Supported field types are 'char', 'selection' and 'many2one'. The background color of the badge can be customized by using the decoration-xxx mechanism, e.g. Part of task 2195254 --- .../static/src/js/fields/basic_fields_owl.js | 23 ++++++ .../src/js/fields/field_registry_owl.js | 1 + addons/web/static/src/scss/fields.scss | 8 ++ addons/web/static/src/scss/list_view.scss | 3 + addons/web/static/src/scss/webclient.scss | 5 ++ addons/web/static/src/xml/fields.xml | 4 + .../static/tests/fields/basic_fields_tests.js | 73 +++++++++++++++++++ doc/reference/javascript_reference.rst | 13 ++++ 8 files changed, 130 insertions(+) diff --git a/addons/web/static/src/js/fields/basic_fields_owl.js b/addons/web/static/src/js/fields/basic_fields_owl.js index a00b14fb9ee..8a5915410ee 100644 --- a/addons/web/static/src/js/fields/basic_fields_owl.js +++ b/addons/web/static/src/js/fields/basic_fields_owl.js @@ -7,6 +7,27 @@ odoo.define('web.basic_fields_owl', function (require) { const _lt = core._lt; + + /** + * FieldBadge displays the field's value inside a bootstrap pill badge. + * The supported field's types are 'char', 'selection' and 'many2one'. + * + * By default, the background color of the badge is a light gray, but it can + * be customized by setting a 'decoration-xxx' attribute on the field. + * For instance, + * + * renders a badge with a red background on records matching the condition. + */ + class FieldBadge extends AbstractField { + _getClassFromDecoration(decoration) { + return `bg-${decoration.split('-')[1]}-light`; + } + } + FieldBadge.description = _lt("Badge"); + FieldBadge.supportedFieldTypes = ['selection', 'many2one', 'char']; + FieldBadge.template = 'web.FieldBadge'; + + class FieldBoolean extends AbstractField { patched() { super.patched(); @@ -105,7 +126,9 @@ odoo.define('web.basic_fields_owl', function (require) { FieldBoolean.supportedFieldTypes = ['boolean']; FieldBoolean.template = 'web.FieldBoolean'; + return { + FieldBadge, FieldBoolean, }; }); diff --git a/addons/web/static/src/js/fields/field_registry_owl.js b/addons/web/static/src/js/fields/field_registry_owl.js index f5f7d900ef8..907ea38a9d7 100644 --- a/addons/web/static/src/js/fields/field_registry_owl.js +++ b/addons/web/static/src/js/fields/field_registry_owl.js @@ -27,5 +27,6 @@ odoo.define('web._field_registry_owl', function (require) { // Basic fields registry + .add('badge', basicFields.FieldBadge) .add('boolean', basicFields.FieldBoolean); }); diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss index 6bae4914db1..f0d15adbd52 100644 --- a/addons/web/static/src/scss/fields.scss +++ b/addons/web/static/src/scss/fields.scss @@ -476,6 +476,14 @@ margin: 0 5px 0 0; display: inline-block; } + + &.o_field_badge { + border: 0; + font-size: 12px; + user-select: none; + background-color: rgba(lightgray, 0.5);; + font-weight: 500; + } } span.o_field_copy:empty { diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 4c0633cf0f3..176e31262b5 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -290,6 +290,9 @@ > .o_row_handle { visibility: hidden; // hide sequence when editing } + > .o_field_badge { + width: auto; // override width: 100% to keep the optimal badge width + } } .o_field_x2many_list_row_add, .o_group_field_row_add { diff --git a/addons/web/static/src/scss/webclient.scss b/addons/web/static/src/scss/webclient.scss index 9bc77f1b29d..19f107b4064 100644 --- a/addons/web/static/src/scss/webclient.scss +++ b/addons/web/static/src/scss/webclient.scss @@ -82,6 +82,11 @@ div.o_boolean_toggle.custom-control.custom-checkbox { background-color: #dee2e6; } +// Light version of contextual bg colors (e.g. bg-danger-light) +@each $name in ('primary', 'secondary', 'success', 'danger', 'warning', 'info') { + @include bg-variant('.bg-#{$name}-light', rgba(theme-color($name), .5), #333); +} + .o_web_accesskey_overlay { font-family: $font-family-sans-serif; } diff --git a/addons/web/static/src/xml/fields.xml b/addons/web/static/src/xml/fields.xml index ff7939f36a3..ce271ba2123 100644 --- a/addons/web/static/src/xml/fields.xml +++ b/addons/web/static/src/xml/fields.xml @@ -6,4 +6,8 @@ class="o_field_boolean" t-on-change="_onChange"/> + + + + diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index b04fdda089f..1f78efeb7f2 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -7053,6 +7053,79 @@ QUnit.module('basic_fields', { form.destroy(); }); + + QUnit.module('FieldBadge'); + + QUnit.test('FieldBadge component on a char field in list view', async function (assert) { + assert.expect(3); + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: ``, + }); + + assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(first record)'); + assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(second record)'); + assert.containsOnce(list, '.o_field_badge[name="display_name"]:contains(aaa)'); + + list.destroy(); + }); + + QUnit.test('FieldBadge component on a selection field in list view', async function (assert) { + assert.expect(3); + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: ``, + }); + + assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Blocked)'); + assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Normal)'); + assert.containsOnce(list, '.o_field_badge[name="selection"]:contains(Done)'); + + list.destroy(); + }); + + QUnit.test('FieldBadge component on a many2one field in list view', async function (assert) { + assert.expect(2); + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: ``, + }); + + assert.containsOnce(list, '.o_field_badge[name="trululu"]:contains(first record)'); + assert.containsOnce(list, '.o_field_badge[name="trululu"]:contains(aaa)'); + + list.destroy(); + }); + + QUnit.test('FieldBadge component with decoration-xxx attributes', async function (assert) { + assert.expect(3); + + const list = await createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: ` + + + + `, + }); + + assert.containsN(list, '.o_field_badge[name="foo"]', 5); + assert.containsOnce(list, '.o_field_badge[name="foo"].bg-danger-light'); + assert.containsOnce(list, '.o_field_badge[name="foo"].bg-warning-light'); + + list.destroy(); + }); }); }); }); diff --git a/doc/reference/javascript_reference.rst b/doc/reference/javascript_reference.rst index 9069916a250..7d5b6e6af35 100644 --- a/doc/reference/javascript_reference.rst +++ b/doc/reference/javascript_reference.rst @@ -2028,6 +2028,19 @@ order. - Supported field types: *char, text* +- badge (FieldBadge) + Displays the value inside a bootstrap badge pill. + + - Supported field types: *char*, *selection*, *many2one* + + By default, the badge has a lightgrey background, but it can be customized + by using the decoration-X mechanism. For instance, to display a red badge + under a given condition: + + .. code-block:: xml + + + Relational fields -----------------