Sections
Store Locator

Store Locator


Installation

À la fin du head

Ajoutez le code suivant dans le <head> de votre fichier theme.liquid, juste avant la fermeture de la balise </head> :

<!-- MAPBOX[BEGIN] -->
<script src='https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.css' rel='stylesheet' />
<script src="{{ 'mm_map.js' | asset_url }}" defer="defer"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css" type="text/css">
<!-- MAPBOX[END] -->

Section

mm-map.liquid

Créez un nouveau fichier mm-map.liquid dans le dossier sections :

{{ 'mm-map.css' | asset_url | stylesheet_tag }}
 
<div class="mm-section">
  <input type="hidden" value="{{ shop.metaobjects.pharmacies.mapbox_settings.mapbox_token }}" id="mm-mapbox-token"/>
  <div id="mm-places-json">
    {{ shop.metaobjects.pharmacies.mapbox_settings.places_json | metafield_tag }}
  </div>
  <template id="mm-map-place-template">
    <div data-place-coordinates
         class="mm-map-place-card">
      <p data-place-name></p>
      <p data-place-address style="font-weigth: 300;"></p>
    </div>
  </template>
 
  <div class="mm-map-wrapper">
    <!-- MAP -->
    <div id="mm-map"
         style='width: 100%; height: 600px'></div>
 
    <!-- PLACES -->
    <div id="mm-map-places"></div>
  </div>
</div>
 
<script>
</script>
 
<style>
  @media(max-width:768px) {
  }
  .mm-map-place-card {
    border: 1px solid black;
    margin-bottom: 8px;
  }
  .mm-map-place-card.selected {
    border-color: red !important;
  }
</style>
 
{% schema %}
{
    "name": "MM Map",
    "settings": [
        {
            "type": "text",
            "id": "title",
            "label": "Titre"
        }
    ],
    "presets": [
        {
            "name": "MM Map",
            "category": "Moon Moon"
        }
    ]
}
{% endschema %}

Assets

mm-map.js

Créez un nouveau fichier mm-map.js dans le dossier assets :

// longitude = d'est en ouest
// latitude = du nord au sud
 
const placeCardTemplate = document.getElementById('mm-map-place-template');
const placesContainer = document.getElementById('mm-map-places');
const placesJSONEl = document.querySelector('#mm-places-json .metafield-json');
const mapboxTokenInput = document.getElementById('mm-mapbox-token');
 
const PLACE_SELECTED_CLASS = 'selected';
const MARKER_DEFAULT_COLOR = '#3FB1CE';
const MARKER_SELECTED_COLOR = '#FF0000';
 
mapboxgl.accessToken = mapboxTokenInput.value;
const places = JSON.parse(placesJSONEl.innerText).map((placeData) => {
  return {
    name: placeData.name,
    address: placeData.address,
    coordinates: [placeData.Longitude, placeData.Latitude]
  };
});
 
function mmInsertPlaceCard(place) {
  const card = placeCardTemplate.content.cloneNode(true).firstElementChild;
  const nameEl = card.querySelector('[data-place-name]');
  nameEl.textContent = place.name;
  const addressEl = card.querySelector('[data-place-address]');
  addressEl.textContent = place.address;
  card.dataset.placeCoordinates = JSON.stringify(place.coordinates);
 
  placesContainer.appendChild(card);
}
 
function mmDisplayVisibleMarkers(map) {
  // const { _ne: NorthEast, _sw: SouthWest } = map.getBounds();
  // const maxNorth = NorthEast.lat;
  // const maxEast = NorthEast.lng;
  // const maxSouth = SouthWest.lat;
  // const maxWest = SouthWest.lng;
  // same with destructuring:
  const {
    _ne: { lng: maxEast, lat: maxNorth },
    _sw: { lng: maxWest, lat: maxSouth }
  } = map.getBounds();
  
  const visiblePlaces = places.forEach((place) => {
    // const lng = places.coordinates.lng;
    // const lat = places.coordinates.lat;
    const [lng, lat] = place.coordinates; // [-1.557900, 47.214370]
 
    const visible = lng < maxEast && 
      lng > maxWest &&
      lat > maxSouth &&
      lat < maxNorth
    if (visible) {
      mmInsertPlaceCard(place);
    }
  });
}
 
function mmFlyToPlaceByCoordinates(coordinates) {
  const map = window.mmMap;
  map.flyTo(
    { center: coordinates, zoom: 15 },
    { placeCoordinatesToSelect: coordinates }
  );
}
 
function mmSelectPlaceByCoordinates(coordinates) {
  Array.from(placesContainer.children).forEach((placeCard) => {
    placeCard.classList.remove(PLACE_SELECTED_CLASS);
 
    const [cardLng, cardLat] = JSON.parse(placeCard.dataset.placeCoordinates);
    const [lng, lat] = coordinates;
    if (cardLng === lng && cardLat === lat) {
      placeCard.classList.add(PLACE_SELECTED_CLASS); 
    }
  });
 
  mmHighLightPlaceMarkerByCoordinates(coordinates);
}
 
function mmHighLightPlaceMarkerByCoordinates(coordinates) {
  const [lng, lat] = coordinates;
  const markers = document.querySelectorAll('#mm-map [data-marker-place-coordinates]');
  
  markers.forEach((marker) => {
    const [markerLng, markerLat] = JSON.parse(marker.dataset.markerPlaceCoordinates);
    const markerPath = marker.querySelector('path');
    
    if (markerLng === lng && markerLat === lat) {
      markerPath.setAttribute('fill', MARKER_SELECTED_COLOR);
    } else {
      markerPath.setAttribute('fill', MARKER_DEFAULT_COLOR);
    }
  })
}
 
function mmInitMap() {
  const map = new mapboxgl.Map({
    container: 'mm-map', // container ID
    style: 'mapbox://styles/mapbox/streets-v12', // style URL
    center: [2.25, 46.45], // starting position [lng, lat]
    zoom: 5, // starting zoom
  });
 
  window.mmMap = map;
 
  // Add geocoded search
  map.addControl(
    new MapboxGeocoder({ 
      mapboxgl: mapboxgl, 
      accessToken: mapboxgl.accessToken,
      countries: 'fr,gf' // comma separated list
    })
  );
 
  // Add geolocate control to the map.
  map.addControl(
    new mapboxgl.GeolocateControl({
      positionOptions: {
      enableHighAccuracy: true
    },
    // When active the map will receive updates to the device's location as it changes.
    trackUserLocation: true,
    // Draw an arrow next to the location dot to indicate which direction the device is heading.
    showUserHeading: true
    })
  );
  
  places.forEach((place) => {
    const marker = new mapboxgl.Marker()
      .setLngLat(place.coordinates)
      .addTo(map);
    const markerElement = marker.getElement();
    
    markerElement.dataset.markerPlaceCoordinates = JSON.stringify(place.coordinates);
    
    markerElement.addEventListener('click', () => {
      mmFlyToPlaceByCoordinates(place.coordinates);
    });
  });
  
  map.on('moveend', (event) => {
    placesContainer.innerHTML = null;
 
    if (map.getZoom() > 12) {
      mmDisplayVisibleMarkers(map);
    }
 
    // if move was triggered by mmFlyToPlaceByCoordinates, highlight card in list
    if (event.placeCoordinatesToSelect) {
      mmSelectPlaceByCoordinates(event.placeCoordinatesToSelect);
    }
  });
 
  placesContainer.addEventListener('click', (event) => {
    const target = event.target;
 
    const card = target.closest('.mm-map-place-card');
    const coordinates = JSON.parse(card.dataset.placeCoordinates);
 
    mmFlyToPlaceByCoordinates(coordinates);
  });
}
 
document.addEventListener('DOMContentLoaded', mmInitMap);

mm-map.css

Créez un nouveau fichier mm-map.css dans le dossier assets :

.mm-map-wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 3rem;
}
 
/* PLACES */
.mm-map-place-card {
  border: solid 1px #f5f5f5;
  border-radius: 6px;
  padding: 20px;
  cursor: pointer;
  transition: .3s;
}
.mm-map-place-card.selected {
  border: solid 2px #000;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.mm-map-place-card:hover {
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.mm-map-place-card p[data-place-name] {
  margin: 0 0 10px;
  font-weight: bold;
}
.mm-map-place-card p[data-place-address] {
  margin: 0;
}

Configuration

Metaobjects Shopify

Ce système utilise des metaobjects Shopify pour stocker les données des pharmacies/magasins. Vous devez créer :

  1. Metaobject "pharmacies" avec :

    • mapbox_settings (type: metaobject)
      • mapbox_token : Votre token Mapbox
      • places_json : JSON contenant la liste des lieux
  2. Structure du JSON places_json :

[
  {
    "name": "Nom du magasin",
    "address": "Adresse complète",
    "Longitude": -1.557900,
    "Latitude": 47.214370
  }
]

Fonctionnalités

  • Carte interactive : Utilise Mapbox GL JS pour afficher une carte interactive
  • Recherche géocodée : Barre de recherche pour trouver des adresses
  • Géolocalisation : Bouton pour localiser l'utilisateur
  • Marqueurs : Affiche les magasins/pharmacies sur la carte
  • Liste des lieux : Affiche les lieux visibles dans la zone de la carte
  • Interaction : Clic sur un marqueur ou une carte pour zoomer et sélectionner
  • Responsive : Affichage adaptatif selon la taille de l'écran

Personnalisation

Changer le style de la carte

Modifiez le paramètre style dans mmInitMap() :

style: 'mapbox://styles/mapbox/streets-v12', // streets, dark, light, satellite, etc.

Changer les pays de recherche

Modifiez le paramètre countries dans le MapboxGeocoder :

countries: 'fr,gf' // Ajoutez d'autres codes pays séparés par des virgules

Ajuster le zoom initial

Modifiez les valeurs dans mmInitMap() :

center: [2.25, 46.45], // [longitude, latitude]
zoom: 5, // Niveau de zoom (1-20)

Notes importantes

  1. Token Mapbox : Vous devez obtenir un token Mapbox gratuit sur mapbox.com (opens in a new tab)
  2. Metaobjects : Assurez-vous que les metaobjects sont correctement configurés dans Shopify
  3. Format des coordonnées : Les coordonnées doivent être au format [longitude, latitude]
  4. Performance : Les lieux ne s'affichent que si le zoom est supérieur à 12 pour optimiser les performances