Code Monkey home page Code Monkey logo

Comments (12)

eriktelepovsky avatar eriktelepovsky commented on August 17, 2024 2

Hi @michaelhjulskov I have a working solution ;)

To add or delete a marker, you need access to DjangoGooglePointFieldWidget instance in your JavaScript. I opened an issue for that #62
I believe author will agree with me or he will give me better approach how to use it.

Next thing you need to do is to override template file which renders the map. In my case it was google-point-field-widget.html. So go ahead and create a file with the same name in your app in the path templates/mapwidgets/

You can either paste the whole content there or just extend it like I do:

{% extends "mapwidgets/google-point-field-widget.html" %}

{% load i18n %}

{% block javascript %}
    <script type="application/javascript">
        (function($) {
            var mapOptions = JSON.parse("{{ options|escapejs }}");
            var field_value = JSON.parse("{{ field_value|escapejs }}");

            var wrapElemSelector = "#{{ name }}-mw-wrap";
            var mapElemID = "{{ name }}-map-elem";
            var googleAutoInputID = "{{ name }}-mw-google-address-input";
            var locationInputID = "#{{ id }}";

            var mapWidgetOptions = {
                locationInput: $(locationInputID),
                wrapElemSelector: wrapElemSelector,
                locationFieldValue: field_value,
                mapElement: document.getElementById(mapElemID),
                mapCenterLocationName: mapOptions.mapCenterLocationName,
                mapCenterLocation: mapOptions.mapCenterLocation,
                coordinatesOverlayToggleBtn: $(".mw-btn-coordinates", wrapElemSelector),
                coordinatesOverlayDoneBtn: $(".mw-btn-coordinates-done", wrapElemSelector),
                coordinatesOverlayInputs: $(".mw-overlay-input", wrapElemSelector),
                coordinatesOverlay: $(".mw-coordinates-overlay", wrapElemSelector),
                myLocationBtn: $(".mw-btn-my-location", wrapElemSelector),
                addressAutoCompleteInput: document.getElementById(googleAutoInputID),
                deleteBtn: $(".mw-btn-delete", wrapElemSelector),
                addMarkerBtn: $(".mw-btn-add-marker", wrapElemSelector),
                loaderOverlayElem: $(".mw-loader-overlay", wrapElemSelector),
                zoom: mapOptions.zoom,
                markerFitZoom: mapOptions.markerFitZoom,
                GooglePlaceAutocompleteOptions: mapOptions.GooglePlaceAutocompleteOptions,
                markerCreateTriggerNameSpace: "google_point_map_widget:marker_create",
                markerChangeTriggerNameSpace: "google_point_map_widget:marker_change",
                markerDeleteTriggerNameSpace: "google_point_map_widget:marker_delete",
                placeChangedTriggerNameSpace: "google_point_map_widget:place_changed"
            };
            var widget = new DjangoGooglePointFieldWidget(mapWidgetOptions);
            map_widget_autofill(widget);

            {% block extra_javascript %}

            {% endblock %}
        })(jQuery || django.jQuery);
    </script>
{% endblock javascript %}

Please notice these 2 changed lines:

var widget = new DjangoGooglePointFieldWidget(mapWidgetOptions);
map_widget_autofill(widget);

I haven't touched anything else in this file.

map_widget_autofill() is my own function I have in my JavaScript file script.js. I will describe it below.

You need to tell Django to load your static file in the admin.py:

class MyModelAdminForm(forms.ModelForm):
    class Meta:
        model = MyModel
        exclude = []
        widgets = {
            'point': GooglePointFieldWidget
        }

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    form = MyModelAdminForm
    ...

    class Media:
        js = (
            'js/script.js',
        )

And finally script.js:

(function($) {
    map_widget_autofill = function (widget) {
        var locationInput = widget.locationInput;
        var form = $(locationInput).closest('form');
        var street = form.find('#id_street');
        var postcode = form.find('#id_postcode');
        var city = form.find('#id_city');
        var country = form.find('#id_country');

        // if the location input isn't set, try to fetch GPS coordinates at document load
        if (!locationInput.val()) {
            update_map();
        }

        // update every time the text inputs change
        $([street, postcode, city, country]).each(function(){
            $(this).keyup(function() {
                update_map();
            });
        });

        // update every time the select changes
        $(country).change(function() {
            update_map();
        });

        function update_map() {
            // join all address parts values into single string
            var address = street.val() + ', ' + postcode.val() + ' ' + city.val() + ', ' + country.val();

            // initialize autocomplete service API
            var autocomplete_service = new google.maps.places.AutocompleteService();

            // try to find address prediction using autocomplete service API
            autocomplete_service.getPlacePredictions({input: address}, function (predictions, status) {
                // if status is incorrect, clear search value
                if (status != google.maps.places.PlacesServiceStatus.OK) {
                    $(widget.addressAutoCompleteInput).val('');
                    widget.deleteMarker();
                } else if (predictions.length >= 1) {
                    // otherwise if there is at least 1 prediction available, pick the very first one
                    var address_by_prediction = predictions[0].description;

                    // set the address as search value
                    $(widget.addressAutoCompleteInput).val(address_by_prediction);

                    // try to find the GPS coordinates of the predicted address
                    widget.geocoder.geocode({'address' : address_by_prediction}, function(results, status) {
                        if (status === google.maps.GeocoderStatus.OK) {
                            // check the successful result
                            var geo_location = results[0].geometry.location;
                            var latitude = geo_location.lat();
                            var longitude = geo_location.lng();

                            // add marker to map
                            widget.addMarkerToMap(latitude, longitude);
                            widget.updateLocationInput(latitude, longitude);

                            // set center position (or fit bounds)
                            widget.map.setCenter({lat: latitude, lng: longitude});
                            // widget.fitBoundMarker();

                            // set zoom (change according your needs or use bounds if you wish)
                            widget.map.setZoom(15);

                        } else {
                            // geocoder couldn't find a GPS...
                            widget.deleteMarker();
                            console.warn("Cannot find " + address_by_prediction + " on google geo service.")
                        }
                    });
                }
            });
        }
    }
})(jQuery || django.jQuery);

It works perfectly for me. Hope it helps you.

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

By the way...
Here is Danish translation :)

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:8
msgid "Coordinates"
msgstr "Koordinater"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:11
msgid "Latitude:"
msgstr "Breddegrad"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:12
msgid "Ex: 41.015137"
msgstr "Eks: 41.015137"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:15
msgid "Longitude:"
msgstr "Længdegrad"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:16
msgid "Ex: 28.979530"
msgstr "Eks: 28.979530"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:18
msgid "Done"
msgstr "Færdig"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:22
msgid "Use My Location"
msgstr "Brug Min Nuværende Lokation"

#: mapwidgets/templates/mapwidgets/google-point-field-widget.html:25
msgid "Enter or update the address here"
msgstr "Skriv eller ret adresse her"

from django-map-widgets.

erdem avatar erdem commented on August 17, 2024

Hi @michaelhjulskov

Did you check out the widget jQuery triggers? Sounds like you can do this with a trigger handler.

You can find an example here:
http://django-map-widgets.readthedocs.io/en/latest/widgets/point_field_map_widgets.html#javascript-triggers

Btw, would be great, if you can open a pull request for Danish translation. :)

Thanks.

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

thanks. maybe. i cant see how to use those triggers though.

regarding the "open a pull request for Danish translation."
i would love to, but im not that familiar with github so dont know how to do it. i tried the other day in another repo, but i didnt succeed. do i need to fork this repo to open a pull request? and then change files in my files? or ?

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

i combined some code i found and my own.
this way i will put the mapwidget before the address fields, and try to fill in the fields automatically for the customer.

//gets "street_number", "route", "locality", "country", "postal_code"
function getAddressComponents(components, type) {    
  for (var key in components) {
    if (components.hasOwnProperty(key)) {            
      if (type == components[key].types[0]) {
        return components[key];
      }  
    }        
  }
  return '';
}

$(document).on("google_point_map_widget:place_changed", function (e, place, lat, lng, locationInputElem, mapWrapID) {
  street_number = " " + getAddressComponents(place.address_components, 'street_number').long_name
  $("input[name=street]").val(getAddressComponents(place.address_components, 'route').long_name + street_number)
  $("input[name=zipcode]").val(getAddressComponents(place.address_components, 'postal_code').long_name)
  $("input[name=city]").val(getAddressComponents(place.address_components, 'locality').long_name)
  $('select[name=country] option[value="' + getAddressComponents(place.address_components, 'country').short_name + '"]').prop('selected', true)
});

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

here's my current approach.
My first two fields is company name and city.
When these fields is filled, we copy it to the mapwidget search field and if google api have 1 suggestion, lets assume its correct.
(almost working - I only need to find a way to trigger the mapwidget to set the point - help is much apreciated)

    //gets "street_number", "route", "locality", "country", "postal_code"
    function getAddressComponents(components, type) {    
      for (var key in components) {
        if (components.hasOwnProperty(key)) {            
          if (type == components[key].types[0]) {
            return components[key];
          }  
        }        
      }
      return false;
    }

    $(document).on("google_point_map_widget:place_changed", function (e, place, lat, lng, locationInputElem, mapWrapID) {
      
      console.log(place);
      console.log(locationInputElem); // django widget textarea widget (hidden)
      console.log(lat, lng); // created marker coordinates
      console.log(mapWrapID); // map widget wrapper element ID

      street_number = getAddressComponents(place.address_components, 'street_number');
      route = getAddressComponents(place.address_components, 'route');
      if (route && street_number){$("#id_street").val(route.long_name + " " + street_number.long_name);} 
      else if (route){$("#id_street").val(route.long_name);}
      
      postal_code = getAddressComponents(place.address_components, 'postal_code');
      if (postal_code){$("#id_zipcode").val(postal_code.long_name);}
      
      locality = getAddressComponents(place.address_components, 'locality');
      if (locality){$("#id_city").val(locality.long_name);}
      
      country = getAddressComponents(place.address_components, 'country');
      if (country){$('#id_country option[value="' + country.short_name + '"]').prop('selected', true);}
    });

    function autoCallback(predictions, status) {
        if (status != google.maps.places.PlacesServiceStatus.OK) {
          $("#location-mw-google-address-input").val('');
          return;
        }
        if (predictions.length == 1){$("#location-mw-google-address-input").val(predictions[0].description);}
    }
    function queryAutocomplete(input) {// *Uses Google's autocomplete service to select an address
        var service = new google.maps.places.AutocompleteService();
        service.getPlacePredictions({input: input}, autoCallback);
    }
    $(document).on("change", "#id_name, #id_city", function(event){
      if ($("#id_name").val() && $("#id_city").val()){
        queryAutocomplete( $("#id_name").val() + ", " + $("#id_city").val() );
      }
    });

as soon the point is set on the map, it will autofill the other address fields.

from django-map-widgets.

eriktelepovsky avatar eriktelepovsky commented on August 17, 2024

Hi guys. I would like to achieve a similar thing. I have 1 point field and 2 text inputs (street and city). I would like to achieve the following:

  1. once the user enters something into street and city input fields, the concatenated value will be passed into Google Autocomplete input of the point field
  2. trigger search
  3. then the first available result will be automatically selected
  4. map marker will change according to the result

So far by now, I succeeded with the 1st step:

var map_input = $('#point-mw-google-address-input');
var street = $('#id_street');
var city = $('#id_city');

$('#id_street, #id_city').keyup(function() {
    var address = street.val() + ', ' + city.val();
    map_input.val(address);
});

but I am not able to figure out how to trigger the autocomplete search and add marker automatically.

I tried google.maps.event.trigger(map_input, 'place_changed'); but it didn't work.

Can you please help me? Which triggers should I use? Thank you very much in advance.

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

Eric take a look at my form and maybe my code can be part of the solution. But I got stuck couldn't find a way to call the map so I had to reload the map which doesn't work correctly. See the other comment I made a few minutes ago

from django-map-widgets.

michaelhjulskov avatar michaelhjulskov commented on August 17, 2024

Great Great Great finally breakthrough
I will give it a punch one of the next days.
Thanks for sharing your approach :-)

from django-map-widgets.

josezy avatar josezy commented on August 17, 2024

I couldnt override the template :C
no matter if I create a new one templates/mapwidgets/google-point-field-widget.html in my project folder or if I change template_name = "mapwidgets/my_template.html" in my own widget

class GooglePointFieldWidgetJQuery(GooglePointFieldWidget):
    template_name = "mapwidgets/my_template.html"

Django never look in my defined templates dir (altough it works with custom admin templates)

Template-loader postmortem
Django tried loading these templates, in this order:

Using engine django:

django.template.loaders.filesystem.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/django/forms/templates/mapwidgets/my_template.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/django/contrib/admin/templates/mapwidgets/my_template.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/django/contrib/auth/templates/mapwidgets/my_template.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/graphene_django/templates/mapwidgets/my_template.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/mapwidgets/templates/mapwidgets/my_template.html (Source does not exist)
django.template.loaders.app_directories.Loader: /home/monadical/.local/share/virtualenvs/pennybags-N4zD9cT2/lib/python3.7/site-packages/bootstrap4/templates/mapwidgets/my_template.html (Source does not exist)

from django-map-widgets.

erdem avatar erdem commented on August 17, 2024

@jeasoft This could be related with your template dirs settings. Your class implementation should be fine for template overriding.

https://docs.djangoproject.com/en/2.2/ref/settings/#dirs

from django-map-widgets.

josezy avatar josezy commented on August 17, 2024

ok, i know is more a django issue but thanks for the reply :)

this is my TEMPLATE setting:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATES_DIR],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

with TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') which translates to '/home/monadical/pennybags/pennydjango/templates'. And is in that templates folder where I create another folder mapwidgets and put the template my_template.html.

Anyway, I found that I could use javascript triggers to catch the place_changed event (which is not explict in docs) and with that I could grab the autocompleted address. That was what I needed in first place.

Thanks for the help, is a cool widget 😸

from django-map-widgets.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.