Portal projects map - Supercluster 1

In this example below you will see how to do a Portal projects map - Supercluster 1 with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by plucile, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright plucile ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Portal projects map - Supercluster 1</title>
  <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.38.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.38.0/mapbox-gl.css' rel='stylesheet' />

<script src="https://unpkg.com/supercluster@2.3.0/dist/supercluster.min.js"></script>
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <!-- https://www.mapbox.com/help/building-a-store-locator/ -->

<div class='sidebar pad2'>
  <div class="sidebar-header">
<!--     <div class='heading'>
      <h1>Active Projects</h1>
    </div> -->
  </div>
  <div class="sidebar-content">
    <div id='listings' class='listings'></div>
    <div id="listing-details">
      <button id="close-details">Close</button>
    </div>
  </div>
    
  </div>
<div id='map'></div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/plucile/portal-projects-map-supercluster-1-RxamKW */
body {
  color: #333333;
  font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', Sans-serif;
  margin: 0;
  padding: 0;
  -webkit-font-smoothing: antialiased;
}

* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

#map {
  border-left: 1px solid #fff;
  position: absolute;
  right: 0;
  left: 300px;
  top: 0;
  bottom: 0;
  height: 600px;
}

.marker {
  background-image: url("https://www.mapbox.com/help/demos/custom-markers-gl-js/mapbox-icon.png");
  background-size: cover;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  cursor: pointer;
}

h1 {
  font-size: 22px;
  margin: 0;
  font-weight: 400;
  line-height: 20px;
  padding: 20px 2px;
}

a {
  color: #333333;
  text-decoration: none;
}

a:hover {
  color: #dddddd;
}

.sidebar {
  background: white;
  position: absolute;
  width: 300px;
  height: 600px;
  top: 0;
  left: 0;
  overflow: hidden;
  border-right: 1px solid rgba(0, 0, 0, 0.25);
  display: flex;
  flex-direction: column;
}

.sidebar-header {
  padding: 0 20px;
  border-bottom: 1px solid #eee;
  flex: 0 0 auto;
}

.sidebar-content {
  position: relative;
  flex: 1;
  min-height: 1px;
}

.sidebar-content.details-open #listing-details {
  transform: translateX(-100%);
  -webkit-transform: translateX(-100%);
  -ms-transform: translateX(-100%);
}

#listing-details {
  position: absolute;
  left: 100%;
  top: 0;
  bottom: 0;
  width: 100%;
  background: #f2f2f2;
  z-index: 2;
  padding: 20px;
  transition: transform .3s;
  transform: translateX(0%);
  -webkit-transform: translateX(0%);
  -ms-transform: translateX(0%);
}

#close-details {
  display: block;
  margin-left: auto;
}

.pad2 {
  /*   padding: 20px; */
}

.map {
  position: absolute;
  left: 33.3333%;
  width: 66.6666%;
  top: 0;
  bottom: 0;
}

.heading {
  background: #fff;
  height: 60px;
  line-height: 60px;
}

.listings {
  height: 100%;
  overflow: auto;
}

.listings .item {
  display: block;
  border-bottom: 1px solid #eee;
  text-decoration: none;
}

.listings .item:last-child {
  border-bottom: none;
}

.listings .item .title {
  display: inline-block;
  padding: 20px;
  width: 100%;
  font-size: 18px;
  color: #666666;
  font-weight: 700;
  margin-bottom: 0;
}

.listings .item .details {
  margin-bottom: 20px;
}

.listings .activity ul {
  margin-top: 0;
}

.listings .item .title small {
  font-weight: 400;
}

.listings .item.active .title,
.listings .item .title:hover {
  color: #000000;
}

.listings .item.active {
  background-color: #f8f8f8;
}

::-webkit-scrollbar {
  width: 3px;
  height: 3px;
  border-left: 0;
  background: rgba(0, 0, 0, 0.1);
}

::-webkit-scrollbar-track {
  background: none;
}

::-webkit-scrollbar-thumb {
  background: #666666;
  border-radius: 0;
}

.clearfix {
  display: block;
}

.clearfix::after {
  content: '.';
  display: block;
  height: 0;
  clear: both;
  visibility: hidden;
}

/* Marker tweaks */
.mapboxgl-popup-close-button {
  display: none;
}

.mapboxgl-popup-content {
  font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', Sans-serif;
  width: 180px;
  padding: 10px;
}

.mapboxgl-popup-content-wrapper {
  padding: 1%;
}

.mapboxgl-popup-content h3 {
  background: #333333;
  color: #fff;
  margin: 0;
  display: block;
  padding: 10px;
  border-radius: 3px 3px 0 0;
  font-weight: 700;
  margin-top: -15px;
}

.mapboxgl-popup-content h4 {
  margin: 0;
  display: block;
  font-weight: 400;
}

.mapboxgl-popup-content div {
  padding: 10px;
}

.mapboxgl-container .leaflet-marker-icon {
  cursor: pointer;
}

.mapboxgl-popup-anchor-top > .mapboxgl-popup-content {
  margin-top: 15px;
}

.mapboxgl-popup-anchor-top > .mapboxgl-popup-tip {
  border-bottom-color: #333333;
}

/* Marker tweaks */
.mapboxgl-popup-close-button {
  display: none;
}

.mapboxgl-popup-content {
  font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', Sans-serif;
  width: 180px;
}

.mapboxgl-popup-content-wrapper {
  padding: 1%;
}

.mapboxgl-popup-content h3 {
  background: #333333;
  color: #fff;
  margin: 0;
  display: block;
  padding: 10px;
  border-radius: 3px 3px 0 0;
  font-weight: 700;
  margin-top: -15px;
}

.mapboxgl-popup-content h4 {
  margin: 0;
  display: block;
  font-weight: 400;
}

.mapboxgl-popup-content div {
  padding: 10px;
}

.mapboxgl-container .leaflet-marker-icon {
  cursor: pointer;
}

.mapboxgl-popup-anchor-top > .mapboxgl-popup-content {
  margin-top: 15px;
}

.mapboxgl-popup-anchor-top > .mapboxgl-popup-tip {
  border-bottom-color: #333333;
}

.marker {
  border: none;
  cursor: pointer;
  height: 50px;
  width: 50px;
  background-image: url("https://www.liquid.com.au/img/mapbox/liquid-monogram-g-2.png");
  background-color: rgba(0, 0, 0, 0);
}

.mapboxgl-popup {
  padding-bottom: 25px;
}


/*Downloaded from https://www.codeseek.co/plucile/portal-projects-map-supercluster-1-RxamKW */
// This will let you use the .remove() function later on
if (!('remove' in Element.prototype)) {
  Element.prototype.remove = function() {
    if (this.parentNode) {
      this.parentNode.removeChild(this);
    }
  };
}


// Json for markers
var projects = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.034084142948,
          38.909671288923
        ]
      },
      "properties": {
        "title": "Project 1",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.049766,
          38.900772
        ]
      },
      "properties": {
        "title": "Project 2",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.043929,
          38.910525
        ]
      },
      "properties": {
        "title": "Project 3",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.0672,
          38.90516896
        ]
      },
      "properties": {
        "title": "Project 4",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.002583742142,
          38.887041080933
        ]
      },
      "properties": {
        "title": "Project 5",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -76.933492720127,
          38.99225245786
        ]
      },
      "properties": {
        "title": "Project 6",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.097083330154,
          38.980979
        ]
      },
      "properties": {
        "title": "Project 7",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.359425054188,
          38.958058116661
        ]
      },
      "properties": {
        "title": "Project 8",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.10853099823,
          38.880100922392
        ]
      },
      "properties": {
        "title": "Project 9",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -75.28784,
          40.008008
        ]
      },
      "properties": {
        "title": "Project 10",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -75.20121216774,
          39.954030175164
        ]
      },
      "properties": {
        "title": "Project 11",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.043959498405,
          38.903883387232
        ]
      },
      "properties": {
        "title": "Project 12",
        "description": "Description here...",
        "address": "16 Heath Street, Port Melbourne"
      }
    }
  ]
};





mapboxgl.accessToken = 'pk.eyJ1IjoibGlxdWlkbWVsYm91cm5lIiwiYSI6ImNqNzFqM2RhaTAxYzQycXAxcmV1a3dxMmQifQ._O7C_D3ubcwP7AI9S_HaYA';

// This adds the map to your page
var map = new mapboxgl.Map({
  // container id specified in the HTML
  container: 'map',
  // style URL
  style: 'mapbox://styles/mapbox/light-v9',
  // initial position in [lon, lat] format
  center: [-77.034084, 38.909671],
  // initial zoom
  zoom: 12
});

map.on('load',function(e) {
  
  map.addSource('places', {
    type: 'geojson',
    data: projects
  });

  
  buildListing(projects);
})

// Markers on the map
projects.features.forEach(function(marker, i) {
  // Create a div element for the marker
  var el = document.createElement('div');
  // Add a class called 'marker' to each div
  el.className = 'marker';
  // By default the image for your custom marker will be anchored
  // by its top left corner. Adjust the position accordingly
  // Create the custom markers, set their position, and add to map
  new mapboxgl.Marker(el, { offset: [-25, -25] })
    .setLngLat(marker.geometry.coordinates)
    .addTo(map);
  
  
  el.addEventListener('click', function(e) {
    var activeItem = document.getElementsByClassName('active');
    // 1. Fly to the point
    flyToStore(marker);
    // 2. Close all other popups and display popup for clicked store
    createPopUp(marker);
    // 3. Highlight listing in sidebar (and remove highlight for all other listings)
    e.stopPropagation();
    if (activeItem[0]) {
      activeItem[0].classList.remove('active');
    }
    var listing = document.getElementById('listing-' + i);
    listing.classList.add('active');
    // 4. Scroll to listing in sidebar
    // get jquery lisiting
    var $listing = $('#listing-' + i);
    var $listings = $('#listings');
    // get listing top offset
    var listingOffset = $listing.offset().top;
    // offset = listing offset - listings offset + listings scroll offset
    var offset = listingOffset - $listings.offset().top +  $listings.scrollTop();
    
    $listings.animate({ scrollTop: offset }, 'slow');
    
    // e.stopPropagation();
    // selectMarker(marker);

  });
  
  $('#close-details').on('click', function(e) {
    var activeItem = document.getElementsByClassName('active');
    e.stopPropagation();
    if (activeItem[0]) {
      activeItem[0].classList.remove('active');
    }
    var listing = document.getElementById('listing-' + i);
    listing.classList.add('active');
    // 4. Scroll to listing in sidebar
    // get jquery lisiting
    var $listing = $('#listing-' + i);
    var $listings = $('#listings');
    // get listing top offset
    var listingOffset = $listing.offset().top;
    // offset = listing offset - listings offset + listings scroll offset
    var offset = listingOffset - $listings.offset().top +  $listings.scrollTop();
    
    $listings.animate({ scrollTop: offset }, 'slow');
  })
  
  
})


function buildListing(data) {
  for (i = 0; i < data.features.length; i++) {
    var currentFeature = data.features[i];
    // Shorten data.feature.properties to just `prop` so we're not
    // writing this long form over and over again.
    var prop = currentFeature.properties;
    // Select the listing container in the HTML and append a div
    // with the class 'item' for each store
    
    var listings = document.getElementById('listings');
    var listing = listings.appendChild(document.createElement('div'));
    listing.className = 'item';
    listing.id = 'listing-' + i;

    // Create a new link with the class 'title' for each store
    // and fill it with the store address
    var link = listing.appendChild(document.createElement('a'));
    link.href = '#';
    link.className = 'title';
    link.dataPosition = i;
    link.innerHTML += prop.title;

    // Create a new div with the class 'details' for each store
    // and fill it with the city and phone number
    // var details = listing.appendChild(document.createElement('div'));
    // details.className = 'details';
    // details.innerHTML += prop.description;
    
    // var readMore = listing.appendChild(document.createElement('div'));
    // readMore.className = 'read-more';
    // readMore.innerHTML += '<button>Read More</button>';
    // readMore.setAttribute('data-id',i);
    

    // Add an event listener for the links in the sidebar listing
    link.addEventListener('click', function(e) {
      // Update the currentFeature to the store associated with the clicked link
      var clickedListing = data.features[this.dataPosition];
      // 1. Fly to the point associated with the clicked link
      flyToStore(clickedListing);
      // 2. Close all other popups and display popup for clicked store
      createPopUp(clickedListing);
      // 3. Highlight listing in sidebar (and remove highlight for all other listings)
      var activeItem = document.getElementsByClassName('active');
      if (activeItem[0]) {
        activeItem[0].classList.remove('active');
      }
      this.parentNode.classList.add('active');
    });
    
    $('.read-more').off().on('click', function(e) {
      e.preventDefault();
      
      // get project object
      var projectId = $(this).attr('data-id');
      console.log(projectId);
      var project = projects.features[projectId];
      
      // update details
      buildListingDetails(project);
      
      // show details
      $('.sidebar-content').addClass('details-open');
      
      // fly to marker and highlight listing
      flyToStore(project);
      // 2. Close all other popups and display popup for clicked store
      createPopUp(projects);
      
    });
 
  }
}


function buildListingDetails(listing) {
  
  var prop = listing.properties;
  
  var inner = $('#details-inner');
  
  // if inner div exists, remove
  if (inner.length < 1 ) inner.remove();
  
  // add inner div
  $('#listing-details').append('<div id="details-inner"></div>');
  
  // add to inner: title, content, "go to page" link
  
  console.log(prop);
  
  $('#details-inner').html('<h1>' + prop.title + '</h1><p>' + prop.description + '</p>');
   
  // close details
  $('#close-details').on('click', function() {
    $('.sidebar-content').removeClass('details-open');
  })
}

function flyToStore(currentFeature) {
  map.flyTo({
    center: currentFeature.geometry.coordinates,
    zoom: 13
  });
}

function createPopUp(currentFeature) {
  var popUps = document.getElementsByClassName('mapboxgl-popup');
  // Check if there is already a popup on the map and if so, remove it
  if (popUps[0]) popUps[0].remove();

  var popup = new mapboxgl.Popup({ closeOnClick: false })
    .setLngLat(currentFeature.geometry.coordinates)
    .setHTML(
      '<h4>' + currentFeature.properties.title + '</h4><a href="#">View Project</a>')
    .addTo(map);
}

Comments