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
This commit is contained in:
marination 2021-05-17 20:44:41 +05:30
parent 48b3ce82b9
commit 897c08c8e7
13 changed files with 556 additions and 244 deletions

View File

@ -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>
`;
}
}
}

View File

@ -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>
`;
}
}
}

View File

@ -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;
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(`
<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 @@ 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(`
<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 @@ erpnext.ProductView = class {
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 @@ erpnext.ProductView = class {
}
}
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);
}
}
}

View File

@ -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"
]
}

View File

@ -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);
@ -303,6 +298,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);

View File

@ -79,7 +79,6 @@ class ItemGroup(NestedSet, WebsiteGenerator):
"title": self.name
})
context.sub_categories = get_child_groups(self.name)
if self.slideshow:
values = {
'show_indicators': 1,

View File

@ -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>

View File

@ -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 -%}

View File

@ -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'>

View File

@ -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) => {

View File

@ -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();

View File

@ -2,6 +2,7 @@ import frappe
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 @@ def get_product_filter_data():
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

View File

@ -1,4 +0,0 @@
{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
{{ item_card(item, e_commerce_settings) }}