freealberta/map/app/public/js/map-search.js

200 lines
6.1 KiB
JavaScript

/**
* Map Search Module
* Handles address search functionality for the map
*/
import { map } from './map-manager.js';
import { openAddModal } from './location-manager.js';
export class MapSearch {
constructor() {
this.searchCache = new Map();
this.tempMarker = null;
}
/**
* Search for addresses using the geocoding API
* @param {string} query - The search query
* @returns {Promise<Array>} Array of search results
*/
async search(query) {
if (!query || query.trim().length < 2) {
return [];
}
const trimmedQuery = query.trim();
// Check cache first
if (this.searchCache.has(trimmedQuery)) {
return this.searchCache.get(trimmedQuery);
}
try {
const response = await fetch(`/api/geocode/search?query=${encodeURIComponent(trimmedQuery)}&limit=5`);
if (!response.ok) {
throw new Error(`Search failed: ${response.statusText}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Search failed');
}
const results = data.data || [];
// Cache the results
this.searchCache.set(trimmedQuery, results);
// Clean up cache if it gets too large
if (this.searchCache.size > 100) {
const firstKey = this.searchCache.keys().next().value;
this.searchCache.delete(firstKey);
}
return results;
} catch (error) {
console.error('Map search error:', error);
throw error;
}
}
/**
* Create HTML for a search result
* @param {Object} result - Search result object
* @returns {HTMLElement} Result element
*/
createResultElement(result) {
const resultEl = document.createElement('div');
resultEl.className = 'search-result-item search-result-map';
// Handle both coordinate formats for compatibility
const lat = result.coordinates?.lat || result.latitude || 0;
const lng = result.coordinates?.lng || result.longitude || 0;
// Debugging - log result structure if coordinates are missing
if (!lat && !lng) {
console.warn('Search result missing coordinates:', result);
}
resultEl.innerHTML = `
<div class="result-address">${result.formattedAddress || 'Unknown Address'}</div>
<div class="result-full-address">${result.fullAddress || ''}</div>
<div class="result-coordinates">${lat.toFixed(6)}, ${lng.toFixed(6)}</div>
`;
resultEl.addEventListener('click', () => {
this.selectResult(result);
});
return resultEl;
}
/**
* Handle selection of a search result
* @param {Object} result - Selected result
*/
selectResult(result) {
if (!map) {
console.error('Map not initialized');
return;
}
// Handle both coordinate formats for compatibility
const lat = parseFloat(result.coordinates?.lat || result.latitude || 0);
const lng = parseFloat(result.coordinates?.lng || result.longitude || 0);
if (isNaN(lat) || isNaN(lng)) {
console.error('Invalid coordinates:', result);
return;
}
// Pan and zoom to the location
map.setView([lat, lng], 16);
// Remove any existing temporary marker
this.clearTempMarker();
// Add a temporary marker
this.tempMarker = L.marker([lat, lng], {
icon: L.divIcon({
className: 'temp-search-marker',
html: '<div class="marker-pin"></div>',
iconSize: [30, 30],
iconAnchor: [15, 30]
})
}).addTo(map);
// Create popup content without inline handlers
const popupContent = document.createElement('div');
popupContent.className = 'search-result-popup';
popupContent.innerHTML = `
<h3>${result.formattedAddress || 'Search Result'}</h3>
<p>${result.fullAddress || ''}</p>
<button class="btn btn-primary search-add-location-btn" data-lat="${lat}" data-lng="${lng}">
Add Location Here
</button>
`;
// Bind the popup
this.tempMarker.bindPopup(popupContent).openPopup();
// Add event listener after popup is opened
setTimeout(() => {
const addBtn = document.querySelector('.search-add-location-btn');
if (addBtn) {
addBtn.addEventListener('click', (e) => {
const btnLat = parseFloat(e.target.dataset.lat);
const btnLng = parseFloat(e.target.dataset.lng);
this.openAddLocationModal(btnLat, btnLng);
});
}
}, 100);
}
/**
* Open the add location modal at specified coordinates
* @param {number} lat - Latitude
* @param {number} lng - Longitude
*/
openAddLocationModal(lat, lng) {
this.clearTempMarker();
if (typeof openAddModal === 'function') {
openAddModal(lat, lng);
} else {
// Fallback: trigger the add location button click
const addBtn = document.getElementById('add-location-btn');
if (addBtn) {
// Set a temporary flag for the coordinates
window.tempSearchCoordinates = { lat, lng };
addBtn.click();
}
}
}
/**
* Clear the temporary search marker
*/
clearTempMarker() {
if (this.tempMarker && map) {
map.removeLayer(this.tempMarker);
this.tempMarker = null;
}
}
/**
* Clear the search cache
*/
clearCache() {
this.searchCache.clear();
}
}
// Create a global instance for use in popup buttons
window.mapSearchInstance = new MapSearch();
export default window.mapSearchInstance;