Dynamically work with leaflet overlays (Drupal 8)

In one of the projects the specification asked for usage of leaflet maps. One of the difficulties was to make the leaflet overlays automatically retrieve the list of Drupal content types, not all of them, but only the specific ones. This task seemed to be more difficult because the only solution was working in javascript and I wasn't an expert in either leaflet or javascript.

The project was about sailing around the oceans, and it would give some useful information to people who were traveling.

As a starting position for this task, I already had a leaflet map, which was represented as a block view, and in the view settings it was set that for a country their related nodes are presented as markers on the map.

Yellow icons on the maps are nodes, where each icon represents a different content type.

  1. jQuery(document).bind('leaflet.map', function(event, map, lMap) {
  2.   //custom code goes here
  3. });

Whenever we need to make some custom code for leaflet maps, this is the code syntax and where we need to place our custom code.

  1. //map id
  2. var id = lMap._container.id;
  3. var url = window.location.href;
  4. var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
  5. var drupalSettings = JSON.parse(settingsElement.textContent);
  6. var length = drupalSettings.leaflet[ id ].features.length;
  7. var content_types = drupalSettings.contenttypes;
  8. var ct_names = Object.keys(content_types);
  9. var ct_length = ct_names.length;
  10. var ct_image = Object.values(content_types);
  11. var cnt_array = {};

In the first code snippet I've set the variable id which will retrieve the leaflet map's id from lMap leaflet javascript core variable, and url which I'll need later in the code. Next, I've included drupalSettings (where I'll use to pass some variables from Drupal to javascript). The next thing I needed to do was to set the variables which would retreive the content type names and icon path values, I'd need later. The following example from theme_name.theme file is down bellow:

  1. function THEME_page_attachments_alter(array &$page) {
  2.  
  3.   $sql = "select data from config where name like '%leaflet_icons.settings%'";
  4.   $query = db_query($sql);
  5.   $content = $query->fetchAll();
  6.   $content = $content[0]->data;
  7.   $content = unserialize($content);
  8.   $contentTypes = \Drupal::service('entity.manager')->getStorage('node_type')->loadMultiple();
  9.   $contentTypesList = [];
  10.   // Get content Type information.
  11.   foreach ($contentTypes as $contentType) {
  12.     $contentTypesList[$contentType->id()] = $contentType->label();
  13.   }
  14.  
  15.   foreach ($contentTypesList as $key => $value) {
  16.     if (!empty($content[$key])) {
  17.       $val = $content[$key];
  18.  
  19.       $change = explode('/', $val);
  20.       $lastI = count($change);
  21.       $picture = explode('.', $change[($lastI - 1)]);
  22.  
  23.       if ($picture[0] != $key) {
  24.         unset($content[$key]);
  25.         $key = $picture[0];
  26.       }
  27.       $content[$key] = array($val, ucfirst(strtolower($value)));
  28.     }
  29.   }
  30.  
  31.   ksort($content);
  32.   $page['#attached']['drupalSettings']['contenttypes'] = $content;
  33. }

In my example above, I only needed content types which would be included in the overlays list and that have values for the icon image in the Leaflet Icons settings.

I have selected a value from the database table leaflet icons, but as the content is serialized, we'll first have to unserialize it. After fetching them all, I've sorted everything alphabetically.

  1. $.each(content_types, function(key, value){
  2.   window[key] = new L.LayerGroup();
  3.   window[key+"Icon"] = L.icon({
  4.     iconUrl: value[0],
  5.     iconSize: [30, 30], // size of the icon
  6.         iconAnchor: [0, 0], // point of the icon which will correspond to marker's location
  7.         popupAnchor: [15, 15], // point from which the popup should open relative to the iconAnchor
  8.         className: 'leaflet-custom-layer'
  9.   });
  10. });

In the code above, we went through every content type dynamically, and set icon settings for each of them (url, position, size, ..). We'll need that later as well.

  1. var base_url = "{{ url('<front>') }}";
  2.  
  3. var i;
  4. for (i = 0; i < length; i++) {
  5.  
  6.   var lat = drupalSettings.leaflet[ id ].features[i].lat; //lat
  7.   var lon = drupalSettings.leaflet[ id ].features[i].lon; //lon
  8.  
  9.   var iconUrl = drupalSettings.leaflet[ id ].features[i].icon.iconUrl;
  10.   var exloded = iconUrl.split('/');
  11.   var lengthall = exloded.length;
  12.  
  13.   var extension = exloded[(lengthall - 1)];
  14.   var split = extension.split('.');
  15.   var finalIcon = split[0];
  16.  
  17.   var popup = drupalSettings.leaflet[ id ].features[i].popup; //popup
  18.  
  19.   if (typeof window[finalIcon+'Icon'] != 'undefined') {
  20.     L.marker([lat, lon], {icon: window[finalIcon+'Icon']}).bindPopup(popup).addTo(window[finalIcon]);
  21.   }

Next we need to set the base_url that we'll need for setting the icon path value.

We are going through every icon on the map, using a for loop ni order to set some variables.

lat and lon variables, are the coordinates of the each node.

Next, we'll format the icons.

Variable popup is the value of a popup section for each node.  

In the end, we ask if typeof icon is not undefined, so we can set the node values, including it's coordinates, icon url value and popup. All those nodes will appear on the map as we load the page.

  1. // do not show markers with icons on map load
  2. Drupal.Leaflet.prototype.create_point = function (marker) {
  3.   var latLng = new L.LatLng(marker.lat, marker.lon);
  4.   this.bounds.push(latLng);
  5.   var lMarker;
  6.   if (marker.icon) {
  7.     var icon = this.create_icon(marker.icon);
  8.     //lMarker = new L.Marker(latLng, {icon: icon});
  9.   }
  10.   else {
  11.     lMarker = new L.Marker(latLng);
  12.   }
  13.   return lMarker;
  14. };

As I said before, we did have icons on the map set already, before we started adding our custom code for leaflet icons.

So it means with adding these custom settings, our map would have duplicates of each icon.

We'll have to exclude those already appearing there before our changes.

That could be done with changing the javascript code above. I'll have to see.

  1. var overlays = {};
  2.  
  3. $.each(content_types, function(key, value){
  4.   var text = '<img src="'+value[0]+'" /> '+value[1];
  5.   overlays[text] = window[key];
  6. });
  7.  
  8. L.control.layers(null, overlays).addTo(lMap);

In the end, we need to go again through all selected content types, and set the list of overlays and add them to the leaflet map.

 

FULL CODE:

  1. jQuery(document).bind('leaflet.map', function(event, map, lMap) {
  2.   //map id
  3.   var id = lMap._container.id;
  4.   var url = window.location.href;
  5.   var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
  6.   var drupalSettings = JSON.parse(settingsElement.textContent);
  7.   var length = drupalSettings.leaflet[ id ].features.length;
  8.  
  9.   var content_types = drupalSettings.contenttypes;
  10.   var ct_names = Object.keys(content_types);
  11.   var ct_length = ct_names.length;
  12.   var ct_image = Object.values(content_types);
  13.   var cnt_array = {};
  14.  
  15.   $.each(content_types, function(key, value){
  16.     window[key] = new L.LayerGroup();
  17.     window[key+"Icon"] = L.icon({
  18.       iconUrl: value[0],
  19.       iconSize: [30, 30], // size of the icon
  20.       iconAnchor: [0, 0], // point of the icon which will correspond to marker's location
  21.       popupAnchor: [15, 15], // point from which the popup should open relative to the iconAnchor
  22.       className: 'leaflet-custom-layer'
  23.     });
  24.   });
  25.  
  26.   var base_url = "{{ url('<front>') }}";
  27.   var i;
  28.   for (i = 0; i < length; i++) {
  29.     var lat = drupalSettings.leaflet[ id ].features[i].lat; //lat
  30.     var lon = drupalSettings.leaflet[ id ].features[i].lon; //lon
  31.  
  32.     var iconUrl = drupalSettings.leaflet[ id ].features[i].icon.iconUrl;
  33.     var exloded = iconUrl.split('/');
  34.     var lengthall = exloded.length;
  35.    
  36.     var extension = exloded[(lengthall - 1)];
  37.     var split = extension.split('.');
  38.     var finalIcon = split[0];
  39.  
  40.     var popup = drupalSettings.leaflet[ id ].features[i].popup; //popup
  41.     if (typeof window[finalIcon+'Icon'] != 'undefined') {
  42.       L.marker([lat, lon], {icon: window[finalIcon+'Icon']}).bindPopup(popup).addTo(window[finalIcon]);
  43.     }
  44.   }
  45.   $.each(ct_names, function(index, value){
  46.     lMap.addLayer(window[value]);
  47.   });
  48.  
  49.   // do not show markers with icons on map load
  50.   Drupal.Leaflet.prototype.create_point = function (marker) {
  51.     var latLng = new L.LatLng(marker.lat, marker.lon);
  52.     this.bounds.push(latLng);
  53.     var lMarker;
  54.     if (marker.icon) {
  55.       var icon = this.create_icon(marker.icon);
  56.       //lMarker = new L.Marker(latLng, {icon: icon});
  57.     } else {
  58.       lMarker = new L.Marker(latLng);
  59.     }
  60.     return lMarker;
  61.   };
  62.  
  63.   var overlays = {};
  64.  
  65.   $.each(content_types, function(key, value){
  66.     var text = '<img src="'+value[0]+'" /> '+value[1];
  67.     overlays[text] = window[key];
  68.   });
  69.  
  70.   L.control.layers(null, overlays).addTo(lMap);
  71. });

 

Lazar Padjan

back to top