diff --git a/web_tree_dynamic_colored_field/readme/DESCRIPTION.rst b/web_tree_dynamic_colored_field/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..c3f632caa
--- /dev/null
+++ b/web_tree_dynamic_colored_field/readme/DESCRIPTION.rst
@@ -0,0 +1,15 @@
+This module aims to add support for dynamically coloring fields in tree view
+according to data in the record.
+
+It provides attributes on fields with the similar syntax as the ``colors`` attribute
+in tree tags.
+
+Further, it provides a ``color_field`` attribute on tree tags's ``colors`` to use
+a field's value as color.
+
+Features
+========
+
+* Add attribute ``bg_color`` on field's ``options`` to color background of a cell in tree view
+* Add attribute ``fg_color`` on field's ``options`` to change text color of a cell in tree view
+* Add attribute ``color_field`` on the tree element's ``colors`` to use as color
diff --git a/web_tree_dynamic_colored_field/readme/USAGE.rst b/web_tree_dynamic_colored_field/readme/USAGE.rst
new file mode 100644
index 000000000..921a3262c
--- /dev/null
+++ b/web_tree_dynamic_colored_field/readme/USAGE.rst
@@ -0,0 +1,67 @@
+* In the tree view declaration, put ``options='{"bg_color": "red: customer==True"}`` attribute in the ``field`` tag::
+
+ ...
+
+
+ ...
+
+ ...
+
+
+ ...
+
+ With this example, column which renders 'name' field will have its background colored in red.
+
+* In the tree view declaration, put ``options='{"fg_color": "white:customer == True"}'`` attribute in the ``field`` tag::
+
+ ...
+
+
+ ...
+
+ ...
+
+
+ ...
+
+ With this example, column which renders 'name' field will have its text colored in white on a customer records.
+
+* In the tree view declaration, use ``options='"color_field": "my_color"'`` attribute in the ``tree`` tag::
+
+ ...
+
+
+ ...
+
+ ...
+
+
+ ...
+
+* If you want to use more than one color, you can split the attributes using ';':
+
+.. code::
+
+ options='{"fg_color": "red:red_color == True; green:green_color == True"}'
+
+Example:
+
+.. code:: xml
+
+ ...
+
+
+ ...
+
+ ...
+
+
+ ...
+
+ With this example, the content of the field named `my_color` will be used to
+ populate the `my_color` CSS value. Use a function field to return whichever
+ color you want depending on the other record values. Note that this
+ overrides the rest of `colors` attributes, and that you need the tree
+ to load your field in the first place by adding it as invisible field.
+
+**Note that you should always use single quotes for fields' ``options`` and wrap nested values in double quotes since ``options`` is a JSON object.**
diff --git a/web_tree_dynamic_colored_field/static/description/icon.png b/web_tree_dynamic_colored_field/static/description/icon.png
new file mode 100644
index 000000000..3a0328b51
Binary files /dev/null and b/web_tree_dynamic_colored_field/static/description/icon.png differ
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
new file mode 100644
index 000000000..b1aa0407c
--- /dev/null
+++ b/web_tree_dynamic_colored_field/static/src/js/web_tree_dynamic_colored_field.js
@@ -0,0 +1,130 @@
+odoo.define('web_tree_dynamic_colored_field', function (require) {
+ 'use strict';
+
+ var ListRenderer = require('web.ListRenderer');
+ var pyUtils = require("web.py_utils");
+
+ ListRenderer.include({
+ /**
+ * Look up for a `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 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) {
+ this.colorField = 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, colIndex, options) {
+ 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 {Query Node} $td a | tag inside a table representing a list view
+ * @param {Object} node an XML node (must be a )
+ */
+ applyColorize: function ($td, record, node, ctx) {
+ // safely resolve value of `color_field` given in
+ var treeColor = record.data[this.colorField];
+ if (treeColor) {
+ $td.css('color', treeColor);
+ }
+ // 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 {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
+ */
+ 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, index, ar) {
+ return value !== undefined;
+ });
+ for (var i=0, len=colors.length; i: ` forms to
+ * evaluable expressions
+ *
+ * @param {string} pairColor `color: expression` pair
+ */
+ 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
+ */
+ 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/views/web_tree_dynamic_colored_field.xml b/web_tree_dynamic_colored_field/views/web_tree_dynamic_colored_field.xml
new file mode 100644
index 000000000..116b37db4
--- /dev/null
+++ b/web_tree_dynamic_colored_field/views/web_tree_dynamic_colored_field.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
|