[FIX] website_sale_aplicoop: prevent grid destruction on event listener attachment
The _attachEventListeners() function was cloning the products-grid element without its children (cloneNode(false)) to remove duplicate event listeners. This destroyed all loaded products every time the function was called. Solution: Use a flag (_delegationListenersAttached) to prevent adding duplicate event listeners instead of cloning and replacing the grid node. This fixes the issue where products would disappear ~1-2 seconds after page load.
This commit is contained in:
parent
b15e9bc977
commit
b07b7dc671
6 changed files with 521 additions and 218 deletions
|
|
@ -48,7 +48,17 @@
|
||||||
"assets": {
|
"assets": {
|
||||||
"web.assets_frontend": [
|
"web.assets_frontend": [
|
||||||
"website_sale_aplicoop/static/src/css/website_sale.css",
|
"website_sale_aplicoop/static/src/css/website_sale.css",
|
||||||
|
# i18n and helpers must load first
|
||||||
|
"website_sale_aplicoop/static/src/js/i18n_manager.js",
|
||||||
|
"website_sale_aplicoop/static/src/js/i18n_helpers.js",
|
||||||
|
# Core shop functionality
|
||||||
|
"website_sale_aplicoop/static/src/js/website_sale.js",
|
||||||
|
"website_sale_aplicoop/static/src/js/checkout_labels.js",
|
||||||
|
"website_sale_aplicoop/static/src/js/home_delivery.js",
|
||||||
|
"website_sale_aplicoop/static/src/js/checkout_summary.js",
|
||||||
|
# Search and pagination
|
||||||
"website_sale_aplicoop/static/src/js/infinite_scroll.js",
|
"website_sale_aplicoop/static/src/js/infinite_scroll.js",
|
||||||
|
"website_sale_aplicoop/static/src/js/realtime_search.js",
|
||||||
],
|
],
|
||||||
"web.assets_tests": [
|
"web.assets_tests": [
|
||||||
"website_sale_aplicoop/static/tests/test_suite.js",
|
"website_sale_aplicoop/static/tests/test_suite.js",
|
||||||
|
|
|
||||||
|
|
@ -1441,12 +1441,17 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
all_products = group_order._get_products_for_group_order(group_order.id)
|
all_products = group_order._get_products_for_group_order(group_order.id)
|
||||||
filtered_products = all_products
|
filtered_products = all_products
|
||||||
|
|
||||||
# Apply search
|
# Apply search filter (only if search_query is not empty)
|
||||||
if search_query:
|
if search_query:
|
||||||
|
_logger.info("load_products_ajax: Applying search filter: %s", search_query)
|
||||||
filtered_products = filtered_products.filtered(
|
filtered_products = filtered_products.filtered(
|
||||||
lambda p: search_query.lower() in p.name.lower()
|
lambda p: search_query.lower() in p.name.lower()
|
||||||
or search_query.lower() in (p.description or "").lower()
|
or search_query.lower() in (p.description or "").lower()
|
||||||
)
|
)
|
||||||
|
_logger.info(
|
||||||
|
"load_products_ajax: After search filter: %d products",
|
||||||
|
len(filtered_products),
|
||||||
|
)
|
||||||
|
|
||||||
# Apply category filter
|
# Apply category filter
|
||||||
if category_filter != "0":
|
if category_filter != "0":
|
||||||
|
|
@ -1455,6 +1460,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
selected_category = request.env["product.category"].browse(category_id)
|
selected_category = request.env["product.category"].browse(category_id)
|
||||||
|
|
||||||
if selected_category.exists():
|
if selected_category.exists():
|
||||||
|
_logger.info(
|
||||||
|
"load_products_ajax: Applying category filter: %d (%s)",
|
||||||
|
category_id,
|
||||||
|
selected_category.name,
|
||||||
|
)
|
||||||
all_category_ids = [category_id]
|
all_category_ids = [category_id]
|
||||||
|
|
||||||
def get_all_children(category):
|
def get_all_children(category):
|
||||||
|
|
@ -1489,6 +1499,10 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
|
|
||||||
# Preserve search filter by using intersection
|
# Preserve search filter by using intersection
|
||||||
filtered_products = filtered_products & cat_filtered
|
filtered_products = filtered_products & cat_filtered
|
||||||
|
_logger.info(
|
||||||
|
"load_products_ajax: After category filter: %d products",
|
||||||
|
len(filtered_products),
|
||||||
|
)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
"load_products_ajax: Invalid category filter: %s", str(e)
|
"load_products_ajax: Invalid category filter: %s", str(e)
|
||||||
|
|
@ -1500,6 +1514,16 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
products_page = filtered_products[offset : offset + per_page]
|
products_page = filtered_products[offset : offset + per_page]
|
||||||
has_next = offset + per_page < total_products
|
has_next = offset + per_page < total_products
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"load_products_ajax: Pagination - page=%d, offset=%d, per_page=%d, "
|
||||||
|
"total=%d, has_next=%s",
|
||||||
|
page,
|
||||||
|
offset,
|
||||||
|
per_page,
|
||||||
|
total_products,
|
||||||
|
has_next,
|
||||||
|
)
|
||||||
|
|
||||||
# Get prices
|
# Get prices
|
||||||
pricelist = self._resolve_pricelist()
|
pricelist = self._resolve_pricelist()
|
||||||
product_price_info = {}
|
product_price_info = {}
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,105 @@
|
||||||
|
|
||||||
console.log("[INFINITE_SCROLL] Script loaded!");
|
console.log("[INFINITE_SCROLL] Script loaded!");
|
||||||
|
|
||||||
// Visual indicator for debugging
|
// DEBUG: Add MutationObserver to detect WHO is clearing the products grid
|
||||||
if (typeof document !== "undefined") {
|
(function () {
|
||||||
try {
|
var setupGridObserver = function () {
|
||||||
var debugDiv = document.createElement("div");
|
var grid = document.getElementById("products-grid");
|
||||||
debugDiv.innerHTML = "[INFINITE_SCROLL LOADED]";
|
if (!grid) {
|
||||||
debugDiv.style.position = "fixed";
|
console.log("[MUTATION_DEBUG] products-grid not found yet, will retry...");
|
||||||
debugDiv.style.top = "0";
|
setTimeout(setupGridObserver, 100);
|
||||||
debugDiv.style.right = "0";
|
return;
|
||||||
debugDiv.style.backgroundColor = "#00ff00";
|
}
|
||||||
debugDiv.style.color = "#000";
|
|
||||||
debugDiv.style.padding = "5px 10px";
|
console.log("[MUTATION_DEBUG] 🔍 Setting up MutationObserver on products-grid");
|
||||||
debugDiv.style.fontSize = "12px";
|
console.log("[MUTATION_DEBUG] Initial child count:", grid.children.length);
|
||||||
debugDiv.style.zIndex = "99999";
|
console.log("[MUTATION_DEBUG] Grid innerHTML length:", grid.innerHTML.length);
|
||||||
debugDiv.id = "infinite-scroll-debug";
|
|
||||||
document.body.appendChild(debugDiv);
|
// Watch the grid itself for child changes
|
||||||
} catch (e) {
|
var gridObserver = new MutationObserver(function (mutations) {
|
||||||
console.error("[INFINITE_SCROLL] Error adding debug div:", e);
|
mutations.forEach(function (mutation) {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
if (mutation.removedNodes.length > 0) {
|
||||||
|
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ PRODUCTS REMOVED FROM GRID!");
|
||||||
|
console.log(
|
||||||
|
"[MUTATION_DEBUG] Removed nodes count:",
|
||||||
|
mutation.removedNodes.length
|
||||||
|
);
|
||||||
|
console.log("[MUTATION_DEBUG] Stack trace:");
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
if (mutation.addedNodes.length > 0) {
|
||||||
|
console.log("[MUTATION_DEBUG] Products added:", mutation.addedNodes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
gridObserver.observe(grid, { childList: true, subtree: false });
|
||||||
|
|
||||||
|
// ALSO watch the parent for the grid element itself being replaced/removed
|
||||||
|
var parent = grid.parentElement;
|
||||||
|
if (parent) {
|
||||||
|
console.log(
|
||||||
|
"[MUTATION_DEBUG] 🔍 Also watching parent element:",
|
||||||
|
parent.tagName,
|
||||||
|
parent.className
|
||||||
|
);
|
||||||
|
var parentObserver = new MutationObserver(function (mutations) {
|
||||||
|
mutations.forEach(function (mutation) {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
mutation.removedNodes.forEach(function (node) {
|
||||||
|
if (
|
||||||
|
node.id === "products-grid" ||
|
||||||
|
(node.querySelector && node.querySelector("#products-grid"))
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"[MUTATION_DEBUG] ⚠️⚠️⚠️ PRODUCTS-GRID ELEMENT ITSELF WAS REMOVED!"
|
||||||
|
);
|
||||||
|
console.log("[MUTATION_DEBUG] Stack trace:");
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
parentObserver.observe(parent, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll to detect innerHTML being cleared (as backup)
|
||||||
|
var lastChildCount = grid.children.length;
|
||||||
|
setInterval(function () {
|
||||||
|
var currentGrid = document.getElementById("products-grid");
|
||||||
|
if (!currentGrid) {
|
||||||
|
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ GRID ELEMENT NO LONGER EXISTS!");
|
||||||
|
console.trace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var currentChildCount = currentGrid.children.length;
|
||||||
|
if (currentChildCount !== lastChildCount) {
|
||||||
|
console.log(
|
||||||
|
"[MUTATION_DEBUG] 📊 Child count changed: " +
|
||||||
|
lastChildCount +
|
||||||
|
" → " +
|
||||||
|
currentChildCount
|
||||||
|
);
|
||||||
|
if (currentChildCount === 0 && lastChildCount > 0) {
|
||||||
|
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ GRID WAS EMPTIED!");
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
lastChildCount = currentChildCount;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
console.log("[MUTATION_DEBUG] ✅ Observers attached (grid + parent + polling)");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start observing as soon as possible
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", setupGridObserver);
|
||||||
|
} else {
|
||||||
|
setupGridObserver();
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
@ -45,10 +125,16 @@ if (typeof document !== "undefined") {
|
||||||
config: {},
|
config: {},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
|
console.log("[INFINITE_SCROLL] 🔧 init() called");
|
||||||
|
|
||||||
// Get configuration from page data
|
// Get configuration from page data
|
||||||
var configEl = document.getElementById("eskaera-config");
|
var configEl = document.getElementById("eskaera-config");
|
||||||
|
console.log("[INFINITE_SCROLL] eskaera-config element:", configEl);
|
||||||
|
|
||||||
if (!configEl) {
|
if (!configEl) {
|
||||||
console.log("[INFINITE_SCROLL] No eskaera-config found, lazy loading disabled");
|
console.error(
|
||||||
|
"[INFINITE_SCROLL] ❌ No eskaera-config found, lazy loading disabled"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,15 +144,33 @@ if (typeof document !== "undefined") {
|
||||||
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
|
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
|
||||||
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
|
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
|
||||||
|
|
||||||
|
console.log("[INFINITE_SCROLL] Config loaded:", {
|
||||||
|
orderId: this.orderId,
|
||||||
|
searchQuery: this.searchQuery,
|
||||||
|
category: this.category,
|
||||||
|
perPage: this.perPage,
|
||||||
|
currentPage: this.currentPage,
|
||||||
|
});
|
||||||
|
|
||||||
// Check if there are more products to load from data attribute
|
// Check if there are more products to load from data attribute
|
||||||
var hasNextAttr = configEl.getAttribute("data-has-next");
|
var hasNextAttr = configEl.getAttribute("data-has-next");
|
||||||
this.hasMore = hasNextAttr === "true" || hasNextAttr === "True";
|
this.hasMore = hasNextAttr === "true" || hasNextAttr === "True";
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] hasMore=" +
|
||||||
|
this.hasMore +
|
||||||
|
" (data-has-next=" +
|
||||||
|
hasNextAttr +
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
|
||||||
if (!this.hasMore) {
|
if (!this.hasMore) {
|
||||||
console.log(
|
console.log(
|
||||||
"[INFINITE_SCROLL] No more products to load (has_next=" + hasNextAttr + ")"
|
"[INFINITE_SCROLL] ⚠️ No more pages available, but keeping initialized for filter handling (has_next=" +
|
||||||
|
hasNextAttr +
|
||||||
|
")"
|
||||||
);
|
);
|
||||||
return;
|
// Don't return - we need to stay initialized so realtime_search can call resetWithFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[INFINITE_SCROLL] Initialized with:", {
|
console.log("[INFINITE_SCROLL] Initialized with:", {
|
||||||
|
|
@ -77,36 +181,50 @@ if (typeof document !== "undefined") {
|
||||||
currentPage: this.currentPage,
|
currentPage: this.currentPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.attachScrollListener();
|
// Only attach scroll listener if there are more pages to load
|
||||||
// Also keep the button listener as fallback
|
if (this.hasMore) {
|
||||||
this.attachFallbackButtonListener();
|
this.attachScrollListener();
|
||||||
|
this.attachFallbackButtonListener();
|
||||||
|
} else {
|
||||||
|
console.log("[INFINITE_SCROLL] Skipping scroll listener (no more pages)");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
attachScrollListener: function () {
|
attachScrollListener: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var scrollThreshold = 0.8; // Load when 80% scrolled
|
var scrollThreshold = 300; // Load when within 300px of the bottom of the grid
|
||||||
|
|
||||||
window.addEventListener("scroll", function () {
|
window.addEventListener("scroll", function () {
|
||||||
if (self.isLoading || !self.hasMore) {
|
if (self.isLoading || !self.hasMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var scrollHeight = document.documentElement.scrollHeight;
|
var grid = document.getElementById("products-grid");
|
||||||
var scrollTop = window.scrollY;
|
if (!grid) {
|
||||||
var clientHeight = window.innerHeight;
|
return;
|
||||||
var scrollPercent = (scrollTop + clientHeight) / scrollHeight;
|
}
|
||||||
|
|
||||||
if (scrollPercent >= scrollThreshold) {
|
// Calculate distance from bottom of grid to bottom of viewport
|
||||||
|
var gridRect = grid.getBoundingClientRect();
|
||||||
|
var gridBottom = gridRect.bottom;
|
||||||
|
var viewportBottom = window.innerHeight;
|
||||||
|
var distanceFromBottom = gridBottom - viewportBottom;
|
||||||
|
|
||||||
|
// Load more if we're within threshold pixels of the grid bottom
|
||||||
|
if (distanceFromBottom <= scrollThreshold && distanceFromBottom > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
"[INFINITE_SCROLL] Scroll threshold reached, loading next page"
|
"[INFINITE_SCROLL] Near grid bottom (distance: " +
|
||||||
|
Math.round(distanceFromBottom) +
|
||||||
|
"px), loading next page"
|
||||||
);
|
);
|
||||||
self.loadNextPage();
|
self.loadNextPage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[INFINITE_SCROLL] Scroll listener attached (threshold:",
|
"[INFINITE_SCROLL] Scroll listener attached (threshold: " +
|
||||||
scrollThreshold * 100 + "%)"
|
scrollThreshold +
|
||||||
|
"px from grid bottom)"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -134,20 +252,53 @@ if (typeof document !== "undefined") {
|
||||||
/**
|
/**
|
||||||
* Reset infinite scroll to page 1 with new filters and reload products.
|
* Reset infinite scroll to page 1 with new filters and reload products.
|
||||||
* Called by realtime_search when filters change.
|
* Called by realtime_search when filters change.
|
||||||
|
*
|
||||||
|
* WARNING: This clears the grid! Only call when filters actually change.
|
||||||
*/
|
*/
|
||||||
console.log(
|
console.log(
|
||||||
"[INFINITE_SCROLL] Resetting with filters: search=" +
|
"[INFINITE_SCROLL] ⚠️⚠️⚠️ resetWithFilters CALLED - search=" +
|
||||||
searchQuery +
|
searchQuery +
|
||||||
" category=" +
|
" category=" +
|
||||||
categoryId
|
categoryId
|
||||||
);
|
);
|
||||||
|
console.trace("[INFINITE_SCROLL] ⚠️⚠️⚠️ WHO CALLED resetWithFilters? Call stack:");
|
||||||
|
|
||||||
this.searchQuery = searchQuery || "";
|
// Normalize values: empty string to "", null to "0" for category
|
||||||
this.category = categoryId || "0";
|
var newSearchQuery = (searchQuery || "").trim();
|
||||||
|
var newCategory = (categoryId || "").trim() || "0";
|
||||||
|
|
||||||
|
// CHECK IF VALUES ACTUALLY CHANGED before clearing grid!
|
||||||
|
if (newSearchQuery === this.searchQuery && newCategory === this.category) {
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] ✅ NO CHANGE - Skipping reset (values are identical)"
|
||||||
|
);
|
||||||
|
return; // Don't clear grid if nothing changed!
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] 🔥 VALUES CHANGED - Old: search=" +
|
||||||
|
this.searchQuery +
|
||||||
|
" category=" +
|
||||||
|
this.category +
|
||||||
|
" → New: search=" +
|
||||||
|
newSearchQuery +
|
||||||
|
" category=" +
|
||||||
|
newCategory
|
||||||
|
);
|
||||||
|
|
||||||
|
this.searchQuery = newSearchQuery;
|
||||||
|
this.category = newCategory;
|
||||||
this.currentPage = 0; // Set to 0 so loadNextPage() increments to 1
|
this.currentPage = 0; // Set to 0 so loadNextPage() increments to 1
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.hasMore = true;
|
this.hasMore = true;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] After normalization: search=" +
|
||||||
|
this.searchQuery +
|
||||||
|
" category=" +
|
||||||
|
this.category
|
||||||
|
);
|
||||||
|
|
||||||
// Update the config element data attributes for consistency
|
// Update the config element data attributes for consistency
|
||||||
var configEl = document.getElementById("eskaera-config");
|
var configEl = document.getElementById("eskaera-config");
|
||||||
if (configEl) {
|
if (configEl) {
|
||||||
|
|
@ -155,26 +306,58 @@ if (typeof document !== "undefined") {
|
||||||
configEl.setAttribute("data-category", this.category);
|
configEl.setAttribute("data-category", this.category);
|
||||||
configEl.setAttribute("data-current-page", "1");
|
configEl.setAttribute("data-current-page", "1");
|
||||||
configEl.setAttribute("data-has-next", "true");
|
configEl.setAttribute("data-has-next", "true");
|
||||||
|
console.log("[INFINITE_SCROLL] Updated eskaera-config attributes");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the grid and reload from page 1
|
// Clear the grid and reload from page 1
|
||||||
var grid = document.getElementById("products-grid");
|
var grid = document.getElementById("products-grid");
|
||||||
if (grid) {
|
if (grid) {
|
||||||
|
console.log("[INFINITE_SCROLL] 🗑️ CLEARING GRID NOW!");
|
||||||
grid.innerHTML = "";
|
grid.innerHTML = "";
|
||||||
console.log("[INFINITE_SCROLL] Grid cleared");
|
console.log("[INFINITE_SCROLL] Grid cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load first page with new filters
|
// Load first page with new filters
|
||||||
|
console.log("[INFINITE_SCROLL] Calling loadNextPage()...");
|
||||||
this.loadNextPage();
|
this.loadNextPage();
|
||||||
},
|
},
|
||||||
|
|
||||||
loadNextPage: function () {
|
loadNextPage: function () {
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] 🚀 loadNextPage() CALLED - currentPage=" +
|
||||||
|
this.currentPage +
|
||||||
|
" isLoading=" +
|
||||||
|
this.isLoading +
|
||||||
|
" hasMore=" +
|
||||||
|
this.hasMore
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isLoading || !this.hasMore) {
|
||||||
|
console.log("[INFINITE_SCROLL] ❌ ABORTING - already loading or no more pages");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.currentPage += 1;
|
|
||||||
|
// Only increment if we're not loading first page (currentPage will be 0 after reset)
|
||||||
|
if (this.currentPage === 0) {
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] ✅ Incrementing from 0 to 1 (first page after reset)"
|
||||||
|
);
|
||||||
|
this.currentPage = 1;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] ✅ Incrementing page " +
|
||||||
|
this.currentPage +
|
||||||
|
" → " +
|
||||||
|
(this.currentPage + 1)
|
||||||
|
);
|
||||||
|
this.currentPage += 1;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[INFINITE_SCROLL] Loading page",
|
"[INFINITE_SCROLL] 📡 About to fetch page",
|
||||||
this.currentPage,
|
this.currentPage,
|
||||||
"for order",
|
"for order",
|
||||||
this.orderId
|
this.orderId
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@
|
||||||
_attachEventListeners: function () {
|
_attachEventListeners: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Flag to prevent filtering during initialization
|
||||||
|
self.isInitializing = true;
|
||||||
|
|
||||||
// Initialize available tags from DOM
|
// Initialize available tags from DOM
|
||||||
self._initializeAvailableTags();
|
self._initializeAvailableTags();
|
||||||
|
|
||||||
|
|
@ -142,8 +145,48 @@
|
||||||
self.originalTagColors = {}; // Maps tag ID to original color
|
self.originalTagColors = {}; // Maps tag ID to original color
|
||||||
|
|
||||||
// Store last values at instance level so polling can access them
|
// Store last values at instance level so polling can access them
|
||||||
self.lastSearchValue = "";
|
// Initialize to current values to avoid triggering reset on first poll
|
||||||
self.lastCategoryValue = "";
|
self.lastSearchValue = self.searchInput.value.trim();
|
||||||
|
self.lastCategoryValue = self.categorySelect.value;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[realtimeSearch] Initial values stored - search:",
|
||||||
|
JSON.stringify(self.lastSearchValue),
|
||||||
|
"category:",
|
||||||
|
JSON.stringify(self.lastCategoryValue)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear search button - DISABLED FOR NOW
|
||||||
|
// TODO: Re-implement without causing initialization issues
|
||||||
|
/*
|
||||||
|
self.clearSearchBtn = document.getElementById("clear-search-btn");
|
||||||
|
if (self.clearSearchBtn) {
|
||||||
|
// Show/hide button based on input content (passive, no filtering)
|
||||||
|
self.searchInput.addEventListener("input", function () {
|
||||||
|
if (self.searchInput.value.trim().length > 0) {
|
||||||
|
self.clearSearchBtn.style.display = "block";
|
||||||
|
} else {
|
||||||
|
self.clearSearchBtn.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear search when button clicked
|
||||||
|
self.clearSearchBtn.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log("[realtimeSearch] Clear search button clicked");
|
||||||
|
self.searchInput.value = "";
|
||||||
|
self.clearSearchBtn.style.display = "none";
|
||||||
|
self.searchInput.focus();
|
||||||
|
// Trigger input event to update search results
|
||||||
|
self.searchInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
if (self.searchInput.value.trim().length > 0) {
|
||||||
|
self.clearSearchBtn.style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Prevent form submission completely
|
// Prevent form submission completely
|
||||||
var form = self.searchInput.closest("form");
|
var form = self.searchInput.closest("form");
|
||||||
|
|
@ -169,6 +212,11 @@
|
||||||
// Search input: listen to 'input' for real-time filtering
|
// Search input: listen to 'input' for real-time filtering
|
||||||
self.searchInput.addEventListener("input", function (e) {
|
self.searchInput.addEventListener("input", function (e) {
|
||||||
try {
|
try {
|
||||||
|
// Skip filtering during initialization
|
||||||
|
if (self.isInitializing) {
|
||||||
|
console.log("[realtimeSearch] INPUT event during init - skipping filter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
|
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
|
||||||
self._filterProducts();
|
self._filterProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -179,6 +227,11 @@
|
||||||
// Also keep 'keyup' for extra compatibility
|
// Also keep 'keyup' for extra compatibility
|
||||||
self.searchInput.addEventListener("keyup", function (e) {
|
self.searchInput.addEventListener("keyup", function (e) {
|
||||||
try {
|
try {
|
||||||
|
// Skip filtering during initialization
|
||||||
|
if (self.isInitializing) {
|
||||||
|
console.log("[realtimeSearch] KEYUP event during init - skipping filter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
|
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
|
||||||
self._filterProducts();
|
self._filterProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -189,6 +242,11 @@
|
||||||
// Category select
|
// Category select
|
||||||
self.categorySelect.addEventListener("change", function (e) {
|
self.categorySelect.addEventListener("change", function (e) {
|
||||||
try {
|
try {
|
||||||
|
// Skip filtering during initialization
|
||||||
|
if (self.isInitializing) {
|
||||||
|
console.log("[realtimeSearch] CHANGE event during init - skipping filter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log(
|
console.log(
|
||||||
'[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"'
|
'[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"'
|
||||||
);
|
);
|
||||||
|
|
@ -315,7 +373,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter products (independent of search/category state)
|
// Filter products (independent of search/category state)
|
||||||
self._filterProducts();
|
// Skip during initialization
|
||||||
|
if (!self.isInitializing) {
|
||||||
|
self._filterProducts();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -328,6 +389,11 @@
|
||||||
var pollingCounter = 0;
|
var pollingCounter = 0;
|
||||||
var pollInterval = setInterval(function () {
|
var pollInterval = setInterval(function () {
|
||||||
try {
|
try {
|
||||||
|
// Skip polling during initialization to avoid clearing products
|
||||||
|
if (self.isInitializing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pollingCounter++;
|
pollingCounter++;
|
||||||
|
|
||||||
// Try multiple ways to get the search value
|
// Try multiple ways to get the search value
|
||||||
|
|
@ -418,10 +484,13 @@
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: filter locally (but this only filters loaded products)
|
// Fallback: filter locally (but this only filters loaded products)
|
||||||
console.log(
|
// Skip during initialization
|
||||||
"[realtimeSearch] infiniteScroll not available, filtering locally only"
|
if (!self.isInitializing) {
|
||||||
);
|
console.log(
|
||||||
self._filterProducts();
|
"[realtimeSearch] infiniteScroll not available, filtering locally only"
|
||||||
|
);
|
||||||
|
self._filterProducts();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -432,6 +501,10 @@
|
||||||
console.log("[realtimeSearch] ✅ Polling interval started with ID:", pollInterval);
|
console.log("[realtimeSearch] ✅ Polling interval started with ID:", pollInterval);
|
||||||
|
|
||||||
console.log("[realtimeSearch] Event listeners attached with polling fallback");
|
console.log("[realtimeSearch] Event listeners attached with polling fallback");
|
||||||
|
|
||||||
|
// Initialization complete - allow filtering now
|
||||||
|
self.isInitializing = false;
|
||||||
|
console.log("[realtimeSearch] ✅ Initialization complete - filtering enabled");
|
||||||
},
|
},
|
||||||
|
|
||||||
_initializeAvailableTags: function () {
|
_initializeAvailableTags: function () {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@
|
||||||
|
|
||||||
console.log("Initializing cart for order:", this.orderId);
|
console.log("Initializing cart for order:", this.orderId);
|
||||||
|
|
||||||
|
// Attach event listeners FIRST (doesn't depend on translations)
|
||||||
|
this._attachEventListeners();
|
||||||
|
console.log("[groupOrderShop] Event listeners attached");
|
||||||
|
|
||||||
// Wait for i18nManager to load translations from server
|
// Wait for i18nManager to load translations from server
|
||||||
i18nManager
|
i18nManager
|
||||||
.init()
|
.init()
|
||||||
|
|
@ -52,8 +56,6 @@
|
||||||
console.log("[groupOrderShop] Translations loaded from server");
|
console.log("[groupOrderShop] Translations loaded from server");
|
||||||
self.labels = i18nManager.getAll();
|
self.labels = i18nManager.getAll();
|
||||||
|
|
||||||
// Initialize event listeners and state after translations are ready
|
|
||||||
self._attachEventListeners();
|
|
||||||
self._loadCart();
|
self._loadCart();
|
||||||
self._checkConfirmationMessage();
|
self._checkConfirmationMessage();
|
||||||
self._initializeTooltips();
|
self._initializeTooltips();
|
||||||
|
|
@ -603,12 +605,70 @@
|
||||||
_attachEventListeners: function () {
|
_attachEventListeners: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Helper function to round decimals correctly
|
||||||
|
function roundDecimal(value, decimals) {
|
||||||
|
var factor = Math.pow(10, decimals);
|
||||||
|
return Math.round(value * factor) / factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ ATTACH CHECKOUT BUTTONS (ALWAYS, on any page) ============
|
||||||
|
// These buttons exist on checkout page, not on cart pages
|
||||||
|
if (!this._cartCheckoutListenersAttached) {
|
||||||
|
console.log("[_attachEventListeners] Attaching checkout button listeners...");
|
||||||
|
|
||||||
|
// Button to save as draft (in checkout page)
|
||||||
|
var saveBtn = document.getElementById("save-order-btn");
|
||||||
|
console.log("[_attachEventListeners] save-order-btn found:", !!saveBtn);
|
||||||
|
|
||||||
|
if (saveBtn) {
|
||||||
|
saveBtn.addEventListener("click", function (e) {
|
||||||
|
console.log("[CLICK] save-order-btn clicked");
|
||||||
|
e.preventDefault();
|
||||||
|
self._saveOrderDraft();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm order button (in checkout page)
|
||||||
|
var confirmBtn = document.getElementById("confirm-order-btn");
|
||||||
|
console.log("[_attachEventListeners] confirm-order-btn found:", !!confirmBtn);
|
||||||
|
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.addEventListener("click", function (e) {
|
||||||
|
console.log("[CLICK] confirm-order-btn clicked");
|
||||||
|
e.preventDefault();
|
||||||
|
self._confirmOrder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button to reload from draft (in My Cart header - cart pages)
|
||||||
|
var reloadCartBtn = document.getElementById("reload-cart-btn");
|
||||||
|
console.log("[_attachEventListeners] reload-cart-btn found:", !!reloadCartBtn);
|
||||||
|
|
||||||
|
if (reloadCartBtn) {
|
||||||
|
reloadCartBtn.addEventListener("click", function (e) {
|
||||||
|
console.log("[CLICK] reload-cart-btn clicked");
|
||||||
|
e.preventDefault();
|
||||||
|
self._loadDraftCart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._cartCheckoutListenersAttached = true;
|
||||||
|
console.log("[_attachEventListeners] Checkout listeners attached (one-time)");
|
||||||
|
}
|
||||||
|
|
||||||
// ============ LAZY LOADING: Load More Button ============
|
// ============ LAZY LOADING: Load More Button ============
|
||||||
this._attachLoadMoreListener();
|
this._attachLoadMoreListener();
|
||||||
|
|
||||||
// Adjust quantity step based on UoM category
|
// ============ USE EVENT DELEGATION FOR QUANTITY & CART BUTTONS ============
|
||||||
// Categories without decimals (per unit): "Unit", "Units", etc.
|
// This way, new products loaded via AJAX will automatically have listeners
|
||||||
// Categories with decimals: "Weight", "Volume", "Length", etc.
|
var productsGrid = document.getElementById("products-grid");
|
||||||
|
|
||||||
|
if (!productsGrid) {
|
||||||
|
console.log("[_attachEventListeners] No products-grid found (checkout page?)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, adjust quantity steps for all existing inputs
|
||||||
var unitInputs = document.querySelectorAll(".product-qty");
|
var unitInputs = document.querySelectorAll(".product-qty");
|
||||||
console.log("=== ADJUSTING QUANTITY STEPS ===");
|
console.log("=== ADJUSTING QUANTITY STEPS ===");
|
||||||
console.log("Found " + unitInputs.length + " quantity inputs");
|
console.log("Found " + unitInputs.length + " quantity inputs");
|
||||||
|
|
@ -638,141 +698,108 @@
|
||||||
}
|
}
|
||||||
console.log("=== END ADJUSTING QUANTITY STEPS ===");
|
console.log("=== END ADJUSTING QUANTITY STEPS ===");
|
||||||
|
|
||||||
// Botones + y - para aumentar/disminuir cantidad
|
// IMPORTANT: Do NOT clone the grid node - this destroys all products!
|
||||||
// Helper function to round decimals correctly
|
// Instead, use a flag to prevent adding duplicate event listeners
|
||||||
function roundDecimal(value, decimals) {
|
if (productsGrid._delegationListenersAttached) {
|
||||||
var factor = Math.pow(10, decimals);
|
console.log(
|
||||||
return Math.round(value * factor) / factor;
|
"[_attachEventListeners] Grid delegation listeners already attached, skipping"
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
productsGrid._delegationListenersAttached = true;
|
||||||
|
console.log("[_attachEventListeners] Attaching grid delegation listeners (one-time)");
|
||||||
|
|
||||||
// Remove old listeners by cloning elements (to avoid duplication)
|
// Quantity decrease button (via event delegation)
|
||||||
var decreaseButtons = document.querySelectorAll(".qty-decrease");
|
productsGrid.addEventListener("click", function (e) {
|
||||||
for (var k = 0; k < decreaseButtons.length; k++) {
|
var decreaseBtn = e.target.closest(".qty-decrease");
|
||||||
var newBtn = decreaseButtons[k].cloneNode(true);
|
if (!decreaseBtn) return;
|
||||||
decreaseButtons[k].parentNode.replaceChild(newBtn, decreaseButtons[k]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var increaseButtons = document.querySelectorAll(".qty-increase");
|
e.preventDefault();
|
||||||
for (var k = 0; k < increaseButtons.length; k++) {
|
var productId = decreaseBtn.getAttribute("data-product-id");
|
||||||
var newBtn = increaseButtons[k].cloneNode(true);
|
var input = document.getElementById("qty_" + productId);
|
||||||
increaseButtons[k].parentNode.replaceChild(newBtn, increaseButtons[k]);
|
if (!input) return;
|
||||||
}
|
|
||||||
|
|
||||||
// Ahora asignar nuevos listeners
|
var step = parseFloat(input.step) || 1;
|
||||||
decreaseButtons = document.querySelectorAll(".qty-decrease");
|
var currentValue = parseFloat(input.value) || 0;
|
||||||
for (var k = 0; k < decreaseButtons.length; k++) {
|
var min = parseFloat(input.min) || 0;
|
||||||
decreaseButtons[k].addEventListener("click", function (e) {
|
var newValue = Math.max(min, roundDecimal(currentValue - step, 1));
|
||||||
e.preventDefault();
|
|
||||||
var productId = this.getAttribute("data-product-id");
|
|
||||||
var input = document.getElementById("qty_" + productId);
|
|
||||||
if (!input) return;
|
|
||||||
|
|
||||||
var step = parseFloat(input.step) || 1;
|
// Si es unidad, mostrar como entero
|
||||||
var currentValue = parseFloat(input.value) || 0;
|
if (input.dataset.isUnit === "true") {
|
||||||
var min = parseFloat(input.min) || 0;
|
input.value = Math.floor(newValue);
|
||||||
var newValue = Math.max(min, roundDecimal(currentValue - step, 1));
|
} else {
|
||||||
|
input.value = newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Si es unidad, mostrar como entero
|
// Quantity increase button (via event delegation)
|
||||||
if (input.dataset.isUnit === "true") {
|
productsGrid.addEventListener("click", function (e) {
|
||||||
input.value = Math.floor(newValue);
|
var increaseBtn = e.target.closest(".qty-increase");
|
||||||
} else {
|
if (!increaseBtn) return;
|
||||||
input.value = newValue;
|
|
||||||
}
|
e.preventDefault();
|
||||||
|
var productId = increaseBtn.getAttribute("data-product-id");
|
||||||
|
var input = document.getElementById("qty_" + productId);
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
var step = parseFloat(input.step) || 1;
|
||||||
|
var currentValue = parseFloat(input.value) || 0;
|
||||||
|
var newValue = roundDecimal(currentValue + step, 1);
|
||||||
|
|
||||||
|
// Si es unidad, mostrar como entero
|
||||||
|
if (input.dataset.isUnit === "true") {
|
||||||
|
input.value = Math.floor(newValue);
|
||||||
|
} else {
|
||||||
|
input.value = newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to cart button (via event delegation)
|
||||||
|
productsGrid.addEventListener("click", function (e) {
|
||||||
|
var cartBtn = e.target.closest(".add-to-cart-btn");
|
||||||
|
if (!cartBtn) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
var form = cartBtn.closest(".add-to-cart-form");
|
||||||
|
var productId = form.getAttribute("data-product-id");
|
||||||
|
var productName = form.getAttribute("data-product-name") || "Product";
|
||||||
|
var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
|
||||||
|
var quantityInput = form.querySelector(".product-qty");
|
||||||
|
var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
|
||||||
|
|
||||||
|
console.log("Adding:", {
|
||||||
|
productId: productId,
|
||||||
|
productName: productName,
|
||||||
|
productPrice: productPrice,
|
||||||
|
quantity: quantity,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
increaseButtons = document.querySelectorAll(".qty-increase");
|
if (quantity > 0) {
|
||||||
for (var k = 0; k < increaseButtons.length; k++) {
|
self._addToCart(productId, productName, productPrice, quantity);
|
||||||
increaseButtons[k].addEventListener("click", function (e) {
|
} else {
|
||||||
e.preventDefault();
|
var labels = self._getLabels();
|
||||||
var productId = this.getAttribute("data-product-id");
|
self._showNotification(
|
||||||
var input = document.getElementById("qty_" + productId);
|
labels.invalid_quantity || "Please enter a valid quantity",
|
||||||
if (!input) return;
|
"warning"
|
||||||
|
);
|
||||||
var step = parseFloat(input.step) || 1;
|
}
|
||||||
var currentValue = parseFloat(input.value) || 0;
|
});
|
||||||
var newValue = roundDecimal(currentValue + step, 1);
|
|
||||||
|
|
||||||
// Si es unidad, mostrar como entero
|
|
||||||
if (input.dataset.isUnit === "true") {
|
|
||||||
input.value = Math.floor(newValue);
|
|
||||||
} else {
|
|
||||||
input.value = newValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Botones de agregar al carrito
|
|
||||||
var buttons = document.querySelectorAll(".add-to-cart-btn");
|
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
|
||||||
buttons[i].addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var form = this.closest(".add-to-cart-form");
|
|
||||||
var productId = form.getAttribute("data-product-id");
|
|
||||||
var productName = form.getAttribute("data-product-name") || "Product";
|
|
||||||
var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
|
|
||||||
var quantityInput = form.querySelector(".product-qty");
|
|
||||||
var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
|
|
||||||
|
|
||||||
console.log("Adding:", {
|
|
||||||
productId: productId,
|
|
||||||
productName: productName,
|
|
||||||
productPrice: productPrice,
|
|
||||||
quantity: quantity,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (quantity > 0) {
|
|
||||||
self._addToCart(productId, productName, productPrice, quantity);
|
|
||||||
} else {
|
|
||||||
var labels = self._getLabels();
|
|
||||||
self._showNotification(
|
|
||||||
labels.invalid_quantity || "Please enter a valid quantity",
|
|
||||||
"warning"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button to save cart as draft (in My Cart header)
|
// Button to save cart as draft (in My Cart header)
|
||||||
var savCartBtn = document.getElementById("save-cart-btn");
|
// Only attach ONCE
|
||||||
if (savCartBtn) {
|
if (!this._cartCheckoutListenersAttached) {
|
||||||
// Remove old listeners by cloning
|
console.log("[_attachEventListeners] Attempting to attach checkout listeners...");
|
||||||
var savCartBtnNew = savCartBtn.cloneNode(true);
|
|
||||||
savCartBtn.parentNode.replaceChild(savCartBtnNew, savCartBtn);
|
|
||||||
savCartBtnNew.addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self._saveCartAsDraft();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button to reload from draft (in My Cart header)
|
var savCartBtn = document.getElementById("save-cart-btn");
|
||||||
var reloadCartBtn = document.getElementById("reload-cart-btn");
|
console.log("[_attachEventListeners] save-cart-btn found:", !!savCartBtn);
|
||||||
if (reloadCartBtn) {
|
|
||||||
// Remove old listeners by cloning
|
|
||||||
var reloadCartBtnNew = reloadCartBtn.cloneNode(true);
|
|
||||||
reloadCartBtn.parentNode.replaceChild(reloadCartBtnNew, reloadCartBtn);
|
|
||||||
reloadCartBtnNew.addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self._loadDraftCart();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button to save as draft
|
if (savCartBtn) {
|
||||||
var saveBtn = document.getElementById("save-order-btn");
|
savCartBtn.addEventListener("click", function (e) {
|
||||||
if (saveBtn) {
|
console.log("[CLICK] save-cart-btn clicked");
|
||||||
saveBtn.addEventListener("click", function (e) {
|
e.preventDefault();
|
||||||
e.preventDefault();
|
self._saveCartAsDraft();
|
||||||
self._saveOrderDraft();
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm order button
|
|
||||||
var confirmBtn = document.getElementById("confirm-order-btn");
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.addEventListener("click", function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
self._confirmOrder();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1475,7 +1502,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
_saveOrderDraft: function () {
|
_saveOrderDraft: function () {
|
||||||
console.log("Saving order as draft:", this.orderId);
|
console.log("[_saveOrderDraft] Starting - this.orderId:", this.orderId);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var items = [];
|
var items = [];
|
||||||
|
|
@ -1979,13 +2006,10 @@
|
||||||
console.log("cart-items-container found:", !!cartContainer);
|
console.log("cart-items-container found:", !!cartContainer);
|
||||||
console.log("confirm-order-btn found:", !!confirmBtn);
|
console.log("confirm-order-btn found:", !!confirmBtn);
|
||||||
|
|
||||||
if (cartContainer || confirmBtn) {
|
// Always initialize - it handles both cart pages and checkout pages
|
||||||
console.log("Calling init()");
|
console.log("Calling init()");
|
||||||
var result = window.groupOrderShop.init();
|
var result = window.groupOrderShop.init();
|
||||||
console.log("init() result:", result);
|
console.log("init() result:", result);
|
||||||
} else {
|
|
||||||
console.warn("No elements found to initialize cart");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle confirm order buttons in portal (My Orders page)
|
// Handle confirm order buttons in portal (My Orders page)
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,14 @@
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<!-- CRITICAL: This input is NOT inside a form to prevent Odoo from transforming it -->
|
<!-- CRITICAL: This input is NOT inside a form to prevent Odoo from transforming it -->
|
||||||
<!-- It must remain a pure HTML input element for realtime_search.js to detect value changes -->
|
<!-- It must remain a pure HTML input element for realtime_search.js to detect value changes -->
|
||||||
|
<!-- TODO: Re-add clear button wrapper after fixing initialization issues
|
||||||
|
<div style="position: relative;">
|
||||||
|
<input type="text" id="realtime-search-input" class="form-control realtime-search-box search-input-styled" placeholder="Search products..." autocomplete="off" style="padding-right: 40px;" />
|
||||||
|
<button type="button" id="clear-search-btn" class="btn btn-link" style="position: absolute; right: 5px; top: 50%; transform: translateY(-50%); padding: 0; width: 30px; height: 30px; display: none; color: #6c757d; text-decoration: none; font-size: 1.5rem; line-height: 1;" aria-label="Clear search" title="Clear search">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
<input type="text" id="realtime-search-input" class="form-control realtime-search-box search-input-styled" placeholder="Search products..." autocomplete="off" />
|
<input type="text" id="realtime-search-input" class="form-control realtime-search-box search-input-styled" placeholder="Search products..." autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
|
|
@ -551,7 +559,18 @@
|
||||||
<t t-call="website_sale_aplicoop.eskaera_shop_products" />
|
<t t-call="website_sale_aplicoop.eskaera_shop_products" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Infinite scroll container -->
|
<!-- Data attributes for infinite scroll configuration (ALWAYS present for JavaScript) -->
|
||||||
|
<div id="eskaera-config"
|
||||||
|
t-attf-data-order-id="{{ group_order.id }}"
|
||||||
|
t-attf-data-search="{{ search_query }}"
|
||||||
|
t-attf-data-category="{{ selected_category }}"
|
||||||
|
t-attf-data-per-page="{{ per_page }}"
|
||||||
|
t-attf-data-current-page="{{ current_page }}"
|
||||||
|
t-attf-data-has-next="{{ 'true' if has_next else 'false' }}"
|
||||||
|
class="d-none">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Infinite scroll container (only if enabled and has more pages) -->
|
||||||
<t t-if="lazy_loading_enabled and has_next">
|
<t t-if="lazy_loading_enabled and has_next">
|
||||||
<div id="infinite-scroll-container" class="row mt-4">
|
<div id="infinite-scroll-container" class="row mt-4">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center">
|
||||||
|
|
@ -578,17 +597,6 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Data attributes for infinite scroll configuration -->
|
|
||||||
<div id="eskaera-config"
|
|
||||||
t-attf-data-order-id="{{ group_order.id }}"
|
|
||||||
t-attf-data-search="{{ search_query }}"
|
|
||||||
t-attf-data-category="{{ selected_category }}"
|
|
||||||
t-attf-data-per-page="{{ per_page }}"
|
|
||||||
t-attf-data-current-page="{{ current_page }}"
|
|
||||||
t-attf-data-has-next="{{ 'true' if has_next else 'false' }}"
|
|
||||||
class="d-none">
|
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
|
|
@ -657,19 +665,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts (in dependency order) -->
|
<!-- Scripts are loaded from web.assets_frontend in __manifest__.py
|
||||||
<!-- Load i18n_manager first - fetches translations from server -->
|
(i18n_manager, i18n_helpers, website_sale, checkout_labels,
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
|
home_delivery, realtime_search, infinite_scroll) -->
|
||||||
<!-- Keep legacy helpers for backwards compatibility -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
|
|
||||||
<!-- Main shop functionality (depends on i18nManager) -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
|
|
||||||
<!-- UI enhancements -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/realtime_search.js" />
|
|
||||||
<!-- Infinite scroll for lazy loading products -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/infinite_scroll.js" />
|
|
||||||
|
|
||||||
<!-- Initialize tooltips using native title attribute -->
|
<!-- Initialize tooltips using native title attribute -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
@ -1004,17 +1002,8 @@
|
||||||
console.log('[LABELS] Initialized from server:', window.groupOrderShop.labels);
|
console.log('[LABELS] Initialized from server:', window.groupOrderShop.labels);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<!-- Scripts (in dependency order) -->
|
<!-- Scripts are loaded from web.assets_frontend in __manifest__.py -->
|
||||||
<!-- Load i18n_manager first - fetches translations from server -->
|
<!-- (i18n_manager, i18n_helpers, website_sale, checkout_labels, home_delivery, checkout_summary) -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
|
|
||||||
<!-- Keep legacy helpers for backwards compatibility -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
|
|
||||||
<!-- Main shop functionality (depends on i18nManager) -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
|
|
||||||
<!-- UI enhancements -->
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
|
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_summary.js" />
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Auto-load cart from localStorage when accessing checkout directly
|
// Auto-load cart from localStorage when accessing checkout directly
|
||||||
(function() {
|
(function() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue