Customizing Geolocation Field

Hello everyone…

For a couple of days I have been trying to customize the Geolocation field but it doesn’t work as expected even if I simply extend the Geolocation control without any modification…

I have been trying to disable all the controls except for the marker and to limit the marker to just one…

I managed to disable all the controls except for the marker but the marker doesn’t appear on the map although the form field event gets triggered and the field value changes, even the map draw events get triggered but the marker still doesn’t appear…

This is my code and if anyone knows why it’s not working, please let me know…

rappe.ui.form.ControlGeolocation = class CustomControlGeolocation extends frappe.ui.form.ControlGeolocation {
    bind_leaflet_draw_control() {
        this.editableLayers = new L.FeatureGroup();
        
        this.drawControl = new L.Control.Draw({
            position: 'topleft',
            draw: {
                polyline: false,
                polygon: false,
                circle: false,
                circlemarker: false,
                rectangle: false
            },
            edit: {
                featureGroup: this.editableLayers,
                remove: true
            }
        });
        this.map.addControl(this.drawControl);
        
        this.map.on('draw:created', (e) => {
            if (e.layerType === 'marker') {
                console.log('map marker created');
                e.layer.bindPopup('Marker');
                this.editableLayers.addLayer(e.layer);
                this.set_value(JSON.stringify(this.editableLayers.toGeoJSON()));
            } else {
                console.log('map item ignored');
                try {
                    e.layer.remove();
                } catch(_) {}
            }
        });
        
        this.map.on('draw:deleted draw:edited', (e) => {
            console.log('map item removed');
            this.editableLayers.removeLayer(e.layer);
            this.set_value(JSON.stringify(this.editableLayers.toGeoJSON()));
        });
    }
};

Best regards…

Hello everyone…

I just want to inform you that I managed to achieve everything I mentioned above by using an HTML field and creating the map just like in the Geolocation field but without all the controls except the locate and refresh buttons…

And instead of using the marker control, I implemented a map click event handler to remove previous marker if any exist and create a new marker using the new coordinates…

I also managed to make the marker draggable which makes it easier to change the location just by repositioning the marker…

This is my modified map code…

// Geolocation field
let map_field = frm.get_field('location'),
// HTML field
html_field = frm.get_field('location_html'),
map_id = frappe.dom.get_unique_id();

// Setup the HTML field, just like the Geolocation field
html_field.$wrapper.removeClass('form-group').append('\
<div class="form-group">\
    <div class="clearfix">\
        <label class="control-label" style="padding-right: 0px;">\
            ' + __(map_field.df.label) + '\
        </label>\
    </div>\
    <div class="control-input-wrapper">\
        <p class="help-box small text-muted text-center">\
            ' + __('(Click on the map to mark a location)') + '\
        </p>\
        <div class="map-wrapper border">\
            <div id="' + map_id + '" style="min-height: 400px; z-index: 1; max-width:100%"></div>\
        </div>\
        <p class="help-box small text-muted">\
            ' + (map_field.df.description ? __(map_field.df.description) : '') + '\
        </p>\
    </div>\
</div>\
');

// Initiate map and attach it to form variable for reference 
frm._map = L.map(map_id);
// Add tile layer to map
L.tileLayer(
    'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    {
        maxZoom: 19,
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }
).addTo(frm._map);

// Use the Geolocation API to set the map view to the user location
// If failed, then set the map view to Frappe's default location 
window.navigator.geolocation.getCurrentPosition(
    function(ret) {
        ret = ret.coords;
        frm._map.setView(
            [ret.latitude, ret.longitude],
            frappe.utils.map_defaults.zoom
        );
    },
    function() {
        frm._map.setView(
            frappe.utils.map_defaults.center,
            frappe.utils.map_defaults.zoom
        );
    },
    {
        maximumAge: 0,
        timeout: Infinity,
        enableHighAccuracy: true
    }
);


// Add the locate control to the map
let locate_control = L.control.locate({position:'topright'});
locate_control.addTo(frm._map);

// Add the refresh control to the map
L.easyButton({
    id: 'refresh-map-' + map_field.df.fieldname,
    position: 'topright',
    type: 'replace',
    leafletClasses: true,
    states:[{
        stateName: 'refresh-map',
        onClick: function(button, map) { map._onResize(); },
        title: 'Refresh map',
        icon: 'fa fa-refresh'
    }]
}).addTo(frm._map);

// Create a marker remover control and add it to the map
frm._marker_remove_control = L.easyButton({
    id: 'remove-marker-' + map_field.df.fieldname,
    position: 'topleft',
    type: 'replace',
    leafletClasses: true,
    states:[{
        stateName: 'remove-marker',
        onClick: function(button, map) {
            removeMarker();
        },
        title: __('Remove Marker'),
        icon: 'fa-trash'
    }]
}).addTo(frm._map);
// Disable the marker remover control
frm._marker_remove_control.disable();

// Register the map click event handler 
function mapClick(e) {
    e = e.latlng;
    if (!frm._marker) addMarker(e.lng, e.lat);
    else {
        frm._marker.setLatLng(new L.LatLng(e.lat, e.lng));
        updateLocation(e.lng, e.lat);
    }
}
frm._map.on('click', mapClick);

// Show the HTML field
frm.toggle_display('location_html', 1);

// Add marker function
function addMarker(lng, lat, old) {
    frm._marker = new L.marker([lat, lng], {draggable: true});
    frm._marker.addTo(frm._map);
    frm._marker.on('dragend', markerDrag);
    frm._marker_remove_control.enable();
    if (!old) updateLocation(lng, lat);
}
// Update the Geolocation field value
function updateLocation(lng, lat) {
    let value = {
        type: 'FeatureCollection',
        features: []
    };
    if (lng && lat)
        value.features.push({
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [lng, lat]
            }
        });
    frm.set_value('location', JSON.stringify(value));
}
// Remove marker function
function removeMarker() {
    if (!frm._marker) return;
    frm._marker_remove_control.disable();
    frm._marker.off('dragend', markerDrag);
    frm._map.removeLayer(frm._marker);
    frm._marker = null;
    updateLocation();
}
// Marker drag handler
function markerDrag(e) {
    e = e.target.getLatLng();
    updateLocation(e.lng, e.lat);
}

// Finally, if Geolocation field has an old value
// then add a marker and move the map to its location
if (!frm.is_new() && cstr(frm.doc.location).length) {
    try {
        let value = JSON.parse(cstr(frm.doc.location));
        if (value.features && value.features.length) {
            value = value.features[0].geometry.coordinates;
            addMarker(value[0], value[1], 1);
            frm._map.flyTo([value[1], value[0]], 16);
        }
    } catch(e) {}
}

Best regards…

2 Likes

Kindly provide what you did to achieve it so others may benefit :wink:

1 Like

@kevingee Sure thing bro…

I have updated the post above…

Best regards…

1 Like

I checked the console and map_field.df.description does not exist

There is an extra ] after value[0]

All in all, I can place a marker on the map in html_field. However, no description is displayed.

Again, you can delete an old marker after you create a new one. You can only delete a fresh marker.

Can you check the above? :wink:

1 Like

@kevingee Hey bro…

The description that is used is the description of the Geolocation field and not the HTML field…

Thank you for pointing out my mistake, I have fixed it in the code above…

As I mentioned above, the description is of the Geolocation field and not the HTML field…

Also, thank you for pointing out my mistake, I have fixed it in the code above…

Only one marker is allowed so clicking on the map while a marker already exist will remove the existing marker and then create a new one for the new location…

But then I changed it in the code above to update the location of the existing marker and the Geolocation field value instead of removing the existing marker and then creating a new one…

Best regards…