diff options
| -rw-r--r-- | addons/website/static/src/img/snippets_thumbs/s_products_searchbar.png | bin | 0 -> 295 bytes | |||
| -rw-r--r-- | addons/website/views/snippets.xml | 2 | ||||
| -rw-r--r-- | addons/website_sale/controllers/main.py | 98 | ||||
| -rw-r--r-- | addons/website_sale/static/src/js/website_sale.editor.js | 88 | ||||
| -rw-r--r-- | addons/website_sale/static/src/js/website_sale.js | 133 | ||||
| -rw-r--r-- | addons/website_sale/static/src/xml/website_sale.editor.xml | 52 | ||||
| -rw-r--r-- | addons/website_sale/static/src/xml/website_sale_utils.xml | 40 | ||||
| -rw-r--r-- | addons/website_sale/views/snippets.xml | 52 | ||||
| -rw-r--r-- | addons/website_sale/views/templates.xml | 28 |
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 Binary files differnew file mode 100644 index 00000000000..4f24bb529cf --- /dev/null +++ b/addons/website/static/src/img/snippets_thumbs/s_products_searchbar.png 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> |
