diff --git a/web_tree_dynamic_colored_field/static/src/js/list_renderer.esm.js b/web_tree_dynamic_colored_field/static/src/js/list_renderer.esm.js
new file mode 100644
index 000000000000..7b14f273a30b
--- /dev/null
+++ b/web_tree_dynamic_colored_field/static/src/js/list_renderer.esm.js
@@ -0,0 +1,78 @@
+/** @odoo-module **/
+
+import {patch} from "@web/core/utils/patch";
+import {ListRenderer} from "@web/views/list/list_renderer";
+import {evaluateBooleanExpr} from "@web/core/py_js/py";
+
+patch(ListRenderer.prototype, {
+ /**
+ * @param {Object} column represents field
+ * @param {Record} record
+ * @returns {String} style code for the html element
+ */
+ getDynamicColoredStyle(column, record) {
+ let style = "";
+
+ let color = this.getDynamicColor(column, record, "bg_color");
+ if (color !== undefined) {
+ style += `background-color: ${color};`;
+ }
+
+ color = this.getDynamicColor(column, record, "fg_color");
+ if (color !== undefined) {
+ // $td.css('color', color);
+ style += `color: ${color};`;
+ }
+
+ return style;
+ },
+
+ /**
+ * Return the `color` that has truthfull expresssion
+ *
+ * @param column {Object} represents field
+ * @param record {Record}
+ * @param color_target {String} 'bg_color' or 'fg_color'
+ * @returns {String | undefined} color
+ */
+ getDynamicColor(column, record, color_target) {
+ if (color_target in column.options) {
+ const definition = column.options[color_target];
+ let result = "";
+ for (const color_def of definition.split(";")) {
+ const color_to_expression = this.pairColorParse(color_def);
+ if (color_to_expression !== undefined) {
+ const [color, expression] = color_to_expression;
+ if (
+ evaluateBooleanExpr(
+ expression,
+ record.evalContextWithVirtualIds
+ )
+ ) {
+ // We don't return first match,
+ // as it can be default color (with "True" expression),
+ // and later more precise condition may be found.
+ result = color;
+ }
+ }
+ }
+ return result | undefined;
+ }
+ },
+
+ /**
+ * @param {String} pairColor `color: expression` pair
+ * @returns {Array} undefined or array of color, expression
+ */
+ pairColorParse: function (pairColor) {
+ if (pairColor !== "") {
+ var pairList = pairColor.split(":"),
+ color = pairList[0],
+ // If one passes a bare color instead of an expression,
+ // then we consider that color is to be shown in any case
+ expression = pairList[1] ? pairList[1] : "True";
+ return [color, expression];
+ }
+ return undefined;
+ },
+});
diff --git a/web_tree_dynamic_colored_field/static/src/js/web_tree_dynamic_colored_field.js b/web_tree_dynamic_colored_field/static/src/js/web_tree_dynamic_colored_field.js
deleted file mode 100644
index 8998e3b6b34e..000000000000
--- a/web_tree_dynamic_colored_field/static/src/js/web_tree_dynamic_colored_field.js
+++ /dev/null
@@ -1,165 +0,0 @@
-odoo.define("web_tree_dynamic_colored_field", function (require) {
- "use strict";
-
- var ListRenderer = require("web.ListRenderer");
- var pyUtils = require("web.py_utils");
- var py = window.py;
-
- ListRenderer.include({
- /**
- * Look up for a `color_field` or ``bg_color_field`` parameter in tree `colors` attribute
- *
- * @override
- */
- _renderBody: function () {
- if (this.arch.attrs.colors) {
- var colorAttr = this.arch.attrs.colors.split(";");
- if (colorAttr.length > 0) {
- var colorType = colorAttr[0].split(":")[0].trim();
- var colorField = colorAttr[0].split(":")[1].trim();
- // Validate the presence of that field in tree view
- if (
- this.state.data.length &&
- colorField in this.state.data[0].data
- ) {
- if (colorType === "color_field") {
- this.colorField = colorField;
- } else if (colorType === "bg_color_field") {
- this.bgColorField = colorField;
- }
- } else {
- console.warn(
- "No field named '" + colorField + "' present in view."
- );
- }
- }
- }
- return this._super();
- },
- /**
- * Colorize a cell during it's render
- *
- * @override
- */
- _renderBodyCell: function (record, node) {
- var $td = this._super.apply(this, arguments);
- var ctx = this.getEvalContext(record);
- this.applyColorize($td, record, node, ctx);
- return $td;
- },
-
- /**
- * Colorize the current cell depending on expressions provided.
- *
- * @param {Element} $td a
tag inside a table representing a list view
- * @param {Object} record
- * @param {Object} node an XML node (must be a )
- * @param {Object} ctx evaluation context for the record
- */
- applyColorize: function ($td, record, node, ctx) {
- if (!node.attrs.options) {
- return;
- }
- if (node.tag !== "field") {
- return;
- }
- var treeBgColor = record.data[this.bgColorField];
- if (treeBgColor) {
- $td.css("background-color", treeBgColor);
- }
- // Apply 's own `options`
- if (!node.attrs.options) {
- return;
- }
- if (node.tag !== "field") {
- return;
- }
- var nodeOptions = node.attrs.options;
- if (!_.isObject(nodeOptions)) {
- nodeOptions = pyUtils.py_eval(nodeOptions);
- }
- this.applyColorizeHelper($td, nodeOptions, node, "fg_color", "color", ctx);
- this.applyColorizeHelper(
- $td,
- nodeOptions,
- node,
- "bg_color",
- "background-color",
- ctx
- );
- },
- /**
- * @param {Element} $td a tag inside a table representing a list view
- * @param {Object} nodeOptions a mapping of nodeOptions parameters to the color itself
- * @param {Object} node an XML node (must be a )
- * @param {String} nodeAttribute an attribute of a node to apply a style onto
- * @param {String} cssAttribute a real CSS-compatible attribute
- * @param {Object} ctx evaluation context for the record
- */
- applyColorizeHelper: function (
- $td,
- nodeOptions,
- node,
- nodeAttribute,
- cssAttribute,
- ctx
- ) {
- if (nodeOptions[nodeAttribute]) {
- var colors = _(nodeOptions[nodeAttribute].split(";"))
- .chain()
- .map(this.pairColors)
- .value()
- .filter(function CheckUndefined(value) {
- return value !== undefined;
- });
- for (var i = 0, len = colors.length; i < len; ++i) {
- var pair = colors[i],
- color = pair[0],
- expression = pair[1];
- if (py.evaluate(expression, ctx).toJSON()) {
- $td.css(cssAttribute, color);
- }
- }
- }
- },
-
- /**
- * Parse `: ` forms to
- * evaluable expressions
- *
- * @param {String} pairColor `color: expression` pair
- * @returns {Array} undefined or array of color, parsed expression,
- * original expression
- */
- pairColors: function (pairColor) {
- if (pairColor !== "") {
- var pairList = pairColor.split(":"),
- color = pairList[0],
- // If one passes a bare color instead of an expression,
- // then we consider that color is to be shown in any case
- expression = pairList[1] ? pairList[1] : "True";
- return [color, py.parse(py.tokenize(expression)), expression];
- }
- return undefined;
- },
- /**
- * Construct domain evaluation context, mostly by passing
- * record's fields's values to local scope.
- *
- * @param {Object} record a record to build a context from
- * @returns {Object} evaluation context for the record
- */
- getEvalContext: function (record) {
- var ctx = _.extend({}, record.data, pyUtils.context());
- for (var key in ctx) {
- var value = ctx[key];
- if (ctx[key] instanceof moment) {
- // Date/datetime fields are represented w/ Moment objects
- // docs: https://momentjs.com/
- ctx[key] = value.format("YYYY-MM-DD hh:mm:ss");
- }
- }
- return ctx;
- },
- });
-});
diff --git a/web_tree_dynamic_colored_field/static/src/xml/list.xml b/web_tree_dynamic_colored_field/static/src/xml/list.xml
new file mode 100644
index 000000000000..42515c439597
--- /dev/null
+++ b/web_tree_dynamic_colored_field/static/src/xml/list.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ {{getDynamicColoredStyle(column, record)}}
+
+
+
+
| |