pythondjangogoogle-mapsgoogle-maps-api-3canjs

Google maps API issue with version 3.35 in a django form field widget


I am having a problem with the Google maps API, after upgrading it to version 3.35. The goal is to display the map in a form so that the user can define the geolocation. I am using Django 1.11.12 and CanJS 2.0.2.

Here is the code (reduced for simplicity) which works fine with the Google maps API version 3.34, but not with the version 3.35.

models.py

from django.db import models
from maps.models import JSONField

class Institution(models.Model):
    geolocation = JSONField(blank=True, null=True)

maps/models.py

from django.db import models
from maps.forms import LatLngWidget

class JSONField(models.TextField):
    def formfield(self, **kwargs):
        kwargs.update({'widget': LatLngWidget()})
        return super(JSONField, self).formfield(**kwargs)

maps/forms.py

from django import forms
from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
from django.forms.widgets import Widget

class LatLngWidget(Widget):
    def __init__(self, attrs=None):
        super(LatLngWidget, self).__init__(attrs)
        self.width = 640
        self.height = 480

    def render(self, name, value, attrs=None):
        data = dict(name=name, value=value, width=self.width, height=self.height)
        html = render_to_string("maps/widget.html", data)
        return mark_safe(html)

    def _media(self):
        return forms.Media(css={"all": [static("maps/css/gmap.css"),],},)

    media = property(_media)

widget.html

<div class="map-container" id="geolocation-container">
<div class="map-box">
    <div class="map map-canvas" id="gmap-geolocation"></div>
</div>
</div>

<script src="https://maps.googleapis.com/maps/api/js?v=3.35&key=xxx"></script>

<script type="text/javascript">
    Map = can.Control({
        init: function(el, options) {
            this.map = null;
            this.marker = null;

            this.name = options.name;
            this.lat = options.lat || 0;
            this.lng = options.lng || 0;
            this.zoom = options.zoom || 2;

            this.createMap();
        },

        createMap: function() {
            var data = {
                'name': this.options.name,
                'width': this.options.width,
                'height': this.options.height,
                'lat': this.lat,
                'lng': this.lng,
                'zoom': this.zoom
            };

            var mapOptions = {
                zoom: 5,
                center: {lat: -25.344, lng: 131.036},
            };

            var target = 'gmap-'+this.name;
            console.log(target);

            this.map = new google.maps.Map(document.getElementById(target), mapOptions);
            // This code works with 3.34. Initial map is rendered with 3.35, but without buttons and not updated if zooming.
            // 3.34
            // > gmap-geolocation
            // 3.35
            // > gmap-geolocation
            // > gmap-undefined
            // > Uncaught TypeError: Cannot read property 'firstChild' of null js?v=3.35&key=xxx:70
            // > gmap-undefined

            //this.map = new google.maps.Map(document.getElementById('gmap-geolocation'), mapOptions);
            // This code works with 3.34. This code does not work at all with 3.35 - browser freezes.
        },
    });

    {% autoescape off %}
    var data = {% firstof value "{}" %};
    {% endautoescape %}
    data.name = "{{ name }}";
    data.width = "{{ width }}";
    data.height = "{{ height }}";
    new Map($("#geolocation-container"), data);
</script>

edit.html

<form method="post" action="" enctype="multipart/form-data">
   {% csrf_token %}
   {% formrow form.geolocation %}
</form>

The problem appears to be in widget.html. Once again, if the entire code stays the same, and only the Google maps API is upgraded from 3.34 to 3.35, I get the error "Uncaught TypeError: Cannot read property 'firstChild' of null".

I would appreciate any tip how to fix this.


Solution

  • The issue has been fixed by renaming the CanJS control function "Map". If in widget.html

    Map = can.Control({
    

    is changed to

    Map2 = can.Control({
    

    ... and the instance created with

    new Map2($("#geolocation-container"), data);
    

    ... then everything works fine again with the Google maps javascript API v3.35.