[FIX] web: Fix behaviors of ProgressBarField

This commit fixes behavior issues in the progress bar field:

- the progressbar would not write on a field if it was declared as
'readonly' on the server (it sounds counter-intuitive but this widget
was supposed to write on records unless given the 'readonly' flag in the
field options);

- the "isEditable" prop was incorrectly computed, resulting in the field
not being editable in kanban views when it should be;

- when editing a value in the field, the focus should be automatically
given to the first input.

These issues have been fixed in this commit.

closes odoo/odoo#97411

Signed-off-by: Bruno Boi (boi) <boi@odoo.com>
This commit is contained in:
Julien Mougenot
2022-08-25 12:05:03 +02:00
parent de0d409efc
commit b8d4c41331
5 changed files with 63 additions and 15 deletions
@@ -5,7 +5,7 @@ import { ProgressBarField } from "./progress_bar_field";
export class KanbanProgressBarField extends ProgressBarField {
onClick() {
if (this.props.isEditable && !this.props.record.isReadonly(this.props.name)) {
if (this.props.isEditable) {
this.state.isEditing = true;
}
}
@@ -1,10 +1,11 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { useAutofocus } from "@web/core/utils/hooks";
import { useNumpadDecimal } from "../numpad_decimal_hook";
import { parseFloat } from "../parsers";
import { standardFieldProps } from "../standard_field_props";
import { useNumpadDecimal } from "../numpad_decimal_hook";
const { Component, onWillUpdateProps, useState } = owl;
const formatters = registry.category("formatters");
@@ -13,6 +14,8 @@ const parsers = registry.category("parsers");
export class ProgressBarField extends Component {
setup() {
useNumpadDecimal();
useAutofocus({ refName: "max-value", selectAll: true });
useAutofocus({ refName: "current-value", selectAll: true });
this.state = useState({
currentValue: this.getCurrentValue(this.props),
maxValue: this.getMaxValue(this.props),
@@ -62,10 +65,10 @@ export class ProgressBarField extends Component {
return formatter(this.state.maxValue, { humanReadable });
}
onCurrentValueChange(value) {
onCurrentValueChange(ev) {
let parsedValue;
try {
parsedValue = parseFloat(value);
parsedValue = parseFloat(ev.target.value);
} catch {
this.props.record.setInvalidField(this.props.name);
return;
@@ -81,10 +84,10 @@ export class ProgressBarField extends Component {
this.props.record.save();
}
}
onMaxValueChange(value) {
onMaxValueChange(ev) {
let parsedValue;
try {
parsedValue = parseFloat(value);
parsedValue = parseFloat(ev.target.value);
} catch {
this.props.record.setInvalidField(this.props.name);
return;
@@ -151,7 +154,7 @@ ProgressBarField.extractProps = ({ attrs }) => {
maxValueField: attrs.options.max_value,
currentValueField: attrs.options.current_value,
isPercentage: !attrs.options.max_value,
isEditable: attrs.options.editable,
isEditable: !attrs.options.readonly && attrs.options.editable,
isEditableInReadonly: attrs.options.editable_readonly,
isCurrentValueEditable:
attrs.options.editable &&
@@ -30,8 +30,8 @@
inputmode="numeric"
t-att-value="formatCurrentValue() or ''"
t-att-required="props.required"
t-on-change="(ev) => this.onCurrentValueChange(ev.target.value)"
t-on-input="(ev) => this.onCurrentValueInput(ev)"
t-on-change="onCurrentValueChange"
t-on-input="onCurrentValueInput"
t-on-blur="onBlur"
/>
<span>%</span>
@@ -40,23 +40,25 @@
<t t-else="">
<input
t-if="props.isCurrentValueEditable"
t-ref="current-value"
class="o_progressbar_value o_input"
type="text"
inputmode="numeric"
t-att-value="formatCurrentValue()"
t-on-change="(ev) => this.onCurrentValueChange(ev.target.value)"
t-on-input="(ev) => this.onCurrentValueInput(ev)"
t-on-change="onCurrentValueChange"
t-on-input="onCurrentValueInput"
t-on-blur="onBlur"
/>
<span t-if="props.isCurrentValueEditable and props.isMaxValueEditable" t-esc="'/'" />
<input
t-if="props.isMaxValueEditable"
t-ref="max-value"
class="o_progressbar_value o_input"
type="text"
inputmode="numeric"
t-att-value="formatMaxValue()"
t-on-change="(ev) => this.onMaxValueChange(ev.target.value)"
t-on-input="(ev) => this.onMaxValueInput(ev)"
t-on-change="onMaxValueChange"
t-on-input="onMaxValueInput"
t-on-blur="onBlur"
/>
</t>
@@ -65,4 +67,3 @@
</t>
</templates>
@@ -72,6 +72,11 @@ export class KanbanArchParser extends XMLParser {
node.setAttribute("widget", "many2many_tags");
}
const fieldInfo = Field.parseFieldNode(node, models, modelName, "kanban", jsClass);
if (!node.hasAttribute("force_save")) {
// Force save is true by default on kanban views:
// this allows to write on any field regardless of its modifiers.
fieldInfo.forceSave = true;
}
const name = fieldInfo.name;
fieldNodes[name] = fieldInfo;
node.setAttribute("field_id", name);
@@ -364,6 +364,8 @@ QUnit.module("Fields", (hooks) => {
QUnit.test("ProgressBarField: field is editable in kanban", async function (assert) {
assert.expect(7);
serverData.models.partner.fields.int_field.readonly = true;
serverData.models.partner.records[0].int_field = 99;
await makeView({
@@ -412,6 +414,7 @@ QUnit.module("Fields", (hooks) => {
);
await editInput(target, ".o_progressbar_value.o_input", "69");
assert.strictEqual(
target.querySelector(".o_progressbar_value").textContent,
"69 / 100",
@@ -423,6 +426,42 @@ QUnit.module("Fields", (hooks) => {
);
});
QUnit.test("force readonly in kanban", async (assert) => {
assert.expect(3);
serverData.models.partner.records[0].int_field = 99;
await makeView({
serverData,
type: "kanban",
resModel: "partner",
arch: /* xml */ `
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="int_field" widget="progressbar" options="{'editable': true, 'max_value': 'float_field', 'readonly': True}" />
</div>
</t>
</templates>
</kanban>`,
resId: 1,
mockRPC(route, { method, args }) {
if (method === "write") {
throw new Error("Not supposed to write");
}
},
});
assert.strictEqual(target.querySelector(".o_progressbar_value").textContent, "99 / 100");
// Clicking on the progress bar should not change the value
await click(target.querySelector(".o_progress"));
assert.strictEqual(target.querySelector(".o_progressbar_value").textContent, "99 / 100");
assert.containsNone(target, ".o_progressbar_value.o_input");
});
QUnit.test(
"ProgressBarField: readonly and editable attrs/options in kanban",
async function (assert) {