Add sessionStorage caching for AJAX requests and URL state restoration
- Add unified AjaxCache for all AJAX responses (5 min expiry) - Cache key based on request params (minus nonce), coordinates rounded to 4 decimals - Clean expired cache entries on page load - URL hash stores page, scroll, lat/lng/zoom for state restoration - Bulk load pages in parallel on restore, use cache when available - Add min-height 100vh to property results in map view - Change all scroll animations to instant
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -9,6 +9,139 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* AJAX Cache Manager
|
||||
* Caches any AJAX response in sessionStorage based on request data
|
||||
* Key: HOMEPROZ_AJAX_{hash of request data minus nonce}
|
||||
* Expires after 5 minutes
|
||||
*/
|
||||
var AjaxCache = {
|
||||
PREFIX: 'HOMEPROZ_AJAX_',
|
||||
EXPIRY_MS: 5 * 60 * 1000, // 5 minutes
|
||||
|
||||
/**
|
||||
* Initialize - clean up expired entries on page load
|
||||
*/
|
||||
init: function() {
|
||||
this.cleanExpired();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean up all expired cache entries
|
||||
*/
|
||||
cleanExpired: function() {
|
||||
try {
|
||||
var now = Date.now();
|
||||
var keysToRemove = [];
|
||||
|
||||
for (var i = 0; i < sessionStorage.length; i++) {
|
||||
var key = sessionStorage.key(i);
|
||||
if (key && key.indexOf(this.PREFIX) === 0) {
|
||||
try {
|
||||
var cached = JSON.parse(sessionStorage.getItem(key));
|
||||
if (now - cached.time > this.EXPIRY_MS) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
} catch (e) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keysToRemove.forEach(function(key) {
|
||||
sessionStorage.removeItem(key);
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize request data for cache key (remove nonce, round coordinates)
|
||||
*/
|
||||
normalizeData: function(data) {
|
||||
var normalized = {};
|
||||
|
||||
for (var key in data) {
|
||||
if (key === 'nonce') continue; // Skip nonce
|
||||
|
||||
var value = data[key];
|
||||
|
||||
// Round coordinate arrays to 4 decimal places (~11m precision)
|
||||
if (Array.isArray(value)) {
|
||||
normalized[key] = value.map(function(v) {
|
||||
return typeof v === 'number' ? Math.round(v * 10000) / 10000 : v;
|
||||
});
|
||||
} else {
|
||||
normalized[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate cache key from request data
|
||||
*/
|
||||
getKey: function(data) {
|
||||
var normalized = this.normalizeData(data);
|
||||
var str = JSON.stringify(normalized);
|
||||
|
||||
// Simple hash
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
|
||||
return this.PREFIX + Math.abs(hash).toString(36);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get cached response if valid
|
||||
*/
|
||||
get: function(data) {
|
||||
try {
|
||||
var key = this.getKey(data);
|
||||
var stored = sessionStorage.getItem(key);
|
||||
if (!stored) return null;
|
||||
|
||||
var cached = JSON.parse(stored);
|
||||
var now = Date.now();
|
||||
|
||||
// Check expiry
|
||||
if (now - cached.time > this.EXPIRY_MS) {
|
||||
sessionStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.data;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Store response in cache
|
||||
*/
|
||||
set: function(data, response) {
|
||||
try {
|
||||
var key = this.getKey(data);
|
||||
var cached = {
|
||||
time: Date.now(),
|
||||
data: response
|
||||
};
|
||||
sessionStorage.setItem(key, JSON.stringify(cached));
|
||||
} catch (e) {
|
||||
// Storage full or disabled - ignore
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize cache cleanup on load
|
||||
AjaxCache.init();
|
||||
|
||||
/**
|
||||
* Request Queue Manager
|
||||
* Handles debouncing and cancellation of AJAX requests to prevent race conditions.
|
||||
@@ -209,6 +342,10 @@
|
||||
var self = this;
|
||||
this.map.on('moveend zoomend', function() {
|
||||
self.loadClusters();
|
||||
// Update URL after a delay to let scroll reset happen first
|
||||
setTimeout(function() {
|
||||
PropertyFilters.updateUrlState();
|
||||
}, 400);
|
||||
});
|
||||
|
||||
// Bind card hover events
|
||||
@@ -221,6 +358,7 @@
|
||||
/**
|
||||
* Load clusters/markers from server based on viewport
|
||||
* Uses RequestQueue for debouncing and cancellation
|
||||
* Caches responses in sessionStorage for 5 minutes
|
||||
*/
|
||||
loadClusters: function() {
|
||||
if (!this.map) return;
|
||||
@@ -254,6 +392,27 @@
|
||||
// Also update the property list with the same viewport (queued separately)
|
||||
PropertyFilters.updateFromMap(boundsArray, centerArray);
|
||||
|
||||
// Check cache first
|
||||
var cached = AjaxCache.get(requestData);
|
||||
if (cached && cached.success) {
|
||||
// Use cached data immediately
|
||||
var data = cached.data;
|
||||
this.currentMode = data.type;
|
||||
|
||||
switch (data.type) {
|
||||
case 'density':
|
||||
this.renderDensity(data.dots);
|
||||
break;
|
||||
case 'clusters':
|
||||
this.renderClusters(data.clusters);
|
||||
break;
|
||||
case 'markers':
|
||||
this.renderMarkers(data.markers);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the cluster request with debounce and cancellation
|
||||
RequestQueue.queue(
|
||||
'clusters',
|
||||
@@ -266,6 +425,9 @@
|
||||
},
|
||||
function(response, requestId) {
|
||||
if (response.success) {
|
||||
// Cache the full response
|
||||
AjaxCache.set(requestData, response);
|
||||
|
||||
var data = response.data;
|
||||
self.currentMode = data.type;
|
||||
|
||||
@@ -926,18 +1088,17 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize filters from URL (hash for page)
|
||||
* Initialize filters from URL (hash for page, scroll, map state)
|
||||
*/
|
||||
initFromUrl: function() {
|
||||
var self = this;
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var hasFilters = false;
|
||||
|
||||
// Set form values from URL query params (filters only, not page)
|
||||
this.$form.find('select').each(function() {
|
||||
var name = $(this).attr('name');
|
||||
if (params.has(name)) {
|
||||
$(this).val(params.get(name));
|
||||
hasFilters = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -946,16 +1107,232 @@
|
||||
var name = $(this).attr('name');
|
||||
if (params.has(name)) {
|
||||
$(this).val(params.get(name));
|
||||
hasFilters = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Check for page in hash
|
||||
var page = this.getPageFromHash();
|
||||
// Get full state from hash
|
||||
var state = this.getStateFromHash();
|
||||
|
||||
// If we have a page > 1 in hash, load that page
|
||||
if (page > 1) {
|
||||
this.filterProperties(page, false);
|
||||
// Store restoration state for map init to use
|
||||
if (state && (state.lat !== null || state.page > 1)) {
|
||||
this.pendingRestoreState = state;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore full page state (called after map is ready)
|
||||
* Loads all pages up to saved page, then restores scroll position
|
||||
*/
|
||||
restoreState: function() {
|
||||
var self = this;
|
||||
var state = this.pendingRestoreState;
|
||||
|
||||
if (!state) return;
|
||||
this.pendingRestoreState = null;
|
||||
|
||||
// If we have map coordinates, set map position first (without triggering load)
|
||||
if (state.lat !== null && state.lng !== null && state.zoom !== null && PropertyMap.map) {
|
||||
// Temporarily disable map events
|
||||
PropertyMap.map.off('moveend zoomend');
|
||||
|
||||
// Set map view
|
||||
PropertyMap.map.setView([state.lat, state.lng], state.zoom);
|
||||
|
||||
// Get bounds from restored map position for AJAX requests
|
||||
var bounds = PropertyMap.map.getBounds();
|
||||
var center = PropertyMap.map.getCenter();
|
||||
this.mapBounds = [
|
||||
bounds.getSouthWest().lat,
|
||||
bounds.getSouthWest().lng,
|
||||
bounds.getNorthEast().lat,
|
||||
bounds.getNorthEast().lng
|
||||
];
|
||||
this.mapCenter = [center.lat, center.lng];
|
||||
|
||||
// Load map clusters for the restored position
|
||||
PropertyMap.loadClusters();
|
||||
|
||||
// Re-enable map events after a tick
|
||||
setTimeout(function() {
|
||||
PropertyMap.map.on('moveend zoomend', function() {
|
||||
PropertyMap.loadClusters();
|
||||
setTimeout(function() {
|
||||
PropertyFilters.updateUrlState();
|
||||
}, 400);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// If we have a page > 1, bulk load all pages
|
||||
if (state.page > 1) {
|
||||
this.bulkLoadPages(state.page, state.scroll);
|
||||
} else if (state.scroll > 0) {
|
||||
// Just restore scroll position
|
||||
window.scrollTo({ top: state.scroll, behavior: 'instant' });
|
||||
$(window).trigger('scroll');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk load pages 1 through targetPage, then render and restore scroll
|
||||
* Uses sessionStorage cache for instant restores when data is cached
|
||||
*/
|
||||
bulkLoadPages: function(targetPage, scrollPosition) {
|
||||
var self = this;
|
||||
var formData = this.getFormData();
|
||||
|
||||
// Block infinite scroll during restoration
|
||||
InfiniteScrollState.isRestoring = true;
|
||||
|
||||
// Create array of page numbers to load (1 through targetPage + 1 for buffer)
|
||||
var pagesToLoad = [];
|
||||
for (var i = 1; i <= targetPage + 1; i++) {
|
||||
pagesToLoad.push(i);
|
||||
}
|
||||
|
||||
// Build base request data
|
||||
var baseRequestData = {
|
||||
action: 'homeproz_filter_properties',
|
||||
nonce: homeprozAjax.nonce,
|
||||
property_type: formData.property_type,
|
||||
property_location: formData.property_location,
|
||||
zip: formData.zip,
|
||||
min_price: formData.min_price,
|
||||
max_price: formData.max_price,
|
||||
beds: formData.beds,
|
||||
cards_only: 'true'
|
||||
};
|
||||
|
||||
// Add map bounds if available
|
||||
if (this.mapBounds) {
|
||||
baseRequestData.bounds = this.mapBounds;
|
||||
}
|
||||
if (this.mapCenter) {
|
||||
baseRequestData.center = this.mapCenter;
|
||||
}
|
||||
|
||||
// Check cache for all pages
|
||||
var cachedResults = [];
|
||||
var uncachedPages = [];
|
||||
|
||||
pagesToLoad.forEach(function(page) {
|
||||
var requestData = $.extend({}, baseRequestData, { paged: page });
|
||||
var cached = AjaxCache.get(requestData);
|
||||
if (cached && cached.success && cached.data && cached.data.html) {
|
||||
cachedResults.push({
|
||||
page: page,
|
||||
html: cached.data.html,
|
||||
max_pages: cached.data.max_pages || 1
|
||||
});
|
||||
} else {
|
||||
uncachedPages.push(page);
|
||||
}
|
||||
});
|
||||
|
||||
// If all pages are cached, render immediately
|
||||
if (uncachedPages.length === 0) {
|
||||
this.renderBulkResults(cachedResults, targetPage, scrollPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading spinner only if we need to fetch
|
||||
this.$results.html('<div class="property-results-loading"><div class="spinner"></div></div>');
|
||||
|
||||
// Fire requests only for uncached pages
|
||||
var requests = uncachedPages.map(function(page) {
|
||||
var requestData = $.extend({}, baseRequestData, { paged: page });
|
||||
return $.ajax({
|
||||
url: homeprozAjax.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: requestData
|
||||
}).then(function(response) {
|
||||
var result = {
|
||||
page: page,
|
||||
html: response.success ? response.data.html : '',
|
||||
max_pages: response.success ? response.data.max_pages : 0
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
if (response.success) {
|
||||
AjaxCache.set(requestData, response);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for all to complete
|
||||
$.when.apply($, requests).done(function() {
|
||||
// Collect results (handle both single and multiple args)
|
||||
var fetchedResults = requests.length === 1 ? [arguments[0]] :
|
||||
Array.prototype.slice.call(arguments);
|
||||
|
||||
// Combine cached and fetched results
|
||||
var allResults = cachedResults.concat(fetchedResults);
|
||||
|
||||
self.renderBulkResults(allResults, targetPage, scrollPosition);
|
||||
}).fail(function() {
|
||||
// On error, fall back to normal loading
|
||||
InfiniteScrollState.isRestoring = false;
|
||||
self.filterProperties(1, false);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render bulk loaded results to the DOM
|
||||
*/
|
||||
renderBulkResults: function(results, targetPage, scrollPosition) {
|
||||
var self = this;
|
||||
|
||||
// Sort by page number
|
||||
results.sort(function(a, b) { return a.page - b.page; });
|
||||
|
||||
// Get max_pages from first result
|
||||
var maxPages = results[0] ? results[0].max_pages : 1;
|
||||
|
||||
// Build the full HTML with page wrappers
|
||||
var fullHtml = '<div class="properties-meta"><p class="properties-count">Loading...</p></div>';
|
||||
fullHtml += '<div id="property-results-grid" class="properties-grid">';
|
||||
|
||||
results.forEach(function(result) {
|
||||
if (result.html && result.page <= maxPages) {
|
||||
fullHtml += '<div class="infinite-scroll-page" data-page="' + result.page + '" data-state="populated">';
|
||||
fullHtml += result.html;
|
||||
fullHtml += '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
fullHtml += '</div>';
|
||||
|
||||
// Render all at once
|
||||
this.$results.html(fullHtml);
|
||||
|
||||
// Update infinite scroll state
|
||||
InfiniteScrollState.currentPage = Math.min(targetPage + 1, maxPages);
|
||||
InfiniteScrollState.maxPages = maxPages;
|
||||
InfiniteScrollState.pages = {};
|
||||
|
||||
// Register all loaded pages
|
||||
results.forEach(function(result) {
|
||||
if (result.page <= maxPages) {
|
||||
InfiniteScrollState.pages[result.page] = { state: 'populated' };
|
||||
}
|
||||
});
|
||||
|
||||
// Restore scroll position
|
||||
if (scrollPosition > 0) {
|
||||
window.scrollTo({ top: scrollPosition, behavior: 'instant' });
|
||||
}
|
||||
|
||||
// Unblock infinite scroll
|
||||
InfiniteScrollState.isRestoring = false;
|
||||
|
||||
// Trigger scroll event for image loader and other listeners
|
||||
$(window).trigger('scroll');
|
||||
|
||||
// Start image loader
|
||||
if (typeof CardImageLoader !== 'undefined') {
|
||||
CardImageLoader.process();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -982,10 +1359,30 @@
|
||||
InfiniteScroll.reset();
|
||||
}
|
||||
|
||||
// Block scroll state until user actually scrolls
|
||||
InfiniteScrollState.currentPage = 1;
|
||||
this._scrollBlocked = true;
|
||||
this.clearScrollFromUrl();
|
||||
|
||||
// Reset to page 1 when map viewport changes
|
||||
this.filterProperties(1, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove scroll parameter from URL hash
|
||||
*/
|
||||
clearScrollFromUrl: function() {
|
||||
var hash = window.location.hash.replace('#', '');
|
||||
if (!hash) return;
|
||||
|
||||
var parts = hash.split('&').filter(function(part) {
|
||||
return !part.startsWith('scroll=');
|
||||
});
|
||||
|
||||
var newHash = parts.length ? '#' + parts.join('&') : '';
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search + newHash);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear pin selection state and visuals
|
||||
*/
|
||||
@@ -1002,9 +1399,8 @@
|
||||
* Get page number from URL hash
|
||||
*/
|
||||
getPageFromHash: function() {
|
||||
var hash = window.location.hash;
|
||||
var match = hash.match(/#page=(\d+)/);
|
||||
return match ? parseInt(match[1]) : 1;
|
||||
var state = this.getStateFromHash();
|
||||
return state ? state.page : 1;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1093,20 +1489,14 @@
|
||||
var $card = $('#property-' + PropertyMap.selectedPropertyId);
|
||||
if ($card.length) {
|
||||
// Scroll to the selected card
|
||||
$('html, body').animate({
|
||||
scrollTop: $card.offset().top - 120
|
||||
}, 300, function() {
|
||||
// Ensure card stays highlighted
|
||||
window.scrollTo({ top: $card.offset().top - 120, behavior: 'instant' });
|
||||
$card.addClass('property-card-highlighted');
|
||||
});
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
// Scroll to top of results on page change (non-pin triggered)
|
||||
else if (page > 1) {
|
||||
$('html, body').animate({
|
||||
scrollTop: self.$filters.offset().top - 100
|
||||
}, 300);
|
||||
window.scrollTo({ top: self.$filters.offset().top - 100, behavior: 'instant' });
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1152,7 +1542,8 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Update browser URL (filters in query, page in hash)
|
||||
* Update browser URL (filters in query, state in hash)
|
||||
* Stores: page, scroll position, map position/zoom
|
||||
*/
|
||||
updateUrl: function(formData, page) {
|
||||
var url = new URL(homeprozAjax.archiveUrl);
|
||||
@@ -1164,17 +1555,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Add page to hash if > 1
|
||||
// Build hash with all state info
|
||||
var hashParts = [];
|
||||
|
||||
if (page > 1) {
|
||||
url.hash = 'page=' + page;
|
||||
} else {
|
||||
url.hash = '';
|
||||
hashParts.push('page=' + page);
|
||||
}
|
||||
|
||||
// Add scroll position (unless blocked - requires user scroll to unblock)
|
||||
if (!this._scrollBlocked) {
|
||||
var scrollY = window.pageYOffset || document.documentElement.scrollTop;
|
||||
if (scrollY > 0) {
|
||||
hashParts.push('scroll=' + Math.round(scrollY));
|
||||
}
|
||||
}
|
||||
|
||||
// Add map state if map exists
|
||||
if (PropertyMap.map) {
|
||||
var center = PropertyMap.map.getCenter();
|
||||
var zoom = PropertyMap.map.getZoom();
|
||||
hashParts.push('lat=' + center.lat.toFixed(6));
|
||||
hashParts.push('lng=' + center.lng.toFixed(6));
|
||||
hashParts.push('zoom=' + zoom);
|
||||
}
|
||||
|
||||
url.hash = hashParts.length ? hashParts.join('&') : '';
|
||||
|
||||
// Use replaceState to avoid adding history entries for every page
|
||||
history.replaceState(null, '', url.toString());
|
||||
},
|
||||
|
||||
/**
|
||||
* Update URL with current state (called on scroll and map move)
|
||||
* Debounced to avoid excessive history updates
|
||||
*/
|
||||
updateUrlState: function() {
|
||||
var self = this;
|
||||
|
||||
// Debounce URL updates
|
||||
clearTimeout(this._urlUpdateTimeout);
|
||||
this._urlUpdateTimeout = setTimeout(function() {
|
||||
// Get current page from infinite scroll state or default to 1
|
||||
var page = InfiniteScrollState.currentPage || 1;
|
||||
var formData = self.getFormData();
|
||||
self.updateUrl(formData, page);
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse state from URL hash
|
||||
*/
|
||||
getStateFromHash: function() {
|
||||
var hash = window.location.hash.replace('#', '');
|
||||
if (!hash) return null;
|
||||
|
||||
var state = {};
|
||||
hash.split('&').forEach(function(part) {
|
||||
var kv = part.split('=');
|
||||
if (kv.length === 2) {
|
||||
state[kv[0]] = kv[1];
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
page: state.page ? parseInt(state.page) : 1,
|
||||
scroll: state.scroll ? parseInt(state.scroll) : 0,
|
||||
lat: state.lat ? parseFloat(state.lat) : null,
|
||||
lng: state.lng ? parseFloat(state.lng) : null,
|
||||
zoom: state.zoom ? parseInt(state.zoom) : null
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get page number from pagination link URL
|
||||
*/
|
||||
@@ -1223,6 +1674,11 @@
|
||||
if (this.isAboveBreakpoint && this.isMapView && typeof homeprozMapData !== 'undefined') {
|
||||
PropertyMap.init(homeprozMapData.initialFilters || {});
|
||||
this.mapInitialized = true;
|
||||
|
||||
// Restore state if we have pending state to restore
|
||||
if (PropertyFilters.pendingRestoreState) {
|
||||
PropertyFilters.restoreState();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resize
|
||||
@@ -1258,6 +1714,11 @@
|
||||
if (!this.mapInitialized && typeof homeprozMapData !== 'undefined') {
|
||||
PropertyMap.init(homeprozMapData.initialFilters || {});
|
||||
this.mapInitialized = true;
|
||||
|
||||
// Restore state if we have pending state to restore
|
||||
if (PropertyFilters.pendingRestoreState) {
|
||||
PropertyFilters.restoreState();
|
||||
}
|
||||
} else if (PropertyMap.map) {
|
||||
// Invalidate size to fix map rendering after show
|
||||
setTimeout(function() {
|
||||
@@ -1441,8 +1902,10 @@
|
||||
pages: {}, // pageNum -> {state, height, html}
|
||||
totalPages: 0,
|
||||
totalPosts: 0,
|
||||
currentPage: 1, // Current page based on scroll position
|
||||
pendingPage: null, // Currently loading page number (only one at a time)
|
||||
isEnabled: false,
|
||||
isRestoring: false, // True during bulk page restoration
|
||||
cardsPerPage: 12
|
||||
};
|
||||
|
||||
@@ -1525,8 +1988,15 @@
|
||||
clearTimeout(self.scrollTimeout);
|
||||
self.scrollTimeout = setTimeout(function() {
|
||||
self.syncPages();
|
||||
// Update URL state on scroll
|
||||
PropertyFilters.updateUrlState();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Listen for actual user wheel events to unblock scroll state
|
||||
$(window).on('wheel.infiniteScroll', function() {
|
||||
PropertyFilters._scrollBlocked = false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1534,6 +2004,7 @@
|
||||
*/
|
||||
syncPages: function() {
|
||||
if (!this.$grid || !InfiniteScrollState.isEnabled) return;
|
||||
if (InfiniteScrollState.isRestoring) return; // Block during state restoration
|
||||
|
||||
var TP = InfiniteScrollState.totalPages;
|
||||
|
||||
@@ -1544,6 +2015,9 @@
|
||||
if (CP > TP - 2) CP = TP - 2;
|
||||
if (CP < 1) CP = 1;
|
||||
|
||||
// Track current page for URL state
|
||||
InfiniteScrollState.currentPage = CP;
|
||||
|
||||
// 3. Calculate DLP (Desired Loaded Pages)
|
||||
var DLP = [CP - 2, CP - 1, CP, CP + 1, CP + 2];
|
||||
|
||||
@@ -1763,14 +2237,12 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a page via AJAX
|
||||
* Load a page via AJAX (with sessionStorage cache)
|
||||
*/
|
||||
loadPage: function(pageNum) {
|
||||
var self = this;
|
||||
|
||||
InfiniteScrollState.pendingPage = pageNum;
|
||||
|
||||
var formData = PropertyFilters.getFormData();
|
||||
|
||||
var requestData = {
|
||||
action: 'homeproz_filter_properties',
|
||||
nonce: homeprozAjax.nonce,
|
||||
@@ -1791,12 +2263,41 @@
|
||||
requestData.center = PropertyFilters.mapCenter;
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
var cached = AjaxCache.get(requestData);
|
||||
if (cached && cached.success && cached.data && cached.data.html) {
|
||||
// Use cached data immediately
|
||||
if (!InfiniteScrollState.pages[pageNum]) {
|
||||
InfiniteScrollState.pages[pageNum] = {};
|
||||
}
|
||||
InfiniteScrollState.pages[pageNum].state = 'populated';
|
||||
|
||||
var $page = self.$grid.find('.infinite-scroll-page[data-page="' + pageNum + '"]');
|
||||
if ($page.length) {
|
||||
$page.html(cached.data.html);
|
||||
$page.attr('data-state', 'populated');
|
||||
|
||||
if (typeof CardImageLoader !== 'undefined') {
|
||||
CardImageLoader.process();
|
||||
}
|
||||
}
|
||||
|
||||
// Continue loading more pages if needed
|
||||
self.syncPages();
|
||||
return;
|
||||
}
|
||||
|
||||
InfiniteScrollState.pendingPage = pageNum;
|
||||
|
||||
$.ajax({
|
||||
url: homeprozAjax.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: requestData,
|
||||
success: function(response) {
|
||||
if (response.success && response.data.html) {
|
||||
// Cache the response
|
||||
AjaxCache.set(requestData, response);
|
||||
|
||||
// Update state
|
||||
if (!InfiniteScrollState.pages[pageNum]) {
|
||||
InfiniteScrollState.pages[pageNum] = {};
|
||||
|
||||
@@ -202,6 +202,13 @@
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Min height in map view to prevent layout collapse during loading/restore
|
||||
@media (min-width: 1024px) {
|
||||
.is-map-view & #property-results {
|
||||
min-height: 100vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view properties grid
|
||||
|
||||
Reference in New Issue
Block a user