[FIX] web, *: fix ProgressBarField behaviors
*: survey This commit brings back the correct behavior on blur to the progressbar field. It turns as a text value as soon as the user has focused out of the input. It also removes the wrong usage of the max_value option. The behavior was wrongly adapted from the legacy progressbar implementation. Tests have been adapted to assert the correct behavior. closes odoo/odoo#98902 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -263,10 +263,7 @@
|
||||
type="object"
|
||||
name="action_survey_user_input_certified"
|
||||
class="fw-bold">
|
||||
<field name="success_ratio" widget="progressbar"
|
||||
options="{'current_value': 'success_ratio',
|
||||
'max_value': 100,
|
||||
'editable': false}"/>
|
||||
<field name="success_ratio" widget="progressbar"/>
|
||||
<span class="text-muted" t-if="!record.certification.raw_value">Passed</span>
|
||||
<span class="text-muted" t-else="">Certified</span>
|
||||
</a>
|
||||
|
||||
@@ -3,24 +3,27 @@
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useAutofocus } from "@web/core/utils/hooks";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { useNumpadDecimal } from "../numpad_decimal_hook";
|
||||
import { parseFloat } from "../parsers";
|
||||
import { standardFieldProps } from "../standard_field_props";
|
||||
|
||||
const { Component, onWillUpdateProps, useState } = owl;
|
||||
const { Component, onWillUpdateProps, useRef, useState } = owl;
|
||||
const formatters = registry.category("formatters");
|
||||
const parsers = registry.category("parsers");
|
||||
|
||||
export class ProgressBarField extends Component {
|
||||
setup() {
|
||||
useNumpadDecimal();
|
||||
useAutofocus({ refName: "max-value", selectAll: true });
|
||||
useAutofocus({ refName: "current-value", selectAll: true });
|
||||
useAutofocus({ refName: "maxValue", selectAll: true });
|
||||
useAutofocus({ refName: "currentValue", selectAll: true });
|
||||
this.root = useRef("numpadDecimal");
|
||||
this.state = useState({
|
||||
currentValue: this.getCurrentValue(this.props),
|
||||
maxValue: this.getMaxValue(this.props),
|
||||
isEditing: false,
|
||||
});
|
||||
this.onBlurDebounced = useDebounced(this.onBlur);
|
||||
onWillUpdateProps((nextProps) => {
|
||||
Object.assign(this.state, {
|
||||
currentValue: this.getCurrentValue(nextProps),
|
||||
@@ -38,6 +41,9 @@ export class ProgressBarField extends Component {
|
||||
get isMaxValueInteger() {
|
||||
return this.state.maxValue % 1 === 0;
|
||||
}
|
||||
get isPercentage() {
|
||||
return !this.props.maxValueField || !isNaN(this.props.maxValueField);
|
||||
}
|
||||
|
||||
getCurrentValueField(p) {
|
||||
return typeof p.currentValueField === "string" ? p.currentValueField : p.name;
|
||||
@@ -80,7 +86,6 @@ export class ProgressBarField extends Component {
|
||||
this.state.currentValue = parsedValue;
|
||||
this.props.record.update({ [this.getCurrentValueField(this.props)]: parsedValue });
|
||||
if (this.props.readonly) {
|
||||
this.state.isEditing = false;
|
||||
this.props.record.save();
|
||||
}
|
||||
}
|
||||
@@ -99,7 +104,6 @@ export class ProgressBarField extends Component {
|
||||
this.state.maxValue = parsedValue;
|
||||
this.props.record.update({ [this.getMaxValueField(this.props)]: parsedValue });
|
||||
if (this.props.readonly) {
|
||||
this.state.isEditing = false;
|
||||
this.props.record.save();
|
||||
}
|
||||
}
|
||||
@@ -109,10 +113,14 @@ export class ProgressBarField extends Component {
|
||||
this.state.isEditing = true;
|
||||
}
|
||||
}
|
||||
// When both max and current value are editable, as one input is blurred when
|
||||
// switching to the other, the state would revert to isEditing = false. We need
|
||||
// to stay in edition mode if the focus is still in the field.
|
||||
onBlur() {
|
||||
if (this.props.readonly) {
|
||||
this.state.isEditing = false;
|
||||
if (this.root.el && this.root.el.contains(document.activeElement)) {
|
||||
return;
|
||||
}
|
||||
this.state.isEditing = false;
|
||||
}
|
||||
|
||||
onCurrentValueInput(ev) {
|
||||
@@ -138,7 +146,6 @@ ProgressBarField.props = {
|
||||
...standardFieldProps,
|
||||
maxValueField: { type: [String, Number], optional: true },
|
||||
currentValueField: { type: String, optional: true },
|
||||
isPercentage: { type: Boolean, optional: true },
|
||||
isEditable: { type: Boolean, optional: true },
|
||||
isEditableInReadonly: { type: Boolean, optional: true },
|
||||
isCurrentValueEditable: { type: Boolean, optional: true },
|
||||
@@ -153,7 +160,6 @@ ProgressBarField.extractProps = ({ attrs }) => {
|
||||
return {
|
||||
maxValueField: attrs.options.max_value,
|
||||
currentValueField: attrs.options.current_value,
|
||||
isPercentage: !attrs.options.max_value,
|
||||
isEditable: !attrs.options.readonly && attrs.options.editable,
|
||||
isEditableInReadonly: attrs.options.editable_readonly,
|
||||
isCurrentValueEditable:
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
<t t-name="web.ProgressBarField" owl="1">
|
||||
<t t-if="!state.isEditing">
|
||||
<div class="o_progressbar w-100" t-on-click="onClick">
|
||||
<div class="o_progressbar w-100 d-flex align-items-center" t-on-click="onClick">
|
||||
<div t-if="props.title" class="o_progressbar_title"><t t-esc="props.title"/></div>
|
||||
<div class="o_progress" aria-valuemin="0" t-att-aria-valuemax="state.maxValue" t-att-aria-valuenow="state.currentValue">
|
||||
<div class="o_progressbar_complete" t-att-style="'width: min(' + 100 * state.currentValue / state.maxValue + '%, 100%)'"></div>
|
||||
</div>
|
||||
<t t-if="props.isPercentage">
|
||||
<t t-if="isPercentage">
|
||||
<div class="o_progressbar_value" t-esc="formatCurrentValue(true) + '%'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
@@ -17,15 +17,16 @@
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="o_progressbar w-100 d-flex" t-ref="numpadDecimal">
|
||||
<div class="o_progressbar w-100 d-flex align-items-center" t-ref="numpadDecimal">
|
||||
<div t-if="props.title" class="o_progressbar_title"><t t-esc="props.title"/></div>
|
||||
<div class="o_progress" aria-valuemin="0" t-att-aria-valuemax="state.maxValue" t-att-aria-valuenow="state.currentValue">
|
||||
<div class="o_progressbar_complete" t-att-style="'width: min(' + 100 * state.currentValue / state.maxValue + '%, 100%)'"></div>
|
||||
</div>
|
||||
<t t-if="props.isPercentage">
|
||||
<t t-if="isPercentage">
|
||||
<div class="d-flex">
|
||||
<input
|
||||
class="o_input o_progressbar_value"
|
||||
t-ref="currentValue"
|
||||
class="o_input o_progressbar_value h-100"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
t-att-value="formatCurrentValue() or ''"
|
||||
@@ -40,26 +41,26 @@
|
||||
<t t-else="">
|
||||
<input
|
||||
t-if="props.isCurrentValueEditable"
|
||||
t-ref="current-value"
|
||||
class="o_progressbar_value o_input"
|
||||
t-ref="currentValue"
|
||||
class="o_progressbar_value o_input h-100"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
t-att-value="formatCurrentValue()"
|
||||
t-on-change="onCurrentValueChange"
|
||||
t-on-input="onCurrentValueInput"
|
||||
t-on-blur="onBlur"
|
||||
t-on-blur="onBlurDebounced"
|
||||
/>
|
||||
<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"
|
||||
t-ref="maxValue"
|
||||
class="o_progressbar_value o_input h-100"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
t-att-value="formatMaxValue()"
|
||||
t-on-change="onMaxValueChange"
|
||||
t-on-input="onMaxValueInput"
|
||||
t-on-blur="onBlur"
|
||||
t-on-blur="onBlurDebounced"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,12 @@ import {
|
||||
makeFakeLocalizationService,
|
||||
makeFakeNotificationService,
|
||||
} from "@web/../tests/helpers/mock_services";
|
||||
import { click, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
@@ -54,7 +59,6 @@ QUnit.module("Fields", (hooks) => {
|
||||
QUnit.test("ProgressBarField: max_value should update", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
serverData.models.partner.records = serverData.models.partner.records.slice(0, 1);
|
||||
serverData.models.partner.records[0].float_field = 2;
|
||||
|
||||
serverData.models.partner.onchanges = {
|
||||
@@ -107,7 +111,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
QUnit.test(
|
||||
"ProgressBarField: value should update in edit mode when typing in input",
|
||||
async function (assert) {
|
||||
assert.expect(5);
|
||||
assert.expect(6);
|
||||
serverData.models.partner.records[0].int_field = 99;
|
||||
|
||||
await makeView({
|
||||
@@ -151,13 +155,21 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
await editInput(target, ".o_progressbar_value.o_input", "69");
|
||||
target.querySelector(".o_progressbar_value.o_input").blur();
|
||||
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_progressbar_value").textContent,
|
||||
"69%",
|
||||
"New value should be different after focusing out of the field"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_form_button_save"));
|
||||
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o_progressbar_value").textContent,
|
||||
"69%",
|
||||
"New value should be different than initial after click"
|
||||
"New value is still displayed after save"
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -271,7 +283,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
QUnit.test(
|
||||
"ProgressBarField: update both max value and current value in edit mode when both options are given",
|
||||
async function (assert) {
|
||||
assert.expect(7);
|
||||
assert.expect(10);
|
||||
serverData.models.partner.records[0].int_field = 99;
|
||||
|
||||
await makeView({
|
||||
@@ -310,9 +322,23 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
const currentVal = target.querySelectorAll(".o_progressbar_value.o_input")[0];
|
||||
const maxVal = target.querySelectorAll(".o_progressbar_value.o_input")[1];
|
||||
assert.strictEqual(document.activeElement, currentVal, "First input is focused");
|
||||
assert.strictEqual(currentVal.value, "99", "Initial value in input is correct");
|
||||
assert.strictEqual(maxVal.value, "0.44", "Initial value in input is correct");
|
||||
|
||||
maxVal.focus();
|
||||
await nextTick();
|
||||
assert.strictEqual(document.activeElement, maxVal, "Second input is focused");
|
||||
|
||||
maxVal.blur();
|
||||
await nextTick();
|
||||
assert.containsNone(
|
||||
target,
|
||||
".o_progressbar input",
|
||||
"Inputs are no longer visible when focusing out"
|
||||
);
|
||||
|
||||
await click(target.querySelector(".o_progress"));
|
||||
await editInput(target, ".o_progressbar input:nth-of-type(1)", "2000");
|
||||
await editInput(target, ".o_progressbar input:nth-of-type(2)", "69");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user