PageSourceSearch

https://mediumspringgreen-bee-829457.hostingersite.com/wp-content/…/public/js/mlsimport-listings.js?ver=7.1.2

js mediumspringgreen-bee-829457.hostingersite.com collected 2026-10-03 04:41:13 UTC 9,574 bytes, 258 lines download raw bytes

1/**
2 * MLSImport standalone (theme_id 990) front-end filtering.
3 * Submits the search form to admin-ajax, repaints the results grid + pager
4 * without a page reload. No dependencies. Config is localized as MLSImportListings.
5 */
6( function () {
7	'use strict';
8
9	// Bail entirely if the localized config never got printed.
10	if ( typeof window.MLSImportListings === 'undefined' ) {
11		return;
12	}
13
14	// Localized config: { action, nonce, ajaxurl }.
15	var cfg = window.MLSImportListings;
16
17	/**
18	 * forEach helper that works on array-like NodeLists.
19	 * @param {ArrayLike} list Items to iterate.
20	 * @param {Function}  fn   Callback per item.
21	 */
22	function each( list, fn ) {
23		Array.prototype.forEach.call( list, fn );
24	}
25
26	// FormData captures every selected option of a multi-select; a name ending in []
27	// (e.g. state[]) is grouped under its base key (state) as an array so toBody can
28	// re-emit state[]=a&state[]=b — without this only the first option would be sent.
29	/**
30	 * Gather the AJAX request params from the search form, page, and sort control.
31	 * @param {HTMLFormElement} form       The search form (may be null).
32	 * @param {number}          page       1-based page number.
33	 * @param {HTMLSelectElement} sortSelect Toolbar sort control (outside the form).
34	 * @return {Object} Param map (arrays for []-suffixed multi fields).
35	 */
36	function collectParams( form, page, sortSelect ) {
37		// Seed with the AJAX envelope.
38		var params = {
39			action: cfg.action,
40			nonce: cfg.nonce,
41			page: page || 1
42		};
43		if ( form ) {
44			// Fold each form control into params, mirroring half-map's formFilters.
45			new FormData( form ).forEach( function ( value, name ) {
46				// Skip empty values and the envelope keys.
47				if ( value === '' || name === 'action' || name === 'nonce' || name === 'page' ) {
48					return;
49				}
50				// Base key + whether this was a []-suffixed multi field.
51				var key   = name.replace( /\[\]$/, '' );
52				var multi = key !== name;
53				// Merge repeated keys into an array; else set scalar / single-item array.
54				if ( Object.prototype.hasOwnProperty.call( params, key ) ) {
55					if ( ! Array.isArray( params[ key ] ) ) {
56						params[ key ] = [ params[ key ] ];
57					}
58					params[ key ].push( value );
59				} else {
60					params[ key ] = multi ? [ value ] : value;
61				}
62			} );
63		}
64		// The Sort control sits in the results toolbar, OUTSIDE the search form, so
65		// FormData( form ) never captures it — read it in so refine/paginate keep the sort.
66		if ( sortSelect && sortSelect.value !== '' ) {
67			params.orderby = sortSelect.value;
68		}
69		return params;
70	}
71
72	/**
73	 * Serialize a param map to an application/x-www-form-urlencoded body.
74	 * @param {Object} params Param map (values may be arrays).
75	 * @return {string} Encoded body string.
76	 */
77	function toBody( params ) {
78		var parts = [];
79		Object.keys( params ).forEach( function ( k ) {
80			var v = params[ k ];
81			// Arrays re-emit as repeated key[]=item pairs.
82			if ( Array.isArray( v ) ) {
83				v.forEach( function ( item ) {
84					parts.push( encodeURIComponent( k + '[]' ) + '=' + encodeURIComponent( item ) );
85				} );
86			} else {
87				parts.push( encodeURIComponent( k ) + '=' + encodeURIComponent( v ) );
88			}
89		} );
90		return parts.join( '&' );
91	}
92
93	// One skeleton card mirrors the real card's box (.mlsimport-listing-card) so the
94	// grid keeps its exact columns and sizing while cards load; the media block and
95	// text lines shimmer (styled in mlsimport-listings.css).
96	/**
97	 * @param {number} count How many skeleton cards to build.
98	 * @return {string} Concatenated skeleton-card markup.
99	 */
100	function skeletonCards( count ) {
101		// Markup for a single shimmering placeholder card.
102		var one = '<article class="mlsimport-listing-card mlsimport-skeleton-card" aria-hidden="true">' +
103			'<div class="mlsimport-listing-card__media mlsimport-skeleton__box"></div>' +
104			'<div class="mlsimport-listing-card__body">' +
105			'<span class="mlsimport-skeleton__line mlsimport-skeleton__line--price"></span>' +
106			'<span class="mlsimport-skeleton__line mlsimport-skeleton__line--title"></span>' +
107			'<span class="mlsimport-skeleton__line mlsimport-skeleton__line--specs"></span>' +
108			'</div></article>';
109		// Repeat the single card `count` times.
110		var out = '';
111		for ( var i = 0; i < count; i++ ) {
112			out += one;
113		}
114		return out;
115	}
116
117	// Show as many placeholders as cards currently on screen so the grid height
118	// doesn't jump; on a first load with no cards yet, fall back to the page size.
119	/**
120	 * @param {Element}         grid The results grid (may be null).
121	 * @param {HTMLFormElement} form The search form (read for the page limit).
122	 * @return {number} Skeleton count, capped at 24.
123	 */
124	function skeletonCount( grid, form ) {
125		// Prefer matching the number of cards already rendered.
126		var existing = grid ? grid.querySelectorAll( '.mlsimport-listing-card' ).length : 0;
127		if ( existing > 0 ) {
128			return Math.min( existing, 24 );
129		}
130		// First load: fall back to the form's limit, else a small default of 6.
131		var limitEl = form ? form.querySelector( '[name="limit"]' ) : null;
132		var n = limitEl ? parseInt( limitEl.value, 10 ) : 0;
133		return n > 0 ? Math.min( n, 24 ) : 6;
134	}
135
136	/**
137	 * Paint the server response into the grid, result meta, and pager.
138	 * @param {Element} root The .mlsimport-listings block root.
139	 * @param {Object}  data Response payload ({ html, total, pager }).
140	 */
141	function render( root, data ) {
142		// Locate the three output regions.
143		var grid = root.querySelector( '.mlsimport-results__grid' );
144		var meta = root.querySelector( '.mlsimport-results__meta' );
145		var pager = root.querySelector( '.mlsimport-results__pager' );
146		if ( grid ) {
147			// Cards markup, or an empty-state message.
148			grid.innerHTML = data.html || '<p class="mlsimport-results__empty">No listings match your search.</p>';
149			// Let listeners (e.g. favorites hydration) re-process the fresh cards.
150			grid.dispatchEvent( new CustomEvent( 'mlsimport:cards-rendered', { bubbles: true } ) );
151		}
152		if ( meta ) {
153			// Result count with singular/plural "result(s)".
154			meta.textContent = ( data.total || 0 ) + ' result' + ( 1 === data.total ? '' : 's' );
155		}
156		if ( pager ) {
157			pager.innerHTML = data.pager || '';
158		}
159	}
160
161	/**
162	 * Fire the AJAX search and repaint the block with skeletons in the interim.
163	 * @param {Element}         root The block root.
164	 * @param {HTMLFormElement} form The search form.
165	 * @param {number}          page 1-based page to fetch.
166	 */
167	function request( root, form, page ) {
168		// Results wrapper, grid, and (out-of-form) sort control.
169		var results    = root.querySelector( '.mlsimport-results' );
170		var grid       = root.querySelector( '.mlsimport-results__grid' );
171		var sortSelect = root.querySelector( '.mlsimport-results__toolbar select[name="orderby"]' );
172		// Flag the block as loading (for CSS state).
173		if ( results ) {
174			results.classList.add( 'is-loading' );
175		}
176		// Swap real cards for shimmering skeletons for the duration of the fetch
177		// (count read before the swap, so it reflects what was on screen).
178		if ( grid ) {
179			grid.innerHTML = skeletonCards( skeletonCount( grid, form ) );
180		}
181
182		// Build and send the urlencoded POST.
183		var xhr = new XMLHttpRequest();
184		xhr.open( 'POST', cfg.ajaxurl );
185		xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
186		// Failure paths must clear the skeletons too, or they shimmer forever.
187		function fail() {
188			if ( grid ) {
189				grid.innerHTML = '<p class="mlsimport-results__empty">Could not load listings. Please try again.</p>';
190			}
191		}
192		// On response: clear loading, parse JSON, render on success else fail.
193		xhr.onload = function () {
194			if ( results ) {
195				results.classList.remove( 'is-loading' );
196			}
197			try {
198				var res = JSON.parse( xhr.responseText );
199				if ( res && res.success ) {
200					render( root, res.data );
201				} else {
202					fail();
203				}
204			} catch ( e ) {
205				fail();
206			}
207		};
208		// On transport error: clear loading and show the failure message.
209		xhr.onerror = function () {
210			if ( results ) {
211				results.classList.remove( 'is-loading' );
212			}
213			fail();
214		};
215		// Encode all params and send.
216		xhr.send( toBody( collectParams( form, page, sortSelect ) ) );
217	}
218
219	/**
220	 * Wire one listings block: submit, sort change, and pager clicks.
221	 * @param {Element} root The .mlsimport-listings block root.
222	 */
223	function init( root ) {
224		// The search form and the toolbar sort control.
225		var form = root.querySelector( '.mlsimport-search' );
226		var sort = root.querySelector( '.mlsimport-results__toolbar select[name="orderby"]' );
227
228		// Form submit refines from page 1 (no reload).
229		if ( form ) {
230			form.addEventListener( 'submit', function ( e ) {
231				e.preventDefault();
232				request( root, form, 1 );
233			} );
234		}
235
236		// The toolbar Sort is not inside the form, so a form submit is neither required nor
237		// intuitive above the results — changing it re-sorts (repaints from page 1) at once.
238		if ( sort ) {
239			sort.addEventListener( 'change', function () {
240				request( root, form, 1 );
241			} );
242		}
243
244		// Delegated pager clicks fetch the requested page.
245		root.addEventListener( 'click', function ( e ) {
246			var btn = e.target.closest ? e.target.closest( '.mlsimport-pager__btn' ) : null;
247			if ( btn && btn.dataset.page ) {
248				e.preventDefault();
249				request( root, form, parseInt( btn.dataset.page, 10 ) || 1 );
250			}
251		} );
252	}
253
254	// Initialise every listings block once the DOM is ready.
255	document.addEventListener( 'DOMContentLoaded', function () {
256		each( document.querySelectorAll( '.mlsimport-listings' ), init );
257	} );
258}() );

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.