assets_js_controllers_gathering-location-autocomplete-controller.js

import { Controller } from "@hotwired/stimulus"

/**
 * GatheringLocationAutocompleteController
 * 
 * Provides Google Places Autocomplete for gathering location input fields
 * using the classic google.maps.places.Autocomplete API.
 * 
 * Note: We use the classic API instead of PlaceAutocompleteElement because
 * the new web component API doesn't expose selected place data programmatically.
 * The classic API provides reliable place_changed events and getPlace() method.
 * 
 * @example
 * <input type="text" 
 *        data-controller="gathering-location-autocomplete"
 *        data-gathering-location-autocomplete-api-key-value="YOUR-KEY">
 */
class GatheringLocationAutocompleteController extends Controller {
    static values = {
        apiKey: String  // Google Maps API key
    }
    
    static targets = ["input", "latitude", "longitude"]  // Input field and hidden form fields for lat/lng
    
    /**
     * Initialize the controller
     */
    initialize() {
        this.autocompleteElement = null
        this.isGoogleMapsLoaded = false
        this.isInitialized = false  // Prevent re-initialization loop
        this.lastSelectedAddress = null  // Store the selected address
        this.lastSelectedPlace = null  // Store the full place object with geometry
    }
    
    /**
     * Connect function - runs when controller connects to DOM
     */
    async connect() {
        console.log("GatheringLocationAutocompleteController connected")
        
        // Prevent re-initialization if already set up
        if (this.isInitialized) {
            console.log("Autocomplete already initialized, skipping")
            return
        }
        
        try {
            // Load Google Maps Places library if not already loaded
            await this.loadGoogleMapsPlaces()
            
            // Initialize autocomplete on the input field
            this.initAutocomplete()
            
            // Only mark as initialized after both succeed
            this.isInitialized = true
            console.log("Autocomplete initialization complete")
        } catch (error) {
            // Reset flag on failure to allow future reconnects to retry
            this.isInitialized = false
            console.error("Failed to initialize autocomplete:", error)
            // Optionally rethrow or handle gracefully
            // throw error;
        }
    }
    
    /**
     * Load Google Maps Places library
     */
    async loadGoogleMapsPlaces() {
        // Check if already loaded
        if (typeof google !== 'undefined' && google.maps && google.maps.places) {
            this.isGoogleMapsLoaded = true
            return Promise.resolve()
        }
        
        return new Promise((resolve, reject) => {
            // Create script tag to load Google Maps with Places library
            const script = document.createElement('script')
            const apiKey = this.apiKeyValue || ''
            const keyParam = apiKey ? `key=${apiKey}&` : ''
            
            script.src = `https://maps.googleapis.com/maps/api/js?${keyParam}libraries=places&loading=async&callback=initGatheringLocationAutocomplete`
            script.async = true
            script.defer = true
            
            // Set up callback
            window.initGatheringLocationAutocomplete = () => {
                this.isGoogleMapsLoaded = true
                delete window.initGatheringLocationAutocomplete
                resolve()
            }
            
            script.onerror = () => {
                console.error('Failed to load Google Maps Places library')
                reject(new Error('Failed to load Google Maps Places library'))
            }
            
            document.head.appendChild(script)
        })
    }
    
    /**
     * Initialize Google Places Autocomplete using the classic Autocomplete class
     * (PlaceAutocompleteElement doesn't expose data programmatically, so we use the old API)
     */
    initAutocomplete() {
        if (!this.isGoogleMapsLoaded) {
            console.error('Google Maps not loaded')
            return
        }
        
        // Use the old Autocomplete API which actually works
        // Use 'geocode' type for addresses, or omit types to get all place types
        this.autocomplete = new google.maps.places.Autocomplete(this.inputTarget, {
            types: ['geocode']
        })
        
        // Listen for place selection
        this.autocomplete.addListener('place_changed', () => {
            const place = this.autocomplete.getPlace()
            
            if (place && place.formatted_address) {
                console.log('✓ Place selected:', place.formatted_address)
                this.lastSelectedAddress = place.formatted_address
                this.lastSelectedPlace = place
                this.inputTarget.value = place.formatted_address
                
                // Extract and store latitude/longitude if available
                if (place.geometry && place.geometry.location) {
                    const lat = place.geometry.location.lat()
                    const lng = place.geometry.location.lng()
                    
                    console.log('✓ Coordinates:', lat, lng)
                    
                    // Update hidden form fields if they exist
                    if (this.hasLatitudeTarget) {
                        this.latitudeTarget.value = lat
                        console.log('✓ Set latitude field:', lat)
                    }
                    if (this.hasLongitudeTarget) {
                        this.longitudeTarget.value = lng
                        console.log('✓ Set longitude field:', lng)
                    }
                } else {
                    console.log('⚠ No geometry data available for selected place')
                }
            }
        })
        
        console.log('Google Places Autocomplete initialized (classic API)')
    }
    
    /**
     * Cleanup when controller disconnects
     */
    disconnect() {
        // Clean up the autocomplete
        if (this.autocomplete) {
            google.maps.event.clearInstanceListeners(this.autocomplete)
        }
        
        this.autocomplete = null
        this.isInitialized = false
    }
}

// Add to global controllers registry
if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["gathering-location-autocomplete"] = GatheringLocationAutocompleteController;

console.log("GatheringLocationAutocompleteController registered in window.Controllers");