PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 6.3.7
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v6.3.7
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-field-selector.js

mlsimport-field-selector.js in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 6.3.7, at admin/js/mlsimport-field-selector.js

1,167 lines 38.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MLS Import Field Selector JavaScript
3 *
4 * Handles client-side functionality for the MLS field selection interface:
5 * - Filtering and searching fields
6 * - Drag and drop reordering
7 * - Bulk actions
8 * - Interactive UI elements
9 *
10 * @package MLSImport
11 * @subpackage MLSImport/js
12 * @since 1.0.0
13 */
14
15
16 (function($) {
17 'use strict';
18
19 /**
20 * Initialize all field selector functionality once the document is ready
21 */
22 $(document).ready(function() {
23 initProgressiveLoading();
24 initializeFilters();
25 initializeBulkActions();
26 initializeDragAndDrop();
27 initRowReordering();
28 initializeFieldSorting();
29 preventEnterSubmission();
30 });
31
32 /**
33 * Prevent form submission when pressing Enter inside label or postmeta inputs
34 */
35 function preventEnterSubmission() {
36 jQuery(document).on('keydown', '.mlsimport-label-input, .mlsimport-postmeta-input', function(e) {
37 if (e.key === 'Enter') {
38 e.preventDefault();
39 }
40 });
41 }
42
43
44 /**
45 * Initialize filter and search functionality
46 */
47 function initializeFilters() {
48 // Field search functionality
49 $('#mlsimport-field-search')
50 .on('keydown', function(e) {
51 // Prevent form submission when pressing Enter inside the search box
52 if (e.key === 'Enter') {
53 e.preventDefault();
54 }
55 })
56 .on('keyup', function() {
57 const searchTerm = $(this).val().toLowerCase();
58
59 if (searchTerm.length > 2 || searchTerm.length === 0) {
60 filterFieldsBySearch(searchTerm);
61 }
62 });
63
64 // Import status filter functionality
65 $('#mlsimport-import-filter').on('change', function() {
66 const filterValue = $(this).val();
67 filterFieldsByImportStatus(filterValue);
68 });
69
70 // Add alphabetical filters if present
71 $('.mlsimport-alpha-filter').on('click', function(e) {
72 e.preventDefault();
73
74 // Remove active class from all alpha filters
75 $('.mlsimport-alpha-filter').removeClass('active');
76
77 // Add active class to clicked filter
78 $(this).addClass('active');
79
80 const letter = $(this).data('letter');
81 filterFieldsByAlphabet(letter);
82 });
83
84 // Reset filters button
85 $('#mlsimport-reset-filters').on('click', function(e) {
86 e.preventDefault();
87 resetAllFilters();
88 });
89
90 // Pagination links
91 $('.mlsimport-page-link').on('click', function(e) {
92 e.preventDefault();
93
94 const page = $(this).data('page');
95 navigateToPage(page);
96 });
97 }
98
99 /**
100 * Filter table rows based on search term
101 *
102 * @param {string} searchTerm - The term to search for
103 */
104 function filterFieldsBySearch(searchTerm) {
105 // If search is empty, show all rows (respecting other filters)
106 if (searchTerm === '') {
107 $('.mlsimport-field-row').show();
108 return;
109 }
110
111 // Hide all rows first
112 $('.mlsimport-field-row').hide();
113
114 // Show rows that match the search term
115 $('.mlsimport-field-row').each(function() {
116 const fieldName = $(this).data('field-key').toLowerCase();
117
118 if (fieldName.indexOf(searchTerm) !== -1) {
119 $(this).show();
120 }
121 });
122
123 // Update the "no results" message
124 updateNoResultsMessage();
125 }
126
127 /**
128 * Filter table rows based on import status
129 *
130 * @param {string} status - The import status to filter by ('all', 'selected', 'not_selected', 'mandatory')
131 */
132 function filterFieldsByImportStatus(status) {
133 // If all, show all rows
134 if (status === 'all') {
135 $('.mlsimport-field-row').show();
136 return;
137 }
138
139 // Hide all rows first
140 $('.mlsimport-field-row').hide();
141
142 // Show rows based on import status
143 $('.mlsimport-field-row').each(function() {
144 const isChecked = $(this).find('.mlsimport-import-checkbox').prop('checked');
145 const isMandatory = $(this).data('is-mandatory') === 'true';
146
147 if (status === 'mandatory' && isMandatory) {
148 $(this).show();
149 } else if (status === 'selected' && (isChecked || isMandatory)) {
150 $(this).show();
151 } else if (status === 'not_selected' && !isChecked && !isMandatory) {
152 $(this).show();
153 }
154 });
155
156 // Update the "no results" message
157 updateNoResultsMessage();
158 }
159
160 /**
161 * Filter table rows based on first letter
162 *
163 * @param {string} letter - The first letter to filter by
164 */
165 function filterFieldsByAlphabet(letter) {
166 // If letter is empty or 'all', show all rows
167 if (!letter || letter === 'all') {
168 $('.mlsimport-field-row').show();
169 return;
170 }
171
172 // Hide all rows first
173 $('.mlsimport-field-row').hide();
174
175 // Show rows that start with the specified letter
176 $('.mlsimport-field-row').each(function() {
177 const fieldName = $(this).data('field-key');
178 const firstLetter = fieldName.charAt(0).toUpperCase();
179
180 if (firstLetter === letter.toUpperCase()) {
181 $(this).show();
182 }
183 });
184
185 // Update the "no results" message
186 updateNoResultsMessage();
187 }
188
189 /**
190 * Reset all filters and show all fields
191 */
192 function resetAllFilters() {
193 // Reset search input
194 $('#mlsimport-field-search').val('');
195
196 // Reset import filter dropdown
197 $('#mlsimport-import-filter').val('all');
198
199 // Reset alphabetical filter
200 $('.mlsimport-alpha-filter').removeClass('active');
201 $('.mlsimport-alpha-filter[data-letter="all"]').addClass('active');
202
203 // Show all rows
204 $('.mlsimport-field-row').show();
205
206 // Hide the "no results" message
207 $('.mlsimport-no-results').hide();
208 }
209
210 /**
211 * Check if there are any visible rows and show/hide the "no results" message
212 */
213 function updateNoResultsMessage() {
214 const visibleRows = $('.mlsimport-field-row:visible').length;
215
216 if (visibleRows === 0) {
217 // If no results message doesn't exist, create it
218 if ($('.mlsimport-no-results').length === 0) {
219 const colspan = $('.mlsimport-fields-table thead th').length;
220 const message = $('<tr class="mlsimport-no-results"><td colspan="' + colspan + '">No fields found matching your criteria.</td></tr>');
221 $('#mlsimport-fields-table-body').append(message);
222 } else {
223 $('.mlsimport-no-results').show();
224 }
225 } else {
226 $('.mlsimport-no-results').hide();
227 }
228 }
229
230 /**
231 * Navigate to a specific page
232 *
233 * @param {number} page - The page number to navigate to
234 */
235 function navigateToPage(page) {
236 // This would typically reload the page with the new page parameter
237 // For this implementation, we'll use JavaScript to update the form and submit
238
239 // Create or update a hidden input for the page
240 if ($('input[name="mlsimport_page"]').length > 0) {
241 $('input[name="mlsimport_page"]').val(page);
242 } else {
243 $('<input>').attr({
244 type: 'hidden',
245 name: 'mlsimport_page',
246 value: page
247 }).appendTo('.mlsimport-fields-form');
248 }
249
250 // Submit the form
251 $('.mlsimport-fields-form').submit();
252 }
253
254 /**
255 * Initialize bulk action functionality
256 */
257 function initializeBulkActions() {
258 // Select All for Import checkboxes (only non-mandatory fields)
259 jQuery('#mlsimport-select-all-import').on('click', function(e) {
260 e.preventDefault();
261 bulkSaveImportSelections(true);
262 });
263
264 jQuery('#mlsimport-select-none-import').on('click', function(e) {
265 e.preventDefault();
266 bulkSaveImportSelections(false);
267 });
268
269 // Select All for Admin Only checkboxes
270 jQuery('#mlsimport-select-all-admin').on('click', function(e) {
271 e.preventDefault();
272 bulkSaveAdminSelections(true);
273 });
274
275 // Select None for Admin Only checkboxes
276 jQuery('#mlsimport-select-none-admin').on('click', function(e) {
277 e.preventDefault();
278 bulkSaveAdminSelections(false);
279 });
280
281 // Update stats when a checkbox is clicked
282 jQuery('.mlsimport-import-checkbox').on('change', function() {
283 updateFieldStats();
284 });
285 }
286
287
288 /**
289 * Update the field statistics displayed at the top
290 */
291 window.updateFieldStats = function() {
292 const totalFields = $('.mlsimport-field-row').length;
293 const mandatoryFields = $('.mlsimport-field-row[data-is-mandatory="true"]').length;
294 const selectedFields = $('.mlsimport-import-checkbox:checked').length + mandatoryFields;
295
296 // Count fields with empty labels that are selected for import
297 let missingLabels = 0;
298
299 // Count for non-mandatory checked fields with missing labels
300 $('.mlsimport-import-checkbox:checked').each(function() {
301 const row = $(this).closest('tr');
302 const labelValue = row.find('.mlsimport-label-input').val();
303
304 if (!labelValue || labelValue.trim() === '') {
305 missingLabels++;
306 }
307 });
308
309 // Count for mandatory fields with missing labels
310 $('.mlsimport-field-row[data-is-mandatory="true"]').each(function() {
311 const labelValue = $(this).find('.mlsimport-label-input').val();
312
313 if (!labelValue || labelValue.trim() === '') {
314 missingLabels++;
315 }
316 });
317
318 // Update the stats display
319 $('.mlsimport-field-stats li').eq(0).text(totalFields + ' fields total');
320 $('.mlsimport-field-stats li').eq(1).text(selectedFields + ' marked for import');
321 $('.mlsimport-field-stats li').eq(2).text(missingLabels + ' missing labels');
322 }
323
324 /**
325 * Initialize drag and drop functionality for reordering fields
326 */
327 function initializeDragAndDrop() {
328
329 // Check if jQuery UI sortable is available
330 if ($.fn.sortable) {
331
332 // Make the entire row draggable. Interactive elements like
333 // inputs and buttons remain excluded via the default `cancel`
334 // option so they can still be used without initiating a drag.
335 $('#mlsimport-fields-table-body').sortable({
336 cancel: 'input, textarea, button, select',
337 helper: function(e, tr) {
338 // Create a helper that maintains cell widths
339 const $originals = tr.children();
340 const $helper = tr.clone();
341
342 $helper.children().each(function(index) {
343 $(this).width($originals.eq(index).width());
344 });
345
346 return $helper;
347 },
348 update: function(event, ui) {
349
350
351 const $movedRow = jQuery(ui.item);
352 const $prevRow = $movedRow.prev('.mlsimport-field-row');
353
354 const movingOrder = parseInt($movedRow.attr('data-field-order'), 10);
355
356 // If no previous row exists, it's the first position
357 if (!$prevRow.length) {
358
359 saveFieldPosition(movingOrder, 0, 'before');
360 } else {
361 // Normal case - moving after another row
362 const prevOrder = parseInt($prevRow.attr('data-field-order'), 10);
363 saveFieldPosition(movingOrder, prevOrder, 'after');
364 }
365
366 refreshRowPositions();
367 }
368
369 });
370
371 // Add visual cue for draggable rows
372 $('.mlsimport-field-row td').css('cursor', 'move');
373
374 } else {
375 console.error('jQuery UI sortable not available. Drag and drop ordering is disabled.');
376 }
377 }
378
379
380
381
382 })(jQuery);
383
384
385
386 function initVirtualScrolling() {
387
388
389 // Store all rows
390 let allRows = jQuery('.mlsimport-field-row').toArray();
391
392
393 let rowsPerPage = 50;
394 let visibleCount = rowsPerPage;
395 let loading = false;
396
397 // Make sure only initial rows are visible by hiding everything and then showing first 50
398 jQuery('.mlsimport-field-row').hide();
399
400 for (let i = 0; i < Math.min(rowsPerPage, allRows.length); i++) {
401 jQuery(allRows[i]).show();
402 }
403
404 // Add loading indicator if there are more than 50 rows
405 if (allRows.length > rowsPerPage) {
406 jQuery('<div class="mlsimport-loading" style="text-align: center; padding: 20px; margin-top: 20px; background: #f0f0f0; border-top: 1px solid #ddd;">Scroll down to load more fields...</div>')
407 .insertAfter('.mlsimport-fields-table');
408
409 // Detect scroll
410 jQuery(window).on('scroll', function() {
411 if (loading) return;
412
413 // Check if user has scrolled near the bottom
414 let scrollPosition = jQuery(window).scrollTop() + jQuery(window).height();
415 let documentHeight = jQuery(document).height();
416
417 if (scrollPosition > documentHeight - 300) {
418 loadMoreRows();
419 }
420 });
421 }
422
423 // Function to load more rows
424 function loadMoreRows() {
425 loading = true;
426 jQuery('.mlsimport-loading').text('Loading more fields...').show();
427
428 // Simulate loading delay for visual feedback
429 setTimeout(function() {
430 // Show next batch of rows
431 let endIndex = Math.min(visibleCount + rowsPerPage, allRows.length);
432
433 for (let i = visibleCount; i < endIndex; i++) {
434 jQuery(allRows[i]).show();
435 }
436
437 visibleCount = endIndex;
438 loading = false;
439
440 // Update or remove loading message
441 if (visibleCount >= allRows.length) {
442 jQuery('.mlsimport-loading').remove(); // Remove instead of just changing text
443 } else {
444 jQuery('.mlsimport-loading').text('Scroll down to load more fields...');
445 }
446 }, 300);
447 }
448 }
449
450 // Complete row movement implementation with debugging
451 function initRowReordering() {
452
453
454 // Attach click handlers to up buttons
455 jQuery('.mlsimport-move-up').on('click', function(e) {
456 e.preventDefault();
457 e.stopPropagation(); // Prevent event bubbling
458 var $row = jQuery(this).closest('.mlsimport-field-row');
459
460 moveRowUp($row);
461 });
462
463 // Attach click handlers to down buttons
464 jQuery('.mlsimport-move-down').on('click', function(e) {
465 e.preventDefault();
466 e.stopPropagation(); // Prevent event bubbling
467 var $row = jQuery(this).closest('.mlsimport-field-row');
468
469 moveRowDown($row);
470 });
471
472 // Attach click handlers to move top buttons
473 jQuery('.mlsimport-move-top').on('click', function(e) {
474 e.preventDefault();
475 e.stopPropagation();
476 var $row = jQuery(this).closest('.mlsimport-field-row');
477
478 moveRowTop($row);
479 });
480
481 // Attach click handlers to move bottom buttons
482 jQuery('.mlsimport-move-bottom').on('click', function(e) {
483 e.preventDefault();
484 e.stopPropagation();
485 var $row = jQuery(this).closest('.mlsimport-field-row');
486
487 moveRowBottom($row);
488 });
489 }
490
491
492 let moveUpTimer = null;
493 let originalMovingOrder = null;
494 let movingFieldKey = null;
495
496 function moveRowUp($row) {
497 const $prev = $row.prev('.mlsimport-field-row');
498 if (!$prev.length) {
499
500 return;
501 }
502
503 try {
504 const $button = $row.find('.mlsimport-move-up');
505 const oldTop = $button.offset().top;
506
507 // Get the field key (unique identifier for the row)
508 const fieldKey = $row.attr('data-field-key');
509
510 // If this is a new field being moved (not continuation of previous moves)
511 if (fieldKey !== movingFieldKey) {
512 movingFieldKey = fieldKey;
513 originalMovingOrder = parseInt($row.attr('data-field-order'), 10);
514
515 }
516
517
518
519 // Get current orders for the swap
520 const movingOrder = parseInt($row.attr('data-field-order'), 10);
521 const targetOrder = parseInt($prev.attr('data-field-order'), 10);
522
523 // Swap order attributes
524 $row.attr('data-field-order', targetOrder);
525 $prev.attr('data-field-order', movingOrder);
526
527 // Move the row
528 $row.insertBefore($prev);
529 refreshRowPositions();
530 highlightRow($row);
531
532 const newTop = $button.offset().top;
533 const deltaY = newTop - oldTop;
534 window.scrollBy(0, deltaY);
535
536 // Clear existing timer
537 if (moveUpTimer !== null) {
538 clearTimeout(moveUpTimer);
539 }
540
541 // Set new timer
542 moveUpTimer = setTimeout(function() {
543
544
545 // Find the row by field key
546 const $movedRow = jQuery(`.mlsimport-field-row[data-field-key="${movingFieldKey}"]`);
547
548 if ($movedRow.length) {
549 // Get the previous row
550 const $prevRow = $movedRow.prev('.mlsimport-field-row');
551
552 if ($prevRow.length) {
553 const prevOrder = parseInt($prevRow.attr('data-field-order'), 10);
554
555 saveFieldPosition(originalMovingOrder, prevOrder, 'before');
556 } else {
557 // Row is at the top
558
559 saveFieldPosition(originalMovingOrder, 0, 'before');
560 }
561 }
562
563 // Reset tracking variables
564 moveUpTimer = null;
565 originalMovingOrder = null;
566 movingFieldKey = null;
567 }, 1000);
568
569 } catch (e) {
570 console.error("Error moving row up:", e);
571 }
572 }
573
574
575
576
577 let moveDownTimer = null;
578 let originalDownOrder = null;
579 let movingDownFieldKey = null;
580
581 // Queue for saving field positions so requests don't overlap
582 let positionSaving = false;
583 let positionQueue = [];
584
585 function moveRowDown($row) {
586 // Get the next row specifically with the same class
587 var $next = $row.next('.mlsimport-field-row');
588
589
590 if ($next.length) {
591 try {
592 // Save exact mouse position on screen
593 var mouseY = window.event.clientY;
594 var mouseX = window.event.clientX;
595 var $button = $row.find('.mlsimport-move-down');
596 var oldOffset = $button.offset();
597 var oldTop = oldOffset.top;
598
599 // Get the field key (unique identifier for the row)
600 const fieldKey = $row.attr('data-field-key');
601
602 // If this is a new field being moved (not continuation of previous moves)
603 if (fieldKey !== movingDownFieldKey) {
604 movingDownFieldKey = fieldKey;
605 originalDownOrder = parseInt($row.attr('data-field-order'), 10);
606
607 }
608
609 // Get current order values
610 var movingOrder = parseInt($row.attr('data-field-order'), 10);
611 var targetOrder = parseInt($next.attr('data-field-order'), 10);
612
613 // Swap the data-field-order values
614 $row.attr('data-field-order', targetOrder);
615 $next.attr('data-field-order', movingOrder);
616
617 // Move the row in the UI
618 $row.insertAfter($next);
619 refreshRowPositions();
620
621 // Highlight to confirm movement
622 highlightRow($row);
623
624 // Get new position
625 var newOffset = $button.offset();
626
627 // Find how much the position changed
628 var deltaY = newOffset.top - oldTop;
629
630 // Adjust scroll to keep relative position
631 window.scrollBy(0, deltaY);
632
633 // Clear existing timer
634 if (moveDownTimer !== null) {
635 clearTimeout(moveDownTimer);
636 }
637
638 // Set new timer
639 moveDownTimer = setTimeout(function() {
640
641
642 // Find the row by field key
643 const $movedRow = jQuery(`.mlsimport-field-row[data-field-key="${movingDownFieldKey}"]`);
644
645 if ($movedRow.length) {
646 // Get the next row
647 const $nextRow = $movedRow.next('.mlsimport-field-row');
648
649 if ($nextRow.length) {
650 let nextOrder = parseInt($nextRow.attr('data-field-order'), 10);
651 nextOrder=nextOrder-1;
652
653 saveFieldPosition(originalDownOrder, nextOrder, 'after');
654 } else {
655 // Row is at the bottom
656
657 const lastOrder = parseInt($movedRow.attr('data-field-order'), 10);
658 saveFieldPosition(originalDownOrder, lastOrder, 'after');
659 }
660 }
661
662 // Reset tracking variables
663 moveDownTimer = null;
664 originalDownOrder = null;
665 movingDownFieldKey = null;
666 }, 1000);
667
668 } catch (e) {
669 console.error("Error moving row down:", e);
670 }
671 } else {
672 console.log("No next row found, can't move down");
673 }
674 }
675
676
677
678
679
680
681
682
683 function saveFieldPosition(movingOrder, targetOrder, position) {
684 positionQueue.push({ movingOrder, targetOrder, position });
685 processPositionQueue();
686 }
687
688 function processPositionQueue() {
689 if (positionSaving || positionQueue.length === 0 || window.mlsimportSaving) {
690 return;
691 }
692
693 positionSaving = true;
694 window.mlsimportSaving = true;
695 const item = positionQueue.shift();
696
697 jQuery('.mlsimport-move-up, .mlsimport-move-down').prop('disabled', true);
698 let nonce = '';
699 if (jQuery('#mlsimport_field_selector_nonce').length > 0) {
700 nonce = jQuery('#mlsimport_field_selector_nonce').val();
701 } else if (typeof mlsimport_params !== 'undefined' && mlsimport_params.nonce) {
702 nonce = mlsimport_params.nonce;
703 }
704
705 const $notification = jQuery('<div class="mlsimport-notification mlsimport-notification-info">Saving field position...</div>');
706 jQuery('body').append($notification).fadeIn();
707
708 jQuery.ajax({
709 url: mlsimport_params.ajax_url,
710 type: 'POST',
711 data: {
712 action: 'mlsimport_save_field_position',
713 security: nonce,
714 moving_index: item.movingOrder,
715 target_index: item.targetOrder,
716 position: item.position
717 },
718 success: function(response) {
719 if (response.success) {
720 showNotification('Field position saved.', 'success');
721 } else {
722 showNotification('Error saving position: ' + (response.data || 'Unknown error'), 'error');
723 }
724 },
725 error: function() {
726 showNotification('Server error while saving position.', 'error');
727 },
728 complete: function() {
729 jQuery('.mlsimport-move-up, .mlsimport-move-down').prop('disabled', false);
730 $notification.remove();
731 positionSaving = false;
732 window.mlsimportSaving = false;
733 processPositionQueue();
734 if (typeof window.processSaveQueue === 'function') {
735 window.processSaveQueue();
736 }
737 }
738 });
739 }
740
741
742 // Highlight function
743 function highlightRow($row) {
744 $row.css('background-color', '#ffffd0');
745 setTimeout(function() {
746 $row.css('background-color', '');
747 }, 500);
748 }
749
750 // Refresh data-field-order attributes and debug positions
751 function refreshRowPositions() {
752 jQuery('#mlsimport-fields-table-body .mlsimport-field-row').each(function(index) {
753 jQuery(this).attr('data-field-order', index);
754 jQuery(this).find('.field-position').text((index + 1) + '. ');
755 });
756 }
757
758 // Move a row directly to the top
759 function moveRowTop($row) {
760 const $first = jQuery('.mlsimport-field-row').first();
761 if ($row.is($first)) return;
762
763 const $button = $row.find('.mlsimport-move-top');
764 const oldTop = $button.offset().top;
765 const originalOrder = parseInt($row.attr('data-field-order'), 10);
766
767 $row.insertBefore($first);
768 refreshRowPositions();
769 highlightRow($row);
770
771 const newTop = $button.offset().top;
772 window.scrollBy(0, newTop - oldTop);
773
774 saveFieldPosition(originalOrder, 0, 'before');
775 }
776
777 // Move a row directly to the bottom
778 function moveRowBottom($row) {
779 const $last = jQuery('.mlsimport-field-row').last();
780 if ($row.is($last)) return;
781
782 const $button = $row.find('.mlsimport-move-bottom');
783 const oldTop = $button.offset().top;
784 const originalOrder = parseInt($row.attr('data-field-order'), 10);
785 const lastOrder = parseInt($last.attr('data-field-order'), 10);
786
787 $row.insertAfter($last);
788 refreshRowPositions();
789 highlightRow($row);
790
791 const newTop = $button.offset().top;
792 window.scrollBy(0, newTop - oldTop);
793
794 saveFieldPosition(originalOrder, lastOrder, 'after');
795 }
796
797
798 /**
799 * Handles the sorting dropdown functionality
800 */
801 function initializeFieldSorting() {
802 // Add change event handler to the sorting dropdown
803 jQuery('#mlsimport-field-sort').on('change', function() {
804 const sortValue = jQuery(this).val();
805 sortFields(sortValue);
806 });
807 }
808
809 /**
810 * Initialize tooltips for row action buttons using jQuery UI
811 */
812 function initializeMoveButtonTooltips() {
813 if (jQuery.fn.tooltip) {
814 jQuery(document).tooltip({
815 items: '.mlsimport-row-actions .mlsimport-move-btn',
816 classes: { 'ui-tooltip': 'mlsimport-move-tooltip' }
817 });
818 } else {
819 console.warn('jQuery UI tooltip not available.');
820 }
821 }
822
823 /**
824 * Sort fields based on selected criteria
825 *
826 * @param {string} sortBy - The field to sort by
827 */
828 // Debug and fix for label sorting
829 function sortFields(sortBy) {
830
831 const $tbody = jQuery('#mlsimport-fields-table-body');
832 let $rows = $tbody.find('tr.mlsimport-field-row').toArray();
833
834 // Split sort value into criteria and direction
835 const [criteria, direction] = sortBy.split('_');
836
837
838 // Sort the rows based on the selected criteria
839 $rows.sort(function(a, b) {
840 const $a = jQuery(a);
841 const $b = jQuery(b);
842 let result = 0;
843
844 // Debug the actual elements to make sure we're accessing correctly
845 // if (criteria === 'label') {
846
847 // }
848
849 switch (criteria) {
850 case 'label':
851 // Sort by the label input value
852 let labelA = $a.find('.mlsimport-label-input').val() || '';
853 let labelB = $b.find('.mlsimport-label-input').val() || '';
854
855 labelA = labelA.toLowerCase();
856 labelB = labelB.toLowerCase();
857
858 if (labelA === '' && labelB !== '') return 1;
859 if (labelA !== '' && labelB === '') return -1;
860 if (labelA === '' && labelB === '') {
861 return $a.data('field-key').toLowerCase().localeCompare($b.data('field-key').toLowerCase());
862 }
863
864 result = labelA.localeCompare(labelB);
865 break;
866
867 case 'postmeta':
868 let pmA = ($a.find('.mlsimport-postmeta-input').val() || '').toLowerCase();
869 let pmB = ($b.find('.mlsimport-postmeta-input').val() || '').toLowerCase();
870 result = pmA.localeCompare(pmB);
871 break;
872
873 case 'category':
874 let catA = ($a.find('.mlsimport-field-taxonomy select option:selected').text() || '').toLowerCase();
875 let catB = ($b.find('.mlsimport-field-taxonomy select option:selected').text() || '').toLowerCase();
876 result = catA.localeCompare(catB);
877 break;
878
879 case 'import':
880 let impA = $a.find('.mlsimport-import-checkbox').is(':checked');
881 let impB = $b.find('.mlsimport-import-checkbox').is(':checked');
882 if (impA === impB) {
883 result = 0;
884 } else {
885 result = impA ? -1 : 1; // selected first
886 }
887 break;
888
889 case 'hidden':
890 let hidA = $a.find('.mlsimport-admin-checkbox').is(':checked');
891 let hidB = $b.find('.mlsimport-admin-checkbox').is(':checked');
892 if (hidA === hidB) {
893 result = 0;
894 } else {
895 result = hidA ? -1 : 1; // selected first
896 }
897 break;
898
899 default:
900 const defaultNameA = $a.data('field-key').toLowerCase();
901 const defaultNameB = $b.data('field-key').toLowerCase();
902 result = defaultNameA.localeCompare(defaultNameB);
903 break;
904 }
905
906 // Apply direction for ascending/descending sorts or selected/unselected
907 if (criteria === 'import' || criteria === 'hidden') {
908 if (direction === 'unselected') {
909 result = -result;
910 }
911 } else if (direction === 'desc') {
912 result = -result;
913 }
914
915 return result;
916 });
917
918 // Reattach the sorted rows to the table
919 jQuery.each($rows, function(index, row) {
920 $tbody.append(row);
921 });
922
923 // Visual feedback that sorting occurred
924 $tbody.fadeOut(100).fadeIn(100);
925 }
926
927 /**
928 * Show a notification message to the user
929 *
930 * @param {string} message - The message to display
931 * @param {string} type - The type of message ('success', 'error', 'info')
932 */
933 function showNotification(message, type) {
934 console.log('doing notification');
935 // Create notification element if it doesn't exist
936 if (jQuery('.mlsimport-notification').length === 0) {
937 jQuery('<div class="mlsimport-notification"></div>').appendTo('body');
938 }
939
940 // Set message and type
941 jQuery('.mlsimport-notification')
942 .attr('class', 'mlsimport-notification mlsimport-notification-' + type)
943 .text(message)
944 .fadeIn()
945 .delay(3000)
946 .fadeOut();
947 }
948
949 /**
950 * Save import selections for all visible fields in bulk
951 *
952 * @param {boolean} checked - Whether checkboxes should be checked or not
953 */
954 function bulkSaveImportSelections(checked) {
955 const fields = {};
956
957 jQuery('.mlsimport-import-checkbox:not([disabled]):visible').each(function() {
958 const $checkbox = jQuery(this);
959 const $row = $checkbox.closest('.mlsimport-field-row');
960 const fieldKey = $row.data('field-key');
961
962 $checkbox.prop('checked', checked);
963 fields[fieldKey] = checked ? 1 : 0;
964
965 // Add saving indicator similar to progressive-save.js
966 addBulkSavingIndicator($row, '.mlsimport-field-import');
967 });
968
969 if (Object.keys(fields).length === 0) {
970 updateFieldStats();
971 return;
972 }
973
974 jQuery.ajax({
975 url: mlsimport_params.ajax_url,
976 type: 'POST',
977 data: {
978 action: 'mlsimport_save_bulk_import',
979 security: mlsimport_params.nonce,
980 fields: fields
981 },
982 success: function(response) {
983 const status = response.success ? 'success' : 'error';
984 jQuery.each(fields, function(key) {
985 const $row = jQuery('.mlsimport-field-row[data-field-key="' + key + '"]');
986 updateBulkSavingIndicator($row, status, '.mlsimport-field-import');
987 });
988 updateFieldStats();
989 },
990 error: function() {
991 jQuery.each(fields, function(key) {
992 const $row = jQuery('.mlsimport-field-row[data-field-key="' + key + '"]');
993 updateBulkSavingIndicator($row, 'error', '.mlsimport-field-import');
994 });
995 }
996 });
997 }
998
999 /**
1000 * Save admin visibility selections for all visible fields in bulk
1001 *
1002 * @param {boolean} checked - Whether checkboxes should be checked or not
1003 */
1004 function bulkSaveAdminSelections(checked) {
1005 const fields = {};
1006
1007 jQuery('.mlsimport-admin-checkbox:visible').each(function() {
1008 const $checkbox = jQuery(this);
1009 const $row = $checkbox.closest('.mlsimport-field-row');
1010 const fieldKey = $row.data('field-key');
1011
1012 $checkbox.prop('checked', checked);
1013 fields[fieldKey] = checked ? 1 : 0;
1014
1015 addBulkSavingIndicator($row, '.mlsimport-field-admin');
1016 });
1017
1018 if (Object.keys(fields).length === 0) {
1019 updateFieldStats();
1020 return;
1021 }
1022
1023 jQuery.ajax({
1024 url: mlsimport_params.ajax_url,
1025 type: 'POST',
1026 data: {
1027 action: 'mlsimport_save_bulk_admin',
1028 security: mlsimport_params.nonce,
1029 fields: fields
1030 },
1031 success: function(response) {
1032 const status = response.success ? 'success' : 'error';
1033 jQuery.each(fields, function(key) {
1034 const $row = jQuery('.mlsimport-field-row[data-field-key="' + key + '"]');
1035 updateBulkSavingIndicator($row, status, '.mlsimport-field-admin');
1036 });
1037 updateFieldStats();
1038 },
1039 error: function() {
1040 jQuery.each(fields, function(key) {
1041 const $row = jQuery('.mlsimport-field-row[data-field-key="' + key + '"]');
1042 updateBulkSavingIndicator($row, 'error', '.mlsimport-field-admin');
1043 });
1044 }
1045 });
1046 }
1047
1048 // Helper to add saving indicator for bulk updates
1049 function addBulkSavingIndicator($row, columnSelector) {
1050 const $element = $row.find(columnSelector);
1051 $element.find('.save-indicator').remove();
1052 $element.append('<span class="save-indicator" style="margin-left: 5px; box-sizing: border-box; display: inline-block; width: 16px; height: 16px; border: 2px solid #635BFF; border-radius: 50%; border-top-color: transparent; animation: mlsimport-spin 1s linear infinite;"></span>');
1053
1054 if (!jQuery('#mlsimport-spin-animation').length) {
1055 jQuery('head').append('<style id="mlsimport-spin-animation">@keyframes mlsimport-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }</style>');
1056 }
1057 }
1058
1059 // Helper to update indicator after bulk save
1060 function updateBulkSavingIndicator($row, status, columnSelector) {
1061 const $element = $row.find(columnSelector);
1062 const $indicator = $element.find('.save-indicator');
1063
1064 if (status === 'success') {
1065 $indicator.css({
1066 'border': 'none',
1067 'animation': 'none',
1068 'color': '#46b450',
1069 'font-size': '16px'
1070 }).html('');
1071
1072 setTimeout(function() {
1073 $indicator.fadeOut(500, function() {
1074 $indicator.remove();
1075 });
1076 }, 1000);
1077 } else {
1078 $indicator.css({
1079 'border': 'none',
1080 'animation': 'none',
1081 'color': '#dc3232',
1082 'font-size': '16px'
1083 }).html('');
1084 }
1085 }
1086
1087 /**
1088 * Progressive Field Loading - Replaces the virtual scrolling with timed display
1089 * This shows fields one by one with a smooth animation regardless of scrolling
1090 */
1091 function initProgressiveLoading() {
1092 console.log("Progressive loading initialization started");
1093
1094 // Store all rows
1095 let allRows = jQuery('.mlsimport-field-row').toArray();
1096 console.log("Found " + allRows.length + " total rows");
1097
1098 // Hide all rows initially - ensure none are visible at start
1099 jQuery('.mlsimport-field-row').hide().css('opacity', 0);
1100
1101 // Create progress bar container
1102 const progressContainer = jQuery('<div class="mlsimport-loading-progress" style="position: sticky; top: 32px; z-index: 100; padding: 10px; background: #f9f9f9; border-bottom: 1px solid #ddd; text-align: center;"></div>');
1103 const progressText = jQuery('<div class="mlsimport-loading-text">Loading fields: <span class="mlsimport-loading-count">0</span> of ' + allRows.length + '</div>');
1104 const progressBar = jQuery('<div class="mlsimport-progress-bar" style="height: 10px; background: #eee; margin-top: 5px; border-radius: 5px;"><div class="mlsimport-progress-fill" style="width: 0%; height: 100%; background: #635BFF; border-radius: 5px; transition: width 0.3s;"></div></div>');
1105
1106 progressContainer.append(progressText).append(progressBar);
1107
1108 // Add progress container at the top of the table
1109 jQuery('.mlsimport-field-selector-container').prepend(progressContainer);
1110
1111 // Variables for loading control
1112 let loadedCount = 0;
1113 let batchSize = 10; // How many rows to show at once
1114 let interval = 7; // Milliseconds between batches (adjust for speed)
1115 let isLoading = true;
1116
1117 // Function to update progress display
1118 function updateProgress(count) {
1119 const percentage = Math.floor((count / allRows.length) * 100);
1120 jQuery('.mlsimport-loading-count').text(count);
1121 jQuery('.mlsimport-progress-fill').css('width', percentage + '%');
1122
1123 // If complete, remove progress or change to completion message
1124 if (count >= allRows.length) {
1125 setTimeout(function() {
1126 progressContainer.fadeOut(500, function() {
1127 progressContainer.remove();
1128 });
1129 }, 1000);
1130 }
1131 }
1132
1133 // Start loading rows with a slight delay
1134 setTimeout(function() {
1135 const loadingInterval = setInterval(function() {
1136 if (!isLoading) {
1137 clearInterval(loadingInterval);
1138 return;
1139 }
1140
1141 // Load next batch of rows
1142 let endIndex = Math.min(loadedCount + batchSize, allRows.length);
1143
1144 for (let i = loadedCount; i < endIndex; i++) {
1145 jQuery(allRows[i])
1146 .css('opacity', 0)
1147 .show()
1148 .animate({opacity: 1}, 300);
1149 }
1150
1151 loadedCount = endIndex;
1152 updateProgress(loadedCount);
1153
1154 // Check if we're done
1155 if (loadedCount >= allRows.length) {
1156 isLoading = false;
1157 clearInterval(loadingInterval);
1158 console.log("All fields have been loaded");
1159
1160
1161
1162 }
1163 }, interval);
1164 }, 500); // Small initial delay before starting
1165
1166 // No control buttons needed - just let the loading progress automatically
1167 }