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-onboarding.js

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

480 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MLSImport Onboarding Wizard JavaScript
3 *
4 * Handles all the frontend functionality for the onboarding wizard including
5 * navigation, form validation, and AJAX requests.
6 *
7 * Depends on the localized `mlsimportOnboarding` object (ajaxurl, nonce,
8 * current_step, steps, strings) printed by the PHP that enqueues this file.
9 */
10
11 (function($) {
12 'use strict';
13
14 // Store wizard state
15 var MLSImportWizard = {
16 currentStep: '', // Slug of the step currently being shown
17 steps: {}, // Map of step slug -> step config (order defines navigation)
18 formData: {}, // Scratch object for collected form values
19 /**
20 * Bootstrap the wizard: pull state from localized data and wire it up.
21 */
22 init: function() {
23 // Set initial state from localized data
24 this.currentStep = mlsimportOnboarding.current_step;
25 this.steps = mlsimportOnboarding.steps;
26
27 // Initialize event listeners
28 this.initEvents();
29
30 // Initialize step-specific functionality
31 this.initCurrentStep();
32 },
33
34 /**
35 * Attach the wizard's global event handlers.
36 */
37 initEvents: function() {
38 // Form submission
39 $('#mlsimport-wizard-form').on('submit', this.handleFormSubmit);
40
41 // Back button handling
42 $('.mlsimport-wizard-back').on('click', this.handleBackClick);
43
44 // Save data when navigating away
45 $(window).on('beforeunload', this.saveCurrentData);
46
47 // Step navigation
48 $('.mlsimport-wizard-step').on('click', this.handleStepClick);
49 },
50 /**
51 * Handle a click on a step indicator: save, gate skipping, then navigate.
52 *
53 * @param {Event} e - The click event.
54 */
55 handleStepClick: function(e) {
56 e.preventDefault();
57
58 // Save current data
59 MLSImportWizard.saveCurrentData();
60
61 // Get clicked step index
62 var $step = $(this);
63 var stepIndex = $step.index();
64 var stepKeys = Object.keys(MLSImportWizard.steps);
65 var targetStep = stepKeys[stepIndex];
66
67 // Don't allow skipping ahead - only go to completed steps or next step
68 var currentIndex = stepKeys.indexOf(MLSImportWizard.currentStep);
69 if (stepIndex > currentIndex + 1) {
70 // Block the jump and prompt the user to finish the current step
71 alert(mlsimportOnboarding.strings.complete_current_step || 'Please complete the current step first.');
72 return;
73 }
74
75 // Navigate to the step
76 window.location.href = 'admin.php?page=mlsimport-onboarding&step=' + targetStep;
77 },
78
79
80 /**
81 * Run any per-step setup based on the current step slug.
82 * Most steps do their own setup inline in their templates.
83 */
84 initCurrentStep: function() {
85 // Step-specific initialization
86 switch(this.currentStep) {
87 case 'welcome':
88 // Nothing special for welcome step
89 break;
90 case 'account':
91 // Initialize autocomplete already handled in template
92 break;
93 case 'field-mapping':
94 // Template selection handler already in template
95 break;
96 case 'import-config':
97 // Initialize Select2 if available already in template
98 break;
99 case 'test-import':
100 // Test import handlers already in template
101 break;
102 case 'success':
103 // Success page doesn't need special handling
104 break;
105 }
106 },
107
108 /**
109 * On form submit, persist the data then let the native submit proceed.
110 *
111 * @param {Event} e - The submit event.
112 * @return {boolean} Always true (do not cancel the submit).
113 */
114 handleFormSubmit: function(e) {
115 // Save current form data
116 MLSImportWizard.saveCurrentData();
117
118 // Let the form submit normally - PHP will handle the processing
119 return true;
120 },
121
122 /**
123 * On Back click, persist the data then let the link navigate.
124 *
125 * @param {Event} e - The click event.
126 * @return {boolean} Always true (do not cancel the navigation).
127 */
128 handleBackClick: function(e) {
129 // Save current form data before going back
130 MLSImportWizard.saveCurrentData();
131
132 // Let the link work normally
133 return true;
134 },
135
136 /**
137 * Serialize the wizard form and save it via a synchronous AJAX call
138 * (synchronous so it completes before the page unloads).
139 */
140 saveCurrentData: function() {
141 // Collect form data
142 var formData = $('#mlsimport-wizard-form').serializeArray();
143 var data = {};
144
145 // Convert to object
146 $.each(formData, function(i, field) {
147 // Multi-value (`name[]`) fields collapse into an array under the base name
148 if (field.name.indexOf('[]') !== -1) {
149 // Handle array values
150 var name = field.name.replace('[]', '');
151 if (!data[name]) {
152 data[name] = [];
153 }
154 data[name].push(field.value);
155 } else {
156 // Scalar field: store directly
157 data[field.name] = field.value;
158 }
159 });
160
161 // Save via AJAX
162 $.ajax({
163 url: mlsimportOnboarding.ajaxurl,
164 method: 'POST',
165 data: {
166 action: 'mlsimport_save_step_data',
167 step: MLSImportWizard.currentStep,
168 data: data,
169 nonce: mlsimportOnboarding.nonce
170 },
171 async: false // Make sure data is saved before page unloads
172 });
173 },
174
175 // Utility function to show step-specific sections
176 /**
177 * Hide all step sections and reveal only the one matched by selector.
178 *
179 * @param {string} selector - Selector of the section to show.
180 */
181 showStepSection: function(selector) {
182 $('.mlsimport-step-section').hide();
183 $(selector).show();
184 },
185
186 // Utility function to validate current step
187 /**
188 * Validate that every [required] field in the form has a value.
189 *
190 * @return {boolean} True if all required fields are filled.
191 */
192 validateStep: function() {
193 var isValid = true;
194 var requiredFields = $('#mlsimport-wizard-form').find('[required]');
195
196 // Flag each empty required field and clear the flag when filled
197 requiredFields.each(function() {
198 if (!$(this).val()) {
199 isValid = false;
200 $(this).addClass('mlsimport-field-error');
201 } else {
202 $(this).removeClass('mlsimport-field-error');
203 }
204 });
205
206 return isValid;
207 },
208
209 // Utility function to show error message
210 /**
211 * Display an inline error notice above the form and scroll to it.
212 *
213 * @param {string} message - Error text to display.
214 */
215 showError: function(message) {
216 // Create the notice element once if it isn't already present
217 if (!$('.mlsimport-error-notice').length) {
218 $('<div class="mlsimport-error-notice"></div>').insertBefore('#mlsimport-wizard-form');
219 }
220
221 // Populate and reveal the notice
222 $('.mlsimport-error-notice').html('<p>' + message + '</p>').show();
223
224 // Scroll to error
225 $('html, body').animate({
226 scrollTop: $('.mlsimport-error-notice').offset().top - 50
227 }, 200);
228 },
229
230 // Utility function to hide error message
231 /**
232 * Hide the inline error notice.
233 */
234 hideError: function() {
235 $('.mlsimport-error-notice').hide();
236 }
237 };
238
239 // Initialize the wizard on document ready
240 $(document).ready(function() {
241 MLSImportWizard.init();
242 });
243
244 // Add utility functions for step templates that run inline JS
245 window.MLSImportWizard = MLSImportWizard;
246
247 })(jQuery);
248
249 /**
250 * Helper function to get a URL parameter by name
251 *
252 * @param {string} name - Query-string parameter name.
253 * @return {string} Decoded value, or '' when absent.
254 */
255 function getUrlParameter(name) {
256 // Escape regex-special bracket characters in the parameter name
257 name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
258 // Build a matcher for `?name=` / `&name=` and run it against the query string
259 var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
260 var results = regex.exec(location.search);
261 // No match returns empty; otherwise URL-decode (treating '+' as space)
262 return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
263 }
264
265 /**
266 * Helper function to format large numbers with commas
267 *
268 * @param {number|string} num - Value to format.
269 * @return {string} Number with thousands separators.
270 */
271 function formatNumber(num) {
272 // Insert a comma before every group of three trailing digits
273 return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
274 }
275
276 /**
277 * Helper function to show a loading state on a button
278 *
279 * @param {jQuery} button - The button element.
280 * @param {string} loadingText - Optional label; defaults to the localized "loading" string.
281 */
282 function showButtonLoading(button, loadingText) {
283 // Stash the original label so it can be restored later
284 button.data('original-text', button.html());
285 // Swap in the loading label and disable the button
286 button.html(loadingText || mlsimportOnboarding.strings.loading);
287 button.prop('disabled', true);
288 }
289
290 /**
291 * Helper function to restore a button from loading state
292 *
293 * @param {jQuery} button - The button element.
294 */
295 function hideButtonLoading(button) {
296 // Restore the stashed label and re-enable the button
297 button.html(button.data('original-text'));
298 button.prop('disabled', false);
299 }
300
301
302
303
304
305
306
307
308
309
310
311 // Second ready block: account / MLS-credential save handlers for the wizard.
312 jQuery(document).ready(function (jQuery) {
313
314
315
316
317 /**
318 * Show transient status text on a button, optionally reverting after 2s.
319 *
320 * @param {jQuery} button - The button element.
321 * @param {string} statusText - Text to display.
322 * @param {boolean} reset - When true, restore the original label after 2s.
323 */
324 function showButtonStatus(button, statusText, reset = true) {
325 // Remember the original label (once) so we can restore it
326 const originalText = button.data('original-text') || button.text();
327 if (!button.data('original-text')) {
328 button.data('original-text', originalText);
329 }
330 // Apply the status text; optionally schedule a revert
331 button.text(statusText);
332 if (reset) {
333 setTimeout(() => button.text(originalText), 2000);
334 }
335 }
336
337 // Save the MLSImport account username/password via AJAX
338 jQuery('.mlsimport-save-account').on('click', function (e) {
339 e.preventDefault();
340 const button = jQuery(this);
341 // Show a persistent "saving" label while the request is in flight
342 showButtonStatus(button, mlsimportOnboarding.strings.saving, false);
343
344 // Read the entered credentials
345 const username = jQuery('#mlsimport_admin_options-mlsimport_username').val();
346 const password = jQuery('#mlsimport_admin_options-mlsimport_password').val();
347
348 // POST the credentials to the account-save endpoint
349 jQuery.post(mlsimportOnboarding.ajaxurl, {
350 action: 'mlsimport_save_account',
351 security: mlsimportOnboarding.nonce,
352 mlsimport_username: username,
353 mlsimport_password: password
354 }, function (response) {
355 if (response.success) {
356 // Success: flash the success label
357 showButtonStatus(button, mlsimportOnboarding.strings.success);
358
359 // Replace the status feedback message
360 jQuery('.mlsimport_warning').remove();
361 jQuery('#mlsimport_admin_options-mlsimport_username')
362 .closest('fieldset')
363 .before(response.data.html);
364 } else {
365 // Server reported failure
366 showButtonStatus(button, mlsimportOnboarding.strings.error);
367 }
368 }).fail(function () {
369 // Transport failure
370 showButtonStatus(button, mlsimportOnboarding.strings.error);
371 });
372 });
373
374 // Save the MLS-specific settings (everything except the account credentials)
375 jQuery('.mlsimport-save-mls-data').on('click', function (e) {
376 e.preventDefault();
377
378 // Disable the button and show the "saving" label
379 const button = jQuery(this);
380 const originalText = button.text();
381 button.text(mlsimportOnboarding.strings.saving).prop('disabled', true);
382
383 // Base payload
384 const data = {
385 action: 'mlsimport_save_mls_data',
386 security: mlsimportOnboarding.nonce
387 };
388
389 // Collect every mlsimport_admin_options[...] field except the credentials
390 jQuery('[name^="mlsimport_admin_options"]').each(function () {
391 const name = jQuery(this).attr('name').replace('mlsimport_admin_options[', '').replace(']', '');
392 if (name !== 'mlsimport_username' && name !== 'mlsimport_password') {
393 data[name] = jQuery(this).val();
394 }
395 });
396
397 // POST the collected MLS settings
398 jQuery.post(mlsimportOnboarding.ajaxurl, data, function (response) {
399 // Restore the button regardless of outcome
400 button.text(originalText).prop('disabled', false);
401
402 if (response.success) {
403 // Remove all .mlsimport_warning except the validated one (account message)
404 jQuery('.mlsimport_warning').not('.mlsimport_validated').remove();
405
406 // Insert new MLS connection message before MLS input
407 jQuery('#mlsimport_mls_name_front')
408 .closest('fieldset')
409 .before(response.data.html);
410
411 // MLS connection confirmed: gather the metadata + save the
412 // import-field configuration in the background right away, so
413 // the Field Mapping step is ready when the user reaches it.
414 // Fire-and-forget on purpose: the shared reloading helper
415 // would yank the wizard step from under the user on success.
416 if (response.data.connected) {
417 jQuery.post(mlsimportOnboarding.ajaxurl, {
418 action: 'mlsimport_saas_get_metadata_function',
419 security: jQuery('#mlsimport_saas_get_metadata').val()
420 });
421 }
422 } else {
423 // Server reported failure
424 button.text(mlsimportOnboarding.strings.error);
425 }
426 }).fail(function () {
427 // Transport failure: show error and re-enable
428 button.text(mlsimportOnboarding.strings.error).prop('disabled', false);
429 });
430 });
431
432
433
434 // code for the acocunt page
435
436 // Only run this block on the account step
437 if (jQuery('.mlsimport-wizard-content-account').length) {
438 // Set the Continue button's initial enabled/disabled state
439 updateContinueButton();
440
441 // Add continue button navigation
442 jQuery('.mlsimport-wizard-content-account .mlsimport-wizard-next').on('click', function(e) {
443 e.preventDefault();
444 // Only navigate onward when the button isn't disabled
445 if (!jQuery(this).prop('disabled')) {
446 // Derive the admin.php URL from ajaxurl and go to the field-mapping step
447 window.location.href = ajaxurl.replace('admin-ajax.php', 'admin.php') + '?page=mlsimport-onboarding&step=field-mapping';
448 }
449 });
450
451 // Update after AJAX calls
452 jQuery(document).ajaxComplete(function() {
453 // Re-evaluate the Continue button shortly after any AJAX completes
454 setTimeout(updateContinueButton, 500);
455 });
456 }
457
458 });
459
460
461
462
463 /**
464 * Enable the account-step Continue button only once both credentials
465 * (account + MLS) have been validated.
466 */
467 function updateContinueButton() {
468 // Count how many validated confirmation messages are present
469 const validatedCount = jQuery('.mlsimport_warning.mlsimport_validated').length;
470 console.log('nwe thing');
471 const continueButton = jQuery('.mlsimport-wizard-content-account .mlsimport-wizard-next');
472
473 // Both checks passed (>=2): enable; otherwise disable
474 if (validatedCount >= 2) {
475 continueButton.prop('disabled', false).removeClass('disabled');
476 } else {
477 continueButton.prop('disabled', true).addClass('disabled');
478 }
479 }
480