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
diff --git a/erpnext/e_commerce/product_grid.js b/erpnext/e_commerce/product_grid.js
new file mode 100644
index 0000000..638f017
--- /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 += `<div class="col-sm-4 item-card"><div class="card text-left">`;
+ html += me.get_image_html(item, title);
+ html += me.get_card_body_html(item, title, me.settings);
+ html += `</div></div>`;
+ })
+
+ 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 `
+ <div class="card-img-container">
+ <a href="/${ item.route || '#' }" style="text-decoration: none;">
+ <img class="card-img" src="${ image }" alt="${ title }">
+ </a>
+ </div>
+ `;
+ } else {
+ return `
+ <a href="/${ item.route || '#' }" style="text-decoration: none;">
+ <div class="card-img-top no-image">
+ ${ frappe.get_abbr(title) }
+ </div>
+ </a>
+ `;
+ }
+ }
+
+ get_card_body_html(item, title, settings) {
+ let body_html = `
+ <div class="card-body text-left" style="width:100%">
+ <div style="margin-top: 16px; display: flex;">
+ `;
+ 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 += `</div>`; // close div on line 50
+ body_html += `<div class="product-category">${ item.item_group || '' }</div>`;
+
+ if (item.formatted_price) {
+ body_html += this.get_price_html(item);
+ }
+
+ body_html += this.get_primary_button(item, settings);
+ body_html += `</div>`; // close div on line 49
+
+ return body_html;
+ }
+
+ get_title_with_indicator(item, title, settings) {
+ let title_html = `
+ <a href="/${ item.route || '#' }">
+ <div class="product-title">
+ ${ title || '' }
+ `;
+ if (item.in_stock) {
+ title_html += `<span class="indicator ${ item.in_stock } card-indicator"></span>`;
+ }
+ title_html += `</div></a>`;
+ return title_html
+ }
+
+ get_wishlist_icon(item) {
+ let icon_class = item.wished ? "wished" : "not-wished";
+ return `
+ <div class="like-action"
+ data-item-code="${ item.item_code }"
+ data-price="${ item.price || '' }"
+ data-formatted-price="${ item.formatted_price || '' }">
+ <svg class="icon sm">
+ <use class="${ icon_class } wish-icon" href="#icon-heart"></use>
+ </svg>
+ </div>
+ `;
+ }
+
+ get_price_html(item) {
+ let price_html = `
+ <div class="product-price">
+ ${ item.formatted_price || '' }
+ `;
+
+ if (item.formatted_mrp) {
+ price_html += `
+ <small class="ml-1 text-muted">
+ <s>${ item.formatted_mrp }</s>
+ </small>
+ <small class="ml-1" style="color: #F47A7A; font-weight: 500;">
+ ${ item.discount } OFF
+ </small>
+ `;
+ }
+ price_html += `</div>`;
+ return price_html;
+ }
+
+ get_primary_button(item, settings) {
+ if (item.has_variants) {
+ return `
+ <a href="/${ item.route || '#' }">
+ <div class="btn btn-sm btn-explore-variants w-100 mt-4">
+ ${ __('Explore') }
+ </div>
+ </a>
+ `;
+ } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) {
+ return `
+ <div id="${ item.name }" class="btn
+ btn-sm btn-add-to-cart-list not-added w-100 mt-4"
+ data-item-code="${ item.item_code }">
+ ${ __('Add to Cart') }
+ </div>
+ `;
+ }
+ }
+}
\ 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 0000000..e693f1e
--- /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 = `<br><br>`;
+
+ 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 += `<div class='row mt-6 w-100' style="border-bottom: 1px solid var(--table-border-color); padding-bottom: 1rem;">`;
+ html += me.get_image_html(item, title);
+ html += me.get_row_body_html(item, title, me.settings);
+ html += `</div>`;
+ })
+
+ 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 `
+ <div class="col-2 border text-center rounded product-image" style="overflow: hidden; max-height: 200px;">
+ <a class="product-link product-list-link" href="/${ item.route || '#' }">
+ <img itemprop="image" class="website-image h-100 w-100" alt="${ title }"
+ src="${ image }">
+ </a>
+ </div>
+ `;
+ } else {
+ return `
+ <a href="/${ item.route || '#' }" style="text-decoration: none;">
+ <div class="card-img-top no-image">
+ ${ frappe.get_abbr(title) }
+ </div>
+ </a>
+ `;
+ }
+ }
+
+ get_row_body_html(item, title, settings) {
+ let body_html = `<div class='col-9 text-left'>`;
+ body_html += this.get_title_html(item, title, settings);
+ body_html += this.get_item_details(item, settings);
+ body_html += `</div>`;
+ return body_html;
+ }
+
+ get_title_html(item, title, settings) {
+ let title_html = `<div style="display: flex; margin-left: -15px;">`;
+ title_html += `
+ <div class="col-8" style="margin-right: -15px;">
+ <a class="" href="/${ item.route || '#' }"
+ style="color: var(--gray-800); font-weight: 500;">
+ ${ title }
+ </a>
+ `;
+
+ if (item.in_stock) {
+ title_html += `<span class="indicator ${ item.in_stock } card-indicator"></span>`;
+ }
+ title_html += `</div>`;
+
+ if (settings.enable_wishlist || settings.enabled) {
+ title_html += `<div class="col-4" style="display:flex">`;
+ if (!item.has_variants && settings.enable_wishlist) {
+ title_html += this.get_wishlist_icon(item);
+ }
+ title_html += this.get_primary_button(item, settings);
+ title_html += `</div>`;
+ }
+ title_html += `</div>`;
+
+ return title_html;
+ }
+
+ get_item_details(item, settings) {
+ let details = `
+ <p class="product-code">
+ Item Code : ${ item.item_code }
+ </p>
+ <div class="text-muted mt-2">
+ ${ item.description || '' }
+ </div>
+ <div class="product-price">
+ ${ item.formatted_price || '' }
+ `;
+
+ if(item.formatted_mrp) {
+ details += `
+ <small class="ml-1 text-muted">
+ <s>${ item.formatted_mrp }</s>
+ </small>
+ <small class="ml-1" style="color: #F47A7A; font-weight: 500;">
+ ${ item.discount } OFF
+ </small>
+ `;
+ }
+ details += `</div>`;
+
+ return details;
+ }
+
+ get_wishlist_icon(item) {
+ let icon_class = item.wished ? "wished" : "not-wished";
+
+ return `
+ <div class="like-action mr-4"
+ data-item-code="${ item.item_code }"
+ data-price="${ item.price || '' }"
+ data-formatted-price="${ item.formatted_price || '' }">
+ <svg class="icon sm">
+ <use class="${ icon_class } wish-icon" href="#icon-heart"></use>
+ </svg>
+ </div>
+ `;
+ }
+
+ get_primary_button(item, settings) {
+ if (item.has_variants) {
+ return `
+ <a href="/${ item.route || '#' }">
+ <div class="btn btn-sm btn-explore-variants" style="margin-bottom: 0; margin-top: 4px; max-height: 30px;">
+ ${ __('Explore') }
+ </div>
+ </a>
+ `;
+ } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock !== "red")) {
+ return `
+ <div id="${ item.name }" class="btn
+ btn-sm btn-add-to-cart-list not-added"
+ data-item-code="${ item.item_code }"
+ style="margin-bottom: 0; margin-top: 0px; max-height: 30px;">
+ ${ __('Add to Cart') }
+ </div>
+ `;
+ }
+ }
+
+}
diff --git a/erpnext/e_commerce/product_view.js b/erpnext/e_commerce/product_view.js
index 660db66..923bdb1 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(`
- <div class="form-group mb-0" id="toggle-view">
- <button id="${icon}" class="btn ${view} mr-2">
- <span>
- <svg class="icon icon-md">
- <use href="#icon-${icon}"></use>
- </svg>
- </span>
- </button>
- </div>`);
- });
-
- $("#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(`
- <br><br>
- <div id="products-area" class="row products-list mt-4"></div>
- `);
+ 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;
+ let args = this.get_query_filters();
- field_filters = field_filters ? JSON.parse(field_filters) : {};
- attribute_filters = attribute_filters ? JSON.parse(attribute_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(`
- <div class="d-flex justify-content-center p-3 text-muted">
- ${__('No products found')}
- </div>`);
-
+ me.render_no_products_section();
}
+
+ $('#list').prop('disabled', false);
+ $('#image-view').prop('disabled', false);
}
});
}
@@ -83,11 +59,159 @@
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(`
+ <br>
+ <div id="products-${view}-area" class="row products-list ${ top_margin } ${ left_margin }"></div>
+ `);
+ }
+
+ 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 = `
+ <div class="row product-paging-area mt-5">
+ <div class="col-3">
+ </div>
+ <div class="col-9 text-right">
+ `;
+ 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 += `
+ <button class="btn btn-default btn-prev" data-start="${ start - page_length }" style="float: left">
+ ${ __("Prev") }
+ </button>`;
+ }
+ if(this.products.length > page_length || this.products.length == page_length) {
+ paging_html += `
+ <button class="btn btn-default btn-next" data-start="${ start + page_length }">
+ ${ __("Next") }
+ </button>
+ `;
+ }
+ paging_html += `</div></div>`;
+
+ $(".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(`
+ <div class="form-group mb-0" id="toggle-view">
+ <button id="${ icon }" class="btn ${ view } mr-2">
+ <span>
+ <svg class="icon icon-md">
+ <use href="#icon-${ icon }"></use>
+ </svg>
+ </span>
+ </button>
+ </div>`);
+ });
+ }
+
+ 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(`
<div id="discount-filters" class="mb-4 filter-block pb-5">
- <div class="filter-label mb-3">${__("Discounts")}</div>
+ <div class="filter-label mb-3">${ __("Discounts") }</div>
</div>
`);
@@ -95,13 +219,13 @@
filter_data.forEach(filter => {
html += `
<div class="checkbox">
- <label data-value="${filter[0]}">
+ <label data-value="${ filter[0] }">
<input type="radio" class="product-filter discount-filter"
- name="discount" id="${filter[0]}"
- data-filter-name="discount" data-filter-value="${filter[0]}"
+ name="discount" id="${ filter[0] }"
+ data-filter-name="discount" data-filter-value="${ filter[0] }"
>
- <span class="label-area" for="${filter[0]}">
- ${filter[1]}
+ <span class="label-area" for="${ filter[0] }">
+ ${ filter[1] }
</span>
</label>
</div>
@@ -113,12 +237,35 @@
}
}
- render_list_view() {
- // loop over data and add list html to it
+ render_no_products_section() {
+ $("#products-area").append(`
+ <div class="d-flex justify-content-center p-3 text-muted">
+ ${ __('No products found') }
+ </div>
+ `);
}
- render_grid_view() {
- // loop over data and add grid html to it
- }
+ render_item_sub_categories(categories) {
+ if(categories) {
+ let sub_group_html = `
+ <div class="sub-category-container">
+ <div class="heading"> ${ __('Sub Categories') } </div>
+ </div>
+ <div class="sub-category-container scroll-categories">
+ `;
+ categories.forEach(category => {
+ sub_group_html += `
+ <a href="${ category.route || '#' }" style="text-decoration: none;">
+ <div class="category-pill">
+ ${ category.name }
+ </div>
+ </a>
+ `;
+ })
+ sub_group_html += `</div>`;
+
+ $("#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 aa8ef6d..51082bd 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -68,6 +68,8 @@
"public/js/hierarchy_chart/hierarchy_chart_mobile.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 acd97ad..33bd88f 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -229,11 +229,6 @@
color: var(--text-color);
}
- .product-code {
- color: var(--text-muted);
- font-size: 13px;
- }
-
.product-description {
font-size: 13px;
color: var(--gray-800);
@@ -303,6 +298,11 @@
}
}
+.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 1823680..d8ae763 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -79,7 +79,6 @@
"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 3ae9a89..20ad7fb 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -35,24 +35,9 @@
</div>
<div class="row">
<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
- {% if sub_categories %}
- <div class="sub-category-container">
- <div class="heading"> {{ _('Sub Categories') }} </div>
- </div>
- <div class="sub-category-container scroll-categories">
- {% for row in sub_categories%}
- <a href="{{ row.route or '#' }}" style="text-decoration: none;">
- <div class="category-pill">
- {{ row.name }}
- </div>
- </a>
- {% endfor %}
- </div>
- {% endif %}
-
<!-- Products Rendered in all-products/index.js-->
-
</div>
+
<div class="col-12 order-1 col-md-3 order-md-1">
<div class="collapse d-md-block mr-4 filters-section" id="product-filters">
<div class="d-flex justify-content-between align-items-center mb-5 title-section">
@@ -87,23 +72,6 @@
</script>
</div>
</div>
- <div class="row mt-6">
- <div class="col-3">
- </div>
- <div class="col-9">
- {% if frappe.form_dict.start|int > 0 %}
- <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">
- {{ _("Prev") }}
- </button>
- {% endif %}
- {% if items|length >= page_length %}
- <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}"
- style="float: right;">
- {{ _("Next") }}
- </button>
- {% endif %}
- </div>
- </div>
</div>
<script>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index d6d6629..aec23a2 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 @@
<img class="card-img" src="{{ image }}" alt="{{ title }}">
</div>
<div class="col-md-6">
- {{ item_card_body(title, settings, description, item, is_featured, align) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
</div>
{% else %}
<div class="col-md-12">
- {{ item_card_body(title, settings, description, item, is_featured, align) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
{% endif %}
</div>
@@ -106,13 +106,13 @@
</div>
</a>
{% endif %}
- {{ item_card_body(title, settings, description, item, is_featured, align) }}
+ {{ item_card_body(title, description, item, is_featured, align) }}
</div>
</div>
{% 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 %}
</div>
</a>
- {% if not item.has_variants and settings.enable_wishlist %}
- <div class="like-action"
- data-item-code="{{ item.item_code }}"
- data-price="{{ item.price }}"
- data-formatted-price="{{ item.get('formatted_price') }}">
- <svg class="icon sm">
- {%- set icon_class = "wished" if item.wished else "not-wished"-%}
- <use class="{{ icon_class }} wish-icon" href="#icon-heart"></use>
- </svg>
- </div>
- {% endif %}
</div>
{% if is_featured %}
<div class="product-price">{{ item.formatted_price or '' }}</div>
<div class="product-description ellipsis">{{ description or '' }}</div>
{% else %}
<div class="product-category">{{ item.item_group or '' }}</div>
-
- {% if item.formatted_price %}
- <div class="product-price">
- {{ item.formatted_price or '' }}
-
- {% if item.get("formatted_mrp") %}
- <small class="ml-1 text-muted">
- <s>{{ item.formatted_mrp }}</s>
- </small>
- <small class="ml-1" style="color: #F47A7A; font-weight: 500;">
- {{ item.discount }} OFF
- </small>
- {% endif %}
-
- </div>
- {% endif %}
-
- {% if item.has_variants %}
- <a href="/{{ item.route or '#' }}">
- <div class="btn btn-sm btn-explore-variants w-100 mt-4">
- {{ _('Explore') }}
- </div>
- </a>
- {% elif settings.enabled and (settings.allow_items_not_in_stock or item.in_stock != "red")%}
- <div id="{{ item.name }}" class="btn btn-sm btn-add-to-cart-list not-added w-100 mt-4"
- data-item-code="{{ item.item_code }}">
- {{ _('Add to Cart') }}
- </div>
- {% endif %}
{% endif %}
</div>
{%- endmacro -%}
diff --git a/erpnext/templates/includes/products_as_list.html b/erpnext/templates/includes/products_as_list.html
index 976d614..a9369bb 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 %}
-
+<!-- Used in Product Search -->
<a class="product-link product-list-link" href="{{ route|abs_url }}">
<div class='row'>
<div class='col-xs-3 col-sm-2 product-image-wrapper'>
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index ad40796..f89ee65 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -32,8 +32,8 @@
</div>
</div> -->
-<!-- Items section -->
<div class="row">
+ <!-- Items section -->
<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
<!-- Rendered via JS -->
</div>
@@ -82,21 +82,6 @@
</div>
</div>
-<!-- TODO -->
-<!-- Paging Section -->
-<!-- <div class="row product-paging-area mt-5">
- <div class="col-3">
- </div>
- <div class="col-9 text-right">
- {% if frappe.form_dict.start|int > 0 %}
- <button class="btn btn-default btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
- {% endif %}
- {% if items|length >= page_length %}
- <button class="btn btn-default btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
- {% endif %}
- </div>
-</div> -->
-
<script>
frappe.ready(() => {
$('.btn-prev, .btn-next').click((e) => {
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 94b4c6f..acf42f2 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -1,14 +1,16 @@
$(() => {
class ProductListing {
constructor() {
+ let me = this;
let is_item_group_page = $(".item-group-content").data("item-group");
- let item_group = is_item_group_page || null;
+ this.item_group = is_item_group_page || null;
- // Render Products
+ // Render Products and Discount Filters
frappe.require('assets/js/e-commerce.min.js', function() {
new erpnext.ProductView({
+ view_type: "List",
products_section: $('#product-listing'),
- item_group: item_group
+ item_group: me.item_group
});
});
@@ -19,6 +21,7 @@
}
bind_filters() {
+ let me = this;
this.field_filters = {};
this.attribute_filters = {};
@@ -73,17 +76,14 @@
window.history.pushState('filters', '', `${location.pathname}?` + query_string);
$('.page_content input').prop('disabled', true);
- this.get_items_with_filters()
- .then(html => {
- $('.products-list').html(html);
- })
- .then(data => {
- $('.page_content input').prop('disabled', false);
- return data;
- })
- .catch(() => {
- $('.page_content input').prop('disabled', false);
+ frappe.require('assets/js/e-commerce.min.js', function() {
+ new erpnext.ProductView({
+ view_type: "List",
+ products_section: $('#product-listing'),
+ item_group: me.item_group
});
+ $('.page_content input').prop('disabled', false);
+ });
}, 1000));
}
@@ -133,27 +133,6 @@
this.attribute_filters = attribute_filters;
}
}
-
- get_items_with_filters() {
- const { attribute_filters, field_filters } = this;
- const args = {
- field_filters: if_key_exists(field_filters),
- attribute_filters: if_key_exists(attribute_filters)
- };
-
- const item_group = $(".item-group-content").data('item-group');
- if (item_group) {
- Object.assign(field_filters, { item_group });
- }
- return new Promise((resolve, reject) => {
- frappe.call('erpnext.www.all-products.index.get_products_html_for_website', args)
- .then(r => {
- if (r.exc) reject(r.exc);
- else resolve(r.message);
- })
- .fail(reject);
- });
- }
}
new ProductListing();
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 2472dad..a6c39db 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -2,6 +2,7 @@
from frappe.utils import cint
from erpnext.e_commerce.product_query import ProductQuery
from erpnext.e_commerce.filters import ProductFiltersBuilder
+from erpnext.setup.doctype.item_group.item_group import get_child_groups
sitemap = 1
@@ -24,56 +25,25 @@
search = frappe.form_dict.search
field_filters = frappe.parse_json(frappe.form_dict.field_filters)
attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
- start = cint(frappe.parse_json(frappe.form_dict.start))
+ start = cint(frappe.parse_json(frappe.form_dict.start)) if frappe.form_dict.start else 0
item_group = frappe.form_dict.item_group
else:
search, attribute_filters, item_group = None, None, None
field_filters = {}
start = 0
+ sub_categories = []
if item_group:
field_filters['item_group'] = item_group
+ sub_categories = get_child_groups(item_group)
engine = ProductQuery()
items, discounts = engine.query(attribute_filters, field_filters, search_term=search, start=start)
- item_html = []
- for item in items:
- item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
- 'item': item,
- 'e_commerce_settings': engine.settings
- }))
- html = ''.join(item_html)
-
- if not items:
- html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
-
# discount filter data
filters = {}
if discounts:
filter_engine = ProductFiltersBuilder()
filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
- return html, filters
-
-@frappe.whitelist(allow_guest=True)
-def get_products_html_for_website(field_filters=None, attribute_filters=None):
- """Get Products on filter change."""
- field_filters = frappe.parse_json(field_filters)
- attribute_filters = frappe.parse_json(attribute_filters)
-
- engine = ProductQuery()
- items, discounts = engine.query(attribute_filters, field_filters, search_term=None, start=0)
-
- item_html = []
- for item in items:
- item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
- 'item': item,
- 'e_commerce_settings': engine.settings
- }))
- html = ''.join(item_html)
-
- if not items:
- html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
-
- return html
+ return items or [], filters, engine.settings, sub_categories
\ No newline at end of file
diff --git a/erpnext/www/all-products/item_row.html b/erpnext/www/all-products/item_row.html
deleted file mode 100644
index 538ce3b..0000000
--- a/erpnext/www/all-products/item_row.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
-
-{{ item_card(item, e_commerce_settings) }}
-