diff --git a/web_tree_dynamic_colored_field/README.rst b/web_tree_dynamic_colored_field/README.rst new file mode 100644 index 000000000..d661197cb --- /dev/null +++ b/web_tree_dynamic_colored_field/README.rst @@ -0,0 +1,107 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +============================ +Colorize field in tree views +============================ + +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 + +Usage +===== + +* 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:: + + ... + + + ... + + ... + + + ... + + 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.** + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Damien Crier +* Holger Brunn +* Artem Kostyuk + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_tree_dynamic_colored_field/__init__.py b/web_tree_dynamic_colored_field/__init__.py new file mode 100644 index 000000000..8945c43ca --- /dev/null +++ b/web_tree_dynamic_colored_field/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2015-2018 Camptocamp SA, Damien Crier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/web_tree_dynamic_colored_field/__manifest__.py b/web_tree_dynamic_colored_field/__manifest__.py new file mode 100644 index 000000000..665310b81 --- /dev/null +++ b/web_tree_dynamic_colored_field/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2015-2018 Camptocamp SA, Damien Crier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + 'name': 'Colorize field in tree views', + 'summary': 'Allows you to dynamically color fields on tree views', + 'category': 'Hidden/Dependency', + 'version': '11.0.1.0.0', + 'depends': ['web'], + 'author': "Camptocamp, Therp BV, Odoo Community Association (OCA)", + 'license': 'AGPL-3', + 'website': 'https://github.com/OCA/web', + 'demo': [ + "demo/res_users.xml", + ], + 'data': [ + 'views/web_tree_dynamic_colored_field.xml', + ], + 'installable': True, +} diff --git a/web_tree_dynamic_colored_field/demo/res_users.xml b/web_tree_dynamic_colored_field/demo/res_users.xml new file mode 100644 index 000000000..ef364722f --- /dev/null +++ b/web_tree_dynamic_colored_field/demo/res_users.xml @@ -0,0 +1,24 @@ + + + + res.users + + + + color_field: lang + + + { + "bg_color": "#9e1635: login_date == False", + "fg_color": "white: login_date == False" + } + + + { + "bg_color": "blue; #653b5b: login == 'admin'", + "fg_color": "white" + } + + + + 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..2a96aba66 --- /dev/null +++ b/web_tree_dynamic_colored_field/static/src/js/web_tree_dynamic_colored_field.js @@ -0,0 +1,129 @@ +odoo.define('web_tree_dynamic_colored_field', function (require) { + 'use strict'; + + var ListRenderer = require('web.ListRenderer'); + var pyeval = require('web.pyeval'); + + ListRenderer.include({ + /** + * Look up for a `color_field` parameter in tree `colors` attribute + * + * @override + */ + _renderBody: function () { + if (this.arch.attrs.colors) { + var colorField = this.arch.attrs.colors.split(';') + .filter(color => color.trim().startsWith('color_field'))[0] + .split(':')[1] + .trim(); + // validate the presence of that field in tree view + var fieldNames = _(this.columns).map( + (value) => { return value.attrs.name; } + ); + if (fieldNames.indexOf(colorField) === -1) { + console.warn( + "No field named '" + colorField + "' present in view." + ); + } else { + this.colorField = colorField; + } + } + 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; } + var nodeOptions = JSON.parse(node.attrs.options); + 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 + * evaluatable 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, + pyeval.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..ea8e11291 --- /dev/null +++ b/web_tree_dynamic_colored_field/views/web_tree_dynamic_colored_field.xml @@ -0,0 +1,8 @@ + + + +