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:
parent
48b3ce82b9
commit
897c08c8e7
148
erpnext/e_commerce/product_grid.js
Normal file
148
erpnext/e_commerce/product_grid.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
158
erpnext/e_commerce/product_list.js
Normal file
158
erpnext/e_commerce/product_list.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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 -%}
|
||||
|
@ -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'>
|
||||
|
@ -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) => {
|
||||
|
@ -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();
|
||||
|
@ -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
|
@ -1,4 +0,0 @@
|
||||
{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
|
||||
|
||||
{{ item_card(item, e_commerce_settings) }}
|
||||
|
Loading…
x
Reference in New Issue
Block a user