feat: (wip) Toggle Views

- Auto Height on Cards
- Title with ellipses on length exceed
- Changed namepaces
- Moved product card rendering to JS
- Added Image and List View Toggling buttons
- Kept basic filters rendering just as before
This commit is contained in:
marination 2021-05-13 01:22:05 +05:30
parent f9929ed8a6
commit 48b3ce82b9
15 changed files with 224 additions and 117 deletions

View File

@ -22,7 +22,7 @@ class ProductQuery:
def __init__(self):
self.settings = frappe.get_doc("E Commerce Settings")
self.page_length = self.settings.products_per_page or 20
self.fields = ['wi.name', 'wi.item_name', 'wi.item_code', 'wi.website_image', 'wi.variant_of',
self.fields = ['wi.web_item_name', 'wi.name', 'wi.item_name', 'wi.item_code', 'wi.website_image', 'wi.variant_of',
'wi.has_variants', 'wi.item_group', 'wi.image', 'wi.web_long_description', 'wi.description',
'wi.route', 'wi.website_warehouse']
self.conditions = ""

View File

@ -0,0 +1,124 @@
erpnext.ProductView = class {
/* Options: View Type */
constructor(options) {
Object.assign(this, options);
this.render_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>
`);
}
get_item_filter_data() {
// Get Items and Discount Filters to render
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) : {};
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
},
callback: function(result) {
if (!result.exc) {
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]);
} else {
$("#products-area").append(`
<div class="d-flex justify-content-center p-3 text-muted">
${__('No products found')}
</div>`);
}
}
});
}
render_filters(filter_data) {
this.get_discount_filter_html(filter_data.discount_filters);
}
get_discount_filter_html(filter_data) {
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>
`);
let html = `<div class="filter-options">`;
filter_data.forEach(filter => {
html += `
<div class="checkbox">
<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]}"
>
<span class="label-area" for="${filter[0]}">
${filter[1]}
</span>
</label>
</div>
`;
});
html += `</div>`;
$("#discount-filters").append(html);
}
}
render_list_view() {
// loop over data and add list html to it
}
render_grid_view() {
// loop over data and add grid html to it
}
}

View File

@ -66,5 +66,8 @@
"js/hierarchy-chart.min.js": [
"public/js/hierarchy_chart/hierarchy_chart_desktop.js",
"public/js/hierarchy_chart/hierarchy_chart_mobile.js"
],
"js/e-commerce.min.js": [
"e_commerce/product_view.js"
]
}

View File

@ -2,8 +2,8 @@
// License: GNU General Public License v3. See license.txt
// shopping cart
frappe.provide("erpnext.shopping_cart");
var shopping_cart = erpnext.shopping_cart;
frappe.provide("e_commerce.shopping_cart");
var shopping_cart = e_commerce.shopping_cart;
var getParams = function (url) {
var params = [];
@ -214,7 +214,7 @@ $.extend(shopping_cart, {
this.animate_add_to_cart($btn);
const item_code = $btn.data('item-code');
erpnext.shopping_cart.update_cart({
e_commerce.shopping_cart.update_cart({
item_code,
qty: 1
});

View File

@ -1,8 +1,8 @@
frappe.provide("erpnext.wishlist");
var wishlist = erpnext.wishlist;
frappe.provide("e_commerce.wishlist");
var wishlist = e_commerce.wishlist;
frappe.provide("erpnext.shopping_cart");
var shopping_cart = erpnext.shopping_cart;
frappe.provide("e_commerce.shopping_cart");
var shopping_cart = e_commerce.shopping_cart;
$.extend(wishlist, {
set_wishlist_count: function() {
@ -79,7 +79,7 @@ $.extend(wishlist, {
let me = this;
let success_action = function() {
erpnext.wishlist.set_wishlist_count();
e_commerce.wishlist.set_wishlist_count();
};
if ($wish_icon.hasClass('wished')) {

View File

@ -68,7 +68,6 @@ body.product-page {
.item-card-group-section {
.card {
height: 400px;
align-items: center;
justify-content: center;
@ -779,3 +778,7 @@ body.product-page {
padding: 6px;
font-size: 14px;
}
#toggle-view {
float: right;
}

View File

@ -65,37 +65,14 @@ class ItemGroup(NestedSet, WebsiteGenerator):
self.delete_child_item_groups_key()
def get_context(self, context):
context.show_search=True
context.show_search = True
context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page')) or 6
context.e_commerce_settings = frappe.get_cached_doc('E Commerce Settings', 'E Commerce Settings')
context.search_link = '/product_search'
if frappe.form_dict:
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 = frappe.parse_json(frappe.form_dict.start)
else:
search = None
attribute_filters = None
field_filters = {}
start = 0
if not field_filters:
field_filters = {}
# Ensure the query remains within current item group
field_filters['item_group'] = self.name
engine = ProductQuery()
context.items, discounts = engine.query(attribute_filters, field_filters, search, start)
filter_engine = ProductFiltersBuilder(self.name)
context.field_filters = filter_engine.get_field_filters()
context.attribute_filters = filter_engine.get_attribute_filters()
if discounts:
context.discount_filters = filter_engine.get_discount_filters(discounts)
context.update({
"parents": get_parent_item_groups(self.parent_item_group),
@ -124,6 +101,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
context.no_breadcrumbs = False
context.title = self.website_title or self.name
context.name = self.name
return context

View File

@ -143,7 +143,7 @@
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
const item_code = $btn.data('item-code');
erpnext.shopping_cart.update_cart({
e_commerce.shopping_cart.update_cart({
item_code,
qty: 1,
callback(r) {
@ -170,11 +170,11 @@
};
let success_action = function() {
$btn.prop('disabled', false);
erpnext.wishlist.set_wishlist_count();
e_commerce.wishlist.set_wishlist_count();
$('.btn-add-to-wishlist, .btn-view-in-wishlist').toggleClass('hidden');
};
erpnext.wishlist.add_remove_from_wishlist("add", args, success_action, failure_action);
e_commerce.wishlist.add_remove_from_wishlist("add", args, success_action, failure_action);
});
$('.page_content').on('click', '.offer-details', (e) => {

View File

@ -247,7 +247,7 @@ class ItemConfigure {
const additional_notes = Object.keys(this.range_values || {}).map(attribute => {
return `${attribute}: ${this.range_values[attribute]}`;
}).join('\n');
erpnext.shopping_cart.update_cart({
e_commerce.shopping_cart.update_cart({
item_code,
additional_notes,
qty: 1

View File

@ -16,7 +16,8 @@
{% endblock %}
{% block page_content %}
<div class="item-group-content" itemscope itemtype="http://schema.org/Product" data-item-group="{{ name }}">
<div class="item-group-content" itemscope itemtype="http://schema.org/Product"
data-item-group="{{ name }}">
<div class="item-group-slideshow">
{% if slideshow %}<!-- slideshow -->
{{ web_block(
@ -33,7 +34,7 @@
{% endif %}
</div>
<div class="row">
<div class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
<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>
@ -48,15 +49,9 @@
{% endfor %}
</div>
{% endif %}
<div class="row products-list">
{% if items %}
{% for item in items %}
{% include "erpnext/www/all-products/item_row.html" %}
{% endfor %}
{% else %}
{% include "erpnext/www/all-products/not_found.html" %}
{% endif %}
</div>
<!-- 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">
@ -70,10 +65,6 @@
<!-- attribute filters -->
{{ attribute_filter_section(attribute_filters) }}
<!-- discount filters -->
{% if discount_filters %}
{{ discount_range_filters(discount_filters) }}
{% endif %}
</div>
<script>

View File

@ -4,8 +4,8 @@
// js inside blog page
// shopping cart
frappe.provide("erpnext.shopping_cart");
var shopping_cart = erpnext.shopping_cart;
frappe.provide("e_commerce.shopping_cart");
var shopping_cart = e_commerce.shopping_cart;
$.extend(shopping_cart, {
show_error: function(title, text) {

View File

@ -66,7 +66,8 @@
'align-items-start': align == 'Left',
}) -%}
{%- set col_size = 3 if is_full_width else 4 -%}
{%- set title = item.item_name or item.item_code -%}
{%- set title = item.web_item_name or item.item_name or item.item_code -%}
{%- set title = title[:50] + "..." if title|len > 50 else title -%}
{%- set image = item.website_image or item.image -%}
{%- set description = item.website_description or item.description-%}
@ -120,11 +121,13 @@
<div class="card-body {{ align_class }}" style="width:100%">
<div style="margin-top: 16px; display: flex;">
<a href="/{{ item.route or '#' }}">
<div class="product-title">{{ title or '' }}</div>
<div class="product-title">
{{ title or '' }}
{% if item.in_stock %}
<span class="indicator {{ item.in_stock }} card-indicator"></span>
{% endif %}
</div>
</a>
{% if item.in_stock %}
<span class="indicator {{ item.in_stock }} card-indicator"></span>
{% endif %}
{% if not item.has_variants and settings.enable_wishlist %}
<div class="like-action"
data-item-code="{{ item.item_code }}"
@ -363,7 +366,7 @@
<div class="filter-options">
{% for attr_value in attribute.item_attribute_values %}
<div class="checkbox">
<label data-value="{{ value }}">
<label data-value="{{ attr_value }}">
<input type="checkbox"
class="product-filter attribute-filter"
id="{{attr_value.name}}"
@ -381,24 +384,3 @@
</div>
{% endfor %}
{%- endmacro -%}
{%- macro discount_range_filters(filters)-%}
<div class="mb-4 filter-block pb-5">
<div class="filter-label mb-3">{{ _("Discounts") }}</div>
<div class="filter-options">
{% for entry in filters %}
<div class="checkbox">
<label data-value="{{ entry[0] }}">
<input type="radio" class="product-filter discount-filter"
name="discount" id="{{ entry[0] }}"
data-filter-name="discount" data-filter-value="{{ entry[0] }}"
>
<span class="label-area" for="{{ entry[0] }}">
{{ entry[1] }}
</span>
</label>
</div>
{% endfor %}
</div>
</div>
{%- endmacro -%}

View File

@ -7,7 +7,8 @@
{% endblock header %}
{% block page_content %}
<div class="row" style="display: none;">
<!-- Old Search -->
<!-- <div class="row" style="display: none;">
<div class="col-8">
<div class="input-group input-group-sm mb-3">
<input type="search" class="form-control" placeholder="{{_('Search')}}"
@ -29,22 +30,16 @@
{{ _('Toggle Filters') }}
</button>
</div>
</div>
</div> -->
<!-- Items section -->
<div class="row">
<div class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
<div class="row products-list">
{% if items %}
{% for item in items %}
{% include "erpnext/www/all-products/item_row.html" %}
{% endfor %}
{% else %}
{% include "erpnext/www/all-products/not_found.html" %}
{% endif %}
</div>
<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
<!-- Rendered via JS -->
</div>
<div class="col-12 order-1 col-md-3 order-md-1">
<!-- Filters Section -->
<div class="col-12 order-1 col-md-3 order-md-1">
{% if frappe.form_dict.start or frappe.form_dict.field_filters or frappe.form_dict.attribute_filters or frappe.form_dict.search %}
@ -64,11 +59,6 @@
{% if attribute_filters %}
{{ attribute_filter_section(attribute_filters) }}
{% endif %}
<!-- discount filters -->
{% if discount_filters %}
{{ discount_range_filters(discount_filters) }}
{% endif %}
</div>
<script>
@ -91,7 +81,10 @@
</script>
</div>
</div>
<div class="row product-paging-area mt-5">
<!-- TODO -->
<!-- Paging Section -->
<!-- <div class="row product-paging-area mt-5">
<div class="col-3">
</div>
<div class="col-9 text-right">
@ -102,7 +95,7 @@
<button class="btn btn-default btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
{% endif %}
</div>
</div>
</div> -->
<script>
frappe.ready(() => {

View File

@ -1,6 +1,17 @@
$(() => {
class ProductListing {
constructor() {
let is_item_group_page = $(".item-group-content").data("item-group");
let item_group = is_item_group_page || null;
// Render Products
frappe.require('assets/js/e-commerce.min.js', function() {
new erpnext.ProductView({
products_section: $('#product-listing'),
item_group: item_group
});
});
this.bind_filters();
this.bind_card_actions();
this.bind_search();
@ -77,8 +88,8 @@ $(() => {
}
bind_card_actions() {
erpnext.shopping_cart.bind_add_to_cart_action();
erpnext.wishlist.bind_wishlist_action();
e_commerce.shopping_cart.bind_add_to_cart_action();
e_commerce.wishlist.bind_wishlist_action();
}
bind_search() {

View File

@ -6,33 +6,55 @@ from erpnext.e_commerce.filters import ProductFiltersBuilder
sitemap = 1
def get_context(context):
# Add homepage as parent
context.parents = [{"name": frappe._("Home"), "route":"/"}]
filter_engine = ProductFiltersBuilder()
context.field_filters = filter_engine.get_field_filters()
context.attribute_filters = filter_engine.get_attribute_filters()
context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page'))or 20
context.no_cache = 1
@frappe.whitelist(allow_guest=True)
def get_product_filter_data():
"""Get pre-rendered filtered products and discount filters on load."""
if frappe.form_dict:
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))
item_group = frappe.form_dict.item_group
else:
search = field_filters = attribute_filters = None
search, attribute_filters, item_group = None, None, None
field_filters = {}
start = 0
if item_group:
field_filters['item_group'] = item_group
engine = ProductQuery()
context.items, discounts = engine.query(attribute_filters, field_filters, search, start)
items, discounts = engine.query(attribute_filters, field_filters, search_term=search, start=start)
# Add homepage as parent
context.parents = [{"name": frappe._("Home"), "route":"/"}]
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)
filter_engine = ProductFiltersBuilder()
if not items:
html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
context.field_filters = filter_engine.get_field_filters()
context.attribute_filters = filter_engine.get_attribute_filters()
# discount filter data
filters = {}
if discounts:
context.discount_filters = filter_engine.get_discount_filters(discounts)
filter_engine = ProductFiltersBuilder()
filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
context.e_commerce_settings = engine.settings
context.page_length = engine.settings.products_per_page or 20
context.no_cache = 1
return html, filters
@frappe.whitelist(allow_guest=True)
def get_products_html_for_website(field_filters=None, attribute_filters=None):
@ -47,7 +69,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None):
for item in items:
item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
'item': item,
'e_commerce_settings': None
'e_commerce_settings': engine.settings
}))
html = ''.join(item_html)