1/** 2 * Remote Brands â AJAX Load More + Sort. 3 * 4 * Uses obfuscated selectors passed via wp_localize_script. 5 * 6 * @since 1.1.0 7 **/ 8( function () { 9 10 'use strict'; 11 12 if ( typeof starterProTableOptions === 'undefined' ) { 13 return; 14 } 15 16 const cfg = starterProTableOptions; 17 const sel = cfg.selectors; 18 19 /** Dot-prefixed selectors for querySelector. */ 20 const S = {}; 21 Object.keys( sel ).forEach( function ( key ) { 22 S[ key ] = '.' + sel[ key ]; 23 } ); 24 25 /** Container selectors. */ 26 const containerSelector = S.brand_table_box + ', ' + S.bonus_table_box; 27 28 /** 29 * Find the parent table container from an element. 30 * 31 * @param {Element} el 32 * @return {Element|null} 33 */ 34 function findContainer( el ) { 35 return el.closest( containerSelector ); 36 } 37 38 /** 39 * Read base64-encoded attributes from the script tag following the container. 40 * 41 * @param {Element} container 42 * @return {string} Raw base64 string. 43 */ 44 function getEncodedAtts( container ) { 45 const script = container.nextElementSibling; 46 if ( script && script.type === 'text/base64' ) { 47 return script.textContent.trim(); 48 } 49 return ''; 50 } 51 52 /** 53 * Set loading state on cards and sort controls. 54 * 55 * @param {Element} container 56 * @param {boolean} isLoading 57 */ 58 function setLoadingState( container, isLoading ) { 59 60 const cardSelector = S.brand_card + ', ' + S.bonus_card; 61 const cards = container.querySelectorAll( cardSelector ); 62 const sortBox = container.querySelector( S.sort_controls_box ); 63 64 if ( isLoading ) { 65 cards.forEach( function ( card ) { card.classList.add( sel.loading ); } ); 66 if ( sortBox ) { sortBox.classList.add( sel.loading ); } 67 } else { 68 cards.forEach( function ( card ) { card.classList.remove( sel.loading ); } ); 69 if ( sortBox ) { sortBox.classList.remove( sel.loading ); } 70 } 71 } 72 73 /** 74 * Load More â click handler. 75 */ 76 document.body.addEventListener( 'click', function ( e ) { 77 78 const btn = e.target.closest( S.load_more + ' button' ); 79 if ( ! btn ) return; 80 81 e.preventDefault(); 82
83 const loadMoreDiv = btn.closest( S.load_more ); 84 const container = findContainer( loadMoreDiv ); 85 if ( ! container ) return; 86 87 const atts = getEncodedAtts( container ); 88 if ( ! atts ) return; 89 90 const currentPage = parseInt( btn.getAttribute( 'data-page' ) || '1', 10 ); 91 const nextPage = currentPage + 1; 92 93 btn.classList.add( sel.loading ); 94 btn.disabled = true; 95 96 /** Read current sort state from the active sort link. */ 97 const activeSort = container.querySelector( S.sort_list + ' a.' + sel.active ); 98 const sortType = activeSort ? ( activeSort.getAttribute( 'data-sort' ) || '' ) : ''; 99 const sortDir = activeSort ? ( activeSort.getAttribute( 'data-direction' ) || '' ) : ''; 100 101 const body = new FormData(); 102 body.append( 'action', 'rb_load_more' ); 103 body.append( 'nonce', cfg.nonce ); 104 body.append( 'atts', atts ); 105 body.append( 'page', nextPage.toString() ); 106 if ( sortType ) { body.append( 'sort_type', sortType ); } 107 if ( sortDir ) { body.append( 'sort_direction', sortDir ); } 108 109 fetch( cfg.ajax_url, { method: 'POST', body: body } ) 110 .then( function ( r ) { return r.json(); } ) 111 .then( function ( res ) { 112 if ( res.success && res.data.data ) { 113 loadMoreDiv.insertAdjacentHTML( 'beforebegin', res.data.data ); 114 btn.setAttribute( 'data-page', nextPage.toString() ); 115 116 if ( ! res.data.more ) { 117 loadMoreDiv.style.display = 'none'; 118 } 119 120 /** Update page total label. */ 121 if ( res.data.quantityLabel ) { 122 const totalSpan = container.querySelector( S.page_total_label ); 123 if ( totalSpan ) { 124 totalSpan.textContent = res.data.quantityLabel; 125 } 126 } 127 } 128 } ) 129 .catch( function () {} ) 130 .finally( function () { 131 btn.classList.remove( sel.loading ); 132 btn.disabled = false; 133 } ); 134 } ); 135 136 /** 137 * Sort â click handler. 138 */ 139 document.body.addEventListener( 'click', function ( e ) { 140 141 const link = e.target.closest( S.sort_list + ' a' ); 142 if ( ! link ) return; 143 144 e.preventDefault(); 145 146 /** Prevent sorting if the link is already active. */ 147 if ( link.classList.contains( sel.active ) ) { return; } 148 149 const sortControls = link.closest( S.sort_controls ); 150 const sortBox = link.closest( S.sort_controls_box ); 151 const container = findContainer( sortBox ); 152 if ( ! container ) return; 153 154 const atts = getEncodedAtts( container ); 155 if ( ! atts ) return; 156 157 const sortType = link.getAttribute( 'data-sort' ) || 'popular'; 158 const sortDirection = link.getAttribute( 'data-direction' ) || 'desc'; 159 160 /** Toggle active class. */ 161 sortBox.querySelectorAll( S.sort_list + ' a' ).forEach( function ( a ) { 162 a.classList.remove( sel.active ); 163 } ); 164 link.classList.add( sel.active ); 165 166 /** Update toggle value for mobile dropdown. */ 167 const toggleValue = sortBox.querySelector( S.sort_toggle_value ); 168 if ( toggleValue ) { 169 toggleValue.textContent = link.textContent.trim(); 170 } 171 172 /** Close mobile dropdown. */ 173 if ( sortControls ) { 174 sortControls.classList.remove( sel.open ); 175 } 176 177 /** Show loading state. */ 178 setLoadingState( container, true ); 179 180 const body = new FormData(); 181 body.append( 'action', 'rb_sort' ); 182 body.append( 'nonce', cfg.nonce ); 183 body.append( 'atts', atts ); 184 body.append( 'sort_type', sortType ); 185 body.append( 'sort_direction', sortDirection ); 186 187 fetch( cfg.ajax_url, { method: 'POST', body: body } ) 188 .then( function ( r ) { return r.json(); } ) 189 .then( function ( res ) { 190 if ( ! res.success ) return; 191 192 /** Remove existing cards. */ 193 const cardSelector = S.brand_card + ', ' + S.bonus_card; 194 container.querySelectorAll( cardSelector ).forEach( function ( card ) { 195 card.remove(); 196 } ); 197 198 /** Insert new cards before load-more div. */
199 const loadMoreDiv = container.querySelector( S.load_more ); 200 if ( loadMoreDiv ) { 201 loadMoreDiv.insertAdjacentHTML( 'beforebegin', res.data.data ); 202 203 /** Reset page and show/hide load more. */ 204 const btn = loadMoreDiv.querySelector( 'button' ); 205 if ( btn ) { 206 btn.setAttribute( 'data-page', '1' ); 207 } 208 loadMoreDiv.style.display = res.data.more ? '' : 'none'; 209 } else { 210 /** No load-more div â append after sort controls box. */ 211 sortBox.insertAdjacentHTML( 'afterend', res.data.data ); 212 } 213 214 /** Update page total label. */ 215 if ( res.data.quantityLabel ) { 216 const totalSpan = container.querySelector( S.page_total_label ); 217 if ( totalSpan ) { 218 totalSpan.textContent = res.data.quantityLabel; 219 } 220 } 221 } ) 222 .catch( function () {} ) 223 .finally( function () { 224 setLoadingState( container, false ); 225 } ); 226 } ); 227 228 /** 229 * Sort toggle â show/hide sort list (mobile dropdown). 230 */ 231 document.body.addEventListener( 'click', function ( e ) { 232 233 const toggle = e.target.closest( S.sort_toggle ); 234 if ( ! toggle ) return; 235 236 e.preventDefault(); 237 238 const sortControls = toggle.closest( S.sort_controls ); 239 if ( sortControls ) { 240 sortControls.classList.toggle( sel.open ); 241 } 242 } ); 243 244 /** 245 * Close sort dropdown when clicking outside. 246 */ 247 document.addEventListener( 'click', function ( e ) { 248 const openDropdowns = document.querySelectorAll( S.sort_controls + '.' + sel.open ); 249 openDropdowns.forEach( function ( dropdown ) { 250 if ( ! dropdown.contains( e.target ) ) { 251 dropdown.classList.remove( sel.open ); 252 } 253 } ); 254 } ); 255 256 /** 257 * Match sort-toggle width with sort-list width. 258 */ 259 document.addEventListener( 'DOMContentLoaded', function () { 260 const sortControls = document.querySelectorAll( S.sort_controls ); 261 sortControls.forEach( function ( controls ) { 262 const sortList = controls.querySelector( S.sort_list ); 263 const sortToggle = controls.querySelector( S.sort_toggle ); 264 if ( ! sortList || ! sortToggle ) return; 265 266 /** Temporarily show the list to measure its width. */ 267 const originalDisplay = sortList.style.display; 268 sortList.style.display = 'block'; 269 sortList.style.visibility = 'hidden'; 270 271 const listWidth = sortList.offsetWidth; 272 sortToggle.style.minWidth = listWidth + 'px'; 273 274 /** Restore original display. */ 275 sortList.style.display = originalDisplay; 276 sortList.style.visibility = 'visible'; 277 } ); 278 } ); 279 280 /** 281 * Promo code â copy to clipboard. 282 */ 283 document.body.addEventListener( 'click', function ( e ) { 284 285 const btn = e.target.closest( S.promo_code_copy_btn ); 286 if ( ! btn ) return; 287 288 e.preventDefault(); 289 290 const box = btn.closest( '[data-promo-code]' ); 291 if ( ! box ) return; 292 293 const code = box.getAttribute( 'data-promo-code' ); 294 if ( ! code ) return; 295 296 navigator.clipboard.writeText( code ).then( function () { 297 btn.classList.add( sel.success ); 298 setTimeout( function () { 299 btn.classList.remove( sel.success ); 300 }, 2000 ); 301 } ).catch( function () {} ); 302 } ); 303 304} )();
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.