From 7942406e851183315eba4b42bb3170407a71502c Mon Sep 17 00:00:00 2001 From: aaronmadison Date: Thu, 24 Jul 2025 07:19:10 -0500 Subject: [PATCH 01/10] Enhancement: Remove jquery, update google maps, fix admin styling --- .../css/google-maps-admin.css | 39 +++- .../js/google-maps-admin.js | 215 +++++++++++++----- .../widgets/map_widget.html | 9 +- django_google_maps/widgets.py | 5 +- 4 files changed, 199 insertions(+), 69 deletions(-) diff --git a/django_google_maps/static/django_google_maps/css/google-maps-admin.css b/django_google_maps/static/django_google_maps/css/google-maps-admin.css index 5ba4d8c7..b98181de 100644 --- a/django_google_maps/static/django_google_maps/css/google-maps-admin.css +++ b/django_google_maps/static/django_google_maps/css/google-maps-admin.css @@ -1,11 +1,36 @@ @media (min-width: 848px) { - .map_canvas_wrapper {margin-left: 170px;} - .main.shifted .map_canvas_wrapper {margin-left: 0;} + .main.shifted .map_canvas_wrapper { + margin-left: 0; + } } -@media (min-width: 1118px) { - .main.shifted .map_canvas_wrapper {margin-left: 170px;} + +#id_address { + width: 40em; +} + +.map_canvas_wrapper { + width: 100%; +} + +#map_canvas { + width: 100%; + height: 40em; +} + +#map_message_box { + background-color: #eff6ff; + color: #1e40af; + padding: 12px 20px; + border-radius: 8px; + border: 1px solid #bfdbfe; + display: none; /* Hidden by default */ + font-size: 0.9rem; + opacity: 0; + transition: opacity 0.3s ease-in-out; + float: none; } -#id_address {width: 40em;} -.map_canvas_wrapper {width: 100%;} -#map_canvas {width: 100%; height: 40em;} +#map_message_box.show { + display: block; + opacity: 1; +} diff --git a/django_google_maps/static/django_google_maps/js/google-maps-admin.js b/django_google_maps/static/django_google_maps/js/google-maps-admin.js index 8fe5a845..03d59e23 100644 --- a/django_google_maps/static/django_google_maps/js/google-maps-admin.js +++ b/django_google_maps/static/django_google_maps/js/google-maps-admin.js @@ -1,4 +1,3 @@ - /* Integration for Google Maps in the django admin. @@ -24,116 +23,169 @@ This script expects: function googleMapAdmin() { var autocomplete; - var geocoder = new google.maps.Geocoder(); + var geocoder; var map; var marker; var geolocationId = 'id_geolocation'; var addressId = 'id_address'; + var messageBoxId = 'map_message_box'; var self = { - initialize: function() { + /** + * Initializes the Google Map, Autocomplete, and sets up event listeners. + */ + initialize: function () { + // Initialize Geocoder + geocoder = new google.maps.Geocoder(); var lat = 0; var lng = 0; - var zoom = 2; - // set up initial map to be world view. also, add change - // event so changing address will update the map - var existinglocation = self.getExistingLocation(); - - if (existinglocation) { - lat = existinglocation[0]; - lng = existinglocation[1]; - zoom = 18; + var zoom = 2; // Default to world view + + // Get existing location from the geolocation input field + var existingLocation = self.getExistingLocation(); + + if (existingLocation) { + lat = parseFloat(existingLocation[0]); // Ensure latitude is a number + lng = parseFloat(existingLocation[1]); // Ensure longitude is a number + zoom = 18; // Zoom in if a location already exists } - var latlng = new google.maps.LatLng(lat,lng); + // Create a LatLng object for the map center + var latlng = {lat: lat, lng: lng}; var myOptions = { - zoom: zoom, - center: latlng, - mapTypeId: self.getMapType() + zoom: zoom, + center: latlng, + mapTypeId: self.getMapType(), + streetViewControl: false, + mapTypeControl: true, + fullscreenControl: false }; + + // Create the map instance map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); - if (existinglocation) { + + // If an existing location is present, set a marker + if (existingLocation) { self.setMarker(latlng); } + // Initialize Google Places Autocomplete on the address input field autocomplete = new google.maps.places.Autocomplete( /** @type {!HTMLInputElement} */(document.getElementById(addressId)), self.getAutoCompleteOptions()); - // this only triggers on enter, or if a suggested location is chosen - // todo: if a user doesn't choose a suggestion and presses tab, the map doesn't update + // Add listener for when a place is selected from the autocomplete suggestions + // This triggers when the user presses enter or selects a suggestion autocomplete.addListener("place_changed", self.codeAddress); - // don't make enter submit the form, let it just trigger the place_changed event - // which triggers the map update & geocode - $("#" + addressId).keydown(function (e) { - if (e.keyCode == 13) { // enter key + // Prevent the 'Enter' key from submitting the form when in the address field. + // Instead, it should trigger the place_changed event for autocomplete. + document.getElementById(addressId).addEventListener("keydown", function (e) { + if (e.key === "Enter") { e.preventDefault(); return false; } }); }, - getMapType : function() { + /** + * Determines the map type based on a 'data-map-type' attribute on the address input. + * Falls back to 'hybrid' if not specified or invalid. + * @returns {string} The map type string (e.g., 'roadmap', 'satellite'). + */ + getMapType: function () { // https://developers.google.com/maps/documentation/javascript/maptypes - var geolocation = document.getElementById(addressId); - var allowedType = ['roadmap', 'satellite', 'hybrid', 'terrain']; - var mapType = geolocation.getAttribute('data-map-type'); + var addressInput = document.getElementById(addressId); + var allowedTypes = ['roadmap', 'satellite', 'hybrid', 'terrain']; + var mapType = addressInput.getAttribute('data-map-type'); - if (mapType && -1 !== allowedType.indexOf(mapType)) { + if (mapType && allowedTypes.includes(mapType)) { return mapType; } - return google.maps.MapTypeId.HYBRID; + return 'hybrid'; // Default to hybrid map type }, - getAutoCompleteOptions : function() { - var geolocation = document.getElementById(addressId); - var autocompleteOptions = geolocation.getAttribute('data-autocomplete-options'); + /** + * Retrieves autocomplete options from a 'data-autocomplete-options' attribute. + * Defaults to geocode type if not specified. + * @returns {object} Autocomplete options object. + */ + getAutoCompleteOptions: function () { + var addressInput = document.getElementById(addressId); + var autocompleteOptions = addressInput.getAttribute('data-autocomplete-options'); if (!autocompleteOptions) { return { - types: ['geocode'] + types: ['geocode'] }; } - return JSON.parse(autocompleteOptions); + try { + return JSON.parse(autocompleteOptions); + } catch (e) { + console.error("Error parsing data-autocomplete-options:", e); + self.showMessage("Error: Invalid autocomplete options format. Using default.", 'error'); + return {types: ['geocode']}; + } }, - getExistingLocation: function() { - var geolocation = document.getElementById(geolocationId).value; - if (geolocation) { - return geolocation.split(','); + /** + * Retrieves existing latitude and longitude from the geolocation input field. + * @returns {Array|undefined} An array [latitude, longitude] or undefined if empty. + */ + getExistingLocation: function () { + var geolocationInput = document.getElementById(geolocationId).value; + if (geolocationInput) { + return geolocationInput.split(','); } + return undefined; }, - codeAddress: function() { + /** + * Geocodes the address entered in the autocomplete field. + * Updates the map and marker based on the geocoded location. + */ + codeAddress: function () { var place = autocomplete.getPlace(); - if(place.geometry !== undefined) { + // Checkifa place with geometry (location) was found by Autocomplete + if (place.geometry && place.geometry.location) { self.updateWithCoordinates(place.geometry.location); - } - else { - geocoder.geocode({'address': place.name}, function(results, status) { - if (status == google.maps.GeocoderStatus.OK) { + } else if (place.name) { + // If no geometry, but a place name exists, try to geocode it + geocoder.geocode({'address': place.name}, function (results, status) { + if (status === 'OK' && results.length > 0) { var latlng = results[0].geometry.location; self.updateWithCoordinates(latlng); + } else if (status === 'ZERO_RESULTS') { + self.showMessage("No results found for '" + place.name + "'.", 'warning'); } else { - alert("Geocode was not successful for the following reason: " + status); + self.showMessage("Geocode was not successful for the following reason: " + status, 'error'); } }); + } else { + self.showMessage("Please enter a valid address.", 'warning'); } }, - updateWithCoordinates: function(latlng) { + /** + * Updates the map center, zoom, marker, and geolocation input with new coordinates. + * @param {google.maps.LatLng} latlng - The new LatLng object. + */ + updateWithCoordinates: function (latlng) { map.setCenter(latlng); map.setZoom(18); self.setMarker(latlng); self.updateGeolocation(latlng); }, - setMarker: function(latlng) { + /** + * Sets or updates the map marker at the given LatLng. + * @param {google.maps.LatLng} latlng - The LatLng for the marker. + */ + setMarker: function (latlng) { if (marker) { self.updateMarker(latlng); } else { @@ -141,7 +193,11 @@ function googleMapAdmin() { } }, - addMarker: function(Options) { + /** + * Adds a new marker to the map. + * @param {object} Options - Marker options, including latlng and draggable. + */ + addMarker: function (Options) { marker = new google.maps.Marker({ map: map, position: Options.latlng @@ -149,31 +205,78 @@ function googleMapAdmin() { var draggable = Options.draggable || false; if (draggable) { - self.addMarkerDrag(marker); + self.addMarkerDrag(); } }, - addMarkerDrag: function() { + /** + * Adds a 'dragend' listener to the marker to update geolocation when dragged. + */ + addMarkerDrag: function () { marker.setDraggable(true); - google.maps.event.addListener(marker, 'dragend', function(new_location) { - self.updateGeolocation(new_location.latLng); + // Use the modern addListener method + marker.addListener('dragend', function (event) { + self.updateGeolocation(event.latLng); }); }, - updateMarker: function(latlng) { + /** + * Updates the position of the existing marker. + * @param {google.maps.LatLng} latlng - The new LatLng for the marker. + */ + updateMarker: function (latlng) { marker.setPosition(latlng); }, - updateGeolocation: function(latlng) { + /** + * Updates the geolocation input field with the new latitude and longitude. + * Manually dispatches a 'change' event for compatibility with other scripts. + * @param {google.maps.LatLng} latlng - The LatLng object to extract coordinates from. + */ + updateGeolocation: function (latlng) { document.getElementById(geolocationId).value = latlng.lat() + "," + latlng.lng(); - $("#" + geolocationId).trigger('change'); + + // Manually trigger a change event on the geolocation input + var event = new Event('change', {bubbles: true}); + document.getElementById(geolocationId).dispatchEvent(event); + }, + + /** + * Displays a temporary message in the message box. + * @param {string} message - The message to display. + * @param {string} type - 'info', 'warning', or 'error' to apply styling. + */ + showMessage: function (message, type = 'info') { + var messageBox = document.getElementById(messageBoxId); + messageBox.textContent = message; + + // Clear previous styling classes + messageBox.className = ''; + messageBox.classList.add('rounded-md', 'p-3', 'text-sm', 'font-medium', 'transition-opacity', 'duration-300', 'ease-in-out'); + + // Apply type-specific styling + if (type === 'error') { + messageBox.classList.add('bg-red-100', 'text-red-800', 'border-red-400'); + } else if (type === 'warning') { + messageBox.classList.add('bg-yellow-100', 'text-yellow-800', 'border-yellow-400'); + } else { // info + messageBox.classList.add('bg-blue-100', 'text-blue-800', 'border-blue-400'); + } + + messageBox.classList.add('show'); // Make it visible + + // Hide the message after 5 seconds + setTimeout(function () { + messageBox.classList.remove('show'); + }, 5000); } }; return self; } -$(document).ready(function() { +// Initialize the map when the DOM is fully loaded +document.addEventListener("DOMContentLoaded", function () { var googlemap = googleMapAdmin(); googlemap.initialize(); }); diff --git a/django_google_maps/templates/django_google_maps/widgets/map_widget.html b/django_google_maps/templates/django_google_maps/widgets/map_widget.html index a6aaea04..7e2e6a8d 100644 --- a/django_google_maps/templates/django_google_maps/widgets/map_widget.html +++ b/django_google_maps/templates/django_google_maps/widgets/map_widget.html @@ -1,2 +1,7 @@ -{% include "django/forms/widgets/text.html" %} -
\ No newline at end of file +
+ {% include "django/forms/widgets/text.html" %} +
+
+
+ +
\ No newline at end of file diff --git a/django_google_maps/widgets.py b/django_google_maps/widgets.py index 22c1cfec..73406aaf 100644 --- a/django_google_maps/widgets.py +++ b/django_google_maps/widgets.py @@ -10,9 +10,6 @@ class GoogleMapsAddressWidget(widgets.TextInput): class Media: css = {"all": ("django_google_maps/css/google-maps-admin.css",)} js = ( - "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", - "https://maps.google.com/maps/api/js?key={}&libraries=places".format( - settings.GOOGLE_MAPS_API_KEY - ), + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&libraries=places", "django_google_maps/js/google-maps-admin.js", ) From 80541442d4b1d148eba2d5fb8c99af9a34817d07 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 24 Jul 2025 10:02:22 -0500 Subject: [PATCH 02/10] Enhancement: Remove jquery, update google maps, fix admin styling # Conflicts: # django_google_maps/static/django_google_maps/js/google-maps-admin.js # django_google_maps/widgets.py --- .../css/google-maps-admin.css | 39 +++- .../js/google-maps-admin.js | 179 ++++++++++++++---- .../widgets/map_widget.html | 9 +- django_google_maps/widgets.py | 4 +- 4 files changed, 179 insertions(+), 52 deletions(-) diff --git a/django_google_maps/static/django_google_maps/css/google-maps-admin.css b/django_google_maps/static/django_google_maps/css/google-maps-admin.css index 5ba4d8c7..b98181de 100644 --- a/django_google_maps/static/django_google_maps/css/google-maps-admin.css +++ b/django_google_maps/static/django_google_maps/css/google-maps-admin.css @@ -1,11 +1,36 @@ @media (min-width: 848px) { - .map_canvas_wrapper {margin-left: 170px;} - .main.shifted .map_canvas_wrapper {margin-left: 0;} + .main.shifted .map_canvas_wrapper { + margin-left: 0; + } } -@media (min-width: 1118px) { - .main.shifted .map_canvas_wrapper {margin-left: 170px;} + +#id_address { + width: 40em; +} + +.map_canvas_wrapper { + width: 100%; +} + +#map_canvas { + width: 100%; + height: 40em; +} + +#map_message_box { + background-color: #eff6ff; + color: #1e40af; + padding: 12px 20px; + border-radius: 8px; + border: 1px solid #bfdbfe; + display: none; /* Hidden by default */ + font-size: 0.9rem; + opacity: 0; + transition: opacity 0.3s ease-in-out; + float: none; } -#id_address {width: 40em;} -.map_canvas_wrapper {width: 100%;} -#map_canvas {width: 100%; height: 40em;} +#map_message_box.show { + display: block; + opacity: 1; +} diff --git a/django_google_maps/static/django_google_maps/js/google-maps-admin.js b/django_google_maps/static/django_google_maps/js/google-maps-admin.js index d6f6f736..03d59e23 100644 --- a/django_google_maps/static/django_google_maps/js/google-maps-admin.js +++ b/django_google_maps/static/django_google_maps/js/google-maps-admin.js @@ -23,50 +23,64 @@ This script expects: function googleMapAdmin() { var autocomplete; - var geocoder = new google.maps.Geocoder(); + var geocoder; var map; var marker; var geolocationId = 'id_geolocation'; var addressId = 'id_address'; + var messageBoxId = 'map_message_box'; var self = { + /** + * Initializes the Google Map, Autocomplete, and sets up event listeners. + */ initialize: function () { + // Initialize Geocoder + geocoder = new google.maps.Geocoder(); var lat = 0; var lng = 0; - var zoom = 2; - // set up initial map to be world view. also, add change - // event so changing address will update the map - var existinglocation = self.getExistingLocation(); - - if (existinglocation) { - lat = existinglocation[0]; - lng = existinglocation[1]; - zoom = 18; + var zoom = 2; // Default to world view + + // Get existing location from the geolocation input field + var existingLocation = self.getExistingLocation(); + + if (existingLocation) { + lat = parseFloat(existingLocation[0]); // Ensure latitude is a number + lng = parseFloat(existingLocation[1]); // Ensure longitude is a number + zoom = 18; // Zoom in if a location already exists } - var latlng = new google.maps.LatLng(lat, lng); + // Create a LatLng object for the map center + var latlng = {lat: lat, lng: lng}; var myOptions = { zoom: zoom, center: latlng, - mapTypeId: self.getMapType() + mapTypeId: self.getMapType(), + streetViewControl: false, + mapTypeControl: true, + fullscreenControl: false }; + + // Create the map instance map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); - if (existinglocation) { + + // If an existing location is present, set a marker + if (existingLocation) { self.setMarker(latlng); } + // Initialize Google Places Autocomplete on the address input field autocomplete = new google.maps.places.Autocomplete( /** @type {!HTMLInputElement} */(document.getElementById(addressId)), - self.getAutoCompleteOptions() - ); + self.getAutoCompleteOptions()); - // this only triggers on enter, or if a suggested location is chosen - // todo: if a user doesn't choose a suggestion and presses tab, the map doesn't update + // Add listener for when a place is selected from the autocomplete suggestions + // This triggers when the user presses enter or selects a suggestion autocomplete.addListener("place_changed", self.codeAddress); - // don't make enter submit the form, let it just trigger the place_changed event - // which triggers the map update & geocode + // Prevent the 'Enter' key from submitting the form when in the address field. + // Instead, it should trigger the place_changed event for autocomplete. document.getElementById(addressId).addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); @@ -75,22 +89,32 @@ function googleMapAdmin() { }); }, + /** + * Determines the map type based on a 'data-map-type' attribute on the address input. + * Falls back to 'hybrid' if not specified or invalid. + * @returns {string} The map type string (e.g., 'roadmap', 'satellite'). + */ getMapType: function () { // https://developers.google.com/maps/documentation/javascript/maptypes - var geolocation = document.getElementById(addressId); - var allowedType = ['roadmap', 'satellite', 'hybrid', 'terrain']; - var mapType = geolocation.getAttribute('data-map-type'); + var addressInput = document.getElementById(addressId); + var allowedTypes = ['roadmap', 'satellite', 'hybrid', 'terrain']; + var mapType = addressInput.getAttribute('data-map-type'); - if (mapType && -1 !== allowedType.indexOf(mapType)) { + if (mapType && allowedTypes.includes(mapType)) { return mapType; } - return google.maps.MapTypeId.HYBRID; + return 'hybrid'; // Default to hybrid map type }, + /** + * Retrieves autocomplete options from a 'data-autocomplete-options' attribute. + * Defaults to geocode type if not specified. + * @returns {object} Autocomplete options object. + */ getAutoCompleteOptions: function () { - var geolocation = document.getElementById(addressId); - var autocompleteOptions = geolocation.getAttribute('data-autocomplete-options'); + var addressInput = document.getElementById(addressId); + var autocompleteOptions = addressInput.getAttribute('data-autocomplete-options'); if (!autocompleteOptions) { return { @@ -98,35 +122,58 @@ function googleMapAdmin() { }; } - return JSON.parse(autocompleteOptions); + try { + return JSON.parse(autocompleteOptions); + } catch (e) { + console.error("Error parsing data-autocomplete-options:", e); + self.showMessage("Error: Invalid autocomplete options format. Using default.", 'error'); + return {types: ['geocode']}; + } }, + /** + * Retrieves existing latitude and longitude from the geolocation input field. + * @returns {Array|undefined} An array [latitude, longitude] or undefined if empty. + */ getExistingLocation: function () { - var geolocation = document.getElementById(geolocationId).value; - if (geolocation) { - return geolocation.split(','); + var geolocationInput = document.getElementById(geolocationId).value; + if (geolocationInput) { + return geolocationInput.split(','); } + return undefined; }, + /** + * Geocodes the address entered in the autocomplete field. + * Updates the map and marker based on the geocoded location. + */ codeAddress: function () { var place = autocomplete.getPlace(); - if (place.geometry !== undefined) { + // Checkifa place with geometry (location) was found by Autocomplete + if (place.geometry && place.geometry.location) { self.updateWithCoordinates(place.geometry.location); - } else { + } else if (place.name) { + // If no geometry, but a place name exists, try to geocode it geocoder.geocode({'address': place.name}, function (results, status) { - if (status == google.maps.GeocoderStatus.OK && results.length > 0) { // Add results.length check + if (status === 'OK' && results.length > 0) { var latlng = results[0].geometry.location; self.updateWithCoordinates(latlng); - } else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) { - alert("No results found for " + place.name + "."); + } else if (status === 'ZERO_RESULTS') { + self.showMessage("No results found for '" + place.name + "'.", 'warning'); } else { - alert("Geocode was not successful for the following reason: " + status); + self.showMessage("Geocode was not successful for the following reason: " + status, 'error'); } }); + } else { + self.showMessage("Please enter a valid address.", 'warning'); } }, + /** + * Updates the map center, zoom, marker, and geolocation input with new coordinates. + * @param {google.maps.LatLng} latlng - The new LatLng object. + */ updateWithCoordinates: function (latlng) { map.setCenter(latlng); map.setZoom(18); @@ -134,6 +181,10 @@ function googleMapAdmin() { self.updateGeolocation(latlng); }, + /** + * Sets or updates the map marker at the given LatLng. + * @param {google.maps.LatLng} latlng - The LatLng for the marker. + */ setMarker: function (latlng) { if (marker) { self.updateMarker(latlng); @@ -142,6 +193,10 @@ function googleMapAdmin() { } }, + /** + * Adds a new marker to the map. + * @param {object} Options - Marker options, including latlng and draggable. + */ addMarker: function (Options) { marker = new google.maps.Marker({ map: map, @@ -150,33 +205,77 @@ function googleMapAdmin() { var draggable = Options.draggable || false; if (draggable) { - self.addMarkerDrag(marker); + self.addMarkerDrag(); } }, + /** + * Adds a 'dragend' listener to the marker to update geolocation when dragged. + */ addMarkerDrag: function () { marker.setDraggable(true); - google.maps.event.addListener(marker, 'dragend', function (new_location) { - self.updateGeolocation(new_location.latLng); + // Use the modern addListener method + marker.addListener('dragend', function (event) { + self.updateGeolocation(event.latLng); }); }, + /** + * Updates the position of the existing marker. + * @param {google.maps.LatLng} latlng - The new LatLng for the marker. + */ updateMarker: function (latlng) { marker.setPosition(latlng); }, + /** + * Updates the geolocation input field with the new latitude and longitude. + * Manually dispatches a 'change' event for compatibility with other scripts. + * @param {google.maps.LatLng} latlng - The LatLng object to extract coordinates from. + */ updateGeolocation: function (latlng) { document.getElementById(geolocationId).value = latlng.lat() + "," + latlng.lng(); - // manually trigger a change event + // Manually trigger a change event on the geolocation input var event = new Event('change', {bubbles: true}); document.getElementById(geolocationId).dispatchEvent(event); + }, + + /** + * Displays a temporary message in the message box. + * @param {string} message - The message to display. + * @param {string} type - 'info', 'warning', or 'error' to apply styling. + */ + showMessage: function (message, type = 'info') { + var messageBox = document.getElementById(messageBoxId); + messageBox.textContent = message; + + // Clear previous styling classes + messageBox.className = ''; + messageBox.classList.add('rounded-md', 'p-3', 'text-sm', 'font-medium', 'transition-opacity', 'duration-300', 'ease-in-out'); + + // Apply type-specific styling + if (type === 'error') { + messageBox.classList.add('bg-red-100', 'text-red-800', 'border-red-400'); + } else if (type === 'warning') { + messageBox.classList.add('bg-yellow-100', 'text-yellow-800', 'border-yellow-400'); + } else { // info + messageBox.classList.add('bg-blue-100', 'text-blue-800', 'border-blue-400'); + } + + messageBox.classList.add('show'); // Make it visible + + // Hide the message after 5 seconds + setTimeout(function () { + messageBox.classList.remove('show'); + }, 5000); } }; return self; } +// Initialize the map when the DOM is fully loaded document.addEventListener("DOMContentLoaded", function () { var googlemap = googleMapAdmin(); googlemap.initialize(); diff --git a/django_google_maps/templates/django_google_maps/widgets/map_widget.html b/django_google_maps/templates/django_google_maps/widgets/map_widget.html index a6aaea04..7e2e6a8d 100644 --- a/django_google_maps/templates/django_google_maps/widgets/map_widget.html +++ b/django_google_maps/templates/django_google_maps/widgets/map_widget.html @@ -1,2 +1,7 @@ -{% include "django/forms/widgets/text.html" %} -
\ No newline at end of file +
+ {% include "django/forms/widgets/text.html" %} +
+
+
+ +
\ No newline at end of file diff --git a/django_google_maps/widgets.py b/django_google_maps/widgets.py index 08d0de84..73406aaf 100644 --- a/django_google_maps/widgets.py +++ b/django_google_maps/widgets.py @@ -10,8 +10,6 @@ class GoogleMapsAddressWidget(widgets.TextInput): class Media: css = {"all": ("django_google_maps/css/google-maps-admin.css",)} js = ( - "https://maps.google.com/maps/api/js?key={}&libraries=places".format( - settings.GOOGLE_MAPS_API_KEY - ), + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&libraries=places", "django_google_maps/js/google-maps-admin.js", ) From 3121d22e6dde4ef4481eb1b1369f6e8323f1cdc2 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 24 Jul 2025 10:08:37 -0500 Subject: [PATCH 03/10] Test: Fixed tests with updated template --- django_google_maps/tests/test_widget.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/django_google_maps/tests/test_widget.py b/django_google_maps/tests/test_widget.py index 15a6332e..eeebf13a 100644 --- a/django_google_maps/tests/test_widget.py +++ b/django_google_maps/tests/test_widget.py @@ -7,23 +7,27 @@ class WidgetTests(test.TestCase): def test_render_returns_xxxxxxx(self): widget = GoogleMapsAddressWidget() results = widget.render("name", "value", attrs={"a1": 1, "a2": 2}) - expected = '' + expected = '
' + expected += '' expected += '
' expected += '
' + expected += '
' self.assertHTMLEqual(expected, results) def test_render_returns_blank_for_value_when_none(self): widget = GoogleMapsAddressWidget() results = widget.render("name", None, attrs={"a1": 1, "a2": 2}) - expected = '' + expected = '
' + expected += '' expected += '
' expected += '
' + expected += '
' self.assertHTMLEqual(expected, results) def test_maps_js_uses_api_key(self): widget = GoogleMapsAddressWidget() google_maps_js = ( - "https://maps.google.com/maps/api/js?key={}&libraries=places".format( + "https://maps.googleapis.com/maps/api/js?key={}&libraries=places".format( settings.GOOGLE_MAPS_API_KEY ) ) From ef3dc71f506eaf9750cd19a39648060a0200a6f9 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 24 Jul 2025 11:00:40 -0500 Subject: [PATCH 04/10] Enhancement: Load Google Maps async. Also update to newer markers --- .../js/google-maps-admin.js | 29 +++++++++++-------- django_google_maps/widgets.py | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/django_google_maps/static/django_google_maps/js/google-maps-admin.js b/django_google_maps/static/django_google_maps/js/google-maps-admin.js index 03d59e23..fe0ca211 100644 --- a/django_google_maps/static/django_google_maps/js/google-maps-admin.js +++ b/django_google_maps/static/django_google_maps/js/google-maps-admin.js @@ -59,7 +59,8 @@ function googleMapAdmin() { mapTypeId: self.getMapType(), streetViewControl: false, mapTypeControl: true, - fullscreenControl: false + fullscreenControl: false, + mapId: "dj-google-maps-admin" }; // Create the map instance @@ -198,24 +199,24 @@ function googleMapAdmin() { * @param {object} Options - Marker options, including latlng and draggable. */ addMarker: function (Options) { - marker = new google.maps.Marker({ + var draggable = Options.draggable || false; + marker = new google.maps.marker.AdvancedMarkerElement({ map: map, - position: Options.latlng + position: Options.latlng, + gmpDraggable: draggable }); - var draggable = Options.draggable || false; if (draggable) { - self.addMarkerDrag(); + self.addMarkerDrag(marker); } }, /** * Adds a 'dragend' listener to the marker to update geolocation when dragged. */ - addMarkerDrag: function () { - marker.setDraggable(true); + addMarkerDrag: function (draggableMarker) { // Use the modern addListener method - marker.addListener('dragend', function (event) { + draggableMarker.addListener('dragend', function (event) { self.updateGeolocation(event.latLng); }); }, @@ -225,7 +226,7 @@ function googleMapAdmin() { * @param {google.maps.LatLng} latlng - The new LatLng for the marker. */ updateMarker: function (latlng) { - marker.setPosition(latlng); + marker.position = latlng; }, /** @@ -275,8 +276,12 @@ function googleMapAdmin() { return self; } -// Initialize the map when the DOM is fully loaded -document.addEventListener("DOMContentLoaded", function () { + +async function initGoogleMap() { + await google.maps.importLibrary("maps"); + await google.maps.importLibrary("marker"); + await google.maps.importLibrary("places"); + var googlemap = googleMapAdmin(); googlemap.initialize(); -}); +} diff --git a/django_google_maps/widgets.py b/django_google_maps/widgets.py index 73406aaf..e7d8bb64 100644 --- a/django_google_maps/widgets.py +++ b/django_google_maps/widgets.py @@ -10,6 +10,6 @@ class GoogleMapsAddressWidget(widgets.TextInput): class Media: css = {"all": ("django_google_maps/css/google-maps-admin.css",)} js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&libraries=places", + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap", "django_google_maps/js/google-maps-admin.js", ) From 59404ec51c93b3e6db81b2644922ab226bb29564 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 24 Jul 2025 11:05:30 -0500 Subject: [PATCH 05/10] Test: fix test --- django_google_maps/tests/test_widget.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/django_google_maps/tests/test_widget.py b/django_google_maps/tests/test_widget.py index eeebf13a..6a978848 100644 --- a/django_google_maps/tests/test_widget.py +++ b/django_google_maps/tests/test_widget.py @@ -27,8 +27,6 @@ def test_render_returns_blank_for_value_when_none(self): def test_maps_js_uses_api_key(self): widget = GoogleMapsAddressWidget() google_maps_js = ( - "https://maps.googleapis.com/maps/api/js?key={}&libraries=places".format( - settings.GOOGLE_MAPS_API_KEY - ) + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" ) self.assertEqual(google_maps_js, widget.Media().js[0]) From 44500fc25143b711aac17a42997f114ad2b51e9b Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Thu, 24 Jul 2025 11:34:18 -0500 Subject: [PATCH 06/10] Style: Ignore Line too long --- django_google_maps/tests/test_widget.py | 2 +- django_google_maps/widgets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_google_maps/tests/test_widget.py b/django_google_maps/tests/test_widget.py index 6a978848..0882fbe6 100644 --- a/django_google_maps/tests/test_widget.py +++ b/django_google_maps/tests/test_widget.py @@ -27,6 +27,6 @@ def test_render_returns_blank_for_value_when_none(self): def test_maps_js_uses_api_key(self): widget = GoogleMapsAddressWidget() google_maps_js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" # noqa: E501 ) self.assertEqual(google_maps_js, widget.Media().js[0]) diff --git a/django_google_maps/widgets.py b/django_google_maps/widgets.py index e7d8bb64..cd8d548a 100644 --- a/django_google_maps/widgets.py +++ b/django_google_maps/widgets.py @@ -10,6 +10,6 @@ class GoogleMapsAddressWidget(widgets.TextInput): class Media: css = {"all": ("django_google_maps/css/google-maps-admin.css",)} js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap", + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap", # noqa: E501 "django_google_maps/js/google-maps-admin.js", ) From 34250bb67431d4d2d5bb561117431a5618858ca4 Mon Sep 17 00:00:00 2001 From: aaronmadison Date: Tue, 16 Sep 2025 20:19:26 -0500 Subject: [PATCH 07/10] Enhancement: Move inline styling to css --- .../static/django_google_maps/css/google-maps-admin.css | 6 ++++++ .../templates/django_google_maps/widgets/map_widget.html | 2 +- django_google_maps/tests/test_widget.py | 7 ++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/django_google_maps/static/django_google_maps/css/google-maps-admin.css b/django_google_maps/static/django_google_maps/css/google-maps-admin.css index b98181de..24e363d0 100644 --- a/django_google_maps/static/django_google_maps/css/google-maps-admin.css +++ b/django_google_maps/static/django_google_maps/css/google-maps-admin.css @@ -8,6 +8,12 @@ width: 40em; } +.map_widget_wrapper { + display: flex; + flex-direction: column; + width: 100%; +} + .map_canvas_wrapper { width: 100%; } diff --git a/django_google_maps/templates/django_google_maps/widgets/map_widget.html b/django_google_maps/templates/django_google_maps/widgets/map_widget.html index 7e2e6a8d..288e26dd 100644 --- a/django_google_maps/templates/django_google_maps/widgets/map_widget.html +++ b/django_google_maps/templates/django_google_maps/widgets/map_widget.html @@ -1,4 +1,4 @@ -
+
{% include "django/forms/widgets/text.html" %}
diff --git a/django_google_maps/tests/test_widget.py b/django_google_maps/tests/test_widget.py index 0882fbe6..345d61d9 100644 --- a/django_google_maps/tests/test_widget.py +++ b/django_google_maps/tests/test_widget.py @@ -7,7 +7,7 @@ class WidgetTests(test.TestCase): def test_render_returns_xxxxxxx(self): widget = GoogleMapsAddressWidget() results = widget.render("name", "value", attrs={"a1": 1, "a2": 2}) - expected = '
' + expected = '
' expected += '' expected += '
' expected += '
' @@ -17,7 +17,7 @@ def test_render_returns_xxxxxxx(self): def test_render_returns_blank_for_value_when_none(self): widget = GoogleMapsAddressWidget() results = widget.render("name", None, attrs={"a1": 1, "a2": 2}) - expected = '
' + expected = '
' expected += '' expected += '
' expected += '
' @@ -27,6 +27,7 @@ def test_render_returns_blank_for_value_when_none(self): def test_maps_js_uses_api_key(self): widget = GoogleMapsAddressWidget() google_maps_js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" # noqa: E501 + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" + # noqa: E501 ) self.assertEqual(google_maps_js, widget.Media().js[0]) From 0d1c6dd1f66e9c3511413bba0f929f4d017a7975 Mon Sep 17 00:00:00 2001 From: aaronmadison Date: Tue, 16 Sep 2025 20:25:16 -0500 Subject: [PATCH 08/10] Style: fix flake8 violation --- django_google_maps/tests/test_widget.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django_google_maps/tests/test_widget.py b/django_google_maps/tests/test_widget.py index 345d61d9..2674fb25 100644 --- a/django_google_maps/tests/test_widget.py +++ b/django_google_maps/tests/test_widget.py @@ -27,7 +27,6 @@ def test_render_returns_blank_for_value_when_none(self): def test_maps_js_uses_api_key(self): widget = GoogleMapsAddressWidget() google_maps_js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" - # noqa: E501 + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap" # noqa: E501 ) self.assertEqual(google_maps_js, widget.Media().js[0]) From 186bd2defc5d9d1a39bd9a29c222b51252c180f9 Mon Sep 17 00:00:00 2001 From: aaronmadison Date: Tue, 16 Sep 2025 20:48:11 -0500 Subject: [PATCH 09/10] Enhancement: use django 5.2 script object when available --- django_google_maps/widgets.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/django_google_maps/widgets.py b/django_google_maps/widgets.py index cd8d548a..3c02033f 100644 --- a/django_google_maps/widgets.py +++ b/django_google_maps/widgets.py @@ -1,6 +1,13 @@ +import django from django.conf import settings from django.forms import widgets +# Check if we're on Django 5.2+ +USE_SCRIPT_OBJECT = django.VERSION >= (5, 2) + +if USE_SCRIPT_OBJECT: + from django.forms.widgets import Script + class GoogleMapsAddressWidget(widgets.TextInput): """a widget that will place a google map right after the #id_address field""" @@ -9,7 +16,18 @@ class GoogleMapsAddressWidget(widgets.TextInput): class Media: css = {"all": ("django_google_maps/css/google-maps-admin.css",)} - js = ( - f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap", # noqa: E501 - "django_google_maps/js/google-maps-admin.js", - ) + + if USE_SCRIPT_OBJECT: + js = ( + Script( + f'https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap', # noqa: E501 + **{ + "async": True, + }), + 'django_google_maps/js/google-maps-admin.js', + ) + else: + js = ( + f"https://maps.googleapis.com/maps/api/js?key={settings.GOOGLE_MAPS_API_KEY}&loading=async&callback=initGoogleMap", # noqa: E501 + 'django_google_maps/js/google-maps-admin.js', + ) From 5f10d8465a45cec966695fbdccbd9b7ecc66c679 Mon Sep 17 00:00:00 2001 From: Aaron Madison Date: Sun, 21 Sep 2025 07:50:39 -0500 Subject: [PATCH 10/10] Enhancement: Add space between address input field and map --- .../static/django_google_maps/css/google-maps-admin.css | 1 + 1 file changed, 1 insertion(+) diff --git a/django_google_maps/static/django_google_maps/css/google-maps-admin.css b/django_google_maps/static/django_google_maps/css/google-maps-admin.css index 24e363d0..5cd25a47 100644 --- a/django_google_maps/static/django_google_maps/css/google-maps-admin.css +++ b/django_google_maps/static/django_google_maps/css/google-maps-admin.css @@ -12,6 +12,7 @@ display: flex; flex-direction: column; width: 100%; + gap: 0.5rem; } .map_canvas_wrapper {