xref: /webtrees/resources/views/modules/place-hierarchy/map.phtml (revision bd6ca2dba57449e50319a753712ec88aeaaf6680)
1<?php
2
3use Fisharebest\Webtrees\View;
4
5/**
6 * @var array<mixed> $data
7 * @var object       $leaflet_config
8 */
9
10?>
11
12<div class="py-4">
13    <div class="row gchart wt-map-wrapper">
14        <div id="wt-map" class="col-sm-9 wt-ajax-load wt-map-user" dir="ltr"></div>
15        <ul class="col-sm-3 wt-map-sidebar wt-page-options-value list-unstyled px-md-1"></ul>
16    </div>
17</div>
18
19<?php View::push('styles') ?>
20<style>
21    .wt-map-wrapper, .wt-map-user {
22        height: 70vh
23    }
24
25    .wt-map-sidebar {
26        height: 100%;
27        overflow-x: hidden;
28        overflow-y: auto;
29        font-size: small;
30    }
31</style>
32<?php View::endpush() ?>
33
34<?php View::push('javascript') ?>
35<script>
36  'use strict';
37
38  (function () {
39    const config = <?= json_encode($leaflet_config, JSON_THROW_ON_ERROR) ?>;
40
41    let map = null;
42    const sidebar = document.querySelector('.wt-map-sidebar');
43
44    const scrollOptions = {
45      behavior: "smooth",
46      block: "start",
47      inline: "start"
48    };
49
50    // Map components
51    let markers = L.markerClusterGroup({
52      showCoverageOnHover: false,
53    });
54
55    let resetControl = L.Control.extend({
56      options: {
57        position: 'topleft',
58      },
59
60      onAdd: function (map) {
61        let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom');
62        container.onclick = function () {
63          map.flyToBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 });
64          sidebar.firstElementChild.scrollIntoView(scrollOptions);
65          return false;
66        };
67        let anchor = L.DomUtil.create('a', 'leaflet-control-reset', container);
68        let reset = config.i18n.reset;
69        anchor.setAttribute('aria-label', reset);
70        anchor.href = '#';
71        anchor.title = reset;
72        anchor.role = 'button';
73        let image = L.DomUtil.create('i', 'fas fa-redo', anchor);
74        image.alt = reset;
75
76        return container;
77      },
78    });
79
80    /**
81     * @private
82     */
83    let _drawMap = function () {
84      map = webtrees.buildLeafletJsMap('wt-map', config)
85        .addControl(new resetControl());
86    };
87
88    /**
89     * @private
90     */
91    let _buildMapData = function () {
92      let data = <?= json_encode($data['markers'], JSON_THROW_ON_ERROR) ?>;
93
94      let geoJsonLayer = L.geoJson(data, {
95        pointToLayer: function (feature, latlng) {
96          return new L.Marker(latlng, {
97            icon: L.BeautifyIcon.icon({
98              icon: 'bullseye fas',
99              borderColor: 'transparent',
100              backgroundColor: '#1e90ff',
101              iconShape: 'marker',
102              textColor: 'white',
103            }),
104            title: feature.properties.tooltip,
105            alt: feature.properties.tooltip,
106            id: feature.id,
107          })
108            .on('popupopen', function (e) {
109              let item = document.querySelector('.mapped[data-wt-feature-id="' + e.target.feature.id + '"]');
110              item.classList.add('messagebox');
111              item.scrollIntoView(scrollOptions);
112            })
113            .on('popupclose', function () {
114              sidebar.querySelectorAll('.mapped').forEach(e => e.classList.remove('messagebox'));
115              sidebar.firstElementChild.scrollIntoView(scrollOptions);
116            });
117        },
118        onEachFeature: function (feature, layer) {
119          layer.bindPopup(feature.properties.popup);
120        },
121      });
122
123      if (data.features.length > 0) {
124        markers.addLayer(geoJsonLayer);
125        map.addLayer(markers);
126      }
127
128      map.fitBounds(<?= json_encode($data['bounds'], JSON_THROW_ON_ERROR) ?>, { padding: [50, 30] });
129      sidebar.innerHTML = <?= json_encode($data['sidebar'], JSON_THROW_ON_ERROR) ?>;
130   };
131
132    window.onload = function() {
133      // Activate marker popup when sidebar entry clicked
134      sidebar.querySelectorAll('.mapped').forEach((element) => {
135        var eventId = parseInt(element.dataset.wtFeatureId);
136
137        element.addEventListener('click', () => {
138          // first close any existing
139          map.closePopup();
140          //find the marker corresponding to the clicked event
141          let mkrLayer = markers.getLayers().filter(function (v) {
142            return v.feature !== undefined && v.feature.id === eventId;
143          });
144
145          let mkr = mkrLayer.pop();
146
147          markers.zoomToShowLayer(mkr, function (e) {
148            mkr.openPopup();
149          });
150
151          return false;
152        });
153
154        // stop clicking on a person also opening the popup
155        element.querySelectorAll('a').forEach((el) => {
156          el.addEventListener('click', (e) => {
157            e.stopPropagation();
158          });
159        });
160      });
161    }
162
163    _drawMap();
164    _buildMapData();
165  })();
166</script>
167<?php View::endpush() ?>
168