New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customizing Icon image? #9
Comments
@webrgp Have you found a way to do it? I'm trying to figure out the same thing right now |
Yes @cedrvanh, but it took a lot of looking at both mapbox.js and dynamicmap.js. Here is the gist of it: In the template that will render the map, it's mostly how the docs describe. I've opted to feed the entries as an array so I could set the marker {% set locations = craft.entries({
section: 'mapLocations',
with: [
'locationCover',
'programCategory',
'programCategory.programIcon',
]
})
.collect()
.map( l => {
id: l.id,
title: l.title,
lat: l.address.lat,
lng: l.address.lng,
color: l.programCategory[0].programColor.color[0].color,
iconUrl: l.programCategory[0].programIcon[0].url|default(null),
})
.toArray()
%}
{% set map = mapbox.map(locations, {
id: 'impact-map',
width: '100%',
style: 'light-v11',
mapOptions: {
maxZoom: 11,
minZoom: 6,
},
popupTemplate: '_partials/_mapPopup',
popupOptions: {
focusAfterOpen: false,
closeButton: false,
anchor: 'bottom',
maxWidth: '320px',
offset: [0, -24]
}
}) %}
<div x-data="impactMap" class="flex flex-col gap-4">
<div class="max-w-screen-narrow mx-auto w-full aspect-[4/6] md:aspect-[8/5]">
{{ map.tag({ init: false }) }}
</div>
</div> I've implemented the javascript part using AlpineJS components: import { AlpineComponent } from 'alpinejs'
import {
FitBoundsOptions,
LngLatBounds,
LngLatLike,
Map as MBMap,
Marker,
MarkerOptions,
Popup
} from 'mapbox-gl'
// Shim the Mapbox Craft plugin JS Interfaces
interface IDADynamicMap {
div: null | HTMLElement
_map: MBMap
_locationBounds: (locations: LngLatLike[]) => LngLatBounds
_checkMapVisibility: () => void
getBounds: () => LngLatBounds
changeMarker: (markerId: string | number, options: MarkerOptions) => IDADynamicMap
panToMarker: (locationId: string | number) => IDADynamicMap
openPopup: (popupId: string | number) => IDADynamicMap
closePopup: (popupId: string | number) => IDADynamicMap
showMarker: (markerId: string | number) => IDADynamicMap
hideMarker: (markerId: string | number) => IDADynamicMap
getMarker: (markerId: string | number, assumeSuccess?: boolean) => Marker
getPopup: (popupId: string | number, assumeSuccess?: boolean) => Popup
fit: (options?: FitBoundsOptions, assumeSuccess?: boolean) => IDADynamicMap
center: (coord?: LngLatLike, assumeSuccess?: boolean) => IDADynamicMap
}
interface IDAMapbox {
init: (mapId: string, callback?: () => null) => void
getMap: (mapId: string, assumeSuccess?: boolean) => IDADynamicMap
}
// Map Locations from twig
interface LocationEntry {
id: number
title: string
lat: number
lng: number
color: string
iconUrl: string
}
const animationDuration = 1000
const fitDefaults = {
duration: animationDuration,
padding: {
top: 70,
right: 40,
bottom: 40,
left: 40
}
}
const impactMap = () =>
({
locations: LocationEntry[],
mapbox: window.mapbox as IDAMapbox,
map: {} as IDADynamicMap,
init() {
// Wait to make sure the Mapbox Craft Plugin is loaded in the page
if (window.mapbox === undefined) {
document.addEventListener('DOMContentLoaded', () => {
this.handleMapboxInit()
})
} else {
this.initMap()
}
},
handleMapboxInit() {
this.mapbox = window.mapbox as IDAMapbox
this.mapbox.init('impact-map')
this.initMap()
},
initMap() {
this.map = this.mapbox.getMap('impact-map')
this.loadLocations()
this.initMarkers()
},
loadLocations() {
// Here are grab the same data attribute the plugins uses
// to store the map configuration
const DNAString = this.map?.div?.dataset.dna
if (DNAString) {
const dna = JSON.parse(DNAString)
this.locations = dna
.filter((b: { type: string }) => b.type === 'markers')
.map((b: { locations: LocationEntry[] }) => b.locations[0])
}
},
initMarkers() {
for (const location of this.locations) {
// This is where the marker customization happens
const markerEl = this.createMarkerElement(location)
this.map?.changeMarker(location.id, {
element: markerEl
})
}
},
createMarkerElement(location: LocationEntry): HTMLElement {
const markerEl = document.createElement('div')
markerEl.classList.add('marker-el')
markerEl.style.setProperty(
'--marker-icon-url',
'url(' + location.iconUrl + ')'
)
if (location.isActive) {
markerEl.style.setProperty(
'--marker-bg-color',
location.color
)
}
const markerIcon = document.createElement('div')
markerIcon.classList.add('marker-icon')
markerEl.appendChild(markerIcon)
markerEl.addEventListener('click', (e: Event) => {
e.preventDefault()
e.stopPropagation()
this.map.closePopup('*')
this.handleClickForLocation(location.id)
})
return markerEl
},
handleClickForLocation(locationId: number) {
const _map = this.map._map
const marker = this.map?.getMarker(locationId, true)
if (_map && marker) {
const popup = marker.getPopup()
const isPopupOpen = popup.isOpen()
if (!isPopupOpen) {
marker.togglePopup()
// Once the popup is open, calculate it's height and
// adjust the mapbox's map padding so the popup is centered.
const { height } = popup
.getElement()
.getBoundingClientRect()
this.fitMap(
this.map._locationBounds([marker.getLngLat()]),
{
...fitDefaults,
padding: {
top: height,
bottom: 0,
left: 0,
right: 0
}
}
)
}
}
},
fitMap(bounds?: LngLatBounds, options?: FitBoundsOptions) {
const fitBounds = bounds || this.getMapBounds()
const fitOptions = options || {
...fitDefaults,
center: fitBounds.getCenter()
}
this.map._map.fitBounds(fitBounds, fitOptions)
},
getMapBounds() {
return this.map._locationBounds(this.locations)
}
} as ImpactMapAlpineComponent)
export default impactMap I've trimmed down the implementation here for clarity, but in the final implementation I've added related entries as categories for locations, and the color and icon is part of that category. I've added functionality to the Alipne component to show / hide locations based on selected category and adjust the map boundaries to the narrowed locations. Overall the plugin was worth it, but I can see it's limitations unless you know Mapbox pretty well. One thought that I had, while inspecting the dynamicmap.js file, is that since it's mostly a wrapper for That way we could return the options with the |
Great news, this is now entirely possible (and relatively easy) using the Thanks for your workaround @webrgp, thankfully it's no longer needed. 🙂 This new feature is still only on the dev branch... would either of you mind testing it out when you get a chance? "doublesecretagency/craft-mapbox": "dev-v1-dev" |
Great news, v1.1 has been officially released with these changes. 🎉 Hope that makes working with custom marker icons a bit easier! |
Can you provide an example of customizing an marker icon image using your plugin? Thanks!
The text was updated successfully, but these errors were encountered: