Skip to content

Commit 7942406

Browse files
committed
Enhancement: Remove jquery, update google maps, fix admin styling
1 parent ffa3428 commit 7942406

File tree

4 files changed

+199
-69
lines changed

4 files changed

+199
-69
lines changed
Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
11
@media (min-width: 848px) {
2-
.map_canvas_wrapper {margin-left: 170px;}
3-
.main.shifted .map_canvas_wrapper {margin-left: 0;}
2+
.main.shifted .map_canvas_wrapper {
3+
margin-left: 0;
4+
}
45
}
5-
@media (min-width: 1118px) {
6-
.main.shifted .map_canvas_wrapper {margin-left: 170px;}
6+
7+
#id_address {
8+
width: 40em;
9+
}
10+
11+
.map_canvas_wrapper {
12+
width: 100%;
13+
}
14+
15+
#map_canvas {
16+
width: 100%;
17+
height: 40em;
18+
}
19+
20+
#map_message_box {
21+
background-color: #eff6ff;
22+
color: #1e40af;
23+
padding: 12px 20px;
24+
border-radius: 8px;
25+
border: 1px solid #bfdbfe;
26+
display: none; /* Hidden by default */
27+
font-size: 0.9rem;
28+
opacity: 0;
29+
transition: opacity 0.3s ease-in-out;
30+
float: none;
731
}
832

9-
#id_address {width: 40em;}
10-
.map_canvas_wrapper {width: 100%;}
11-
#map_canvas {width: 100%; height: 40em;}
33+
#map_message_box.show {
34+
display: block;
35+
opacity: 1;
36+
}
Lines changed: 159 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/*
32
Integration for Google Maps in the django admin.
43
@@ -24,156 +23,260 @@ This script expects:
2423
function 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
});
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
{% include "django/forms/widgets/text.html" %}
2-
<div class="map_canvas_wrapper"><div id="map_canvas"></div></div>
1+
<div style="display: flex; flex-direction: column;">
2+
{% include "django/forms/widgets/text.html" %}
3+
<div class="map_canvas_wrapper">
4+
<div id="map_canvas"></div>
5+
</div>
6+
<div id="map_message_box" role="alert"></div>
7+
</div>

django_google_maps/widgets.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ class GoogleMapsAddressWidget(widgets.TextInput):
1010
class Media:
1111
css = {"all": ("django_google_maps/css/google-maps-admin.css",)}
1212
js = (
13-
"https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js",
14-
"https://maps.google.com/maps/api/js?key={}&libraries=places".format(
15-
settings.GOOGLE_MAPS_API_KEY
16-
),
13+
f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&libraries=places",
1714
"django_google_maps/js/google-maps-admin.js",
1815
)

0 commit comments

Comments
 (0)