1/** 2 * Primary Listing Page Builder Section 3 * Handles loading and displaying primary property listings in grid or slider layout 4 */ 5 6(function() { 7 'use strict'; 8 9 document.addEventListener('DOMContentLoaded', function() { 10 // Initialize all primary listing sections on the page 11 const sections = document.querySelectorAll('.primary-listing-section'); 12 sections.forEach(section => { 13 initPrimaryListingSection(section); 14 }); 15 }); 16 17 function initPrimaryListingSection(section) { 18 if (!section) return; 19 20 const isSlider = section.dataset.layoutStyle === 'slider'; 21 const params = section.dataset.defaultParams; 22 23 if (!params) { 24 console.error('Primary Listing: No params found'); 25 return; 26 } 27 28 // Load properties 29 loadPrimaryListingProperties(section, params, isSlider); 30 } 31 32 function getPrimaryListingApiUrl() { 33 const baseUrl = (typeof global_params !== 'undefined' && global_params.url) ? global_params.url : ''; 34 return baseUrl + '/wp-json/bank-expo/v1/primary/filter'; 35 } 36 37 function setQueryPage(queryString, page) { 38 const searchParams = new URLSearchParams(queryString); 39 searchParams.set('page', String(page)); 40 return searchParams.toString(); 41 } 42 43 function requestPrimaryListingPage(queryString, page) { 44 const apiEndpoint = getPrimaryListingApiUrl(); 45 const fullUrl = `${apiEndpoint}?${setQueryPage(queryString, page)}`; 46 47 return fetch(fullUrl).then(response => { 48 if (!response.ok) { 49 return response.text().then(text => { 50 console.error('Primary Listing: Error response:', text); 51 throw new Error(`HTTP ${response.status}: ${text}`); 52 }); 53 } 54 return response.json(); 55 }); 56 } 57 58 function fetchPrimaryListingRows(queryString, fetchAllPages, maxPages) { 59 return requestPrimaryListingPage(queryString, 1).then(firstResponse => { 60 if (!firstResponse || !firstResponse.data || !Array.isArray(firstResponse.data.rows_html)) { 61 throw new Error('Invalid data structure'); 62 } 63 64 const firstRows = firstResponse.data.rows_html; 65 if (!fetchAllPages) { 66 return firstRows; 67 } 68 69 const totalPage = parseInt(firstResponse.data.total_page, 10) || 1; 70 const pagesToFetch = Math.min(totalPage, Math.max(1, maxPages || 1)); 71 72 if (pagesToFetch <= 1) { 73 return firstRows; 74 } 75 76 return fetchPrimaryListingPagesSequentially(queryString, 2, pagesToFetch).then(extraRows => { 77 return firstRows.concat(extraRows); 78 }); 79 }); 80 } 81 82 function fetchPrimaryListingPagesSequentially(queryString, fromPage, toPage) { 83 let chain = Promise.resolve([]); 84 85 for (let page = fromPage; page <= toPage; page++) { 86 const pageNum = page; 87 chain = chain.then(rows => 88 requestPrimaryListingPage(queryString, pageNum).then(response => { 89 if (response && response.data && Array.isArray(response.data.rows_html)) { 90 return rows.concat(response.data.rows_html); 91 } 92 return rows; 93 }) 94 ); 95 } 96 97 return chain; 98 } 99 100 function loadPrimaryListingProperties(section, encodedParams, isSlider) { 101 let params; 102 103 try { 104 params = atob(encodedParams); 105 } catch (e) { 106 console.error('Primary Listing: Invalid base64 params:', e); 107 showError(section, 'Invalid parameters'); 108 return; 109 } 110 111 const fetchAllPages = section.dataset.fetchAllPages === '1'; 112 const maxPages = parseInt(section.dataset.maxPages, 10) || 20; 113 114 fetchPrimaryListingRows(params, fetchAllPages, maxPages) 115 .then(rowsHtml => { 116 if (!rowsHtml.length) { 117 console.warn('Primary Listing: No rows returned'); 118 showError(section, 'No properties available'); 119 return; 120 } 121 renderPropertiesFromHTML(section, rowsHtml, isSlider); 122 }) 123 .catch(error => { 124 console.error('Primary Listing: Error loading properties:', error); 125 showError(section, 'Error loading properties: ' + error.message); 126 }); 127 } 128 129 function renderPropertiesFromHTML(section, htmlArray, isSlider) { 130 const wrapper = isSlider 131 ? section.querySelector('.primary-listing-wrapper') 132 : section.querySelector('.primary-listing-grid-wrapper'); 133 134 if (!wrapper) { 135 console.error('Primary Listing: Wrapper not found'); 136 return; 137 } 138 139 // Clear loading state 140 wrapper.innerHTML = ''; 141 142 if (htmlArray.length === 0) { 143 wrapper.innerHTML = '<div class="no-properties"><p>No properties available at this time.</p></div>'; 144 return; 145 } 146 147 // Render each property HTML 148 htmlArray.forEach((cardHtml) => { 149 const tempDiv = document.createElement('div'); 150 tempDiv.innerHTML = cardHtml.trim(); 151 const card = tempDiv.firstElementChild; 152 153 if (card) { 154 if (isSlider) { 155 const slideDiv = document.createElement('div'); 156 slideDiv.className = 'swiper-slide primary-listing-slide'; 157 slideDiv.appendChild(card); 158 wrapper.appendChild(slideDiv); 159 } else { 160 const cardWrapper = document.createElement('div'); 161 cardWrapper.className = 'primary-listing-card-wrapper'; 162 cardWrapper.appendChild(card); 163 wrapper.appendChild(cardWrapper); 164 } 165 } 166 }); 167 168 // Initialize Swiper if slider layout 169 if (isSlider) { 170 initializePrimaryListingSwiper(section); 171 } 172 173 // Initialize card galleries (for image sliders within cards) 174 if (typeof window.reinitializeCardGalleries === 'function') { 175 setTimeout(function() { 176 window.reinitializeCardGalleries(); 177 }, 100); 178 } 179 } 180 181 function initializePrimaryListingSwiper(section) { 182 const swiperContainer = section.querySelector('.primary-listing-swiper'); 183 const prevBtn = section.querySelector('.nav-prev'); 184 const nextBtn = section.querySelector('.nav-next'); 185 186 if (!swiperContainer) return; 187 188 new Swiper(swiperContainer, { 189 slidesPerView: 'auto', 190 spaceBetween: 0, 191 allowTouchMove: true, 192 grabCursor: true, 193 navigation: { 194 nextEl: nextBtn, 195 prevEl: prevBtn, 196 }, 197 breakpoints: { 198 320: { 199 slidesPerView: 1 200 }, 201 768: { 202 slidesPerView: 1.5 203 }, 204 1024: { 205 slidesPerView: 2.2 206 } 207 } 208 }); 209 } 210 211 function showError(section, message) { 212 const wrapper = section.querySelector('.primary-listing-wrapper') || 213 section.querySelector('.primary-listing-grid-wrapper'); 214 215 if (wrapper) { 216 wrapper.innerHTML = `<div class="error-message"><p>${escapeHtml(message)}</p></div>`; 217 } 218 } 219 220 function escapeHtml(text) { 221 const map = { 222 '&': '&', 223 '<': '<', 224 '>': '>', 225 '"': '"', 226 "'": ''' 227 }; 228 return String(text).replace(/[&<>"']/g, m => map[m]); 229 } 230 231})();
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.