1-
21/*
32Integration for Google Maps in the django admin.
43
@@ -24,156 +23,260 @@ This script expects:
2423function googleMapAdmin ( ) {
2524
2625 var autocomplete ;
27- var geocoder = new google . maps . Geocoder ( ) ;
26+ var geocoder ;
2827 var map ;
2928 var marker ;
3029
3130 var geolocationId = 'id_geolocation' ;
3231 var addressId = 'id_address' ;
32+ var messageBoxId = 'map_message_box' ;
3333
3434 var self = {
35- initialize : function ( ) {
35+ /**
36+ * Initializes the Google Map, Autocomplete, and sets up event listeners.
37+ */
38+ initialize : function ( ) {
39+ // Initialize Geocoder
40+ geocoder = new google . maps . Geocoder ( ) ;
3641 var lat = 0 ;
3742 var lng = 0 ;
38- var zoom = 2 ;
39- // set up initial map to be world view. also, add change
40- // event so changing address will update the map
41- var existinglocation = self . getExistingLocation ( ) ;
42-
43- if ( existinglocation ) {
44- lat = existinglocation [ 0 ] ;
45- lng = existinglocation [ 1 ] ;
46- zoom = 18 ;
43+ var zoom = 2 ; // Default to world view
44+
45+ // Get existing location from the geolocation input field
46+ var existingLocation = self . getExistingLocation ( ) ;
47+
48+ if ( existingLocation ) {
49+ lat = parseFloat ( existingLocation [ 0 ] ) ; // Ensure latitude is a number
50+ lng = parseFloat ( existingLocation [ 1 ] ) ; // Ensure longitude is a number
51+ zoom = 18 ; // Zoom in if a location already exists
4752 }
4853
49- var latlng = new google . maps . LatLng ( lat , lng ) ;
54+ // Create a LatLng object for the map center
55+ var latlng = { lat : lat , lng : lng } ;
5056 var myOptions = {
51- zoom : zoom ,
52- center : latlng ,
53- mapTypeId : self . getMapType ( )
57+ zoom : zoom ,
58+ center : latlng ,
59+ mapTypeId : self . getMapType ( ) ,
60+ streetViewControl : false ,
61+ mapTypeControl : true ,
62+ fullscreenControl : false
5463 } ;
64+
65+ // Create the map instance
5566 map = new google . maps . Map ( document . getElementById ( "map_canvas" ) , myOptions ) ;
56- if ( existinglocation ) {
67+
68+ // If an existing location is present, set a marker
69+ if ( existingLocation ) {
5770 self . setMarker ( latlng ) ;
5871 }
5972
73+ // Initialize Google Places Autocomplete on the address input field
6074 autocomplete = new google . maps . places . Autocomplete (
6175 /** @type {!HTMLInputElement } */ ( document . getElementById ( addressId ) ) ,
6276 self . getAutoCompleteOptions ( ) ) ;
6377
64- // this only triggers on enter, or if a suggested location is chosen
65- // todo: if a user doesn't choose a suggestion and presses tab, the map doesn't update
78+ // Add listener for when a place is selected from the autocomplete suggestions
79+ // This triggers when the user presses enter or selects a suggestion
6680 autocomplete . addListener ( "place_changed" , self . codeAddress ) ;
6781
68- // don't make enter submit the form, let it just trigger the place_changed event
69- // which triggers the map update & geocode
70- $ ( "#" + addressId ) . keydown ( function ( e ) {
71- if ( e . keyCode == 13 ) { // enter key
82+ // Prevent the 'Enter' key from submitting the form when in the address field.
83+ // Instead, it should trigger the place_changed event for autocomplete.
84+ document . getElementById ( addressId ) . addEventListener ( "keydown" , function ( e ) {
85+ if ( e . key === "Enter" ) {
7286 e . preventDefault ( ) ;
7387 return false ;
7488 }
7589 } ) ;
7690 } ,
7791
78- getMapType : function ( ) {
92+ /**
93+ * Determines the map type based on a 'data-map-type' attribute on the address input.
94+ * Falls back to 'hybrid' if not specified or invalid.
95+ * @returns {string } The map type string (e.g., 'roadmap', 'satellite').
96+ */
97+ getMapType : function ( ) {
7998 // https://developers.google.com/maps/documentation/javascript/maptypes
80- var geolocation = document . getElementById ( addressId ) ;
81- var allowedType = [ 'roadmap' , 'satellite' , 'hybrid' , 'terrain' ] ;
82- var mapType = geolocation . getAttribute ( 'data-map-type' ) ;
99+ var addressInput = document . getElementById ( addressId ) ;
100+ var allowedTypes = [ 'roadmap' , 'satellite' , 'hybrid' , 'terrain' ] ;
101+ var mapType = addressInput . getAttribute ( 'data-map-type' ) ;
83102
84- if ( mapType && - 1 !== allowedType . indexOf ( mapType ) ) {
103+ if ( mapType && allowedTypes . includes ( mapType ) ) {
85104 return mapType ;
86105 }
87106
88- return google . maps . MapTypeId . HYBRID ;
107+ return 'hybrid' ; // Default to hybrid map type
89108 } ,
90109
91- getAutoCompleteOptions : function ( ) {
92- var geolocation = document . getElementById ( addressId ) ;
93- var autocompleteOptions = geolocation . getAttribute ( 'data-autocomplete-options' ) ;
110+ /**
111+ * Retrieves autocomplete options from a 'data-autocomplete-options' attribute.
112+ * Defaults to geocode type if not specified.
113+ * @returns {object } Autocomplete options object.
114+ */
115+ getAutoCompleteOptions : function ( ) {
116+ var addressInput = document . getElementById ( addressId ) ;
117+ var autocompleteOptions = addressInput . getAttribute ( 'data-autocomplete-options' ) ;
94118
95119 if ( ! autocompleteOptions ) {
96120 return {
97- types : [ 'geocode' ]
121+ types : [ 'geocode' ]
98122 } ;
99123 }
100124
101- return JSON . parse ( autocompleteOptions ) ;
125+ try {
126+ return JSON . parse ( autocompleteOptions ) ;
127+ } catch ( e ) {
128+ console . error ( "Error parsing data-autocomplete-options:" , e ) ;
129+ self . showMessage ( "Error: Invalid autocomplete options format. Using default." , 'error' ) ;
130+ return { types : [ 'geocode' ] } ;
131+ }
102132 } ,
103133
104- getExistingLocation : function ( ) {
105- var geolocation = document . getElementById ( geolocationId ) . value ;
106- if ( geolocation ) {
107- return geolocation . split ( ',' ) ;
134+ /**
135+ * Retrieves existing latitude and longitude from the geolocation input field.
136+ * @returns {Array<string>|undefined } An array [latitude, longitude] or undefined if empty.
137+ */
138+ getExistingLocation : function ( ) {
139+ var geolocationInput = document . getElementById ( geolocationId ) . value ;
140+ if ( geolocationInput ) {
141+ return geolocationInput . split ( ',' ) ;
108142 }
143+ return undefined ;
109144 } ,
110145
111- codeAddress : function ( ) {
146+ /**
147+ * Geocodes the address entered in the autocomplete field.
148+ * Updates the map and marker based on the geocoded location.
149+ */
150+ codeAddress : function ( ) {
112151 var place = autocomplete . getPlace ( ) ;
113152
114- if ( place . geometry !== undefined ) {
153+ // Checkifa place with geometry (location) was found by Autocomplete
154+ if ( place . geometry && place . geometry . location ) {
115155 self . updateWithCoordinates ( place . geometry . location ) ;
116- }
117- else {
118- geocoder . geocode ( { 'address' : place . name } , function ( results , status ) {
119- if ( status == google . maps . GeocoderStatus . OK ) {
156+ } else if ( place . name ) {
157+ // If no geometry, but a place name exists, try to geocode it
158+ geocoder . geocode ( { 'address' : place . name } , function ( results , status ) {
159+ if ( status === 'OK' && results . length > 0 ) {
120160 var latlng = results [ 0 ] . geometry . location ;
121161 self . updateWithCoordinates ( latlng ) ;
162+ } else if ( status === 'ZERO_RESULTS' ) {
163+ self . showMessage ( "No results found for '" + place . name + "'." , 'warning' ) ;
122164 } else {
123- alert ( "Geocode was not successful for the following reason: " + status ) ;
165+ self . showMessage ( "Geocode was not successful for the following reason: " + status , 'error' ) ;
124166 }
125167 } ) ;
168+ } else {
169+ self . showMessage ( "Please enter a valid address." , 'warning' ) ;
126170 }
127171 } ,
128172
129- updateWithCoordinates : function ( latlng ) {
173+ /**
174+ * Updates the map center, zoom, marker, and geolocation input with new coordinates.
175+ * @param {google.maps.LatLng } latlng - The new LatLng object.
176+ */
177+ updateWithCoordinates : function ( latlng ) {
130178 map . setCenter ( latlng ) ;
131179 map . setZoom ( 18 ) ;
132180 self . setMarker ( latlng ) ;
133181 self . updateGeolocation ( latlng ) ;
134182 } ,
135183
136- setMarker : function ( latlng ) {
184+ /**
185+ * Sets or updates the map marker at the given LatLng.
186+ * @param {google.maps.LatLng } latlng - The LatLng for the marker.
187+ */
188+ setMarker : function ( latlng ) {
137189 if ( marker ) {
138190 self . updateMarker ( latlng ) ;
139191 } else {
140192 self . addMarker ( { 'latlng' : latlng , 'draggable' : true } ) ;
141193 }
142194 } ,
143195
144- addMarker : function ( Options ) {
196+ /**
197+ * Adds a new marker to the map.
198+ * @param {object } Options - Marker options, including latlng and draggable.
199+ */
200+ addMarker : function ( Options ) {
145201 marker = new google . maps . Marker ( {
146202 map : map ,
147203 position : Options . latlng
148204 } ) ;
149205
150206 var draggable = Options . draggable || false ;
151207 if ( draggable ) {
152- self . addMarkerDrag ( marker ) ;
208+ self . addMarkerDrag ( ) ;
153209 }
154210 } ,
155211
156- addMarkerDrag : function ( ) {
212+ /**
213+ * Adds a 'dragend' listener to the marker to update geolocation when dragged.
214+ */
215+ addMarkerDrag : function ( ) {
157216 marker . setDraggable ( true ) ;
158- google . maps . event . addListener ( marker , 'dragend' , function ( new_location ) {
159- self . updateGeolocation ( new_location . latLng ) ;
217+ // Use the modern addListener method
218+ marker . addListener ( 'dragend' , function ( event ) {
219+ self . updateGeolocation ( event . latLng ) ;
160220 } ) ;
161221 } ,
162222
163- updateMarker : function ( latlng ) {
223+ /**
224+ * Updates the position of the existing marker.
225+ * @param {google.maps.LatLng } latlng - The new LatLng for the marker.
226+ */
227+ updateMarker : function ( latlng ) {
164228 marker . setPosition ( latlng ) ;
165229 } ,
166230
167- updateGeolocation : function ( latlng ) {
231+ /**
232+ * Updates the geolocation input field with the new latitude and longitude.
233+ * Manually dispatches a 'change' event for compatibility with other scripts.
234+ * @param {google.maps.LatLng } latlng - The LatLng object to extract coordinates from.
235+ */
236+ updateGeolocation : function ( latlng ) {
168237 document . getElementById ( geolocationId ) . value = latlng . lat ( ) + "," + latlng . lng ( ) ;
169- $ ( "#" + geolocationId ) . trigger ( 'change' ) ;
238+
239+ // Manually trigger a change event on the geolocation input
240+ var event = new Event ( 'change' , { bubbles : true } ) ;
241+ document . getElementById ( geolocationId ) . dispatchEvent ( event ) ;
242+ } ,
243+
244+ /**
245+ * Displays a temporary message in the message box.
246+ * @param {string } message - The message to display.
247+ * @param {string } type - 'info', 'warning', or 'error' to apply styling.
248+ */
249+ showMessage : function ( message , type = 'info' ) {
250+ var messageBox = document . getElementById ( messageBoxId ) ;
251+ messageBox . textContent = message ;
252+
253+ // Clear previous styling classes
254+ messageBox . className = '' ;
255+ messageBox . classList . add ( 'rounded-md' , 'p-3' , 'text-sm' , 'font-medium' , 'transition-opacity' , 'duration-300' , 'ease-in-out' ) ;
256+
257+ // Apply type-specific styling
258+ if ( type === 'error' ) {
259+ messageBox . classList . add ( 'bg-red-100' , 'text-red-800' , 'border-red-400' ) ;
260+ } else if ( type === 'warning' ) {
261+ messageBox . classList . add ( 'bg-yellow-100' , 'text-yellow-800' , 'border-yellow-400' ) ;
262+ } else { // info
263+ messageBox . classList . add ( 'bg-blue-100' , 'text-blue-800' , 'border-blue-400' ) ;
264+ }
265+
266+ messageBox . classList . add ( 'show' ) ; // Make it visible
267+
268+ // Hide the message after 5 seconds
269+ setTimeout ( function ( ) {
270+ messageBox . classList . remove ( 'show' ) ;
271+ } , 5000 ) ;
170272 }
171273 } ;
172274
173275 return self ;
174276}
175277
176- $ ( document ) . ready ( function ( ) {
278+ // Initialize the map when the DOM is fully loaded
279+ document . addEventListener ( "DOMContentLoaded" , function ( ) {
177280 var googlemap = googleMapAdmin ( ) ;
178281 googlemap . initialize ( ) ;
179282} ) ;
0 commit comments