From 6846191e85d3d8f44b7f8d6a032372bd640cbbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Mottet=20=28oms=29?= Date: Mon, 9 Aug 2021 14:22:02 +0000 Subject: [PATCH] [IMP] website: add 'no result' message for industry selection When the user search for an industry in the industry autocomplete input, an information message is now displayed if no industry match the user's search. task-2602521 closes odoo/odoo#76773 X-original-commit: 93df93c966b097f671ab63de2e778ad1662953d6 Signed-off-by: Quentin Smetz (qsm) --- .../components/configurator/configurator.js | 123 ++++++++++++++---- .../components/configurator/configurator.xml | 2 +- .../website/static/src/scss/configurator.scss | 9 ++ 3 files changed, 111 insertions(+), 23 deletions(-) diff --git a/addons/website/static/src/components/configurator/configurator.js b/addons/website/static/src/components/configurator/configurator.js index 3bd0b41dc81..d7a51d80836 100644 --- a/addons/website/static/src/components/configurator/configurator.js +++ b/addons/website/static/src/components/configurator/configurator.js @@ -91,6 +91,7 @@ class DescriptionScreen extends Component { this.labelToId = {}; this.getters = useGetters(); this.dispatch = useDispatch(); + this.autocompleteHasResults = true; } mounted() { @@ -99,8 +100,10 @@ class DescriptionScreen extends Component { appendTo: '.o_configurator_industry_wrapper', delay: 400, minLength: 1, - source: this.autocompleteSearch.bind(this), - select: this.selectIndustry.bind(this), + source: this._autocompleteSearch.bind(this), + select: this._selectIndustry.bind(this), + open: this._customizeNoResultMenuStyle.bind(this), + focus: this._disableKeyboardNav.bind(this), classes: { 'ui-autocomplete': 'custom-ui-autocomplete shadow-lg border-0 o_configurator_show_fast', } @@ -112,7 +115,66 @@ class DescriptionScreen extends Component { } } - autocompleteSearch(request, response) { + /** + * Clear the input and its parent label and set the selected industry to undefined. + * + * @private + */ + _clearIndustrySelection() { + this.industrySelection.el.value = ''; + this.industrySelection.el.parentNode.dataset.value = ''; + this.dispatch('selectIndustry', undefined, undefined); + } + + /** + * Set the input's parent label value to automatically adapt input size + * and update the selected industry. + * + * @private + * @param {String} label an industry label + */ + _setSelectedIndustry(label) { + this.industrySelection.el.parentNode.dataset.value = label; + const id = this.labelToId[label]; + this.dispatch('selectIndustry', label, id); + this.checkDescriptionCompletion(); + } + + /** + * Called each time the suggestion menu is opened or updated. If there are no + * results to display the style of the "No result found" message is customized. + * + * @private + */ + _customizeNoResultMenuStyle() { + if (!this.autocompleteHasResults) { + const noResultLinkEl = this.industrySelection.el.parentElement.getElementsByTagName('a')[0]; + noResultLinkEl.classList.add('o_no_result'); + } + } + + /** + * Disables keyboard navigation when there are no results to avoid selecting the + * "No result found" message by pressing the down arrow key. + * + * @private + * @param {Event} ev + */ + _disableKeyboardNav(ev) { + if (!this.autocompleteHasResults) { + ev.preventDefault(); + } + } + + /** + * Called each time the autocomplete input's value changes. Only industries containing + * the input value are kept. Industries starting with the input value are put in first + * position then the order is the alphabetical one. The result size is limited to 15. + * + * @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 + */ + _autocompleteSearch(request, response) { const lcTerm = request.term.toLowerCase(); const limit = 15; const matches = this.state.industries.filter((val) => { @@ -127,33 +189,50 @@ class DescriptionScreen extends Component { }); relaxedMatches = relaxedMatches.slice(0, limit - labels.length); results = results.concat(relaxedMatches); - labels = results.map((val) => val.label); } - results.forEach((r) => { - this.labelToId[r.label] = r.id; - }); + this.autocompleteHasResults = !!results.length; + if (this.autocompleteHasResults) { + labels = results.map((val) => val.label); + results.forEach((r) => { + this.labelToId[r.label] = r.id; + }); + } else { + labels = [_t("No result found, broaden your search.")]; + } response(labels); } - selectIndustry(_, ui) { - this.industrySelection.el.parentNode.dataset.value = ui.item.label; - this.dispatch('selectIndustry', ui.item.label, this.labelToId[ui.item.label]); - this.checkDescriptionCompletion(); - } - - blurIndustrySelection(ev) { - const id = this.labelToId[ev.target.value]; - this.dispatch('selectIndustry', ev.target.value, id); - if (id === undefined) { - this.industrySelection.el.value = ''; - this.industrySelection.el.parentNode.dataset.value = ''; + /** + * Called when a menu option is selected. Update the selected industry or + * clear the input if the option is the "No result found" message. + * + * @private + * @param {Event} ev + * @param {Object} ui an object with label and value properties for + * the selected option. + */ + _selectIndustry(ev, ui) { + if (this.autocompleteHasResults) { + this._setSelectedIndustry(ui.item.label); } else { - this.checkDescriptionCompletion(); + this._clearIndustrySelection(); + ev.preventDefault(); } } - inputIndustrySelection(ev) { - this.industrySelection.el.parentNode.dataset.value = ev.target.value; + /** + * Called on industrySelection input blur. Updates the selected industry or + * clears the input if its current value is not a valid industry. + * + * @private + * @param {Event} ev + */ + _blurIndustrySelection(ev) { + if (this.labelToId[ev.target.value] !== undefined) { + this._setSelectedIndustry(ev.target.value); + } else { + this._clearIndustrySelection(); + } } selectWebsiteType(ev) { diff --git a/addons/website/static/src/components/configurator/configurator.xml b/addons/website/static/src/components/configurator/configurator.xml index d5ef46aaa35..a16457dbe5b 100644 --- a/addons/website/static/src/components/configurator/configurator.xml +++ b/addons/website/static/src/components/configurator/configurator.xml @@ -56,7 +56,7 @@
business , diff --git a/addons/website/static/src/scss/configurator.scss b/addons/website/static/src/scss/configurator.scss index e45b78a3f29..7651a122beb 100644 --- a/addons/website/static/src/scss/configurator.scss +++ b/addons/website/static/src/scss/configurator.scss @@ -171,6 +171,15 @@ font-family: $font-family-base; text-decoration: none; line-height: 1.2; + + &.o_no_result, &.o_no_result:hover { + background-color: transparent !important; + color: gray('600') !important; + cursor: default; + padding: $dropdown-item-padding-y*4 $dropdown-item-padding-x; + font-style: italic; + font-size: .5em; + } } } }