diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index bfcef0173a5c2..76e304215daa6 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -587,7 +587,7 @@ def _get_search_order(self, order): return 'is_published desc, %s, id desc' % order @http.route('/website/snippet/autocomplete', type='jsonrpc', auth='public', website=True, readonly=True) - def autocomplete(self, search_type=None, term=None, order=None, limit=6, max_nb_chars=999, options=None): + def autocomplete(self, search_type=None, term=None, order=None, limit=6, offset=0, max_nb_chars=999, options=None): """ Returns list of results according to the term and options @@ -595,6 +595,7 @@ def autocomplete(self, search_type=None, term=None, order=None, limit=6, max_nb_ :param str term: search term written by the user :param str order: :param int limit: number of results to consider, defaults to 5 + :param int offset: number of results to skip, defaults to 0 :param int max_nb_chars: max number of characters for text fields :param dict options: options map containing allowFuzzy: enables the fuzzy matching when truthy @@ -611,7 +612,7 @@ def autocomplete(self, search_type=None, term=None, order=None, limit=6, max_nb_ """ order = self._get_search_order(order) options = options or {} - results_count, search_results, fuzzy_term = request.website._search_with_fuzzy(search_type, term, limit, order, options) + results_count, search_results, fuzzy_term = request.website._search_with_fuzzy(search_type, term, limit, offset, order, options) # Sort result based in sequence for ordered results. search_results.sort(key=lambda d: d.get('sequence', float('inf'))) if not results_count: @@ -667,7 +668,8 @@ def autocomplete(self, search_type=None, term=None, order=None, limit=6, max_nb_ result[group_key] = { "groupName": group_name, "templateKey": search_result.get("template_key"), - "search_count": search_result.get('count'), + "searchCount": search_result.get('count'), + "searchType": search_result.get("model").replace('.', '_'), "data": result_data, } @@ -725,7 +727,8 @@ def hybrid_list(self, search='', search_type='all', **kw): return request.render("website.list_hybrid") options = self._get_hybrid_search_options(**kw) - data = self.autocomplete(search_type=search_type, term=search, order='name asc', limit=100, max_nb_chars=200, options=options) + limit = 24 + data = self.autocomplete(search_type=search_type, term=search, order='name asc', limit=limit, offset=0, max_nb_chars=200, options=options) results = data.get('results', []) search_count = data.get('results_count', 0) @@ -736,6 +739,7 @@ def hybrid_list(self, search='', search_type='all', **kw): 'results': results, 'parts': parts, 'search': search, + 'limit': limit, 'fuzzy_search': data.get('fuzzy_search'), 'search_count': search_count, } diff --git a/addons/website/models/mixins.py b/addons/website/models/mixins.py index c3f2ffb116a93..a16c8ae0728ee 100644 --- a/addons/website/models/mixins.py +++ b/addons/website/models/mixins.py @@ -689,7 +689,7 @@ def _search_get_detail(self, website, order, options): raise NotImplementedError() @api.model - def _search_fetch(self, search_detail, search, limit, order): + def _search_fetch(self, search_detail, search, limit, offset, order): fields = search_detail['search_fields'] base_domain = search_detail['base_domain'] domain = self._search_build_domain(base_domain, search, fields, search_detail.get('search_extra')) @@ -697,6 +697,7 @@ def _search_fetch(self, search_detail, search, limit, order): results = model.search( domain, limit=limit, + offset=offset, order=search_detail.get('order', order) ) count = model.search_count(domain) if limit and limit == len(results) else len(results) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 5b53c00c52fe4..a121349d47c5c 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -2052,17 +2052,18 @@ def _search_get_details(self, search_type, order, options): :return: list of search details obtained from the `website.searchable.mixin`'s `_search_get_detail()` """ result = [] - if search_type in ['pages', 'all']: + if search_type in ['website_page', 'all']: result.append(self.env['website.page']._search_get_detail(self, order, options)) return result - def _search_with_fuzzy(self, search_type, search, limit, order, options): + def _search_with_fuzzy(self, search_type, search, limit, offset, order, options): """ Performs a search with a search text or with a resembling word :param search_type: indicates what to search within, 'all' matches all available types :param search: text against which to match results :param limit: maximum number of results per model type involved in the result + :param offset: number of results to skip per model type involved in the result :param order: order on which to sort results within a model type :param options: search options from the submitted form containing: - allowFuzzy: boolean indicating whether the fuzzy matching must be done @@ -2079,16 +2080,16 @@ def _search_with_fuzzy(self, search_type, search, limit, order, options): if search and options.get('allowFuzzy', True): fuzzy_term = self._search_find_fuzzy_term(search_details, search) if fuzzy_term: - count, results = self._search_exact(search_details, fuzzy_term, limit, order) + count, results = self._search_exact(search_details, fuzzy_term, limit, offset, order) if fuzzy_term.lower() == search.lower(): fuzzy_term = False else: - count, results = self._search_exact(search_details, search, limit, order) + count, results = self._search_exact(search_details, search, limit, offset, order) else: - count, results = self._search_exact(search_details, search, limit, order) + count, results = self._search_exact(search_details, search, limit, offset, order) return count, results, fuzzy_term - def _search_exact(self, search_details, search, limit, order): + def _search_exact(self, search_details, search, limit, offset, order): """ Performs a search with a search text @@ -2108,7 +2109,7 @@ def _search_exact(self, search_details, search, limit, order): total_count = 0 for search_detail in search_details: model = self.env[search_detail['model']] - results, count = model._search_fetch(search_detail, search, limit, order) + results, count = model._search_fetch(search_detail, search, limit, offset, order) search_detail['results'] = results total_count += count search_detail['count'] = count diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py index 57255e9780743..e5755a7fd4b19 100644 --- a/addons/website/models/website_page.py +++ b/addons/website/models/website_page.py @@ -244,7 +244,7 @@ def _search_get_detail(self, website, order, options): } @api.model - def _search_fetch(self, search_detail, search, limit, order): + def _search_fetch(self, search_detail, search, limit, offset, order): with_description = 'description' in search_detail['mapping'] # Cannot rely on the super's _search_fetch because the search must be # performed among the most specific pages only. diff --git a/addons/website/static/src/snippets/s_searchbar/000.xml b/addons/website/static/src/snippets/s_searchbar/000.xml index e9533e1df95bb..ceb9da5dbfa68 100644 --- a/addons/website/static/src/snippets/s_searchbar/000.xml +++ b/addons/website/static/src/snippets/s_searchbar/000.xml @@ -46,11 +46,11 @@
- - + -
- diff --git a/addons/website/static/src/snippets/s_searchbar/search_bar.js b/addons/website/static/src/snippets/s_searchbar/search_bar.js index 7c53e544eb4aa..234c10537cac2 100644 --- a/addons/website/static/src/snippets/s_searchbar/search_bar.js +++ b/addons/website/static/src/snippets/s_searchbar/search_bar.js @@ -23,6 +23,16 @@ export class SearchBar extends Interaction { "t-on-keydown": this.onKeydown, "t-on-search": this.onSearch, }, + ".loadMore": { + "t-on-click": (ev) => { + ev.preventDefault(); + const search_type = ev.currentTarget.dataset.search_type; + // Calculate offset based on how many times load more has been clicked + const offset = + this.limit * (parseInt(ev.currentTarget.dataset.offset_multiplier) || 1); + this.loadMore(offset, search_type, ev.currentTarget); + }, + }, }; autocompleteMinWidth = 300; @@ -94,19 +104,17 @@ export class SearchBar extends Interaction { return "list"; } - async fetch() { + async fetch(offset = 0, search_type = this.searchType) { const res = await rpc("/website/snippet/autocomplete", { - search_type: this.searchType, + search_type: search_type, term: this.inputEl.value, order: this.order, limit: this.limit, max_nb_chars: Math.round( - Math.max( - this.autocompleteMinWidth, - parseInt(this.el.clientWidth / (this.getDisplayType() === "columns" ? 3 : 1)) - ) * 0.22 + Math.max(this.autocompleteMinWidth, parseInt(this.el.clientWidth)) * 0.22 ), options: this.options, + offset: offset, }); const field_set = new Set(this.getFieldsNames()); @@ -229,6 +237,31 @@ export class SearchBar extends Interaction { ev.preventDefault(); } } + + /** + * Lazy Loading + * @params {number} offset + * @params {string} search_type + * @params {HTMLElement} currentTarget + */ + async loadMore(offset, search_type, currentTarget) { + const res = await this.fetch(offset, search_type); + if (res) { + const bucket = Object.values(res.results)[0]; + const template = bucket.templateKey; + if (bucket.searchCount <= offset) { + currentTarget.classList.add("d-none"); + } + + // We need displayType = 'columns' by default. + const row_classes = "o_search_result_item col-12 col-md-6 col-lg-4"; + + this.renderAt(template, { bucket, row_classes }, currentTarget, "beforebegin"); + // Increment offset according to click count + currentTarget.dataset.offset_multiplier = + (parseInt(currentTarget.dataset.offset_multiplier) || 1) + 1; + } + } } registry.category("public.interactions").add("website.search_bar", SearchBar); diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 09fd331288645..3c2a66423d3a9 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -3129,7 +3129,7 @@ Sitemap: sitemap.xml

Pages

@@ -3185,10 +3185,11 @@ Sitemap: sitemap.xml - -