upskill-event-manager/assets/js/find-training-filters.js
ben 4b53d3eab6 fix(find-training): Fix Near Me button mobile layout and add empty results notification
- Fixed CSS bug where Near Me button HTML was replaced without .hvac-btn-text
  wrapper class, causing layout issues on mobile when text became visible
- Applied fix to all 5 locations: loading state, success state, error reset,
  clear filters, and remove location filter
- Added notification when Near Me filter returns no results within 100km radius
  to improve UX feedback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 10:43:11 -04:00

604 lines
22 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: '',
include_past: false
},
// 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 - client-side filtering for instant results
$('#hvac-training-search').on('input', function() {
clearTimeout(self.searchTimer);
const value = $(this).val().toLowerCase().trim();
self.searchTimer = setTimeout(function() {
self.activeFilters.search = value;
// For empty search or server-side filters, use AJAX
if (!value || self.hasActiveServerFilters()) {
self.applyFilters();
} else {
// Client-side filtering for instant results
self.filterActiveTabList(value);
}
}, 150); // Faster for client-side
});
// 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();
});
// Include past events checkbox
$('#hvac-include-past').on('change', function() {
self.activeFilters.include_past = $(this).is(':checked');
self.applyFilters();
self.updateActiveFiltersDisplay();
});
// Mobile include past events checkbox
$('#hvac-include-past-mobile').on('change', function() {
const checked = $(this).is(':checked');
$('#hvac-include-past').prop('checked', checked);
self.activeFilters.include_past = checked;
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'),
show_events: $('#hvac-show-events').is(':checked'),
include_past: this.activeFilters.include_past
};
// 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.events = response.data.events || [];
// Reset visible arrays to all items
HVACTrainingMap.visibleTrainers = HVACTrainingMap.trainers.slice();
HVACTrainingMap.visibleVenues = HVACTrainingMap.venues.slice();
HVACTrainingMap.visibleEvents = HVACTrainingMap.events.slice();
// Reset displayed counts
HVACTrainingMap.displayedCounts = { trainers: 0, venues: 0, events: 0 };
// Update map markers
HVACTrainingMap.updateMarkers();
// Update all counts
HVACTrainingMap.updateAllCounts();
// Render the active tab
HVACTrainingMap.renderActiveTabList();
// Show notification if "Near Me" filter returned no results
if (self.userLocation) {
const totalResults = HVACTrainingMap.trainers.length +
HVACTrainingMap.venues.length +
HVACTrainingMap.events.length;
if (totalResults === 0) {
self.showLocationError('No trainers, venues, or events found within 100km of your location. Try removing the "Near Me" filter to see all results.');
}
}
// 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><span class="hvac-btn-text">Locating...</span>');
// 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><span class="hvac-btn-text">Near Me</span>');
$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><span class="hvac-btn-text">Near Me</span>');
$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">&times;</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: '',
include_past: false
};
// 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('');
$('#hvac-include-past').prop('checked', false);
$('#hvac-include-past-mobile').prop('checked', false);
// Reset Near Me button
$('#hvac-near-me-btn')
.removeClass('active')
.html('<span class="dashicons dashicons-location-alt"></span><span class="hvac-btn-text">Near Me</span>')
.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><span class="hvac-btn-text">Near Me</span>')
.prop('disabled', false);
break;
case 'include_past':
this.activeFilters.include_past = false;
$('#hvac-include-past').prop('checked', false);
$('#hvac-include-past-mobile').prop('checked', 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');
}
// Include past events filter
if (this.activeFilters.include_past) {
this.addActiveFilter('include_past', 'Including Past Events');
}
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">&times;</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.activeFilters.include_past ||
this.userLocation;
if (hasFilters) {
$('.hvac-clear-filters').show();
} else {
$('.hvac-clear-filters').hide();
}
},
/**
* Check if any server-side filters are active
*/
hasActiveServerFilters: function() {
return this.activeFilters.state ||
this.activeFilters.certification ||
this.activeFilters.training_format ||
this.activeFilters.include_past ||
this.userLocation;
},
/**
* Filter the active tab's list client-side for instant results
*/
filterActiveTabList: function(searchTerm) {
const activeTab = HVACTrainingMap.activeTab;
let items, filterFn;
switch (activeTab) {
case 'trainers':
items = HVACTrainingMap.visibleTrainers.length > 0
? HVACTrainingMap.trainers
: HVACTrainingMap.trainers;
filterFn = (trainer) => {
const searchFields = [
trainer.name,
trainer.city,
trainer.state,
trainer.company,
...(trainer.certifications || [])
].filter(Boolean).join(' ').toLowerCase();
return searchFields.includes(searchTerm);
};
break;
case 'venues':
items = HVACTrainingMap.venues;
filterFn = (venue) => {
const searchFields = [
venue.name,
venue.city,
venue.state,
venue.address
].filter(Boolean).join(' ').toLowerCase();
return searchFields.includes(searchTerm);
};
break;
case 'events':
items = HVACTrainingMap.events;
filterFn = (event) => {
const searchFields = [
event.title,
event.venue_name,
event.venue_city,
event.venue_state
].filter(Boolean).join(' ').toLowerCase();
return searchFields.includes(searchTerm);
};
break;
default:
return;
}
// Apply filter
const filteredItems = searchTerm ? items.filter(filterFn) : items;
// Update the visible items for the active tab
switch (activeTab) {
case 'trainers':
HVACTrainingMap.visibleTrainers = filteredItems;
HVACTrainingMap.displayedCounts.trainers = 0;
HVACTrainingMap.updateTrainerGrid();
break;
case 'venues':
HVACTrainingMap.visibleVenues = filteredItems;
HVACTrainingMap.displayedCounts.venues = 0;
HVACTrainingMap.updateVenueGrid();
break;
case 'events':
HVACTrainingMap.visibleEvents = filteredItems;
HVACTrainingMap.displayedCounts.events = 0;
HVACTrainingMap.updateEventGrid();
break;
}
// Update all counts
HVACTrainingMap.updateAllCounts();
},
/**
* 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);