[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:
@@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user