[IMP] web: list: allow to add decorations on field nodes

In list views, one can specify decoration-XXX attributes on the
arch root node. Those decorations are evaluated for each record,
and the corresponding style is applied on rows for which the
condition is true.

This commit allows to specify those decoration-XXX attributes on
field nodes as well. In this case, when the condition is met by a
record, only the field on which the attribute is set will be
impacted.

Part of task 2195254
This commit is contained in:
Aaron Bohy
2020-05-05 08:48:34 +00:00
parent 47144b9ee4
commit a1d3e224d8
5 changed files with 79 additions and 21 deletions
@@ -186,7 +186,7 @@ ListRenderer.include({
if (widgets.length) {
var $row = self._getRow(recordID);
var record = self._getRecord(recordID);
self._setDecorationClasses(record, $row);
self._setDecorationClasses($row, self.rowDecorations, record);
self._updateFooter();
}
return widgets;
@@ -58,12 +58,12 @@ var ListRenderer = BasicRenderer.extend({
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.columnInvisibleFields = params.columnInvisibleFields;
this.rowDecorations = _.chain(this.arch.attrs)
.pick(function (value, key) {
return DECORATIONS.indexOf(key) >= 0;
}).mapObject(function (value) {
return py.parse(py.tokenize(value));
}).value();
this.rowDecorations = this._extractDecorationAttrs(this.arch);
this.fieldDecorations = {};
for (const field of this.arch.children.filter(c => c.tag === 'field')) {
const decorations = this._extractDecorationAttrs(field);
this.fieldDecorations[field.attrs.name] = decorations;
}
this.hasSelectors = params.hasSelectors;
this.selection = params.selectedRecords || [];
this.pagers = []; // instantiated pagers (only for grouped lists)
@@ -186,6 +186,24 @@ var ListRenderer = BasicRenderer.extend({
};
}
},
/**
* Extract the decoration attributes (e.g. decoration-danger) of a node. The
* condition is processed such that it is ready to be evaluated.
*
* @private
* @param {Object} node the <tree> or a <field> node
* @returns {Object}
*/
_extractDecorationAttrs: function (node) {
const decorations = {};
for (const [key, expr] of Object.entries(node.attrs)) {
if (DECORATIONS.includes(key)) {
const cssClass = key.replace('decoration', 'text');
decorations[cssClass] = py.parse(py.tokenize(expr));
}
}
return decorations;
},
/**
*
* @private
@@ -440,6 +458,8 @@ var ListRenderer = BasicRenderer.extend({
return $td.append($el);
}
this._handleAttributes($td, node);
this._setDecorationClasses($td, this.fieldDecorations[node.attrs.name], record);
var name = node.attrs.name;
var field = this.state.fields[name];
var value = record.data[name];
@@ -474,7 +494,7 @@ var ListRenderer = BasicRenderer.extend({
if (node.attrs.icon) {
// if there is an icon, we force the btn-link style, unless a btn-xxx
// style class is explicitely provided
const btnStyleRegex = /\bbtn-(primary|secondary|link|success|info|warning|danger)\b/;
const btnStyleRegex = /\bbtn-[a-z]+\b/;
if (!btnStyleRegex.test(nodeWithoutWidth.attrs.class)) {
extraClass = 'btn-link o_icon_button';
}
@@ -843,7 +863,7 @@ var ListRenderer = BasicRenderer.extend({
if (this.hasSelectors) {
$tr.prepend(this._renderSelector('td', !record.res_id));
}
this._setDecorationClasses(record, $tr);
this._setDecorationClasses($tr, this.rowDecorations, record);
return $tr;
},
/**
@@ -1006,22 +1026,24 @@ var ListRenderer = BasicRenderer.extend({
return Promise.all([this._super.apply(this, arguments), prom]);
},
/**
* Each line can be decorated according to a few simple rules. The arch
* description of the list may have one of the decoration-X attribute with
* a domain as value. Then, for each record, we check if the domain matches
* the record, and add the text-X css class to the element. This method is
* concerned with the computation of the list of css classes for a given
* record.
* Each line or cell can be decorated according to a few simple rules. The
* arch description of the list or the field nodes may have one of the
* decoration-X attributes with a python expression as value. Then, for each
* record, we evaluate the python expression, and conditionnaly add the
* text-X css class to the element. This method is concerned with the
* computation of the list of css classes for a given record.
*
* @private
* @param {jQueryElement} $el the element to which to add the classes (a tr
* or td)
* @param {Object} decorations keys are the decoration classes (e.g.
* 'text-bf') and values are the python expressions to evaluate
* @param {Object} record a basic model record
* @param {jQueryElement} $tr a jquery <tr> element (the row to add decoration)
*/
_setDecorationClasses: function (record, $tr) {
_.each(this.rowDecorations, function (expr, decoration) {
var cssClass = decoration.replace('decoration', 'text');
$tr.toggleClass(cssClass, py.PY_isTrue(py.evaluate(expr, record.evalContext)));
});
_setDecorationClasses: function ($el, decorations, record) {
for (const [cssClass, expr] of Object.entries(decorations)) {
$el.toggleClass(cssClass, py.PY_isTrue(py.evaluate(expr, record.evalContext)));
}
},
/**
* @private
@@ -3356,6 +3356,27 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('support field decoration', async function (assert) {
assert.expect(3);
var list = await createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="foo" decoration-danger="int_field > 5"/>
<field name="int_field"/>
</tree>`,
});
assert.containsN(list, 'tbody tr', 4, "should have 4 rows");
assert.containsN(list, 'tbody td.o_list_char.text-danger', 3);
assert.containsNone(list, 'tbody td.o_list_number.text-danger');
list.destroy();
});
QUnit.test('no content helper when no data', async function (assert) {
assert.expect(5);
+7
View File
@@ -1469,6 +1469,13 @@ Possible children elements of the list view are:
be 3 times larger than the others). Note that when there are records in
the list, we let the browser automatically adapt the column's widths
according to their content, and this attribute is thus ignored.
``decoration-{$name}``
allow changing the style of a cell's text based on the corresponding
record's attributes.
``{$name}`` can be ``bf`` (``font-weight: bold``), ``it``
(``font-style: italic``), or any `bootstrap contextual color`_ (``danger``,
``info``, ``muted``, ``primary``, ``success`` or ``warning``).
.. note::
+8
View File
@@ -264,6 +264,14 @@
<rng:optional><rng:attribute name="write_field" /></rng:optional>
<rng:optional><rng:attribute name="text" /></rng:optional>
<rng:optional><rng:attribute name="optional" /></rng:optional>
<rng:optional><rng:attribute name="decoration-bf"/></rng:optional>
<rng:optional><rng:attribute name="decoration-it"/></rng:optional>
<rng:optional><rng:attribute name="decoration-danger"/></rng:optional>
<rng:optional><rng:attribute name="decoration-info"/></rng:optional>
<rng:optional><rng:attribute name="decoration-muted"/></rng:optional>
<rng:optional><rng:attribute name="decoration-primary"/></rng:optional>
<rng:optional><rng:attribute name="decoration-success"/></rng:optional>
<rng:optional><rng:attribute name="decoration-warning"/></rng:optional>
<rng:optional><rng:attribute name="kanban_view_ref" /></rng:optional>
<rng:optional>
<rng:attribute name="force_save">