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
diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py
index c186a05..9e675e5 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -22,7 +22,7 @@
 	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 = ""
diff --git a/erpnext/e_commerce/product_view.js b/erpnext/e_commerce/product_view.js
new file mode 100644
index 0000000..660db66
--- /dev/null
+++ b/erpnext/e_commerce/product_view.js
@@ -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
+	}
+
+}
\ No newline at end of file
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 6fa3fb9..aa8ef6d 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -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"
 	]
 }
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index b57862b..331d04e 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -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 @@
 			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
 			});
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
index 6bcb6b1..11dae35 100644
--- a/erpnext/public/js/wishlist.js
+++ b/erpnext/public/js/wishlist.js
@@ -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 @@
 			let me = this;
 
 			let success_action = function() {
-				erpnext.wishlist.set_wishlist_count();
+				e_commerce.wishlist.set_wishlist_count();
 			};
 
 			if ($wish_icon.hasClass('wished')) {
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 04bf983..acd97ad 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -68,7 +68,6 @@
 
 .item-card-group-section {
 	.card {
-		height: 400px;
 		align-items: center;
 		justify-content: center;
 
@@ -779,3 +778,7 @@
 	padding: 6px;
 	font-size: 14px;
 }
+
+#toggle-view {
+	float: right;
+}
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index d944509..1823680 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -65,37 +65,14 @@
 		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 @@
 
 		context.no_breadcrumbs = False
 		context.title = self.website_title or self.name
+		context.name = self.name
 
 		return context
 
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index 1da4d15..d42453d 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -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) => {
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 5cb5d15..d7b8d32 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -247,7 +247,7 @@
 		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
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index a27b566..3ae9a89 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -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>
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 4de8ff7..f2b026c 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -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) {
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index d371352..d6d6629 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -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 -%}
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index 1e9b482..ad40796 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -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(() => {
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index d2a3b19..94b4c6f 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -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() {
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index a4662bb..2472dad 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -6,33 +6,55 @@
 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 @@
 	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)