PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.1
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / admin / js / mlsimport-property-tabs.js

mlsimport-property-tabs.js in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.1.1, at admin/js/mlsimport-property-tabs.js

245 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MLS Listing metabox — tab switching, plus the Media tab gallery.
3 *
4 * Vanilla JS (no build, no jQuery dependency). Clicking a tab shows its pane and
5 * remembers the choice so the same tab is active after the post is saved. The
6 * Media tab additionally previews the Photos Count cap as it is typed and deletes
7 * individual images over AJAX.
8 */
9 ( function () {
10 'use strict';
11
12 // Cookie name used to remember which metabox tab was last active
13 var COOKIE = 'mlsimport_mb_tab';
14
15 /**
16 * Persist the active tab slug in a site-wide cookie.
17 *
18 * @param {string} value - Tab slug to store.
19 */
20 function setCookie( value ) {
21 document.cookie = COOKIE + '=' + encodeURIComponent( value ) + '; path=/';
22 }
23
24 /**
25 * Read the remembered tab slug from the cookie.
26 *
27 * @return {string} Stored slug, or '' when absent.
28 */
29 function getCookie() {
30 var match = document.cookie.match( new RegExp( '(?:^|; )' + COOKIE + '=([^;]*)' ) );
31 return match ? decodeURIComponent( match[ 1 ] ) : '';
32 }
33
34 /**
35 * Activate the tab (and matching pane) identified by slug within a metabox.
36 *
37 * @param {Element} box - The metabox root element.
38 * @param {string} slug - Tab slug to activate.
39 * @return {boolean} True if a matching tab was found and activated.
40 */
41 function activate( box, slug ) {
42 // Collect the tab buttons and content panes in this box
43 var tabs = box.querySelectorAll( '.mlsimport-mb__tab' );
44 var panes = box.querySelectorAll( '.mlsimport-mb__pane' );
45 var found = false;
46
47 // Toggle each tab's active state; track whether the slug matched any tab
48 tabs.forEach( function ( tab ) {
49 var on = tab.getAttribute( 'data-tab' ) === slug;
50 tab.classList.toggle( 'is-active', on );
51 found = found || on;
52 } );
53 // No tab matched: leave panes untouched and report failure
54 if ( ! found ) {
55 return false;
56 }
57 // Show the pane whose data-pane matches the slug, hide the rest
58 panes.forEach( function ( pane ) {
59 pane.classList.toggle( 'is-active', pane.getAttribute( 'data-pane' ) === slug );
60 } );
61 return true;
62 }
63
64 // On DOM ready, wire tab clicks and restore the remembered tab
65 document.addEventListener( 'DOMContentLoaded', function () {
66 // Locate the metabox; nothing to do if it isn't on this screen
67 var box = document.querySelector( '.mlsimport-mb' );
68 if ( ! box ) {
69 return;
70 }
71
72 // Delegate clicks so any tab within the box is handled
73 box.addEventListener( 'click', function ( e ) {
74 // Ignore clicks that didn't land on (or inside) a tab
75 var tab = e.target.closest( '.mlsimport-mb__tab' );
76 if ( ! tab ) {
77 return;
78 }
79 // Activate the clicked tab and, if successful, remember it
80 var slug = tab.getAttribute( 'data-tab' );
81 if ( activate( box, slug ) ) {
82 setCookie( slug );
83 }
84 } );
85
86 // Restore the previously active tab from the cookie, if any
87 var stored = getCookie();
88 if ( stored ) {
89 activate( box, stored );
90 }
91
92 wireGallery( box );
93 } );
94
95 /**
96 * Apply the Photos Count cap to the tiles: the first N stay visible, the rest
97 * are hidden. A blank or zero count means no cap. Mirrors the PHP rule in
98 * mlsimport_property_gallery_ids() so the preview matches what gets published.
99 *
100 * @param {Element} box - The metabox root element.
101 */
102 function applyCount( box ) {
103 var input = box.querySelector( '#mlsimport_PhotosCount' );
104 var tiles = box.querySelectorAll( '.mlsimport-mb__thumb' );
105 // A missing input (or no gallery) leaves every tile as the server rendered it
106 if ( ! input || ! tiles.length ) {
107 return;
108 }
109
110 // Anything not a positive number publishes the whole gallery
111 var limit = parseInt( input.value, 10 );
112 if ( isNaN( limit ) || limit < 1 ) {
113 limit = tiles.length;
114 }
115
116 // Hide every tile past the limit; show the ones within it
117 tiles.forEach( function ( tile, index ) {
118 tile.classList.toggle( 'is-beyond-count', index >= limit );
119 } );
120
121 // Keep the heading honest about how many of the stored images are published
122 var title = box.querySelector( '.mlsimport-mb__gallery-title' );
123 if ( title && title.getAttribute( 'data-template' ) ) {
124 var shown = Math.min( limit, tiles.length );
125 title.textContent = title
126 .getAttribute( 'data-template' )
127 .replace( '%1$d', shown )
128 .replace( '%2$d', tiles.length );
129 }
130 }
131
132 /**
133 * Wire the Media tab gallery: live Photos Count preview and per-image delete.
134 *
135 * @param {Element} box - The metabox root element.
136 */
137 function wireGallery( box ) {
138 var gallery = box.querySelector( '.mlsimport-mb__gallery' );
139 // Listings with no imported images render no gallery at all
140 if ( ! gallery ) {
141 return;
142 }
143
144 // Re-apply the cap on every keystroke / spinner click
145 var count = box.querySelector( '#mlsimport_PhotosCount' );
146 if ( count ) {
147 count.addEventListener( 'input', function () {
148 applyCount( box );
149 } );
150 }
151
152 // Delegate delete clicks so tiles removed from the DOM need no cleanup
153 gallery.addEventListener( 'click', function ( e ) {
154 var btn = e.target.closest( '.mlsimport-mb__thumb-del' );
155 // Ignore clicks that landed on the thumbnail link or the grid itself
156 if ( ! btn ) {
157 return;
158 }
159 e.preventDefault();
160 deleteImage( box, gallery, btn );
161 } );
162 }
163
164 /**
165 * Delete one image over AJAX, then drop its tile and re-apply the cap.
166 *
167 * @param {Element} box - The metabox root element.
168 * @param {Element} gallery - The gallery wrapper (carries data-post).
169 * @param {Element} btn - The clicked delete button (carries data-id).
170 */
171 function deleteImage( box, gallery, btn ) {
172 var cfg = window.mlsimportMetabox;
173 // Without the localized config there is no nonce, so no request to make
174 if ( ! cfg ) {
175 return;
176 }
177 // Deleting is permanent — make the editor say yes first
178 if ( ! window.confirm( cfg.confirm ) ) {
179 return;
180 }
181
182 var tile = btn.closest( '.mlsimport-mb__thumb' );
183 // Disable while in flight so a double-click can't fire two deletes
184 btn.disabled = true;
185
186 var body = new URLSearchParams();
187 body.append( 'action', cfg.action );
188 body.append( 'nonce', cfg.nonce );
189 body.append( 'post_id', gallery.getAttribute( 'data-post' ) );
190 body.append( 'attach_id', btn.getAttribute( 'data-id' ) );
191
192 window
193 .fetch( cfg.ajaxUrl, {
194 method: 'POST',
195 credentials: 'same-origin',
196 body: body,
197 } )
198 .then( function ( r ) {
199 return r.json();
200 } )
201 .then( function ( res ) {
202 // Server refused (bad nonce, no capability, not in this gallery)
203 if ( ! res || ! res.success ) {
204 throw new Error( 'refused' );
205 }
206 // Gone for good: drop the tile, then re-cap the survivors
207 tile.remove();
208 // The server may have promoted a new featured image — reflect it
209 markFeatured( box, res.data ? res.data.featured : 0 );
210 applyCount( box );
211 } )
212 .catch( function () {
213 btn.disabled = false;
214 window.alert( cfg.failed );
215 } );
216 }
217
218 /**
219 * Move the "Featured" badge to the tile the server now reports as featured.
220 *
221 * @param {Element} box - The metabox root element.
222 * @param {number} id - Attachment id of the new featured image (0 = none).
223 */
224 function markFeatured( box, id ) {
225 var tiles = box.querySelectorAll( '.mlsimport-mb__thumb' );
226 tiles.forEach( function ( tile ) {
227 var isFeatured = id && tile.getAttribute( 'data-id' ) === String( id );
228 var badge = tile.querySelector( '.mlsimport-mb__thumb-badge' );
229
230 tile.classList.toggle( 'is-featured', !! isFeatured );
231
232 // Add the badge when this tile just became featured
233 if ( isFeatured && ! badge ) {
234 badge = document.createElement( 'span' );
235 badge.className = 'mlsimport-mb__thumb-badge';
236 badge.textContent = window.mlsimportMetabox ? window.mlsimportMetabox.featured : 'Featured';
237 tile.appendChild( badge );
238 // Remove a stale badge from a tile that no longer holds the role
239 } else if ( ! isFeatured && badge ) {
240 badge.remove();
241 }
242 } );
243 }
244 } )();
245