summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/website/static/src/img/snippets_thumbs/s_products_searchbar.pngbin0 -> 295 bytes
-rw-r--r--addons/website/views/snippets.xml2
-rw-r--r--addons/website_sale/controllers/main.py98
-rw-r--r--addons/website_sale/static/src/js/website_sale.editor.js88
-rw-r--r--addons/website_sale/static/src/js/website_sale.js133
-rw-r--r--addons/website_sale/static/src/xml/website_sale.editor.xml52
-rw-r--r--addons/website_sale/static/src/xml/website_sale_utils.xml40
-rw-r--r--addons/website_sale/views/snippets.xml52
-rw-r--r--addons/website_sale/views/templates.xml28
9 files changed, 466 insertions, 27 deletions
diff --git a/addons/website/static/src/img/snippets_thumbs/s_products_searchbar.png b/addons/website/static/src/img/snippets_thumbs/s_products_searchbar.png
new file mode 100644
index 00000000000..4f24bb529cf
--- /dev/null
+++ b/addons/website/static/src/img/snippets_thumbs/s_products_searchbar.png
Binary files differ
diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml
index 1eb191f41c5..93817baa570 100644
--- a/addons/website/views/snippets.xml
+++ b/addons/website/views/snippets.xml
@@ -893,11 +893,13 @@
<template id="external_snippets" inherit_id="website.snippets" priority="8">
<xpath expr="//div[@id='snippet_feature']//t[@t-snippet][last()]" position="after">
<t t-install="website_twitter" string="Twitter Scroller" t-thumbnail="/website/static/src/img/snippets_thumbs/s_twitter_scroll.png"/>
+ <t id="s_products_searchbar" t-install="website_sale" string="Products Search" t-thumbnail="/website/static/src/img/snippets_thumbs/s_products_searchbar.png"/>
</xpath>
<xpath expr="//div[@id='snippet_content']//t[@t-snippet][last()]" position="after">
<t t-install="website_event" string="Local Events" t-thumbnail="/website/static/src/img/snippets_thumbs/s_local_events.png"/>
<t t-install="website_mail_channel" string="Discussion Group" t-thumbnail="/website/static/src/img/snippets_thumbs/s_button_channel_subscribe.png"/>
<t t-install="mass_mailing" string="Newsletter" t-thumbnail="/website/static/src/img/snippets_thumbs/s_newsletter_subscribe_form.png"/>
+ <t id="s_products_searchbar_input" t-install="website_sale" string="Products Search Input" t-thumbnail="/website/static/src/img/snippets_thumbs/s_products_searchbar.png"/>
</xpath>
</template>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index df264792c22..d39145da32c 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -157,18 +157,24 @@ class WebsiteSale(http.Controller):
def _get_search_order(self, post):
# OrderBy will be parsed in orm and so no direct sql injection
# id is added to be sure that order is a unique sort key
- return 'is_published desc,%s , id desc' % post.get('order', 'website_sequence desc')
+ order = post.get('order') or 'website_sequence desc'
+ return 'is_published desc, %s, id desc' % order
- def _get_search_domain(self, search, category, attrib_values):
- domain = request.website.sale_product_domain()
+ def _get_search_domain(self, search, category, attrib_values, search_in_description=True):
+ domains = [request.website.sale_product_domain()]
if search:
for srch in search.split(" "):
- domain += [
- '|', '|', '|', ('name', 'ilike', srch), ('description', 'ilike', srch),
- ('description_sale', 'ilike', srch), ('product_variant_ids.default_code', 'ilike', srch)]
+ subdomains = [
+ [('name', 'ilike', srch)],
+ [('product_variant_ids.default_code', 'ilike', srch)]
+ ]
+ if search_in_description:
+ subdomains.append([('description', 'ilike', srch)])
+ subdomains.append([('description_sale', 'ilike', srch)])
+ domains.append(expression.OR(subdomains))
if category:
- domain += [('public_categ_ids', 'child_of', int(category))]
+ domains.append([('public_categ_ids', 'child_of', int(category))])
if attrib_values:
attrib = None
@@ -180,13 +186,13 @@ class WebsiteSale(http.Controller):
elif value[0] == attrib:
ids.append(value[1])
else:
- domain += [('attribute_line_ids.value_ids', 'in', ids)]
+ domains.append([('attribute_line_ids.value_ids', 'in', ids)])
attrib = value[0]
ids = [value[1]]
if attrib:
- domain += [('attribute_line_ids.value_ids', 'in', ids)]
+ domains.append([('attribute_line_ids.value_ids', 'in', ids)])
- return domain
+ return expression.AND(domains)
@http.route([
'''/shop''',
@@ -1114,3 +1120,75 @@ class WebsiteSale(http.Controller):
lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US')
return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
+
+ # --------------------------------------------------------------------------
+ # Products Search Bar
+ # --------------------------------------------------------------------------
+
+ @http.route('/shop/products/autocomplete', type='json', auth='public', website=True)
+ def products_autocomplete(self, term, options={}, **kwargs):
+ """
+ Returns list of products according to the term and product options
+
+ Params:
+ term (str): search term written by the user
+ options (dict)
+ - 'limit' (int), default to 5: number of products to consider
+ - 'display_description' (bool), default to True
+ - 'display_price' (bool), default to True
+ - 'order' (str)
+ - 'max_nb_chars' (int): max number of characters for the
+ description if returned
+
+ Returns:
+ dict (or False if no result)
+ - 'products' (list): products (only their needed field values)
+ note: the prices will be strings properly formatted and
+ already containing the currency
+ - 'products_count' (int): the number of products in the database
+ that matched the search query
+ """
+ ProductTemplate = request.env['product.template']
+
+ display_description = options.get('display_description', True)
+ display_price = options.get('display_price', True)
+ order = self._get_search_order(options)
+ max_nb_chars = options.get('max_nb_chars', 999)
+
+ category = options.get('category')
+ attrib_values = options.get('attrib_values')
+
+ domain = self._get_search_domain(term, category, attrib_values, display_description)
+ products = ProductTemplate.search(
+ domain,
+ limit=min(20, options.get('limit', 5)),
+ order=order
+ )
+
+ fields = ['id', 'name', 'website_url']
+ if display_description:
+ fields.append('description_sale')
+
+ res = {
+ 'products': products.read(fields),
+ 'products_count': ProductTemplate.search_count(domain),
+ }
+
+ if display_description:
+ for res_product in res['products']:
+ desc = res_product['description_sale']
+ if desc and len(desc) > max_nb_chars:
+ res_product['description_sale'] = "%s..." % desc[:(max_nb_chars - 3)]
+
+ if display_price:
+ FieldMonetary = request.env['ir.qweb.field.monetary']
+ monetary_options = {
+ 'display_currency': request.website.get_current_pricelist().currency_id,
+ }
+ for res_product, product in zip(res['products'], products):
+ combination_info = product._get_combination_info(only_template=True)
+ res_product.update(combination_info)
+ res_product['list_price'] = FieldMonetary.value_to_html(res_product['list_price'], monetary_options)
+ res_product['price'] = FieldMonetary.value_to_html(res_product['price'], monetary_options)
+
+ return res
diff --git a/addons/website_sale/static/src/js/website_sale.editor.js b/addons/website_sale/static/src/js/website_sale.editor.js
index 8c6cd07d3ea..943b695daa4 100644
--- a/addons/website_sale/static/src/js/website_sale.editor.js
+++ b/addons/website_sale/static/src/js/website_sale.editor.js
@@ -52,15 +52,31 @@ WebsiteNewMenu.include({
odoo.define('website_sale.editor', function (require) {
'use strict';
-require('web.dom_ready');
+var core = require('web.core');
+var Dialog = require('web.Dialog');
var options = require('web_editor.snippets.options');
+var publicWidget = require('web.public.widget');
+
+var _t = core._t;
+var qweb = core.qweb;
+
+publicWidget.registry.websiteSaleCurrency = publicWidget.Widget.extend({
+ selector: '.oe_website_sale',
+ disabledInEditableMode: false,
+ edit_events: {
+ 'click .oe_currency_value:o_editable': '_onCurrencyValueClick',
+ },
-if (!$('.js_sale').length) {
- return Promise.reject("DOM doesn't contain '.js_sale'");
-}
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
-$('.oe_website_sale').on('click', '.oe_currency_value:o_editable', function (ev) {
- $(ev.currentTarget).selectContent();
+ /**
+ * @private
+ */
+ _onCurrencyValueClick: function (ev) {
+ $(ev.currentTarget).selectContent();
+ },
});
options.registry.website_sale = options.Class.extend({
@@ -160,4 +176,64 @@ options.registry.website_sale = options.Class.extend({
}).then(this.reload);
}
});
+
+/**
+ * Handles the edition of products search bar snippet.
+ */
+options.registry.ProductsSearchBar = options.Class.extend({
+ xmlDependencies: ['/website_sale/static/src/xml/website_sale.editor.xml'],
+
+ /**
+ * @override
+ */
+ start: function () {
+ this.$searchProductsInput = this.$('.search-query');
+ this.$searchOrderField = this.$('.o_wsale_search_order_by');
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * @see this.selectClass for parameters
+ */
+ openSearchbarSettings: function (previewMode, value, $opt) {
+ var self = this;
+ new Dialog(this, {
+ title: _t("Products Search Bar"),
+ $content: $(qweb.render('website_sale.dialog.productsSearchBar', {
+ currentOrderBy: this.$searchOrderField.val(),
+ currentLimit: parseInt(this.$searchProductsInput.attr('data-limit')),
+ currentDisplayDescription: this.$searchProductsInput.attr('data-display-description') === 'true',
+ currentDisplayPrice: this.$searchProductsInput.attr('data-display-price') === 'true',
+ currentDisplayImage: this.$searchProductsInput.attr('data-display-image') === 'true',
+ })),
+ buttons: [
+ {
+ text: _t("Save"),
+ classes: 'btn-primary',
+ click: function () {
+ self.$searchOrderField.attr({
+ 'value': this.$('#order_by').val(),
+ });
+ self.$searchProductsInput.attr({
+ 'data-limit': this.$('#use_autocomplete').is(':checked') ? this.$('#limit').val() : 0,
+ 'data-display-description': this.$('#display_description').is(':checked'),
+ 'data-display-price': this.$('#display_price').is(':checked'),
+ 'data-display-image': this.$('#display_image').is(':checked'),
+ });
+ self.$target.trigger('content_changed');
+ this.close();
+ },
+ },
+ {
+ text: _t("Discard"),
+ close: true,
+ },
+ ],
+ }).open();
+ },
+});
});
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 5ec92758b62..317dc513d5c 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -120,10 +120,13 @@ odoo.define('website_sale.website_sale', function (require) {
var core = require('web.core');
var config = require('web.config');
+var concurrency = require('web.concurrency');
var publicWidget = require('web.public.widget');
var VariantMixin = require('sale.VariantMixin');
require("website.content.zoomodoo");
+var qweb = core.qweb;
+
publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
selector: '.oe_website_sale',
events: _.extend({}, VariantMixin.events || {}, {
@@ -139,7 +142,7 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, {
'touchend form.js_add_cart_json label': '_onMouseupAddCartLabel',
'change .css_attribute_color input': '_onChangeColorAttribute',
'click .show_coupon': '_onClickShowCoupon',
- 'submit .o_website_sale_search': '_onSubmitSaleSearch',
+ 'submit .o_wsale_products_searchbar_form': '_onSubmitSaleSearch',
'change select[name="country_id"]': '_onChangeCountry',
'change #shipping_use_same': '_onChangeShippingUseSame',
'click .toggle_summary': '_onToggleSummary',
@@ -733,4 +736,132 @@ publicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({
$(ev.currentTarget).closest('tr').find('.js_quantity').val(0).trigger('change');
},
});
+
+/**
+ * @todo maybe the custom autocomplete logic could be extract to be reusable
+ */
+publicWidget.registry.productsSearchBar = publicWidget.Widget.extend({
+ selector: '.o_wsale_products_searchbar_form',
+ xmlDependencies: ['/website_sale/static/src/xml/website_sale_utils.xml'],
+ events: {
+ 'input .search-query': '_onInput',
+ 'focusout': '_onFocusOut',
+ 'keydown .search-query': '_onKeydown',
+ },
+ autocompleteMinWidth: 300,
+
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+
+ this._dp = new concurrency.DropPrevious();
+
+ this._onInput = _.debounce(this._onInput, 400);
+ this._onFocusOut = _.debounce(this._onFocusOut, 100);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this.$input = this.$('.search-query');
+
+ this.order = this.$('.o_wsale_search_order_by').val();
+ this.limit = parseInt(this.$input.data('limit'));
+ this.displayDescription = !!this.$input.data('displayDescription');
+ this.displayPrice = !!this.$input.data('displayPrice');
+ this.displayImage = !!this.$input.data('displayImage');
+
+ if (this.limit) {
+ this.$input.attr('autocomplete', 'off');
+ }
+
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _fetch: function () {
+ return this._rpc({
+ route: '/shop/products/autocomplete',
+ params: {
+ 'term': this.$input.val(),
+ 'options': {
+ 'order': this.order,
+ 'limit': this.limit,
+ 'display_description': this.displayDescription,
+ 'display_price': this.displayPrice,
+ 'max_nb_chars': Math.round(Math.max(this.autocompleteMinWidth, parseInt(this.$el.width())) * 0.22),
+ },
+ },
+ });
+ },
+ /**
+ * @private
+ */
+ _render: function (res) {
+ var $prevMenu = this.$menu;
+ this.$el.toggleClass('dropdown show', !!res);
+ if (res) {
+ var products = res['products'];
+ this.$menu = $(qweb.render('website_sale.productsSearchBar.autocomplete', {
+ products: products,
+ hasMoreProducts: products.length < res['products_count'],
+ currency: res['currency'],
+ widget: this,
+ }));
+ this.$menu.css('min-width', this.autocompleteMinWidth);
+ this.$el.append(this.$menu);
+ }
+ if ($prevMenu) {
+ $prevMenu.remove();
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onInput: function () {
+ if (!this.limit) {
+ return;
+ }
+ this._dp.add(this._fetch()).then(this._render.bind(this));
+ },
+ /**
+ * @private
+ */
+ _onFocusOut: function () {
+ if (!this.$el.has(document.activeElement).length) {
+ this._render();
+ }
+ },
+ /**
+ * @private
+ */
+ _onKeydown: function (ev) {
+ switch (ev.which) {
+ case $.ui.keyCode.ESCAPE:
+ this._render();
+ break;
+ case $.ui.keyCode.UP:
+ ev.preventDefault();
+ this.$menu.children().last().focus();
+ break;
+ case $.ui.keyCode.DOWN:
+ ev.preventDefault();
+ this.$menu.children().first().focus();
+ break;
+ }
+ },
+});
});
diff --git a/addons/website_sale/static/src/xml/website_sale.editor.xml b/addons/website_sale/static/src/xml/website_sale.editor.xml
new file mode 100644
index 00000000000..4cc12eafb1a
--- /dev/null
+++ b/addons/website_sale/static/src/xml/website_sale.editor.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<!-- Products Search Bar settings dialog -->
+<div t-name="website_sale.dialog.productsSearchBar">
+ <div class="form-group">
+ <label for="order_by">Search filter to use <b>by default</b></label>
+ <select class="custom-select" id="order_by">
+ <option value="" t-att-selected="!currentOrderBy ? 'selected' : undefined">Default order</option>
+ <option value="name asc" t-att-selected="currentOrderBy == 'name asc' ? 'selected' : undefined">Order by name (A-Z)</option>
+ <option value="name desc" t-att-selected="currentOrderBy == 'name desc' ? 'selected' : undefined">Order by name (Z-A)</option>
+ <option value="list_price asc" t-att-selected="currentOrderBy == 'list_price asc' ? 'selected' : undefined">Order by price: low to high</option>
+ <option value="list_price desc" t-att-selected="currentOrderBy == 'list_price desc' ? 'selected' : undefined">Order by price: high to low</option>
+ </select>
+ </div>
+ <div class="form-group">
+ <div class="custom-control custom-switch" data-toggle="collapse" data-target="#use_autocomplete_options">
+ <input type="checkbox" class="custom-control-input" id="use_autocomplete" t-att-checked="currentLimit > 0 ? 'checked' : undefined"/>
+ <label class="custom-control-label" for="use_autocomplete">Add product suggestions</label>
+ </div>
+ </div>
+ <div id="use_autocomplete_options" t-attf-class="collapse #{currentLimit > 0 ? 'show' : ''}">
+ <div class="form-group">
+ <label for="limit">Maximum number of products to display</label>
+ <select class="custom-select" id="limit">
+ <option t-foreach="20" t-as="limit"
+ t-esc="limit + 1" t-att-value="limit + 1"
+ t-att-selected="currentLimit == (limit + 1) ? 'selected' : undefined"/>
+ </select>
+ </div>
+ <div class="form-group">
+ <div class="custom-control custom-switch">
+ <input type="checkbox" class="custom-control-input" id="display_description" t-att-checked="currentDisplayDescription ? 'checked' : undefined"/>
+ <label class="custom-control-label" for="display_description">Display product <b>description</b> and allow to search in</label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="custom-control custom-switch">
+ <input type="checkbox" class="custom-control-input" id="display_price" t-att-checked="currentDisplayPrice ? 'checked' : undefined"/>
+ <label class="custom-control-label" for="display_price">Display product <b>price</b></label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="custom-control custom-switch">
+ <input type="checkbox" class="custom-control-input" id="display_image" t-att-checked="currentDisplayImage ? 'checked' : undefined"/>
+ <label class="custom-control-label" for="display_image">Display product <b>image</b></label>
+ </div>
+ </div>
+ </div>
+</div>
+
+</templates>
diff --git a/addons/website_sale/static/src/xml/website_sale_utils.xml b/addons/website_sale/static/src/xml/website_sale_utils.xml
new file mode 100644
index 00000000000..0c470a0d58d
--- /dev/null
+++ b/addons/website_sale/static/src/xml/website_sale_utils.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<!-- Products Search Bar autocomplete item -->
+<div t-name="website_sale.productsSearchBar.autocomplete"
+ class="dropdown-menu show w-100">
+ <t t-if="!products.length">
+ <span class="dropdown-item-text text-muted">No results found. Please try another search.</span>
+ </t>
+ <a t-foreach="products" t-as="product"
+ t-att-href="product['website_url']" class="dropdown-item p-2 text-wrap">
+ <div class="media align-items-center o_search_product_item">
+ <t t-if="widget.displayImage">
+ <img t-attf-src="/web/image/product.template/#{product['id']}/image_small"
+ class="flex-shrink-0"/>
+ </t>
+ <div class="media-body px-3">
+ <t t-set="description" t-value="widget.displayDescription and product['description_sale']"/>
+ <h6 t-attf-class="font-weight-bold #{description ? '' : 'mb-0'}" t-esc="product['name']"/>
+ <p t-if="description" class="mb-0" t-esc="description"/>
+ </div>
+ <div t-if="widget.displayPrice" class="flex-shrink-0">
+ <t t-if="product['has_discounted_price']">
+ <span class="text-danger text-nowrap" style="text-decoration: line-through;">
+ <t t-raw="product['list_price']"/>
+ </span>
+ <br/>
+ </t>
+ <b class="text-nowrap">
+ <t t-raw="product['price']"/>
+ </b>
+ </div>
+ </div>
+ </a>
+ <t t-if="hasMoreProducts">
+ <button type="submit" class="dropdown-item text-center text-primary">All results</button>
+ </t>
+</div>
+
+</templates>
diff --git a/addons/website_sale/views/snippets.xml b/addons/website_sale/views/snippets.xml
index d0e8f3cdc50..0168a0a3652 100644
--- a/addons/website_sale/views/snippets.xml
+++ b/addons/website_sale/views/snippets.xml
@@ -1,8 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
-<template id="website_sale" inherit_id="website.snippets" name="Snippet Ecommerce">
- <xpath expr="//div[@id='snippet_options']" position="inside">
+<template id="s_products_searchbar_input" name="Products Search Input">
+ <t t-call="website_sale.website_sale_products_search_box">
+ <t t-set="_classes" t-value="'s_wsale_products_searchbar_input mb-3'"/>
+ </t>
+</template>
+<template id="s_products_searchbar" name="Products Search">
+ <section class="s_wsale_products_searchbar bg-200 pt24 pb24">
+ <div class="container">
+ <div class="row">
+ <div class="col-lg-8 offset-lg-2">
+ <div class="card">
+ <div class="card-body">
+ <h2>Search for a product</h2>
+ <p>We have amazing products in our shop, check them now !</p>
+ <t t-call="website_sale.s_products_searchbar_input"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+</template>
+
+<template id="snippets" inherit_id="website.snippets" name="e-commerce snippets">
+ <xpath expr="//div[@id='snippet_feature']//t[@t-snippet][last()]" position="after">
+ <t t-snippet="website_sale.s_products_searchbar" t-thumbnail="/website/static/src/img/snippets_thumbs/s_products_searchbar.png"/>
+ </xpath>
+ <xpath expr="//div[@id='snippet_content']//t[@t-snippet][last()]" position="after">
+ <t t-snippet="website_sale.s_products_searchbar_input" t-thumbnail="/website/static/src/img/snippets_thumbs/s_products_searchbar.png"/>
+ </xpath>
+</template>
+
+<template id="remove_external_snippets" inherit_id="website.external_snippets">
+ <xpath expr="//t[@t-install='website_sale'][@id='s_products_searchbar']" position="replace"/>
+ <xpath expr="//t[@t-install='website_sale'][@id='s_products_searchbar_input']" position="replace"/>
+</template>
+
+<template id="snippet_options" inherit_id="website.snippet_options" name="e-commerce snippet options">
+ <xpath expr="." position="inside">
<div data-js='website_sale'
data-selector=".oe_product.oe_grid"
data-no-check="true">
@@ -31,7 +68,16 @@
</div>
</div>
</div>
- </xpath>
+ <div data-js="ProductsSearchBar"
+ data-selector=".s_wsale_products_searchbar_input">
+ <a tabindex="-1" href="#" class="dropdown-item" data-no-preview="true" data-open-searchbar-settings=""><i class="fa fa-search"></i>Products Search Bar</a>
+ </div>
+ </xpath>
+ <xpath expr="//div[@data-js='content']" position="attributes">
+ <attribute name="data-selector" add=".s_wsale_products_searchbar_input" separator=","/>
+ <attribute name="data-drop-near" add=".s_wsale_products_searchbar_input" separator=","/>
+ </xpath>
</template>
+
</odoo>
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index a00b6a0eb85..aca62285f1b 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -65,15 +65,32 @@
</xpath>
</template>
+ <!-- Products Search Bar input-group template -->
+ <template id="website_sale_products_search_box" inherit_id="website.website_search_box" primary="True">
+ <xpath expr="//input[@name='search']" position="attributes">
+ <attribute name="data-limit">5</attribute>
+ <attribute name="data-display-description">true</attribute>
+ <attribute name="data-display-price">true</attribute>
+ <attribute name="data-display-image">true</attribute>
+ </xpath>
+ <xpath expr="//div[hasclass('input-group')]" position="replace">
+ <form t-attf-class="o_wsale_products_searchbar_form #{_classes}" t-att-action="action if action else '/shop'" method="get">
+ <t>$0</t>
+ <input name="order" type="hidden" class="o_wsale_search_order_by" value=""/>
+ <t t-raw="0"/>
+ </form>
+ </xpath>
+ </template>
+
<template id="search" name="Search Box">
- <form t-att-action="keep('/shop'+ ('/category/'+slug(category)) if category else None, search=0)" method="get" t-att-class="_classes">
+ <t t-call="website_sale.website_sale_products_search_box">
+ <t t-set="action" t-value="keep('/shop'+ ('/category/'+slug(category)) if category else None, search=0)"/>
<t t-if="attrib_values">
<t t-foreach="attrib_values" t-as="a">
<input type="hidden" name="attrib" t-att-value="'%s-%s' % (a[0], a[1])" />
</t>
</t>
- <t t-call="website.website_search_box" />
- </form>
+ </t>
</template>
<template id="search count" inherit_id="website.website_search_box" active="False" customize_show="True" name="Show # found">
@@ -189,9 +206,7 @@
<div class="oe_structure" id="oe_structure_website_sale_products_1"/>
<div class="container py-2 oe_website_sale">
<div class="products_pager form-inline justify-content-center">
- <t t-call="website_sale.search">
- <t t-set="_classes">o_website_sale_search</t>
- </t>
+ <t t-call="website_sale.search"/>
<t t-call="website_sale.pricelist_list">
<t t-set="_classes">ml-2</t>
</t>
@@ -1694,5 +1709,4 @@
</div>
</div>
</template>
-
</odoo>