- Add viewport sync: sidebar shows only trainers visible in map area - Add mouseover event on markers showing info window on hover - Set optimized:false on markers for reliable hover events - Add legacy URL redirects (/find-a-trainer → /find-training) - Remove deprecated find-a-trainer page from Page Manager - Update Status.md with session changes - Bump version to 2.2.4 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
448 lines
15 KiB
JavaScript
448 lines
15 KiB
JavaScript
/**
|
|
* Find Training Filters
|
|
*
|
|
* Handles filtering, searching, and geolocation functionality
|
|
* for the Find Training page.
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.2.0
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
// Namespace for filters module
|
|
window.HVACTrainingFilters = {
|
|
|
|
// Debounce timer for search
|
|
searchTimer: null,
|
|
|
|
// Current AJAX request (for aborting)
|
|
currentRequest: null,
|
|
|
|
// Active filters
|
|
activeFilters: {
|
|
state: '',
|
|
certification: '',
|
|
training_format: '',
|
|
search: ''
|
|
},
|
|
|
|
// User location (if obtained)
|
|
userLocation: null,
|
|
|
|
/**
|
|
* Initialize filters
|
|
*/
|
|
init: function() {
|
|
this.bindEvents();
|
|
this.initMobileFilterToggle();
|
|
},
|
|
|
|
/**
|
|
* Bind event handlers
|
|
*/
|
|
bindEvents: function() {
|
|
const self = this;
|
|
|
|
// Search input with debounce
|
|
$('#hvac-training-search').on('input', function() {
|
|
clearTimeout(self.searchTimer);
|
|
const value = $(this).val();
|
|
|
|
self.searchTimer = setTimeout(function() {
|
|
self.activeFilters.search = value;
|
|
self.applyFilters();
|
|
}, 300);
|
|
});
|
|
|
|
// State filter
|
|
$('#hvac-filter-state').on('change', function() {
|
|
self.activeFilters.state = $(this).val();
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
// Certification filter
|
|
$('#hvac-filter-certification').on('change', function() {
|
|
self.activeFilters.certification = $(this).val();
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
// Training format filter
|
|
$('#hvac-filter-format').on('change', function() {
|
|
self.activeFilters.training_format = $(this).val();
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
// Near Me button
|
|
$('#hvac-near-me-btn').on('click', function() {
|
|
self.handleNearMeClick($(this));
|
|
});
|
|
|
|
// Clear all filters
|
|
$('.hvac-clear-filters').on('click', function() {
|
|
self.clearAllFilters();
|
|
});
|
|
|
|
// Remove individual filter
|
|
$(document).on('click', '.hvac-active-filter button', function() {
|
|
const filterType = $(this).parent().data('filter');
|
|
self.removeFilter(filterType);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Apply current filters
|
|
*/
|
|
applyFilters: function() {
|
|
const self = this;
|
|
|
|
// Abort any pending request to prevent race conditions
|
|
if (this.currentRequest && this.currentRequest.readyState !== 4) {
|
|
this.currentRequest.abort();
|
|
}
|
|
|
|
// Build filter data
|
|
const filterData = {
|
|
action: 'hvac_filter_training_map',
|
|
nonce: hvacFindTraining.nonce,
|
|
state: this.activeFilters.state,
|
|
certification: this.activeFilters.certification,
|
|
training_format: this.activeFilters.training_format,
|
|
search: this.activeFilters.search,
|
|
show_trainers: $('#hvac-show-trainers').is(':checked'),
|
|
show_venues: $('#hvac-show-venues').is(':checked')
|
|
};
|
|
|
|
// Add user location if available
|
|
if (this.userLocation) {
|
|
filterData.lat = this.userLocation.lat;
|
|
filterData.lng = this.userLocation.lng;
|
|
filterData.radius = 100; // km
|
|
}
|
|
|
|
// Send filter request and store reference
|
|
this.currentRequest = $.ajax({
|
|
url: hvacFindTraining.ajax_url,
|
|
type: 'POST',
|
|
data: filterData,
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Update map data
|
|
HVACTrainingMap.trainers = response.data.trainers || [];
|
|
HVACTrainingMap.venues = response.data.venues || [];
|
|
HVACTrainingMap.visibleTrainers = HVACTrainingMap.trainers.slice(); // Reset to all
|
|
HVACTrainingMap.updateMarkers();
|
|
HVACTrainingMap.updateCounts(HVACTrainingMap.trainers.length);
|
|
HVACTrainingMap.updateTrainerGrid();
|
|
// Note: syncSidebarWithViewport will be called by map 'idle' event
|
|
}
|
|
},
|
|
complete: function() {
|
|
self.currentRequest = null;
|
|
}
|
|
});
|
|
|
|
// Show/hide clear button
|
|
this.updateClearButtonVisibility();
|
|
},
|
|
|
|
/**
|
|
* Handle Near Me button click
|
|
*/
|
|
handleNearMeClick: function($button) {
|
|
const self = this;
|
|
|
|
// Show loading state
|
|
$button.prop('disabled', true);
|
|
$button.html('<span class="dashicons dashicons-update-alt hvac-spin"></span> Locating...');
|
|
|
|
// Clear any previous error message
|
|
this.clearLocationError();
|
|
|
|
// Get user location
|
|
HVACTrainingMap.getUserLocation(function(location, error) {
|
|
if (location) {
|
|
self.userLocation = location;
|
|
|
|
// Center map on user location
|
|
HVACTrainingMap.centerOnLocation(location.lat, location.lng, 9);
|
|
|
|
// Apply filters with location
|
|
self.applyFilters();
|
|
|
|
// Update button state
|
|
$button.html('<span class="dashicons dashicons-yes-alt"></span> Near Me');
|
|
$button.addClass('active');
|
|
|
|
// Add to active filters display
|
|
self.addActiveFilter('location', 'Near Me');
|
|
} else {
|
|
// Show inline error instead of alert
|
|
self.showLocationError(error || 'Unable to get your location. Please check browser permissions.');
|
|
|
|
// Reset button
|
|
$button.html('<span class="dashicons dashicons-location-alt"></span> Near Me');
|
|
$button.prop('disabled', false);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show location error message inline
|
|
*/
|
|
showLocationError: function(message) {
|
|
// Remove any existing error
|
|
this.clearLocationError();
|
|
|
|
// Create error element
|
|
const $error = $('<div class="hvac-location-error">' +
|
|
'<span class="dashicons dashicons-warning"></span> ' +
|
|
this.escapeHtml(message) +
|
|
'<button type="button" class="hvac-dismiss-error" aria-label="Dismiss">×</button>' +
|
|
'</div>');
|
|
|
|
// Insert after Near Me button
|
|
$('#hvac-near-me-btn').after($error);
|
|
|
|
// Auto-dismiss after 5 seconds
|
|
setTimeout(function() {
|
|
$error.fadeOut(300, function() { $(this).remove(); });
|
|
}, 5000);
|
|
|
|
// Click to dismiss
|
|
$error.find('.hvac-dismiss-error').on('click', function() {
|
|
$error.remove();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Clear location error message
|
|
*/
|
|
clearLocationError: function() {
|
|
$('.hvac-location-error').remove();
|
|
},
|
|
|
|
/**
|
|
* Clear all filters
|
|
*/
|
|
clearAllFilters: function() {
|
|
// Reset filter values
|
|
this.activeFilters = {
|
|
state: '',
|
|
certification: '',
|
|
training_format: '',
|
|
search: ''
|
|
};
|
|
|
|
// Reset user location
|
|
this.userLocation = null;
|
|
|
|
// Reset form elements
|
|
$('#hvac-filter-state').val('');
|
|
$('#hvac-filter-certification').val('');
|
|
$('#hvac-filter-format').val('');
|
|
$('#hvac-training-search').val('');
|
|
|
|
// Reset Near Me button
|
|
$('#hvac-near-me-btn')
|
|
.removeClass('active')
|
|
.html('<span class="dashicons dashicons-location-alt"></span> Near Me')
|
|
.prop('disabled', false);
|
|
|
|
// Clear active filters display
|
|
$('.hvac-active-filters').empty();
|
|
|
|
// Hide clear button
|
|
$('.hvac-clear-filters').hide();
|
|
|
|
// Reset map to default view
|
|
HVACTrainingMap.map.setCenter(HVACTrainingMap.config.defaultCenter);
|
|
HVACTrainingMap.map.setZoom(HVACTrainingMap.config.defaultZoom);
|
|
|
|
// Reload data without filters
|
|
HVACTrainingMap.loadMapData();
|
|
},
|
|
|
|
/**
|
|
* Remove a specific filter
|
|
*/
|
|
removeFilter: function(filterType) {
|
|
switch (filterType) {
|
|
case 'state':
|
|
this.activeFilters.state = '';
|
|
$('#hvac-filter-state').val('');
|
|
break;
|
|
case 'certification':
|
|
this.activeFilters.certification = '';
|
|
$('#hvac-filter-certification').val('');
|
|
break;
|
|
case 'training_format':
|
|
this.activeFilters.training_format = '';
|
|
$('#hvac-filter-format').val('');
|
|
break;
|
|
case 'search':
|
|
this.activeFilters.search = '';
|
|
$('#hvac-training-search').val('');
|
|
break;
|
|
case 'location':
|
|
this.userLocation = null;
|
|
$('#hvac-near-me-btn')
|
|
.removeClass('active')
|
|
.html('<span class="dashicons dashicons-location-alt"></span> Near Me')
|
|
.prop('disabled', false);
|
|
break;
|
|
}
|
|
|
|
this.applyFilters();
|
|
this.updateActiveFiltersDisplay();
|
|
},
|
|
|
|
/**
|
|
* Update active filters display
|
|
*/
|
|
updateActiveFiltersDisplay: function() {
|
|
const $container = $('.hvac-active-filters');
|
|
$container.empty();
|
|
|
|
// State filter
|
|
if (this.activeFilters.state) {
|
|
this.addActiveFilter('state', `State: ${this.activeFilters.state}`);
|
|
}
|
|
|
|
// Certification filter
|
|
if (this.activeFilters.certification) {
|
|
this.addActiveFilter('certification', this.activeFilters.certification);
|
|
}
|
|
|
|
// Training format filter
|
|
if (this.activeFilters.training_format) {
|
|
this.addActiveFilter('training_format', this.activeFilters.training_format);
|
|
}
|
|
|
|
// Search filter
|
|
if (this.activeFilters.search) {
|
|
this.addActiveFilter('search', `"${this.activeFilters.search}"`);
|
|
}
|
|
|
|
// Location filter
|
|
if (this.userLocation) {
|
|
this.addActiveFilter('location', 'Near Me');
|
|
}
|
|
|
|
this.updateClearButtonVisibility();
|
|
},
|
|
|
|
/**
|
|
* Add an active filter chip
|
|
*/
|
|
addActiveFilter: function(type, label) {
|
|
const $container = $('.hvac-active-filters');
|
|
const $chip = $(`
|
|
<span class="hvac-active-filter" data-filter="${type}">
|
|
${this.escapeHtml(label)}
|
|
<button type="button" aria-label="Remove filter">×</button>
|
|
</span>
|
|
`);
|
|
$container.append($chip);
|
|
},
|
|
|
|
/**
|
|
* Update clear button visibility
|
|
*/
|
|
updateClearButtonVisibility: function() {
|
|
const hasFilters = this.activeFilters.state ||
|
|
this.activeFilters.certification ||
|
|
this.activeFilters.training_format ||
|
|
this.activeFilters.search ||
|
|
this.userLocation;
|
|
|
|
if (hasFilters) {
|
|
$('.hvac-clear-filters').show();
|
|
} else {
|
|
$('.hvac-clear-filters').hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Escape HTML for safe output
|
|
*/
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
},
|
|
|
|
/**
|
|
* Initialize mobile filter toggle
|
|
*/
|
|
initMobileFilterToggle: function() {
|
|
const self = this;
|
|
|
|
// Mobile filter panel toggle
|
|
$(document).on('click', '.hvac-mobile-filter-toggle', function() {
|
|
const $toggle = $(this);
|
|
const $panel = $('#hvac-mobile-filter-panel');
|
|
const isExpanded = $toggle.attr('aria-expanded') === 'true';
|
|
|
|
if (isExpanded) {
|
|
$panel.attr('hidden', '');
|
|
$toggle.attr('aria-expanded', 'false');
|
|
} else {
|
|
$panel.removeAttr('hidden');
|
|
$toggle.attr('aria-expanded', 'true');
|
|
}
|
|
});
|
|
|
|
// Sync mobile filter selects with desktop selects
|
|
$('#hvac-filter-state-mobile').on('change', function() {
|
|
const value = $(this).val();
|
|
$('#hvac-filter-state').val(value);
|
|
self.activeFilters.state = value;
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
$('#hvac-filter-certification-mobile').on('change', function() {
|
|
const value = $(this).val();
|
|
$('#hvac-filter-certification').val(value);
|
|
self.activeFilters.certification = value;
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
$('#hvac-filter-format-mobile').on('change', function() {
|
|
const value = $(this).val();
|
|
$('#hvac-filter-format').val(value);
|
|
self.activeFilters.training_format = value;
|
|
self.applyFilters();
|
|
self.updateActiveFiltersDisplay();
|
|
});
|
|
|
|
// Also sync desktop to mobile when desktop changes
|
|
$('#hvac-filter-state').on('change', function() {
|
|
$('#hvac-filter-state-mobile').val($(this).val());
|
|
});
|
|
|
|
$('#hvac-filter-certification').on('change', function() {
|
|
$('#hvac-filter-certification-mobile').val($(this).val());
|
|
});
|
|
|
|
$('#hvac-filter-format').on('change', function() {
|
|
$('#hvac-filter-format-mobile').val($(this).val());
|
|
});
|
|
}
|
|
};
|
|
|
|
// Initialize when document is ready
|
|
$(document).ready(function() {
|
|
if ($('#hvac-training-map').length) {
|
|
HVACTrainingFilters.init();
|
|
}
|
|
});
|
|
|
|
})(jQuery);
|