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
-----------------