1/** 2 * Blog Revamp â Filters, Pagination & AJAX Grid. 3 * Modifications: Abdullah Abbasi 4 * Task: Blog System Revamp 5 * 6 * Handles the interactive behaviour of the /blog listing page: category 7 * filter switching, AJAX-powered post grid loading, and pagination with 8 * smooth scroll-to-top. 9 * 10 * 11 * @package HelloElementorChild 12 */ 13 14var blogAbortController = null; 15 16document.addEventListener('DOMContentLoaded', function () { 17 initBlogFilters(); 18 initBlogPagination(); 19}); 20 21/** 22 * Show the slider section and hide the grid container. 23 * 24 * Called when the "All Posts" category is selected. Restores the default 25 * blog page view with horizontal content sliders. 26 * 27 * @return {void} 28 */ 29function showSlidersHideGrid() { 30 var slidersSection = document.getElementById('blog-sliders-section'); 31 var gridContainer = document.getElementById('blog-grid-container'); 32 if (slidersSection) { slidersSection.style.display = 'block'; slidersSection.style.opacity = '1'; } 33 if (gridContainer) gridContainer.style.display = 'none'; 34} 35 36/** 37 * Show the grid container and hide the slider section. 38 * 39 * Called when a specific category is selected. Displays the AJAX-powered 40 * post grid while hiding the sliders to avoid layout overlap. 41 * 42 * @return {void} 43 */ 44function showGridHideSliders() { 45 var slidersSection = document.getElementById('blog-sliders-section'); 46 var gridContainer = document.getElementById('blog-grid-container'); 47 if (slidersSection) { slidersSection.style.display = 'none'; slidersSection.style.opacity = '1'; } 48 if (gridContainer) gridContainer.style.display = 'block'; 49} 50 51/** 52 * Initialise the category filter bar with click event delegation. 53 * 54 * Attaches a single click handler to .blog-categories and uses event 55 * delegation to handle clicks on any .blog-cat-item button. When a 56 * category is selected: 57 * - Active state is toggled on the clicked button. 58 * - If category is "all", shows sliders and hides grid. 59 * - If gridCache has pre-rendered HTML for the category, injects it 60 * directly for instant display. 61 * - Otherwise, triggers an AJAX fetch via fetchBlogPosts(). 62 * 63 * @return {void} 64 */ 65function initBlogFilters() { 66 document.querySelector('.blog-categories')?.addEventListener('click', function (e) { 67 var btn = e.target.closest('.blog-cat-item'); 68 if (!btn) return; 69 70 /* When categoryRedirect is enabled, tabs link to real archive URLs â let the browser navigate. */ 71 if (blogRevamp.categoryRedirect) { 72 return; 73 } 74 75 document.querySelectorAll('.blog-cat-item').forEach(function (el) { 76 el.classList.remove('active'); 77 }); 78 btn.classList.add('active'); 79 80 var category = btn.getAttribute('data-category'); 81 82 /* "All Posts" restores the default slider view â no AJAX needed. */ 83 if (category === 'all') { 84 showSlidersHideGrid(); 85 return; 86 } 87 88 var container = document.getElementById('blog-grid-container'); 89 if (!container) return; 90 91 /* GridCache hit â pre-rendered HTML from wp_localize_script, instant display. */ 92 if (blogRevamp.gridCache && blogRevamp.gridCache[category]) { 93 container.innerHTML = blogRevamp.gridCache[category]; 94 container.style.opacity = '1'; 95 showGridHideSliders(); 96 return; 97 } 98 99 /* GridCache miss â show loading state on whichever section is currently visible. */ 100 var slidersSection = document.getElementById('blog-sliders-section'); 101 var gridVisible = window.getComputedStyle(container).display !== 'none'; 102 103 if (gridVisible) { 104 container.style.opacity = '0.5'; 105 } else if (slidersSection) { 106 slidersSection.style.opacity = '0.5'; 107 } 108 109 fetchBlogPosts(category, 1); 110 }); 111} 112 113/** 114 * Initialise pagination click interception on the blog grid. 115 * 116 * Attaches a click handler to #blog-grid-container and intercepts clicks 117 * on .blog-page-link elements. Prevents default navigation and converts 118 * the click to an AJAX request via fetchBlogPosts(). 119 * 120 * Page number resolution order: 121 * 1. data-page attribute on the clicked link. 122 * 2. Query string parameter (?paged=N or ?page=N). 123 * 3. Path-based parameter (/page/N). 124 * 125 * @return {void} 126 */ 127function initBlogPagination() { 128 document.getElementById('blog-grid-container')?.addEventListener('click', function (e) { 129 var link = e.target.closest('.blog-page-link'); 130 if (!link || link.classList.contains('active')) return; 131 132 e.preventDefault(); 133 134 var nav = link.closest('.blog-pagination'); 135 if (!nav) return; 136 137 var category = nav.getAttribute('data-category'); 138 var page = link.getAttribute('data-page'); 139 140 /* Fallback: parse page number from URL if data-page attribute is missing. */ 141 if (!page) { 142 var href = link.href || link.getAttribute('href'); 143 var qsMatch = href.match(/[?&]paged?=(\d+)/); 144 if (qsMatch) { 145 page = qsMatch[1]; 146 } else { 147 var pathMatch = href.match(/\/page\/(\d+)/); 148 if (pathMatch) page = pathMatch[1]; 149 } 150 } 151 152 link.classList.add('loading'); 153 154 var container = document.getElementById('blog-grid-container'); 155 if (container) { 156 container.style.opacity = '0.5'; 157 } 158 159 fetchBlogPosts(category, parseInt(page || 1), true); 160 }); 161} 162 163/** 164 * Remove the loading spinner class from all pagination links. 165 * 166 * Resets the UI state after a pagination request completes (success or 167 * failure). Called in both the .then() and .catch() handlers of the 168 * fetch promise. 169 * 170 * @return {void} 171 */ 172function removePaginationLoader() { 173 document.querySelectorAll('.blog-page-link.loading').forEach(function (el) { 174 el.classList.remove('loading'); 175 }); 176} 177 178/** 179 * Fetch blog posts via AJAX and inject the grid HTML into the DOM. 180 * 181 * Main data-fetching function for the blog grid. Handles three scenarios: 182 * 1. Cache hit â if page 1 and gridCache has the category, use it directly. 183 * 2. Cache miss â make AJAX request to WordPress admin-ajax endpoint. 184 * 3. Pagination â always makes AJAX request regardless of cache. 185 * 186 * Uses AbortController to cancel any in-flight request before starting 187 * a new one. This prevents race conditions when the user switches 188 * categories rapidly. 189 * 190 * On successful response: 191 * - Grid HTML is injected into #blog-grid-container. 192 * - Sliders are hidden, grid is shown.
193 * - Page 1 responses are cached in gridCache for instant future access. 194 * - Viewport scrolls to top after a 100ms delay. 195 * 196 * On error: 197 * - AbortError is silently ignored (expected during rapid switching). 198 * - All other errors restore opacity and remove loading spinners. 199 * 200 * @param {string} category The category slug or ID to filter by. 201 * @param {number} page The page number to fetch (1-indexed). 202 * @param {boolean} isPagination Whether this request originated from a 203 * pagination click (bypasses cache). 204 * @return {void} 205 */ 206function fetchBlogPosts(category, page, isPagination) { 207 var container = document.getElementById('blog-grid-container'); 208 if (!container) return; 209 210 /* Cancel any in-flight request to prevent race conditions from rapid category switching. */ 211 if (blogAbortController) { 212 blogAbortController.abort(); 213 } 214 blogAbortController = new AbortController(); 215 var signal = blogAbortController.signal; 216 217 /* Cache hit for page 1 â inject pre-rendered HTML without making an AJAX call. */ 218 if (page === 1 && !isPagination && blogRevamp.gridCache && blogRevamp.gridCache[category]) { 219 container.innerHTML = blogRevamp.gridCache[category]; 220 container.style.opacity = '1'; 221 showGridHideSliders(); 222 removePaginationLoader(); 223 return; 224 } 225 226 /* Build FormData for WordPress admin-ajax.php endpoint. */ 227 var formData = new FormData(); 228 formData.append('action', 'blog_filter'); 229 formData.append('category', category); 230 formData.append('page', page); 231 formData.append('_ajax_nonce', blogRevamp.nonce); 232 if (isPagination) { 233 formData.append('pagination', '1'); 234 } 235 236 fetch(blogRevamp.ajaxUrl, { 237 method: 'POST', 238 body: formData, 239 signal: signal, 240 }) 241 .then(function (res) { return res.json(); }) 242 .then(function (data) { 243 if (data.success) { 244 container.innerHTML = data.data.grid_html; 245 container.style.opacity = '1'; 246 showGridHideSliders(); 247 /* Cache page 1 responses for instant category switching on subsequent visits. */ 248 if (blogRevamp.gridCache && page === 1) { 249 blogRevamp.gridCache[category] = data.data.grid_html; 250 } 251 } 252 removePaginationLoader(); 253 /* Delay scroll-to-top by 100ms to allow DOM injection to complete before viewport moves. */ 254 setTimeout(function() { 255 var container = document.getElementById('blog-grid-container'); 256 if (container) { 257 window.scrollTo({ top: 0, behavior: 'smooth' }); 258 document.documentElement.scrollTo({ top: 0, behavior: 'smooth' }); 259 document.body.scrollTo({ top: 0, behavior: 'smooth' }); 260 } 261 }, 100); 262 }) 263 .catch(function (err) { 264 /* AbortError is expected when the user switches categories rapidly. */ 265 if (err.name === 'AbortError') return; 266 /* For real errors, restore opacity and remove loading spinners so the UI doesn't stay frozen. */ 267 container.style.opacity = '1'; 268 var slidersSection = document.getElementById('blog-sliders-section'); 269 if (slidersSection) slidersSection.style.opacity = '1'; 270 removePaginationLoader(); 271 }); 272}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.