From 0302e2f4df413ef47d9c4c29c5baddcc75b396a3 Mon Sep 17 00:00:00 2001 From: luvi Date: Thu, 8 Jun 2023 06:26:21 +0000 Subject: [PATCH] [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) --- .../src/core/select_menu/select_menu.js | 21 ++++++- .../static/tests/core/select_menu_tests.js | 62 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/core/select_menu/select_menu.js b/addons/web/static/src/core/select_menu/select_menu.js index da86db1ee3a..7312c264247 100644 --- a/addons/web/static/src/core/select_menu/select_menu.js +++ b/addons/web/static/src/core/select_menu/select_menu.js @@ -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) { diff --git a/addons/web/static/tests/core/select_menu_tests.js b/addons/web/static/tests/core/select_menu_tests.js index 87cbb346d7b..275c5b61124 100644 --- a/addons/web/static/tests/core/select_menu_tests.js +++ b/addons/web/static/tests/core/select_menu_tests.js @@ -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` + + `; + + 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" + ); + }); });