1/** 2 * Advanced Post Grid Module JavaScript 3 * Handles AJAX filters, pagination and load more 4 */ 5 6(function($) { 7 'use strict'; 8 9 // Class to manage the Post Grid 10 class D4FPostGrid { 11 constructor(element) { 12 this.$element = $(element); 13 this.moduleId = this.$element.attr('id'); 14 this.$container = this.$element.find('.d4f-post-grid-container'); 15 this.$filters = this.$element.find('.d4f-filters'); 16 this.$pagination = this.$element.find('.d4f-pagination'); 17 18 // Settings from module data attributes 19 this.settings = { 20 postType: this.$element.data('post-type') || 'post', 21 perPage: this.$element.data('per-page') || 9, 22 layout: this.$element.data('layout') || 'grid', 23 columns: this.$element.data('columns') || 3, 24 showImage: this.$element.data('show-image') || 'on', 25 showExcerpt: this.$element.data('show-excerpt') || 'on', 26 excerptLength: this.$element.data('excerpt-length') || 20, 27 showDate: this.$element.data('show-date') || 'on', 28 showAuthor: this.$element.data('show-author') || 'off', 29 showCategories: this.$element.data('show-categories') || 'on', 30 orderBy: this.$element.data('order-by') || 'date', 31 order: this.$element.data('order') || 'DESC', 32 categoryFilter: this.$element.data('category-filter') || '', 33 filterCategories: this.$element.data('filter-categories') || '', 34 currentLang: this.$element.data('current-lang') || '', 35 excludePosts: this.$element.data('exclude-posts') || '' 36 }; 37 38 this.currentPage = 1; 39 this.currentCategory = 'all'; 40 this.isLoading = false; 41 42 this.init(); 43 } 44 45 init() { 46 this.bindEvents(); 47 } 48 49 bindEvents() { 50 // Button filters 51 this.$filters.on('click', '.d4f-filter-btn', (e) => { 52 e.preventDefault(); 53 const $btn = $(e.currentTarget); 54 const category = $btn.data('cat'); 55 56 // Update UI 57 this.$filters.find('.d4f-filter-btn').removeClass('active'); 58 $btn.addClass('active'); 59 60 // Load posts 61 this.currentCategory = category; 62 this.currentPage = 1; 63 this.loadPosts(true); 64 }); 65 66 // Dropdown filter 67 this.$filters.on('change', '.d4f-filter-select', (e) => { 68 const category = $(e.currentTarget).val(); 69 this.currentCategory = category; 70 this.currentPage = 1; 71 this.loadPosts(true); 72 }); 73 74 // AJAX pagination 75 this.$pagination.on('click', 'a', (e) => { 76 if (this.$pagination.hasClass('d4f-pagination-ajax')) { 77 e.preventDefault(); 78 const $link = $(e.currentTarget); 79 const href = $link.attr('href'); 80 81 // Extract page number from URL 82 const match = href.match(/[?&]pg=(\d+)/); 83 if (match) { 84 this.currentPage = parseInt(match[1]); 85 this.loadPosts(true); 86 87 // Scroll to top 88 $('html, body').animate({ 89 scrollTop: this.$element.offset().top - 100 90 }, 500); 91 } 92 } 93 }); 94 95 // Load More button 96 this.$pagination.on('click', '.d4f-load-more-btn', (e) => { 97 e.preventDefault(); 98 const $btn = $(e.currentTarget); 99 this.currentPage = parseInt($btn.data('page')); 100 this.loadPosts(false); // append, non replace 101 }); 102 } 103 104 loadPosts(replace = true) { 105 if (this.isLoading) { 106 return; 107 } 108 109 this.isLoading = true; 110 111 // Loading state 112 if (replace) { 113 this.$container.addClass('d4f-loading'); 114 } else { 115 this.$pagination.find('.d4f-load-more-btn') 116 .text(d4fPostGrid.i18n.loading) 117 .prop('disabled', true); 118 } 119 120 // AJAX request 121 $.ajax({
122 url: d4fPostGrid.ajaxurl, 123 type: 'POST', 124 data: { 125 action: 'd4f_load_posts', 126 nonce: d4fPostGrid.nonce, 127 post_type: this.settings.postType, 128 posts_per_page: this.settings.perPage, 129 page: this.currentPage, 130 category: this.currentCategory, 131 category_filter: this.settings.categoryFilter, 132 filter_categories: this.settings.filterCategories, 133 lang: this.settings.currentLang, 134 exclude_posts: this.settings.excludePosts, 135 order_by: this.settings.orderBy, 136 order: this.settings.order, 137 show_image: this.settings.showImage, 138 show_excerpt: this.settings.showExcerpt, 139 excerpt_length: this.settings.excerptLength, 140 show_date: this.settings.showDate, 141 show_author: this.settings.showAuthor, 142 show_categories: this.settings.showCategories, 143 }, 144 success: (response) => { 145 this.isLoading = false; 146 this.$container.removeClass('d4f-loading'); 147 148 if (response.success) { 149 if (replace) { 150 // Replace all content 151 this.$container.fadeOut(200, () => { 152 this.$container.html(response.html); 153 this.$container.fadeIn(200); 154 }); 155 } else { 156 // Append (load more) 157 const $newPosts = $(response.html); 158 $newPosts.hide().appendTo(this.$container).fadeIn(400); 159 } 160 161 // Update pagination 162 this.updatePagination(response); 163 } else { 164 this.$container.html('<p class="d4f-no-posts">' + d4fPostGrid.i18n.noContent + '</p>'); 165 this.$pagination.html(''); 166 } 167 }, 168 error: (xhr, status, error) => { 169 this.isLoading = false; 170 this.$container.removeClass('d4f-loading'); 171 console.error('AJAX Error:', error); 172 alert(d4fPostGrid.i18n.error); 173 } 174 }); 175 } 176 177 updatePagination(response) { 178 const $loadMoreBtn = this.$pagination.find('.d4f-load-more-btn'); 179 180 if ($loadMoreBtn.length) { 181 // Load More type 182 if (response.has_more) { 183 $loadMoreBtn 184 .data('page', response.current_page + 1) 185 .text(d4fPostGrid.i18n.loadMore) 186 .prop('disabled', false); 187 } else { 188 $loadMoreBtn.fadeOut(); 189 } 190 } else if (this.$pagination.hasClass('d4f-pagination-ajax')) { 191 // Numbered AJAX pagination 192 this.generatePagination(response.current_page, response.max_pages); 193 } 194 } 195 196 generatePagination(currentPage, maxPages) { 197 if (maxPages <= 1) { 198 this.$pagination.html(''); 199 return; 200 } 201 202 let html = '<ul>'; 203 204 // Previous 205 if (currentPage > 1) { 206 html += `<li><a href="?pg=${currentPage - 1}">â</a></li>`; 207 } 208 209 // Pages 210 const range = 2; // Show 2 pages before and after 211 let start = Math.max(1, currentPage - range); 212 let end = Math.min(maxPages, currentPage + range); 213 214 // First page 215 if (start > 1) { 216 html += `<li><a href="?pg=1">1</a></li>`; 217 if (start > 2) { 218 html += '<li><span>...</span></li>'; 219 } 220 } 221 222 // Page numbers 223 for (let i = start; i <= end; i++) { 224 if (i === currentPage) { 225 html += `<li><span class="current">${i}</span></li>`; 226 } else { 227 html += `<li><a href="?pg=${i}">${i}</a></li>`; 228 } 229 } 230 231 // Last page 232 if (end < maxPages) { 233 if (end < maxPages - 1) { 234 html += '<li><span>...</span></li>'; 235 } 236 html += `<li><a href="?pg=${maxPages}">${maxPages}</a></li>`; 237 } 238 239 // Next 240 if (currentPage < maxPages) { 241 html += `<li><a href="?pg=${currentPage + 1}">â</a></li>`; 242 } 243 244 html += '</ul>'; 245 this.$pagination.html(html); 246 } 247 } 248 249 // Initialize all Advanced Post Grid modules 250 function initPostGrids() { 251 $('.d4f-advanced-post-grid').each(function() { 252 // Prevent double initialization 253 if (!$(this).data('d4f-initialized')) { 254 new D4FPostGrid(this); 255 $(this).data('d4f-initialized', true); 256 } 257 }); 258 } 259 260 // Init on document ready 261 $(document).ready(function() { 262 initPostGrids(); 263 });
264 265 // Re-init for Divi Visual Builder (safe approach) 266 $(window).on('et_builder_api_ready', function(event, API) { 267 // Safely handle API if it exists 268 if (typeof API !== 'undefined') { 269 console.log('Divi Builder API ready'); 270 } 271 272 // Use MutationObserver for detecting DOM changes in Visual Builder 273 // This works independently of the API object 274 const observer = new MutationObserver(function(mutations) { 275 mutations.forEach(function(mutation) { 276 if (mutation.addedNodes.length) { 277 initPostGrids(); 278 } 279 }); 280 }); 281 282 // Observe the body for changes 283 observer.observe(document.body, { 284 childList: true, 285 subtree: true 286 }); 287 288 // Fallback: init immediately 289 setTimeout(initPostGrids, 500); 290 }); 291 292 // Additional init for when module is loaded/reloaded in Visual Builder 293 $(window).on('load', function() { 294 setTimeout(initPostGrids, 1000); 295 }); 296 297})(jQuery);
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.