diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
index a795a11b19f..8552d567f33 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
@@ -7,7 +7,7 @@ import frappe
from frappe.utils import cint, comma_and
from frappe import _, msgprint
from frappe.model.document import Document
-from frappe.utils import get_datetime, get_datetime_str, now_datetime, unique
+from frappe.utils import unique
from erpnext.e_commerce.website_item_indexing import create_website_items_index, ALLOWED_INDEXABLE_FIELDS_SET, is_search_module_loaded
class ShoppingCartSetupError(frappe.ValidationError): pass
@@ -58,13 +58,10 @@ class ECommerceSettings(Document):
def validate_search_index_fields(self):
if not self.search_index_fields:
- return
-
- # Clean up
- # Remove whitespaces
+ return
+
fields = self.search_index_fields.replace(' ', '')
- # Remove extra ',' and remove duplicates
- fields = unique(fields.strip(',').split(','))
+ fields = unique(fields.strip(',').split(',')) # Remove extra ',' and remove duplicates
# All fields should be indexable
if not (set(fields).issubset(ALLOWED_INDEXABLE_FIELDS_SET)):
@@ -138,7 +135,7 @@ class ECommerceSettings(Document):
old_doc = self.get_doc_before_save()
old_fields = old_doc.search_index_fields
new_fields = self.search_index_fields
-
+
# if search index fields get changed
if not (new_fields == old_fields):
create_website_items_index()
diff --git a/erpnext/e_commerce/product_grid.js b/erpnext/e_commerce/product_grid.js
index a716efa90a9..bd7a568ac0d 100644
--- a/erpnext/e_commerce/product_grid.js
+++ b/erpnext/e_commerce/product_grid.js
@@ -144,6 +144,8 @@ erpnext.ProductGrid = class {
${ __('Add to Cart') }
`;
+ } else {
+ return ``;
}
}
};
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_list.js b/erpnext/e_commerce/product_list.js
index 3aa6e8c4336..d5ba6f5b299 100644
--- a/erpnext/e_commerce/product_list.js
+++ b/erpnext/e_commerce/product_list.js
@@ -153,6 +153,8 @@ erpnext.ProductList = class {
${ __('Add to Cart') }
`;
+ } else {
+ return ``;
}
}
diff --git a/erpnext/e_commerce/product_search.js b/erpnext/e_commerce/product_search.js
new file mode 100644
index 00000000000..4f8b028fc1b
--- /dev/null
+++ b/erpnext/e_commerce/product_search.js
@@ -0,0 +1,226 @@
+erpnext.ProductSearch = class {
+ constructor() {
+ this.MAX_RECENT_SEARCHES = 4;
+ this.searchBox = $("#search-box");
+
+ this.setupSearchDropDown();
+ this.bindSearchAction();
+ }
+
+ setupSearchDropDown() {
+ this.search_area = $("#dropdownMenuSearch");
+ this.setupSearchResultContainer();
+ this.setupProductsContainer();
+ this.setupCategoryRecentsContainer();
+ this.populateRecentSearches();
+ }
+
+ bindSearchAction() {
+ let me = this;
+
+ this.searchBox.on("focus", (e) => {
+ this.search_dropdown.removeClass("hidden");
+ });
+
+ this.searchBox.on("focusout", (e) => {
+ this.search_dropdown.addClass("hidden");
+ });
+
+ this.searchBox.on("input", (e) => {
+ let query = e.target.value;
+
+ if (query.length < 3 || !query.length) return;
+
+ // Populate recent search chips
+ me.setRecentSearches(query);
+
+ // Fetch and populate product results
+ frappe.call({
+ method: "erpnext.templates.pages.product_search.search",
+ args: {
+ query: query
+ },
+ callback: (data) => {
+ me.populateResults(data);
+ }
+ });
+
+ // Populate categories
+ if (me.category_container) {
+ frappe.call({
+ method: "erpnext.templates.pages.product_search.get_category_suggestions",
+ args: {
+ query: query
+ },
+ callback: (data) => {
+ me.populateCategoriesList(data)
+ }
+ });
+ }
+
+ this.search_dropdown.removeClass("hidden");
+ });
+ }
+
+ setupSearchResultContainer() {
+ this.search_dropdown = this.search_area.append(`
+
- {% if slideshow %}
+ {% if slideshow %}
{{ web_block(
"Hero Slider",
values=slideshow,
@@ -28,8 +37,8 @@
add_bottom_padding=0,
) }}
{% endif %}
-
{{ title }}
- {% if description %}
+
+ {% if description %}
{{ description or ""}}
{% endif %}
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 28fe882dca1..c766dfd0992 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -163,7 +163,7 @@ $.extend(shopping_cart, {
item_code: item_code,
qty: 0
});
- })
+ });
},
render_tax_row: function($cart_taxes, doc, shipping_rules) {
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index df8ba075946..a8ea069ef55 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -6,16 +6,14 @@ from frappe.utils import cstr, nowdate, cint
from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
-# For SEARCH -------
from redisearch import AutoCompleter, Client, Query
from erpnext.e_commerce.website_item_indexing import (
is_search_module_loaded,
- WEBSITE_ITEM_INDEX,
+ WEBSITE_ITEM_INDEX,
WEBSITE_ITEM_NAME_AUTOCOMPLETE,
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
make_key
)
-# -----------------
no_cache = 1
@@ -35,30 +33,29 @@ def get_product_list(search=None, start=0, limit=12):
def get_product_data(search=None, start=0, limit=12):
# limit = 12 because we show 12 items in the grid view
# base query
- query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
- I.description, I.web_long_description as website_description, I.is_stock_item,
- case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock, I.website_warehouse,
- I.has_batch_no
- from `tabItem` I
- left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
- where (I.show_in_website = 1)
- and I.disabled = 0
- and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)"""
+ query = """
+ Select
+ web_item_name, item_name, item_code, brand, route,
+ website_image, thumbnail, item_group,
+ description, web_long_description as website_description,
+ website_warehouse, ranking
+ from `tabWebsite Item`
+ where published = 1
+ """
# search term condition
if search:
- query += """ and (I.web_long_description like %(search)s
- or I.description like %(search)s
- or I.item_name like %(search)s
- or I.name like %(search)s)"""
+ query += """ and (item_name like %(search)s
+ or web_item_name like %(search)s
+ or brand like %(search)s
+ or web_long_description like %(search)s)"""
search = "%" + cstr(search) + "%"
# order by
- query += """ order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (cint(start), cint(limit))
+ query += """ order by ranking asc, modified desc limit %s, %s""" % (cint(start), cint(limit))
return frappe.db.sql(query, {
- "search": search,
- "today": nowdate()
+ "search": search
}, as_dict=1)
@frappe.whitelist(allow_guest=True)
@@ -80,8 +77,8 @@ def search(query, limit=10, fuzzy_search=True):
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red)
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red)
suggestions = ac.get_suggestions(
- query,
- num=limit,
+ query,
+ num=limit,
fuzzy= fuzzy_search and len(query) > 4 # Fuzzy on length < 3 can be real slow
)
@@ -111,11 +108,19 @@ def convert_to_dict(redis_search_doc):
@frappe.whitelist(allow_guest=True)
def get_category_suggestions(query):
- search_results = {"from_redisearch": True, "results": []}
+ search_results = {"results": []}
if not is_search_module_loaded():
- # Redisearch module not loaded
- search_results["from_redisearch"] = False
+ # Redisearch module not loaded, query db
+ categories = frappe.db.get_all(
+ "Item Group",
+ filters={
+ "name": ["like", "%{0}%".format(query)],
+ "show_in_website": 1
+ },
+ fields=["name", "route"]
+ )
+ search_results['results'] = categories
return search_results
if not query:
@@ -125,5 +130,5 @@ def get_category_suggestions(query):
suggestions = ac.get_suggestions(query, num=10)
search_results['results'] = [s.string for s in suggestions]
-
+
return search_results
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index bf84fd51697..667e74c5420 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -3,35 +3,19 @@
{% block title %}{{ _('Products') }}{% endblock %}
{% block header %}
-
{{ _('Products') }}
+
+
{{ _('Products') }}
+
+
+
+
+
{% endblock header %}
{% block page_content %}
-
-
-
@@ -40,11 +24,6 @@
- {% if frappe.form_dict.start or frappe.form_dict.field_filters or frappe.form_dict.attribute_filters or frappe.form_dict.search %}
-
-
- {% endif %}
-
{{ _('Filters') }}
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index ef8c2105bea..e38514a32a0 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -7,8 +7,10 @@ $(() => {
let view_type = "List View";
- // Render Product Views and setup Filters
+ // Render Product Views, Filters & Search
frappe.require('/assets/js/e-commerce.min.js', function() {
+ new erpnext.ProductSearch();
+
new erpnext.ProductView({
view_type: view_type,
products_section: $('#product-listing'),
diff --git a/erpnext/www/all-products/search.css b/erpnext/www/all-products/search.css
deleted file mode 100644
index 687532d2eb4..00000000000
--- a/erpnext/www/all-products/search.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.item-thumb {
- height: 50px;
- width: 50px;
- object-fit: cover;
-}
-
-.brand-line {
- color: gray;
-}
\ No newline at end of file
diff --git a/erpnext/www/all-products/search.html b/erpnext/www/all-products/search.html
deleted file mode 100644
index 735822d7659..00000000000
--- a/erpnext/www/all-products/search.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %}{{ _('Search') }}{% endblock %}
-
-{%- block head_include %}
-
-{% endblock -%}
-
-{% block header %}
-
{{ _('Search Products') }}
-{% endblock header %}
-
-{% block page_content %}
-
-
-
-
-
-
-
-
-
- {% set show_categories = frappe.db.get_single_value('E Commerce Settings', 'show_categories_in_search_autocomplete') %}
- {% if show_categories %}
-
- {% endif %}
-
- {% set show_brand_line = frappe.db.get_single_value('E Commerce Settings', 'show_brand_line') %}
- {% if show_brand_line %}
-
- {% endif %}
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/all-products/search.js b/erpnext/www/all-products/search.js
deleted file mode 100644
index e88b576c789..00000000000
--- a/erpnext/www/all-products/search.js
+++ /dev/null
@@ -1,148 +0,0 @@
-let loading = false;
-
-const MAX_RECENT_SEARCHES = 4;
-
-const searchBox = document.getElementById("search-box");
-const searchButton = document.getElementById("search-button");
-const results = document.getElementById("results");
-const categoryList = document.getElementById("category-suggestions");
-const showBrandLine = document.getElementById("show-brand-line");
-const recentSearchArea = document.getElementById("recent-search-chips");
-
-function getRecentSearches() {
- return JSON.parse(localStorage.getItem("recent_searches") || "[]");
-}
-
-function attachEventListenersToChips() {
- const chips = document.getElementsByClassName("recent-chip");
-
- for (let chip of chips) {
- chip.addEventListener("click", () => {
- searchBox.value = chip.innerText;
-
- // Start search with `recent query`
- const event = new Event("input");
- searchBox.dispatchEvent(event);
- searchBox.focus();
- });
- }
-}
-
-function populateRecentSearches() {
- let recents = getRecentSearches();
-
- if (!recents.length) {
- return;
- }
-
- html = "Recent Searches: ";
- for (let query of recents) {
- html += `
${query} `;
- }
-
- recentSearchArea.innerHTML = html;
- attachEventListenersToChips();
-}
-
-function populateResults(data) {
- if (!data.message.from_redisearch) {
- // Data not from redisearch
- }
-
- if (data.message.results.length === 0) {
- results.innerHTML = 'No results';
- return;
- }
-
- html = ""
- search_results = data.message.results
- for (let res of search_results) {
- html += `
-
- ${res.web_item_name} ${showBrandLine && res.brand ? "by " + res.brand : ""}
- `
- }
- results.innerHTML = html;
-}
-
-function populateCategoriesList(data) {
- if (!data.message.from_redisearch) {
- // Data not from redisearch
- categoryList.innerHTML = "Install Redisearch to enable autocompletions.";
- return;
- }
-
- if (data.message.results.length === 0) {
- categoryList.innerHTML = 'No results';
- return;
- }
-
- html = ""
- search_results = data.message.results
- for (let category of search_results) {
- html += `
${category} `
- }
-
- categoryList.innerHTML = html;
-}
-
-function updateLoadingState() {
- if (loading) {
- results.innerHTML = `
loading...
`;
- }
-}
-
-searchBox.addEventListener("input", (e) => {
- loading = true;
- updateLoadingState();
- frappe.call({
- method: "erpnext.templates.pages.product_search.search",
- args: {
- query: e.target.value
- },
- callback: (data) => {
- populateResults(data);
- loading = false;
- }
- });
-
- // If there is a suggestion list node
- if (categoryList) {
- frappe.call({
- method: "erpnext.templates.pages.product_search.get_category_suggestions",
- args: {
- query: e.target.value
- },
- callback: (data) => {
- populateCategoriesList(data);
- }
- });
- }
-});
-
-searchButton.addEventListener("click", (e) => {
- let query = searchBox.value;
- if (!query) {
- return;
- }
-
- let recents = getRecentSearches();
-
- if (recents.length >= MAX_RECENT_SEARCHES) {
- // Remove the `First` query
- recents.splice(0, 1);
- }
-
- if (recents.indexOf(query) >= 0) {
- return;
- }
-
- recents.push(query);
-
- localStorage.setItem("recent_searches", JSON.stringify(recents));
-
- // Refresh recent searches
- populateRecentSearches();
-});
-
-populateRecentSearches();
\ No newline at end of file
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
index 700d5b22b8c..f94b33ea850 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -71,8 +71,12 @@ def get_category_records(categories):
fields += ["image"]
categorical_data[category] = frappe.db.sql(f"""
- Select {",".join(fields)}
- from `tab{doctype}`""", as_dict=1)
+ Select
+ {",".join(fields)}
+ from
+ `tab{doctype}`""",
+ as_dict=1
+ )
return categorical_data