From 6a802f354b98edac29cae07fa94fd139195d238a Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 17 May 2021 20:44:41 +0530 Subject: [PATCH] feat: Product View toggling - Added fully functional list and grid view toggling - Added ProductGrid and ProductList controllers - Moved html snippets, rendered via JS now - Item Group page also rendered via common controller - Paging section rendered via JS - Minor style changes --- erpnext/e_commerce/product_grid.js | 148 +++++++++ erpnext/e_commerce/product_list.js | 158 ++++++++++ erpnext/e_commerce/product_view.js | 285 +++++++++++++----- erpnext/public/build.json | 4 +- erpnext/public/scss/shopping_cart.scss | 10 +- .../setup/doctype/item_group/item_group.py | 1 - erpnext/templates/generators/item_group.html | 27 +- erpnext/templates/includes/macros.html | 50 +-- .../templates/includes/products_as_list.html | 2 +- erpnext/www/all-products/index.html | 17 +- erpnext/www/all-products/index.js | 43 +-- erpnext/www/all-products/index.py | 40 +-- erpnext/www/all-products/item_row.html | 4 - 13 files changed, 556 insertions(+), 233 deletions(-) create mode 100644 erpnext/e_commerce/product_grid.js create mode 100644 erpnext/e_commerce/product_list.js delete mode 100644 erpnext/www/all-products/item_row.html diff --git a/erpnext/e_commerce/product_grid.js b/erpnext/e_commerce/product_grid.js new file mode 100644 index 00000000000..638f01701b7 --- /dev/null +++ b/erpnext/e_commerce/product_grid.js @@ -0,0 +1,148 @@ +erpnext.ProductGrid = class { + /* Options: + - items: Items + - settings: E Commerce Settings + - products_section: Products Wrapper + - preference: If preference is not grid view, render but hide + */ + constructor(options) { + Object.assign(this, options); + + if (this.preference !== "Grid View") { + this.products_section.addClass("hidden"); + } + + this.make(); + } + + make() { + let me = this; + let html = ``; + + this.items.forEach(item => { + let title = item.web_item_name || item.item_name || item.item_code || ""; + title = title.length > 50 ? title.substr(0, 50) + "..." : title; + + html += `
`; + html += me.get_image_html(item, title); + html += me.get_card_body_html(item, title, me.settings); + html += `
`; + }) + + let $product_wrapper = this.products_section; + $product_wrapper.append(html); + } + + get_image_html(item, title) { + let image = item.website_image || item.image; + + if(image) { + return ` +
+ + ${ title } + +
+ `; + } else { + return ` + +
+ ${ frappe.get_abbr(title) } +
+
+ `; + } + } + + get_card_body_html(item, title, settings) { + let body_html = ` +
+
+ `; + body_html += this.get_title_with_indicator(item, title); + + if (!item.has_variants && settings.enable_wishlist) { + body_html += this.get_wishlist_icon(item); + } + + body_html += `
`; // close div on line 50 + body_html += `
${ item.item_group || '' }
`; + + if (item.formatted_price) { + body_html += this.get_price_html(item); + } + + body_html += this.get_primary_button(item, settings); + body_html += `
`; // close div on line 49 + + return body_html; + } + + get_title_with_indicator(item, title, settings) { + let title_html = ` + +
+ ${ title || '' } + `; + if (item.in_stock) { + title_html += ``; + } + title_html += `
`; + return title_html + } + + get_wishlist_icon(item) { + let icon_class = item.wished ? "wished" : "not-wished"; + return ` +
+ + + +
+ `; + } + + get_price_html(item) { + let price_html = ` +
+ ${ item.formatted_price || '' } + `; + + if (item.formatted_mrp) { + price_html += ` + + ${ item.formatted_mrp } + + + ${ item.discount } OFF + + `; + } + price_html += `
`; + return price_html; + } + + get_primary_button(item, settings) { + if (item.has_variants) { + return ` + +
+ ${ __('Explore') } +
+
+ `; + } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) { + return ` +
+ ${ __('Add to Cart') } +
+ `; + } + } +} \ No newline at end of file diff --git a/erpnext/e_commerce/product_list.js b/erpnext/e_commerce/product_list.js new file mode 100644 index 00000000000..e693f1e24ca --- /dev/null +++ b/erpnext/e_commerce/product_list.js @@ -0,0 +1,158 @@ +erpnext.ProductList = class { + /* Options: + - items: Items + - settings: E Commerce Settings + - products_section: Products Wrapper + - preference: If preference is not list view, render but hide + */ + constructor(options) { + Object.assign(this, options); + + if (this.preference !== "List View") { + this.products_section.addClass("hidden"); + } + + this.make(); + } + + make() { + let me = this; + let html = `

`; + + this.items.forEach(item => { + let title = item.web_item_name || item.item_name || item.item_code || ""; + title = title.length > 200 ? title.substr(0, 200) + "..." : title; + + html += `
`; + html += me.get_image_html(item, title); + html += me.get_row_body_html(item, title, me.settings); + html += `
`; + }) + + let $product_wrapper = this.products_section; + $product_wrapper.append(html); + } + + get_image_html(item, title) { + let image = item.website_image || item.image; + + if(image) { + return ` +
+ + ${ title } + +
+ `; + } else { + return ` + +
+ ${ frappe.get_abbr(title) } +
+
+ `; + } + } + + get_row_body_html(item, title, settings) { + let body_html = `
`; + body_html += this.get_title_html(item, title, settings); + body_html += this.get_item_details(item, settings); + body_html += `
`; + return body_html; + } + + get_title_html(item, title, settings) { + let title_html = `
`; + title_html += ` +
+ + ${ title } + + `; + + if (item.in_stock) { + title_html += ``; + } + title_html += `
`; + + if (settings.enable_wishlist || settings.enabled) { + title_html += `
`; + if (!item.has_variants && settings.enable_wishlist) { + title_html += this.get_wishlist_icon(item); + } + title_html += this.get_primary_button(item, settings); + title_html += `
`; + } + title_html += `
`; + + return title_html; + } + + get_item_details(item, settings) { + let details = ` +

+ Item Code : ${ item.item_code } +

+
+ ${ item.description || '' } +
+
+ ${ item.formatted_price || '' } + `; + + if(item.formatted_mrp) { + details += ` + + ${ item.formatted_mrp } + + + ${ item.discount } OFF + + `; + } + details += `
`; + + return details; + } + + get_wishlist_icon(item) { + let icon_class = item.wished ? "wished" : "not-wished"; + + return ` +
+ + + +
+ `; + } + + get_primary_button(item, settings) { + if (item.has_variants) { + return ` + +
+ ${ __('Explore') } +
+
+ `; + } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) { + return ` +
+ ${ __('Add to Cart') } +
+ `; + } + } + +} diff --git a/erpnext/e_commerce/product_view.js b/erpnext/e_commerce/product_view.js index 660db660215..923bdb12720 100644 --- a/erpnext/e_commerce/product_view.js +++ b/erpnext/e_commerce/product_view.js @@ -1,80 +1,56 @@ erpnext.ProductView = class { - /* Options: View Type */ + /* Options: + - View Type + - Products Section Wrapper, + - Item Group: If its an Item Group page + */ constructor(options) { Object.assign(this, options); - this.render_view_toggler(); + this.preference = "List View"; + + this.products_section.empty(); + this.prepare_view_toggler(); this.get_item_filter_data(); - this.render_list_view(); - this.render_grid_view(); } - render_view_toggler() { - ["btn-list-view", "btn-grid-view"].forEach(view => { - let icon = view === "btn-list-view" ? "list" : "image-view"; - this.products_section.append(` -
- -
`); - }); - - $("#list").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-grid-view").removeClass('btn-primary'); - }) - - $("#image-view").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-list-view").removeClass('btn-primary'); - }); - - this.products_area = this.products_section.append(` -

-
- `); + prepare_view_toggler() { + if(!$("#list").length || !$("#image-view").length) { + this.render_view_toggler(); + this.bind_view_toggler_actions(); + this.set_view_state(); + } } get_item_filter_data() { - // Get Items and Discount Filters to render + // Get and render all Items related components let me = this; - const filters = frappe.utils.get_query_params(); - let {field_filters, attribute_filters} = filters; - - field_filters = field_filters ? JSON.parse(field_filters) : {}; - attribute_filters = attribute_filters ? JSON.parse(attribute_filters) : {}; + let args = this.get_query_filters(); + $('#list').prop('disabled', true); + $('#image-view').prop('disabled', true); frappe.call({ method: 'erpnext.www.all-products.index.get_product_filter_data', - args: { - field_filters: field_filters, - attribute_filters: attribute_filters, - item_group: me.item_group - }, + args: args, callback: function(result) { - if (!result.exc) { + if (!result.exc && result) { me.render_filters(result.message[1]); - // Append pre-rendered products - // TODO: get products as is and style via js - me.products = result.message; - $("#products-area").append(result.message[0]); + if (me.item_group) { + me.render_item_sub_categories(result.message[3]); + } + // Render views + me.render_list_view(result.message[0], result.message[2]); + me.render_grid_view(result.message[0], result.message[2]); + me.products = result.message[0]; + // Bottom paging + me.add_paging_section(result.message[2]); } else { - $("#products-area").append(` -
- ${__('No products found')} -
`); - + me.render_no_products_section(); } + + $('#list').prop('disabled', false); + $('#image-view').prop('disabled', false); } }); } @@ -83,11 +59,159 @@ erpnext.ProductView = class { this.get_discount_filter_html(filter_data.discount_filters); } + render_grid_view(items, settings) { + // loop over data and add grid html to it + let me = this; + this.prepare_product_area_wrapper("grid"); + + frappe.require('assets/js/e-commerce.min.js', function() { + new erpnext.ProductGrid({ + items: items, + products_section: $("#products-grid-area"), + settings: settings, + preference: me.preference + }); + }); + } + + render_list_view(items, settings) { + let me = this; + this.prepare_product_area_wrapper("list"); + + frappe.require('assets/js/e-commerce.min.js', function() { + new erpnext.ProductList({ + items: items, + products_section: $("#products-list-area"), + settings: settings, + preference: me.preference + }); + }); + } + + prepare_product_area_wrapper(view) { + let left_margin = view == "list" ? "ml-2" : ""; + let top_margin = view == "list" ? "mt-8" : "mt-4"; + return this.products_section.append(` +
+
+ `); + } + + get_query_filters() { + const filters = frappe.utils.get_query_params(); + let {field_filters, attribute_filters} = filters; + + field_filters = field_filters ? JSON.parse(field_filters) : {}; + attribute_filters = attribute_filters ? JSON.parse(attribute_filters) : {}; + + return { + field_filters: field_filters, + attribute_filters: attribute_filters, + item_group: this.item_group, + start: filters.start || null + } + } + + add_paging_section(settings) { + $(".product-paging-area").remove(); + + if(this.products) { + let paging_html = ` +
+
+
+
+ `; + let query_params = frappe.utils.get_query_params(); + let start = query_params.start ? cint(JSON.parse(query_params.start)) : 0; + let page_length = settings.products_per_page || 0; + + if(start > 0) { + paging_html += ` + `; + } + if(this.products.length > page_length || this.products.length == page_length) { + paging_html += ` + + `; + } + paging_html += `
`; + + $(".page_content").append(paging_html); + this.bind_paging_action(); + } + } + + render_view_toggler() { + ["btn-list-view", "btn-grid-view"].forEach(view => { + let icon = view === "btn-list-view" ? "list" : "image-view"; + this.products_section.append(` +
+ +
`); + }); + } + + bind_view_toggler_actions() { + $("#list").click(function() { + let $btn = $(this); + $btn.removeClass('btn-primary'); + $btn.addClass('btn-primary'); + $(".btn-grid-view").removeClass('btn-primary'); + + $("#products-grid-area").addClass("hidden"); + $("#products-list-area").removeClass("hidden"); + }) + + $("#image-view").click(function() { + let $btn = $(this); + $btn.removeClass('btn-primary'); + $btn.addClass('btn-primary'); + $(".btn-list-view").removeClass('btn-primary'); + + $("#products-list-area").addClass("hidden"); + $("#products-grid-area").removeClass("hidden"); + }); + } + + set_view_state() { + if (this.preference === "List View") { + $("#list").addClass('btn-primary'); + $("#image-view").removeClass('btn-primary'); + } else { + $("#image-view").addClass('btn-primary'); + $("#list").removeClass('btn-primary'); + } + } + + bind_paging_action() { + $('.btn-prev, .btn-next').click((e) => { + const $btn = $(e.target); + $btn.prop('disabled', true); + const start = $btn.data('start'); + let query_params = frappe.utils.get_query_params(); + query_params.start = start; + let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params); + window.location.href = path; + }); + } + get_discount_filter_html(filter_data) { + $("#discount-filters").remove(); if (filter_data) { $("#product-filters").append(`
-
${__("Discounts")}
+
${ __("Discounts") }
`); @@ -95,13 +219,13 @@ erpnext.ProductView = class { filter_data.forEach(filter => { html += `
-
@@ -113,12 +237,35 @@ erpnext.ProductView = class { } } - render_list_view() { - // loop over data and add list html to it + render_no_products_section() { + $("#products-area").append(` +
+ ${ __('No products found') } +
+ `); } - render_grid_view() { - // loop over data and add grid html to it - } + render_item_sub_categories(categories) { + if(categories) { + let sub_group_html = ` +
+
${ __('Sub Categories') }
+
+
+ `; + categories.forEach(category => { + sub_group_html += ` + +
+ ${ category.name } +
+
+ `; + }) + sub_group_html += `
`; + + $("#product-listing").prepend(sub_group_html); + } + } } \ No newline at end of file diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 0181d463097..69db1857dcd 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -69,6 +69,8 @@ "public/js/bank_reconciliation_tool/dialog_manager.js" ], "js/e-commerce.min.js": [ - "e_commerce/product_view.js" + "e_commerce/product_view.js", + "e_commerce/product_grid.js", + "e_commerce/product_list.js" ] } diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 3482ac37871..95e8636b8ef 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -229,11 +229,6 @@ body.product-page { color: var(--text-color); } - .product-code { - color: var(--text-muted); - font-size: 13px; - } - .product-description { font-size: 13px; color: var(--gray-800); @@ -302,6 +297,11 @@ body.product-page { } } +.product-code { + color: var(--text-muted); + font-size: 13px; +} + .item-configurator-dialog { .modal-header { padding: var(--padding-md) var(--padding-xl); diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 1788f92e68c..fd22d94b67d 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -81,7 +81,6 @@ class ItemGroup(NestedSet, WebsiteGenerator): "title": self.name }) - context.sub_categories = get_child_groups(self.name) if self.slideshow: values = { 'show_indicators': 1, diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 9f3cf85d46c..72c5d502add 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -35,24 +35,9 @@
- {% if sub_categories %} -
-
{{ _('Sub Categories') }}
-
-
- {% for row in sub_categories%} - -
- {{ row.name }} -
-
- {% endfor %} -
- {% endif %} - -
+
@@ -87,15 +72,5 @@
-
-
- {% if frappe.form_dict.start|int > 0 %} - - {% endif %} - {% if items|length >= page_length %} - - {% endif %} -
-
{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index aa6e9f35e96..e8d527ee3cd 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -59,7 +59,7 @@ {% endmacro %} -{%- macro item_card(item, settings=None, is_featured=False, is_full_width=False, align="Left") -%} +{%- macro item_card(item, is_featured=False, is_full_width=False, align="Left") -%} {%- set align_items_class = resolve_class({ 'align-items-end': align == 'Right', 'align-items-center': align == 'Center', @@ -80,12 +80,12 @@ {{ title }}
- {{ item_card_body(title, settings, description, item, is_featured, align) }} + {{ item_card_body(title, description, item, is_featured, align) }}
{% else %}
- {{ item_card_body(title, settings, description, item, is_featured, align) }} + {{ item_card_body(title, description, item, is_featured, align) }}
{% endif %} @@ -106,13 +106,13 @@ {% endif %} - {{ item_card_body(title, settings, description, item, is_featured, align) }} + {{ item_card_body(title, description, item, is_featured, align) }} {% endif %} {%- endmacro -%} -{%- macro item_card_body(title, settings, description, item, is_featured, align) -%} +{%- macro item_card_body(title, description, item, is_featured, align) -%} {%- set align_class = resolve_class({ 'text-right': align == 'Right', 'text-center': align == 'Center' and not is_featured, @@ -128,52 +128,12 @@ {% endif %} - {% if not item.has_variants and settings.enable_wishlist %} -
- - {%- set icon_class = "wished" if item.wished else "not-wished"-%} - - -
- {% endif %} {% if is_featured %}
{{ item.formatted_price or '' }}
{{ description or '' }}
{% else %}
{{ item.item_group or '' }}
- - {% if item.formatted_price %} -
- {{ item.formatted_price or '' }} - - {% if item.get("formatted_mrp") %} - - {{ item.formatted_mrp }} - - - {{ item.discount }} OFF - - {% endif %} - -
- {% endif %} - - {% if item.has_variants %} - -
- {{ _('Explore') }} -
-
- {% elif settings.enabled and (settings.allow_items_not_in_stock or item.in_stock != "red")%} -
- {{ _('Add to Cart') }} -
- {% endif %} {% endif %} {%- endmacro -%} diff --git a/erpnext/templates/includes/products_as_list.html b/erpnext/templates/includes/products_as_list.html index 976d6147bc3..a9369bb8def 100644 --- a/erpnext/templates/includes/products_as_list.html +++ b/erpnext/templates/includes/products_as_list.html @@ -1,5 +1,5 @@ {% from "erpnext/templates/includes/macros.html" import item_card, item_card_body, product_image_square %} - +
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html index fa44101f9ec..bf84fd51697 100644 --- a/erpnext/www/all-products/index.html +++ b/erpnext/www/all-products/index.html @@ -32,8 +32,8 @@
--> -
+
@@ -82,21 +82,6 @@
- - - -