[IMP] web: add onInput props to SelectMenu

This commit adds the onInput props, allowing to run code from the parent
of the SelectMenu depending on the search value. This can be used in
situations where the list of choices must be dynamic. Also, whenever the
list of choices or groups is edited in length, the menu is filtered using
the newer values.

For example, choices can be generated from a fetch of items. Now, instead
of loading all items before instanciating a SelectMenu, a limit could be
set during the fetch, and another fetch can happen as the user type.

A test has been added to verify that the onInput props is called correctly,
and executes its code. In this test, the list of choices is edited, and we
assert that the newer elements are present in the menu.

closes odoo/odoo#124224

Signed-off-by: Bastien Fafchamps (bafa) <bafa@odoo.com>
This commit is contained in:
luvi
2023-06-08 13:10:14 +02:00
parent 9eb008dd77
commit 0302e2f4df
2 changed files with 80 additions and 3 deletions
@@ -66,6 +66,7 @@ export class SelectMenu extends Component {
searchPlaceholder: { type: String, optional: true },
value: { optional: true },
multiSelect: { type: Boolean, optional: true },
onInput: { type: Function, optional: true },
onSelect: { type: Function, optional: true },
slots: { type: Object, optional: true },
};
@@ -93,6 +94,13 @@ export class SelectMenu extends Component {
if (this.props.value !== nextProps.value) {
this.selectedChoice = this.getSelectedChoice(nextProps);
}
if (
this.props.choices.length !== nextProps.choices.length ||
this.props.groups.length !== nextProps.groups.length
) {
const groups = [{ choices: nextProps.choices }, ...nextProps.groups];
this.filterOptions(this.state.searchValue, groups);
}
});
}
@@ -156,6 +164,10 @@ export class SelectMenu extends Component {
}
}
async executeOnInput(searchString) {
await this.props.onInput(searchString);
}
onInput(searchString) {
this.filterOptions(searchString);
this.state.searchValue = searchString;
@@ -165,6 +177,9 @@ export class SelectMenu extends Component {
if (inputEl && inputEl.parentNode) {
inputEl.parentNode.scrollTo(0, 0);
}
if (this.props.onInput) {
this.executeOnInput(searchString);
}
}
onSearchKeydown(ev) {
@@ -218,12 +233,12 @@ export class SelectMenu extends Component {
*
* @param {String} searchString
*/
filterOptions(searchString = "") {
const groups = [{ choices: this.props.choices }, ...this.props.groups];
filterOptions(searchString = "", groups) {
const groupsList = groups || [{ choices: this.props.choices }, ...this.props.groups];
this.state.choices = [];
for (const group of groups) {
for (const group of groupsList) {
let filteredOptions = [];
if (searchString) {
@@ -867,4 +867,66 @@ QUnit.module("Web Components", (hooks) => {
assert.verifySteps(["a"], "value has been selected after keyboard navigation");
}
);
QUnit.test("Props onInput is executed when the search changes", async (assert) => {
class Parent extends Component {
setup() {
this.state = useState({
choices: [{ label: "Hello", value: "hello" }],
value: "hello",
});
}
onInput() {
// This test adds items from the list of choices given by the parent.
// It can be used as a reference to fetch and load content dynamically to the SelectMenu
this.state.choices = [
{ label: "Hello", value: "hello" },
{ label: "Coucou", value: "hello2" },
];
}
onSelect(value) {
assert.step(value);
this.state.value = value;
}
}
Parent.components = { SelectMenu };
Parent.template = xml`
<SelectMenu
choices="state.choices"
value="state.value"
onInput.bind="onInput"
onSelect.bind="onSelect"
/>
`;
await mount(Parent, target, { env });
assert.strictEqual(getValue(), "Hello");
await open();
assert.strictEqual(
target.querySelector(".o_select_menu_menu").textContent,
"Hello",
"SelectMenu has only one choice available"
);
await editInput(target, "input.o_select_menu_sticky", "cou");
assert.strictEqual(
target.querySelector(".o_select_menu_menu").textContent,
"Coucou",
"SelectMenu now has 'Coucou' available and search is filtered"
);
await click(target.querySelectorAll(".o_select_menu_item_label")[0]);
assert.verifySteps(["hello2"], "added item can be selected");
assert.strictEqual(getValue(), "Coucou");
await open();
assert.strictEqual(
target.querySelector(".o_select_menu_menu").textContent,
"CoucouHello",
"SelectMenu has two choices available"
);
});
});