diff --git a/addons/web/static/src/core/autocomplete/autocomplete.js b/addons/web/static/src/core/autocomplete/autocomplete.js index 58133ee3f7b..cde456b1402 100644 --- a/addons/web/static/src/core/autocomplete/autocomplete.js +++ b/addons/web/static/src/core/autocomplete/autocomplete.js @@ -64,9 +64,7 @@ export class AutoComplete extends Component { // position and size if (this.props.dropdown) { - usePosition("sourcesList", () => this.targetDropdown, { - position: "bottom-start", - }); + usePosition("sourcesList", () => this.targetDropdown, this.dropdownOptions); } else { this.open(false); } @@ -76,6 +74,12 @@ export class AutoComplete extends Component { return this.inputRef.el; } + get dropdownOptions() { + return { + position: "bottom-start", + }; + } + get isOpened() { return this.state.open; } diff --git a/addons/website/static/src/client_actions/configurator/configurator.js b/addons/website/static/src/client_actions/configurator/configurator.js index e426de9e331..a7e58f90d20 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.js +++ b/addons/website/static/src/client_actions/configurator/configurator.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { AutoComplete } from "@web/core/autocomplete/autocomplete"; import { delay } from "@web/core/utils/concurrency"; import { getDataURLFromFile } from "@web/core/utils/urls"; import weUtils from '@web_editor/js/common/utils'; @@ -92,12 +93,25 @@ Object.assign(WelcomeScreen, { template: 'website.Configurator.WelcomeScreen', }); +class IndustrySelectionAutoComplete extends AutoComplete { + static timeout = 400; + + get dropdownOptions() { + return { + ...super.dropdownOptions, + position: "bottom-fit", + } + } + + get ulDropdownClass() { + return `${super.ulDropdownClass} custom-ui-autocomplete shadow-lg border-0 o_configurator_show_fast o_configurator_industry_dropdown` + } +} + class DescriptionScreen extends Component { setup() { this.industrySelection = useRef('industrySelection'); this.state = useStore(); - this.labelToId = {}; - this.autocompleteHasResults = true; this.orm = useService('orm'); onMounted(() => this.onMounted()); @@ -105,42 +119,29 @@ class DescriptionScreen extends Component { onMounted() { this.selectWebsitePurpose(); - $(this.industrySelection.el).autocomplete({ - appendTo: '.o_configurator_industry_wrapper', - delay: 400, - minLength: 1, - source: this._autocompleteSearch.bind(this), - select: this._selectIndustry.bind(this), - classes: { - 'ui-autocomplete': 'custom-ui-autocomplete shadow-lg border-0 o_configurator_show_fast o_configurator_industry_dropdown', - }, - }); - if (this.state.selectedIndustry) { - this.industrySelection.el.value = this.state.selectedIndustry.label; - this.industrySelection.el.parentNode.dataset.value = this.state.selectedIndustry.label; - this.labelToId[this.state.selectedIndustry.label] = this.state.selectedIndustry.id; - } } - /** * Set the input's parent label value to automatically adapt input size * and update the selected industry. * * @private - * @param {String} label an industry label + * @param {Object} suggestion an industry */ - _setSelectedIndustry(label) { - this.industrySelection.el.parentNode.dataset.value = label; - if (!this.autocompleteHasResults) { - // Unknown industry. - this.state.selectIndustry(label, -1); - } else { - const id = this.labelToId[label]; - this.state.selectIndustry(label, id); - } + _setSelectedIndustry(suggestion) { + const { label, id } = Object.getPrototypeOf(suggestion); + this.state.selectIndustry(label, id); this.checkDescriptionCompletion(); } + get sources() { + return [ + { + options: (request) => { + return request.length < 1 ? [] : this._autocompleteSearch(request); + }, + }, + ]; + } /** * Called each time the autocomplete input's value changes. Only industries * having a label or a synonym containing all terms of the input value are @@ -150,13 +151,10 @@ class DescriptionScreen extends Component { * sorted alphabetically. * The result size is limited to 30. * - * @param {Object} request object with a single 'term' property which is the - * input current value - * @param {function} response callback which takes the data to suggest as - * argument + * @param {String} term input current value */ - _autocompleteSearch(request, response) { - const terms = request.term.toLowerCase().split(/[|,\n]+/); + _autocompleteSearch(term) { + const terms = term.toLowerCase().split(/[|,\n]+/); const limit = 30; const sortLimit = 7; // `this.state.industries` is already sorted by hit count (from IAP). @@ -178,52 +176,17 @@ class DescriptionScreen extends Component { .slice(0, limit) .sort((x, y) => x.hitCountOrder - y.hitCountOrder); } - this.labelToId = {}; - let labels; - this.autocompleteHasResults = !!matches.length; - if (this.autocompleteHasResults) { - if (matches.length <= sortLimit) { - // Sort results by ascending label if few of them. - matches.sort((x, y) => x.label < y.label ? -1 : x.label > y.label ? 1 : 0); - } - labels = matches.map(val => val.label); - matches.forEach(r => { - this.labelToId[r.label] = r.id; - }); - } else { - labels = [request.term]; + if (matches.length <= sortLimit) { + // Sort results by ascending label if few of them. + matches = matches.sort((x, y) => (x.label < y.label ? -1 : x.label > y.label ? 1 : 0)); } - response(labels); - } - - /** - * Called when a menu option is selected. Update the selected industry. - * - * @private - * @param {Event} ev - * @param {Object} ui an object with label and value properties for - * the selected option. - */ - _selectIndustry(ev, ui) { - this._setSelectedIndustry(ui.item.label); - } - - /** - * Called on industrySelection input blur. Updates the selected industry. - * - * @private - * @param {Event} ev - */ - _blurIndustrySelection(ev) { - // Check if the input is known by the server. - this.autocompleteHasResults = !!this.labelToId[ev.target.value]; - this._setSelectedIndustry(ev.target.value); + return matches.length ? matches : [{ label: term, id: -1 }]; } selectWebsiteType(id) { this.state.selectWebsiteType(id); setTimeout(() => { - this.industrySelection.el.focus(); + this.industrySelection.el.querySelector("input").focus(); }); this.checkDescriptionCompletion(); } @@ -238,7 +201,7 @@ class DescriptionScreen extends Component { if (selectedType && selectedPurpose && selectedIndustry) { // If the industry name is not known by the server, send it to the // IAP server. - if (!this.autocompleteHasResults) { + if (selectedIndustry.id === -1) { this.orm.call('website', 'configurator_missing_industry', [], { 'unknown_industry': selectedIndustry.label, }); @@ -249,7 +212,7 @@ class DescriptionScreen extends Component { } Object.assign(DescriptionScreen, { - components: {SkipButton}, + components: { SkipButton, AutoComplete: IndustrySelectionAutoComplete }, template: 'website.Configurator.DescriptionScreen', }); diff --git a/addons/website/static/src/client_actions/configurator/configurator.scss b/addons/website/static/src/client_actions/configurator/configurator.scss index 0837d35acf5..01054f58fb3 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.scss +++ b/addons/website/static/src/client_actions/configurator/configurator.scss @@ -166,8 +166,10 @@ .dropdown-menu, .custom-ui-autocomplete { @include border-bottom-radius($border-radius); font-size: inherit; - min-width: 100%; overflow-y: auto; + &:not(.o_configurator_industry_dropdown) { + min-width: 100%; + } } .dropdown-item, .fa-angle-down, .ui-menu-item a, .ui-menu-item a.ui-state-active { diff --git a/addons/website/static/src/client_actions/configurator/configurator.xml b/addons/website/static/src/client_actions/configurator/configurator.xml index 0f10f8331e2..f9b101722bc 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.xml +++ b/addons/website/static/src/client_actions/configurator/configurator.xml @@ -51,8 +51,12 @@ for my
-