Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions web_m2x_options/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ in the field's options dict
A dictionary to link field value with a HTML color. This option has
to be used with field_color.

``color_style`` *string* (Default: ``text``)

How the color is shown in the drop-down panel. ``text`` colors the
record's text. ``bar`` draws a thin accent bar on the left of the
record and leaves the text untouched. With ``bar``, a Bootstrap theme
color name (``primary``, ``secondary``, ``success``, ``info``,
``warning``, ``danger``, ``light``, ``dark``) follows the light or
dark theme; any other value is used as a HTML color. This option has
to be used with field_color and colors.

ir.config_parameter options
---------------------------

Expand Down Expand Up @@ -116,6 +126,12 @@ Your XML form view definition could contain:
<field name="partner_id" options="{'limit': 10, 'field_color':'type', 'colors':{'contact':'green', 'invoice': 'red', 'delivery': 'blue'}}"/>
...

.. code:: xml

...
<field name="partner_id" options="{'field_color': 'type', 'color_style': 'bar', 'colors': {'contact': 'success', 'invoice': 'warning', 'delivery': 'info'}}"/>
...

Known issues / Roadmap
======================

Expand Down
5 changes: 4 additions & 1 deletion web_m2x_options/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
"web_m2x_options/static/src/views/fields/standard_field_props.esm.js",
),
"web_m2x_options/static/src/components/base.xml",
]
],
"web.assets_unit_tests": [
"web_m2x_options/static/tests/**/*",
],
},
"installable": True,
}
16 changes: 16 additions & 0 deletions web_m2x_options/readme/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
> A dictionary to link field value with a HTML color. This option has to
> be used with field_color.

`color_style` *string* (Default: `text`)

> How the color is shown in the drop-down panel. `text` colors the
> record's text. `bar` draws a thin accent bar on the left of the record
> and leaves the text untouched. With `bar`, a Bootstrap theme color name
> (`primary`, `secondary`, `success`, `info`, `warning`, `danger`,
> `light`, `dark`) follows the light or dark theme; any other value is
> used as a HTML color. This option has to be used with field_color and
> colors.

## ir.config_parameter options

Now you can disable "Create..." and "Create and Edit..." entry for all
Expand Down Expand Up @@ -59,3 +69,9 @@ Your XML form view definition could contain:
<field name="partner_id" options="{'limit': 10, 'field_color':'type', 'colors':{'contact':'green', 'invoice': 'red', 'delivery': 'blue'}}"/>
...
```

``` xml
...
<field name="partner_id" options="{'field_color': 'type', 'color_style': 'bar', 'colors': {'contact': 'success', 'invoice': 'warning', 'delivery': 'info'}}"/>
...
```
14 changes: 14 additions & 0 deletions web_m2x_options/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,15 @@ <h3><a class="toc-backref" href="#toc-entry-2">in the field’s options dict</a>
<blockquote>
A dictionary to link field value with a HTML color. This option has
to be used with field_color.</blockquote>
<p><tt class="docutils literal">color_style</tt> <em>string</em> (Default: <tt class="docutils literal">text</tt>)</p>
<blockquote>
How the color is shown in the drop-down panel. <tt class="docutils literal">text</tt> colors the
record’s text. <tt class="docutils literal">bar</tt> draws a thin accent bar on the left of the
record and leaves the text untouched. With <tt class="docutils literal">bar</tt>, a Bootstrap theme
color name (<tt class="docutils literal">primary</tt>, <tt class="docutils literal">secondary</tt>, <tt class="docutils literal">success</tt>, <tt class="docutils literal">info</tt>,
<tt class="docutils literal">warning</tt>, <tt class="docutils literal">danger</tt>, <tt class="docutils literal">light</tt>, <tt class="docutils literal">dark</tt>) follows the light or
dark theme; any other value is used as a HTML color. This option has
to be used with field_color and colors.</blockquote>
</div>
<div class="section" id="ir-config-parameter-options">
<h3><a class="toc-backref" href="#toc-entry-3">ir.config_parameter options</a></h3>
Expand Down Expand Up @@ -459,6 +468,11 @@ <h3><a class="toc-backref" href="#toc-entry-4">Example</a></h3>
</span><span class="nt">&lt;field</span><span class="w"> </span><span class="na">name=</span><span class="s">&quot;partner_id&quot;</span><span class="w"> </span><span class="na">options=</span><span class="s">&quot;{'limit': 10, 'field_color':'type', 'colors':{'contact':'green', 'invoice': 'red', 'delivery': 'blue'}}&quot;</span><span class="nt">/&gt;</span><span class="w">
</span>...
</pre>
<pre class="code xml literal-block">
...<span class="w">
</span><span class="nt">&lt;field</span><span class="w"> </span><span class="na">name=</span><span class="s">&quot;partner_id&quot;</span><span class="w"> </span><span class="na">options=</span><span class="s">&quot;{'field_color': 'type', 'color_style': 'bar', 'colors': {'contact': 'success', 'invoice': 'warning', 'delivery': 'info'}}&quot;</span><span class="nt">/&gt;</span><span class="w">
</span>...
</pre>
</div>
</div>
<div class="section" id="known-issues-roadmap">
Expand Down
1 change: 1 addition & 0 deletions web_m2x_options/static/src/components/base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<attribute name="searchLimit">props.searchLimit</attribute>
<attribute name="fieldColor">props.fieldColor</attribute>
<attribute name="fieldColorOptions">props.fieldColorOptions</attribute>
<attribute name="fieldColorStyle">props.fieldColorStyle</attribute>
</xpath>
</t>

Expand Down
41 changes: 38 additions & 3 deletions web_m2x_options/static/src/components/form.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Many2XAutocomplete.props = {

Many2One.props = {
...Many2One.props,
...fieldColorProps,
searchLimit: {type: Number, optional: true},
};

Expand Down Expand Up @@ -94,6 +95,7 @@ export function m2o_options_props(props, attrs, options) {
newProps = m2o_options_props_open(newProps, attrs, options);
newProps.fieldColor = options.field_color;
newProps.fieldColorOptions = options.colors;
newProps.fieldColorStyle = options.color_style;
return newProps;
}

Expand All @@ -115,6 +117,9 @@ many2one.computeM2OProps = (fieldProps) => {
return {
..._super,
searchLimit: fieldProps.searchLimit,
fieldColor: fieldProps.fieldColor,
fieldColorOptions: fieldProps.fieldColorOptions,
fieldColorStyle: fieldProps.fieldColorStyle,
};
};

Expand All @@ -141,6 +146,9 @@ patch(many2OneField.Many2OneField.prototype, {
get m2oProps() {
const props = super.m2oProps;
props.searchLimit = this.props.searchLimit;
props.fieldColor = this.props.fieldColor;
props.fieldColorOptions = this.props.fieldColorOptions;
props.fieldColorStyle = this.props.fieldColorStyle;
return props;
},
});
Expand Down Expand Up @@ -182,6 +190,7 @@ patch(Many2One.prototype, {
if (field_color && field_color_options) {
ret_props.fieldColor = field_color;
ret_props.fieldColorOptions = field_color_options;
ret_props.fieldColorStyle = this.props.fieldColorStyle;
}
if (!evaluateSystemParameterDefaultTrue("create")) {
ret_props.quickCreate = null;
Expand Down Expand Up @@ -246,6 +255,7 @@ patch(many2ManyTagsField, {
newProps = this.m2m_options_props_limit(newProps, attrs, options);
newProps.fieldColor = options.field_color;
newProps.fieldColorOptions = options.colors;
newProps.fieldColorStyle = options.color_style;
return newProps;
},
extractProps({attrs, options, string}, dynamicInfo) {
Expand All @@ -254,13 +264,38 @@ patch(many2ManyTagsField, {
},
});

// Bootstrap theme colours resolve through CSS variables, which Odoo defines
// for both the light and the dark theme. Odoo compiles Bootstrap without the
// "bs-" prefix, so the variable is --success, not --bs-success.
const BOOTSTRAP_COLORS = new Set([
"primary",
"secondary",
"success",
"info",
"warning",
"danger",
"light",
"dark",
]);

function colorStyle(style, color) {
if (style === "bar") {
const css = BOOTSTRAP_COLORS.has(color) ? `var(--${color})` : color;
return `box-shadow: inset 3px 0 0 ${css}`;
}
return "color:" + color;
}

patch(Many2XAutocomplete.prototype, {
async loadOptionsSource(request) {
var options = await super.loadOptionsSource(request);
this.field_color = this.props.fieldColor;
this.colors = this.props.fieldColorOptions;
if (this.colors && this.field_color) {
var value_ids = options.map((result) => result.value);
// Record suggestions carry their record in data; the "Search
// More" and "Create" entries have none.
const recordId = (option) => option.data?.record?.id;
var value_ids = options.map(recordId).filter(Boolean);
const objects = await this.orm.call(
this.props.resModel,
"search_read",
Expand All @@ -272,13 +307,13 @@ patch(Many2XAutocomplete.prototype, {
);
for (var index in objects) {
for (var index_value in options) {
if (options[index_value].value === objects[index].id) {
if (recordId(options[index_value]) === objects[index].id) {
// Find value in values by comparing ids
var option = options[index_value];
// Find color with field value as key
var color =
this.colors[objects[index][this.field_color]] || "black";
option.style = "color:" + color;
option.style = colorStyle(this.props.fieldColorStyle, color);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {standardFieldProps} from "@web/views/fields/standard_field_props";
export const fieldColorProps = {
fieldColor: {type: String, optional: true},
fieldColorOptions: {type: Object, optional: true},
fieldColorStyle: {type: String, optional: true},
};

patch(standardFieldProps, {
Expand Down
109 changes: 109 additions & 0 deletions web_m2x_options/static/tests/field_color.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
contains,
defineModels,
fields,
models,
mountView,
patchWithCleanup,
} from "@web/../tests/web_test_helpers";
import {describe, expect, test} from "@odoo/hoot";
import {queryAll} from "@odoo/hoot-dom";
import {runAllTimers} from "@odoo/hoot-mock";
import {session} from "@web/session";

describe.current.tags("desktop");

class Partner extends models.Model {
name = fields.Char();
type = fields.Selection({
selection: [
["contact", "Contact"],
["delivery", "Delivery"],
],
});
parent_id = fields.Many2one({relation: "partner"});

_records = [
{id: 1, name: "Acme", type: "contact"},
{id: 2, name: "Acme Warehouse", type: "delivery"},
{id: 3, name: "Jane", type: "contact", parent_id: 1},
];
}

defineModels([Partner]);

test("many2one dropdown suggestions are coloured by field_color", async () => {
patchWithCleanup(session, {web_m2x_options: {}});
await mountView({
type: "form",
resModel: "partner",
resId: 3,
arch: `
<form>
<field name="parent_id" options="{
'field_color': 'type',
'colors': {'contact': 'green', 'delivery': 'blue'}
}"/>
</form>`,
});
await contains(".o_field_many2one input").edit("Acme", {confirm: false});
await runAllTimers();

const items = queryAll(
".o_field_many2one .o-autocomplete--dropdown-item:not(.o_m2o_dropdown_option) .dropdown-item"
);
expect(items).toHaveLength(2);
const styles = Object.fromEntries(
items.map((el) => [el.textContent.trim(), el.getAttribute("style")])
);
expect(styles).toEqual({
Acme: "color:green",
"Acme Warehouse": "color:blue",
});
});

async function suggestionStyles(options) {
patchWithCleanup(session, {web_m2x_options: {}});
await mountView({
type: "form",
resModel: "partner",
resId: 3,
arch: `
<form>
<field name="parent_id" options="${options}"/>
</form>`,
});
await contains(".o_field_many2one input").edit("Acme", {confirm: false});
await runAllTimers();
const items = queryAll(
".o_field_many2one .o-autocomplete--dropdown-item:not(.o_m2o_dropdown_option) .dropdown-item"
);
return Object.fromEntries(
items.map((el) => [el.textContent.trim(), el.getAttribute("style")])
);
}

test("color_style 'text' keeps the default text colouring", async () => {
const styles = await suggestionStyles(
"{'field_color': 'type', 'color_style': 'text', 'colors': {'contact': 'success', 'delivery': 'blue'}}"
);
expect(styles).toEqual({
Acme: "color:success",
"Acme Warehouse": "color:blue",
});
});

test("color_style 'bar' draws a theme-aware left accent bar", async () => {
const styles = await suggestionStyles(
"{'field_color': 'type', 'color_style': 'bar', 'colors': {'contact': 'success', 'delivery': '#123456'}}"
);
expect(styles).toEqual({
Acme: "box-shadow: inset 3px 0 0 var(--success)",
"Acme Warehouse": "box-shadow: inset 3px 0 0 #123456",
});
// The theme variable must resolve, or the bar silently disappears.
const acme = queryAll(".o-autocomplete--dropdown-item .dropdown-item").find(
(el) => el.textContent.trim() === "Acme"
);
expect(getComputedStyle(acme).boxShadow).toMatch(/rgb/);
});
1 change: 1 addition & 0 deletions web_m2x_options/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Copyright 2020 initOS GmbH.
from . import test_ir_config_parameter
from . import test_js
16 changes: 16 additions & 0 deletions web_m2x_options/tests/test_js.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo.tests import HttpCase, tagged


@tagged("post_install", "-at_install")
class TestWebM2xOptionsJs(HttpCase):
def test_js(self):
self.browser_js(
"/web/tests?headless&loglevel=2&preset=desktop&timeout=15000"
"&filter=%40web_m2x_options",
"",
"",
login="admin",
timeout=1800,
success_signal="[HOOT] Test suite succeeded",
error_checker=lambda x: "[HOOT]" not in x,
)
Loading