Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
@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_widget_wrapper {
display: flex;
flex-direction: column;
width: 100%;
gap: 0.5rem;
}

.map_canvas_wrapper {
width: 100%;
}

#id_address {width: 40em;}
.map_canvas_wrapper {width: 100%;}
#map_canvas {width: 100%; height: 40em;}
#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;
}

#map_message_box.show {
display: block;
opacity: 1;
}
198 changes: 151 additions & 47 deletions django_google_maps/static/django_google_maps/js/google-maps-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,50 +23,65 @@ 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,
mapId: "dj-google-maps-admin"
};

// 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();
Expand All @@ -75,65 +90,102 @@ 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 {
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']};
}
},

/**
* Retrieves existing latitude and longitude from the geolocation input field.
* @returns {Array<string>|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);
self.setMarker(latlng);
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);
Expand All @@ -142,42 +194,94 @@ 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({
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(marker);
}
},

addMarkerDrag: function () {
marker.setDraggable(true);
google.maps.event.addListener(marker, 'dragend', function (new_location) {
self.updateGeolocation(new_location.latLng);
/**
* Adds a 'dragend' listener to the marker to update geolocation when dragged.
*/
addMarkerDrag: function (draggableMarker) {
// Use the modern addListener method
draggableMarker.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);
marker.position = 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;
}

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();
});
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
{% include "django/forms/widgets/text.html" %}
<div class="map_canvas_wrapper"><div id="map_canvas"></div></div>
<div class="map_widget_wrapper">
{% include "django/forms/widgets/text.html" %}
<div class="map_canvas_wrapper">
<div id="map_canvas"></div>
</div>
<div id="map_message_box" role="alert"></div>
</div>
Loading