[FIX] web: autocomplete inEdition
When a user is editing an Autocomplete, we don't want to delete its value
if the props value changes.
For example:
- In a form view with a many2one field and another field with an onchange
- Edit the second field
- Insert a value in the many2one input
- The onchange of the first field returns a value for the many2one
Before this commit:
The value inserted in the many2one is lost. It is replaced by the
onchange value
After this commit:
The value inserted in the many2one is unchanged.
closes odoo/odoo#136654
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -12,6 +12,7 @@ export class AutoComplete extends Component {
|
||||
this.nextSourceId = 0;
|
||||
this.nextOptionId = 0;
|
||||
this.sources = [];
|
||||
this.inEdition = false;
|
||||
|
||||
this.state = useState({
|
||||
navigationRev: 0,
|
||||
@@ -45,7 +46,6 @@ export class AutoComplete extends Component {
|
||||
}
|
||||
}, this.constructor.timeout);
|
||||
|
||||
|
||||
useExternalListener(window, "scroll", this.onWindowScroll, true);
|
||||
|
||||
this.hotkey = useService("hotkey");
|
||||
@@ -54,8 +54,10 @@ export class AutoComplete extends Component {
|
||||
owl.onWillUpdateProps((nextProps) => {
|
||||
if (this.props.value !== nextProps.value || this.forceValFromProp) {
|
||||
this.forceValFromProp = false;
|
||||
this.state.value = nextProps.value;
|
||||
this.inputRef.el.value = nextProps.value;
|
||||
if (!this.inEdition) {
|
||||
this.state.value = nextProps.value;
|
||||
this.inputRef.el.value = nextProps.value;
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
@@ -156,6 +158,7 @@ export class AutoComplete extends Component {
|
||||
}
|
||||
selectOption(indices, params = {}) {
|
||||
const option = this.sources[indices[0]].options[indices[1]];
|
||||
this.inEdition = false;
|
||||
if (option.unselectable) {
|
||||
this.inputRef.el.value = "";
|
||||
this.close();
|
||||
@@ -234,6 +237,7 @@ export class AutoComplete extends Component {
|
||||
this.props.onBlur({
|
||||
inputValue: value,
|
||||
});
|
||||
this.inEdition = false;
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -250,6 +254,7 @@ export class AutoComplete extends Component {
|
||||
});
|
||||
}
|
||||
async onInput() {
|
||||
this.inEdition = true;
|
||||
this.pendingPromise = this.pendingPromise || new Deferred();
|
||||
this.loadingPromise = this.pendingPromise;
|
||||
this.debouncedProcessInput();
|
||||
|
||||
@@ -397,4 +397,84 @@ QUnit.module("Components", (hooks) => {
|
||||
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input"), document.activeElement);
|
||||
});
|
||||
|
||||
QUnit.test("autocomplete in edition keep edited value before select option", async (assert) => {
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.state = useState({ value: "Hello" });
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.state.value = "My Click";
|
||||
}
|
||||
|
||||
onSelect() {
|
||||
this.state.value = "My Selection";
|
||||
}
|
||||
}
|
||||
Parent.components = { AutoComplete };
|
||||
Parent.template = xml`
|
||||
<button class="myButton" t-on-click="onClick" />
|
||||
<AutoComplete value="this.state.value"
|
||||
sources="[{ options: [{ label: 'My Selection' }] }]"
|
||||
onSelect.bind="onSelect"
|
||||
/>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
const input = target.querySelector(".o-autocomplete input");
|
||||
input.value = "Yolo";
|
||||
await triggerEvent(input, null, "input");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "Yolo");
|
||||
|
||||
await click(target, ".myButton");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "Yolo");
|
||||
|
||||
// Leave inEdition mode when selecting an option
|
||||
await click(target.querySelector(".o-autocomplete input"));
|
||||
await click(target.querySelectorAll(".o-autocomplete--dropdown-item")[0]);
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Selection");
|
||||
|
||||
await click(target, ".myButton");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Click");
|
||||
});
|
||||
|
||||
QUnit.test("autocomplete in edition keep edited value before blur", async (assert) => {
|
||||
let count = 0;
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.state = useState({ value: "Hello" });
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.state.value = `My Click ${count++}`;
|
||||
}
|
||||
}
|
||||
Parent.components = { AutoComplete };
|
||||
Parent.template = xml`
|
||||
<button class="myButton" t-on-click="onClick" />
|
||||
<AutoComplete value="this.state.value"
|
||||
sources="[]"
|
||||
onSelect="() => {}"
|
||||
/>
|
||||
`;
|
||||
|
||||
await mount(Parent, target, { env });
|
||||
let input = target.querySelector(".o-autocomplete input");
|
||||
input.value = "";
|
||||
await triggerEvent(input, null, "input");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
|
||||
|
||||
await click(target, ".myButton");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
|
||||
|
||||
// Leave inEdition mode when blur the input
|
||||
input = target.querySelector(".o-autocomplete input");
|
||||
await triggerEvent(input, null, "blur");
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "");
|
||||
|
||||
await click(target, ".myButton");
|
||||
assert.strictEqual(target.querySelector(".o-autocomplete input").value, "My Click 1");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
selectDropdownItem,
|
||||
triggerEvent,
|
||||
clickDiscard,
|
||||
clickOpenedDropdownItem,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
@@ -360,8 +361,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
const input = target.querySelector(".o_field_widget[name=user_id] input");
|
||||
input.value = "yy";
|
||||
await triggerEvent(input, null, "input");
|
||||
await click(target, ".o_field_widget[name=user_id] input");
|
||||
await selectDropdownItem(target, "user_id", "Create and edit...");
|
||||
await clickOpenedDropdownItem(target, "user_id", "Create and edit...");
|
||||
|
||||
await clickDiscard(target.querySelector(".modal"));
|
||||
assert.strictEqual(target.querySelector(".o_field_widget[name=user_id] input").value, "");
|
||||
|
||||
@@ -692,8 +692,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
const input = target.querySelector(".o_field_widget[name=trululu] input");
|
||||
input.value = "yy";
|
||||
await triggerEvent(input, null, "input");
|
||||
await click(target, ".o_field_widget[name=trululu] input");
|
||||
await selectDropdownItem(target, "trululu", "Create and edit...");
|
||||
await clickOpenedDropdownItem(target, "trululu", "Create and edit...");
|
||||
|
||||
await clickSave(target.querySelector(".modal"));
|
||||
assert.verifySteps([`web_save: [[],{"name":"yy"}]`]);
|
||||
@@ -742,8 +741,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
await click(target.querySelectorAll(".o_data_cell")[0]);
|
||||
const input = target.querySelector(".o_field_widget[name=user_id] input");
|
||||
await editInput(input, null, "yy");
|
||||
await click(target, ".o_field_widget[name=user_id] input");
|
||||
await selectDropdownItem(target, "user_id", 'Create "yy"');
|
||||
await clickOpenedDropdownItem(target, "user_id", 'Create "yy"');
|
||||
|
||||
assert.verifySteps(["name_create"]);
|
||||
}
|
||||
@@ -834,6 +832,46 @@ QUnit.module("Fields", (hooks) => {
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"edit many2one before onchange is finished should not reset the value",
|
||||
async function (assert) {
|
||||
serverData.models.partner.onchanges.display_name = function (obj) {
|
||||
obj.user_id = 19;
|
||||
};
|
||||
|
||||
const def = makeDeferred();
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="user_id"/>
|
||||
</form>`,
|
||||
async mockRPC(route, { method }) {
|
||||
if (method === "onchange") {
|
||||
assert.step("onchange");
|
||||
await def;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "new name");
|
||||
const input = target.querySelector("[name='user_id'] input");
|
||||
input.value = "Plop";
|
||||
await triggerEvent(input, null, "input");
|
||||
assert.strictEqual(target.querySelector("[name='user_id'] input").value, "Plop");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
assert.strictEqual(target.querySelector("[name='user_id'] input").value, "Plop");
|
||||
|
||||
assert.verifySteps(["onchange"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("many2one doesn't trigger field_change when being emptied", async function (assert) {
|
||||
await makeView({
|
||||
type: "list",
|
||||
|
||||
Reference in New Issue
Block a user