misc: use a popup to display markers at same position (#76894) #260

Merged
fpeters merged 2 commits from wip/76894-markers-same-place-popup into main 2023-04-28 10:24:36 +02:00
4 changed files with 105 additions and 37 deletions

View File

@ -13,7 +13,7 @@ from unittest import mock
import pytest
import responses
from django.utils.encoding import force_bytes, force_str
from webtest import Hidden, Radio, Upload
from webtest import Hidden, Upload
from wcs import fields
from wcs.admin.settings import UserFieldsFormDef
@ -9577,19 +9577,21 @@ def test_form_item_map_data_source(pub, http_requests):
resp_geojson = app.get('/api/geojson/foobar')
assert http_requests.count() == 1 # cache was used
assert len(resp_geojson.json['features']) == 2
# simulate qommon.map.js that will create radio inputs
resp.form.fields['f1$marker_id'] = [Radio(form=resp.form, tag='input', name='f1$marker_id', pos=5)]
resp.form.fields['f1$marker_id'][0].options.append(('1', False, None))
resp.form.fields['f1$marker_id'][0].options.append(('2', False, None))
resp.form.fields['f1$marker_id'][0].optionPositions.append(5)
resp.form.fields['f1$marker_id'][0].optionPositions.append(6)
resp.form.field_order.append(('f1$marker_id', resp.form.fields['f1$marker_id'][0]))
# simulate qommon.map.js that will fill the hidden inputs
resp.form['f1$marker_id'].value = '1' # click on marker
resp.form['f1$latlng'] = '1;2' # set via js
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp
# selected option is displayed as readonly:
assert resp.pyquery('input[type=text][value=foo][readonly]')
# check going back keeps item selected
resp = resp.form.submit('previous')
assert resp.pyquery('.qommon-map').attr('data-markers-initial-id') == '1'
resp.form['f1$marker_id'].value = '1' # click on marker
resp.form['f1$latlng'] = '1;2' # set via js
resp = resp.form.submit('submit') # to validation page again
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp

View File

@ -3442,6 +3442,7 @@ class MapMarkerSelectionWidget(MapWidget):
def __init__(self, name, value=None, **kwargs):
CompositeWidget.__init__(self, name, value, **kwargs)
self.add(HiddenWidget, 'marker_id', value=value)
self.marker_id_widget = self.get_widget('marker_id')
self.readonly = kwargs.pop('readonly', False)
self.init_map_attributes(value, **kwargs)
@ -3459,7 +3460,8 @@ class MapMarkerSelectionWidget(MapWidget):
self.value = self.get('marker_id')
def set_value(self, value):
CompositeWidget.set_value(self, value)
self.value = value
self.marker_id_widget.set_value(value)
class HiddenErrorWidget(HiddenWidget):

View File

@ -73,11 +73,21 @@ $(window).on('wcs:maps-init', function() {
}
if ($map_widget.data('markers-url')) {
var radio_name = $map_widget.data('markers-radio-name');
$map_widget.on('change', 'input[name="' + radio_name + '"]', function() {
var markers_by_position = new Object();
var hidden_marker_id = $('input[type=hidden][name="' + radio_name + '"]');
function turn_marker_on(marker_id, lat, lng, position_key) {
hidden.val(lat + ';' + lng);
hidden_marker_id.val(marker_id);
hidden.trigger('change');
$map_widget.find('.marker-icon[data-position-key]').removeClass('marker-icon-on');
$map_widget.find('.marker-icon[data-position-key="' + position_key + '"]').addClass('marker-icon-on');
}
$map_widget.on('change', 'input.marker-selector', function(ev) {
var $radio = $(this);
if ($radio.is(':checked')) {
hidden.val($radio.data('lat') + ';' + $radio.data('lng'));
hidden.trigger('change');
turn_marker_on($radio.val(), $radio.data('lat'), $radio.data('lng'), $radio.data('parent-position-key'));
}
});
$.getJSON($map_widget.data('markers-url')).done(
@ -86,28 +96,70 @@ $(window).on('wcs:maps-init', function() {
var checked_lng = null;
var geo_json = L.geoJson(data, {
pointToLayer: function (feature, latlng) {
var $label = $('<label>', {
title: feature.properties._text
});
var $radio = $('<input>', {
value: feature.properties._id,
name: radio_name,
type: 'radio',
'data-lat': latlng.lat,
'data-lng': latlng.lng
});
if (typeof initial_marker_id !== 'undefined' && feature.properties._id == initial_marker_id) {
checked_lat = latlng.lat;
checked_lng = latlng.lng;
$radio.attr('checked', 'checked');
var position_key = latlng.lat.toFixed(6) + ';' + latlng.lng.toFixed(6);
var marker = markers_by_position[position_key];
var marker_on = (typeof initial_marker_id !== 'undefined' && feature.properties._id == initial_marker_id);
if (typeof marker === 'undefined') {
var $div_content = $('<div></div>', {
title: feature.properties._text,
'data-marker-id': feature.properties._id,
'data-lat': latlng.lat,
'data-lng': latlng.lng
});
var $marker_icon = $('<span></span>', {
'class': 'marker-icon',
'data-marker-id': feature.properties._id,
'data-position-key': position_key
});
if (marker_on) {
$marker_icon.addClass('marker-icon-on');
}
$marker_icon.appendTo($div_content);
div_marker = L.divIcon({
className: 'item-marker',
html: $div_content.prop('outerHTML'),
iconSize: [25, 41]
});
marker = L.marker(latlng, {icon: div_marker});
// keep a list of all features at the same location
marker.radio_array = Array();
marker.radio_array.push({id: feature.properties._id, text: feature.properties._text});
markers_by_position[position_key] = marker;
// add a popup with feature text
var popup = L.popup().setContent($('<div></div>', {text: feature.properties._text}).prop('outerHTML'));
popup.marker = {id: feature.properties._id, lat: latlng.lat, lng: latlng.lng, position_key: position_key};
marker.bindPopup(popup, {offset: [0, -20]});
return marker;
} else {
marker.radio_array.push({id: feature.properties._id, text: feature.properties._text});
var $ul_radios = $('<ul class="multi-marker-selection"></ul>');
$(marker.radio_array).each(function(i, feature_data) {
var $radio = $('<input>', {
value: feature_data.id,
name: 'radio-' + radio_name,
'class': 'marker-selector',
type: 'radio',
'data-lat': latlng.lat,
'data-lng': latlng.lng,
'data-parent-position-key': position_key
});
var $label = $('<label></label>');
$label.append($radio);
$label.append(feature_data.text);
var $li = $('<li></li>');
$label.appendTo($li);
$li.appendTo($ul_radios);
});
marker.unbindPopup().bindPopup($ul_radios.prop('outerHTML'), {offset: [0, -20]});
if (marker_on) {
var marker_icon = marker.getIcon();
var marker_html = $(marker_icon.options.html);
marker_html.find('.marker-icon').addClass('marker-icon-on');
marker_icon.options.html = marker_html.prop('outerHTML');
}
}
$label.append($radio);
$label.append($('<span></span>'));
var div_marker = L.divIcon({
className: 'item-marker',
html: '<div>' + $label.prop('outerHTML') + '</div>'
});
return L.marker(latlng, {icon: div_marker});
}
});
if (checked_lat !== null) {
@ -119,6 +171,19 @@ $(window).on('wcs:maps-init', function() {
map.fitBounds(geo_json.getBounds());
}
geo_json.addTo(map);
map.on('popupopen', function(e) {
var popup = e.popup;
if (popup.marker) {
// if popup is bound to a single marker, turn this on
turn_marker_on(popup.marker.id, popup.marker.lat, popup.marker.lng, popup.marker.position_key);
} else {
// turn radio on
var current_value = hidden_marker_id.val();
$('input[type=radio][name="radio-' + radio_name + '"][value="' + current_value + '"]').prop('checked', true);
}
});
}
);
} else if ($map_widget.data('readonly') && initial_marker_id) {

View File

@ -11,20 +11,19 @@
{% endblock %}
{% block widget-control %}
<input type="hidden" name="{{widget.name}}$marker_id" {% if widget.value %}value="{{widget.value}}"{% endif %}>
{{ block.super }}
<style>
.item-marker input + span {
.item-marker span.marker-icon {
position: relative;
z-index: 1000;
margin-top: -55px;
margin-left: -5px;
display: block;
width: 25px;
height: 41px;
background: url(/static/images/blank-marker-icon.png);
}
.item-marker input:checked + span {
.item-marker span.marker-icon.marker-icon-on {
background: url(/static/xstatic/images/marker-icon.png);
}
</style>