Skip to content

Commit

Permalink
[Map] Inject provider's SDK (L for Leaflet, google for Google) in…
Browse files Browse the repository at this point in the history
… dispatched event

Also added an example to custimize the marker icon.
  • Loading branch information
Kocal committed Aug 9, 2024
1 parent 3651143 commit df1748b
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
protected map: Map;
protected markers: Array<Marker>;
protected infoWindows: Array<InfoWindow>;
initialize(): void;
connect(): void;
protected abstract doCreateMap({ center, zoom, options, }: {
center: Point;
Expand All @@ -53,5 +52,6 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
marker: Marker;
}): InfoWindow;
protected abstract doFitBoundsToMarkers(): void;
protected abstract augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown>;
private dispatchEvent;
}
6 changes: 4 additions & 2 deletions src/Map/assets/dist/abstract_map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class default_1 extends Controller {
this.markers = [];
this.infoWindows = [];
}
initialize() { }
connect() {
const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue;
this.dispatchEvent('pre-connect', { options });
Expand Down Expand Up @@ -36,7 +35,10 @@ class default_1 extends Controller {
return infoWindow;
}
dispatchEvent(name, payload = {}) {
this.dispatch(name, { prefix: 'ux:map', detail: payload });
this.dispatch(name, {
prefix: 'ux:map',
detail: this.augmentEventPayload(payload),
});
}
}
default_1.values = {
Expand Down
9 changes: 6 additions & 3 deletions src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ export default abstract class<
protected markers: Array<Marker> = [];
protected infoWindows: Array<InfoWindow> = [];

initialize() {}

connect() {
const { center, zoom, options, markers, fitBoundsToMarkers } = this.viewValue;

Expand Down Expand Up @@ -136,7 +134,12 @@ export default abstract class<

protected abstract doFitBoundsToMarkers(): void;

protected abstract augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown>;

private dispatchEvent(name: string, payload: Record<string, unknown> = {}): void {
this.dispatch(name, { prefix: 'ux:map', detail: payload });
this.dispatch(name, {
prefix: 'ux:map',
detail: this.augmentEventPayload(payload),
});
}
}
1 change: 1 addition & 0 deletions src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export default class extends AbstractMapController<MapOptions, google.maps.Map,
private createTextOrElement;
private closeInfoWindowsExcept;
protected doFitBoundsToMarkers(): void;
protected augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown>;
}
export {};
4 changes: 4 additions & 0 deletions src/Map/src/Bridge/Google/assets/dist/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class default_1 extends AbstractMapController {
});
this.map.fitBounds(bounds);
}
augmentEventPayload(payload) {
payload.google = _google;
return payload;
}
}
default_1.values = {
providerOptions: Object,
Expand Down
6 changes: 6 additions & 0 deletions src/Map/src/Bridge/Google/assets/src/map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,10 @@ export default class extends AbstractMapController<

this.map.fitBounds(bounds);
}

protected augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown> {
payload.google = _google;

return payload;
}
}
56 changes: 56 additions & 0 deletions src/Map/src/Bridge/Leaflet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,62 @@ $leafletOptions = (new LeafletOptions())
$map->options($leafletOptions);
```

## Use cases

Below are some common or advanced use cases when using a map.

> **Note**:
> As the UX Map component is new, there are not too many examples for the moment.
### Customize the marker

A common use case is to customize the marker. You can listen to the `ux:map:marker:before-create` event to customize the marker before it is created.

Assuming you have a map with a custom controller:
```twig
{{ render_map(map, {'data-controller': 'my-map' }) }}
```

You can create a controller to customize the marker before it is created:
```js
// assets/controllers/my_map_controller.js
import {Controller} from "@hotwired/stimulus";

export default class extends Controller
{
connect() {
this.element.addEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
}

disconnect() {
// Always remove listeners when the controller is disconnected
this.element.removeEventListener('ux:map:marker:before-create', this._onMarkerBeforeCreate);
}

_onMarkerBeforeCreate(event) {
// You can access the marker definition and the Leaflet object
// Note: `definition.rawOptions` is the raw options object that will be passed to the `L.marker` constructor.
const { definition, L } = event.detail;

// Use a custom icon for the marker
const redIcon = L.icon({
// Note: instead of using an hardcoded URL, you can use the `extra` parameter from `new Marker()` (PHP) and access it here with `definition.extra`.
iconUrl: 'https://leafletjs.com/examples/custom-icons/leaf-red.png',
shadowUrl: 'https://leafletjs.com/examples/custom-icons/leaf-shadow.png',
iconSize: [38, 95], // size of the icon
shadowSize: [50, 64], // size of the shadow
iconAnchor: [22, 94], // point of the icon which will correspond to marker's location
shadowAnchor: [4, 62], // the same for the shadow
popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor
})

definition.rawOptions = {
icon: redIcon,
}
}
}
```

## Resources

- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
Expand Down
13 changes: 7 additions & 6 deletions src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
import 'leaflet/dist/leaflet.min.css';
import { type Map as LeafletMap, Marker, type Popup } from 'leaflet';
import * as L from 'leaflet';
import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet';
type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
tileLayer: {
Expand All @@ -10,18 +10,19 @@ type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
options: Record<string, unknown>;
};
};
export default class extends AbstractMapController<MapOptions, typeof LeafletMap, MarkerOptions, Marker, Popup, PopupOptions> {
export default class extends AbstractMapController<MapOptions, typeof L.Map, MarkerOptions, typeof L.Marker, typeof L.Popup, PopupOptions> {
connect(): void;
protected doCreateMap({ center, zoom, options }: {
center: Point;
zoom: number;
options: MapOptions;
}): LeafletMap;
protected doCreateMarker(definition: MarkerDefinition): Marker;
}): L.Map;
protected doCreateMarker(definition: MarkerDefinition): L.Marker;
protected doCreateInfoWindow({ definition, marker, }: {
definition: MarkerDefinition['infoWindow'];
marker: Marker;
}): Popup;
marker: L.Marker;
}): L.Popup;
protected doFitBoundsToMarkers(): void;
protected augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown>;
}
export {};
22 changes: 13 additions & 9 deletions src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import 'leaflet/dist/leaflet.min.css';
import { Marker, divIcon, map, tileLayer, marker } from 'leaflet';
import * as L from 'leaflet';

class map_controller extends AbstractMapController {
connect() {
Marker.prototype.options.icon = divIcon({
L.Marker.prototype.options.icon = L.divIcon({
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="a" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#a)" stroke="url(#b)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
iconSize: [25, 41],
iconAnchor: [12.5, 41],
Expand All @@ -14,24 +14,24 @@ class map_controller extends AbstractMapController {
super.connect();
}
doCreateMap({ center, zoom, options }) {
const map$1 = map(this.element, {
const map = L.map(this.element, {
...options,
center,
zoom,
});
tileLayer(options.tileLayer.url, {
L.tileLayer(options.tileLayer.url, {
attribution: options.tileLayer.attribution,
...options.tileLayer.options,
}).addTo(map$1);
return map$1;
}).addTo(map);
return map;
}
doCreateMarker(definition) {
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;
const marker$1 = marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
if (infoWindow) {
this.createInfoWindow({ definition: infoWindow, marker: marker$1 });
this.createInfoWindow({ definition: infoWindow, marker });
}
return marker$1;
return marker;
}
doCreateInfoWindow({ definition, marker, }) {
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
Expand All @@ -54,6 +54,10 @@ class map_controller extends AbstractMapController {
return [position.lat, position.lng];
}));
}
augmentEventPayload(payload) {
payload.L = L;
return payload;
}
}

export { map_controller as default };
40 changes: 19 additions & 21 deletions src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
import 'leaflet/dist/leaflet.min.css';
import {
map as createMap,
tileLayer as createTileLayer,
marker as createMarker,
divIcon,
type Map as LeafletMap,
Marker,
type Popup,
} from 'leaflet';
import * as L from 'leaflet';
import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions } from 'leaflet';

type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
Expand All @@ -18,14 +10,14 @@ type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {

export default class extends AbstractMapController<
MapOptions,
typeof LeafletMap,
typeof L.Map,
MarkerOptions,
Marker,
Popup,
typeof L.Marker,
typeof L.Popup,
PopupOptions
> {
connect(): void {
Marker.prototype.options.icon = divIcon({
L.Marker.prototype.options.icon = L.divIcon({
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="a" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#a)" stroke="url(#b)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
iconSize: [25, 41],
iconAnchor: [12.5, 41],
Expand All @@ -35,25 +27,25 @@ export default class extends AbstractMapController<
super.connect();
}

protected doCreateMap({ center, zoom, options }: { center: Point; zoom: number; options: MapOptions }): LeafletMap {
const map = createMap(this.element, {
protected doCreateMap({ center, zoom, options }: { center: Point; zoom: number; options: MapOptions }): L.Map {
const map = L.map(this.element, {
...options,
center,
zoom,
});

createTileLayer(options.tileLayer.url, {
L.tileLayer(options.tileLayer.url, {
attribution: options.tileLayer.attribution,
...options.tileLayer.options,
}).addTo(map);

return map;
}

protected doCreateMarker(definition: MarkerDefinition): Marker {
protected doCreateMarker(definition: MarkerDefinition): L.Marker {
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;

const marker = createMarker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
const marker = L.marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);

if (infoWindow) {
this.createInfoWindow({ definition: infoWindow, marker });
Expand All @@ -67,8 +59,8 @@ export default class extends AbstractMapController<
marker,
}: {
definition: MarkerDefinition['infoWindow'];
marker: Marker;
}): Popup {
marker: L.Marker;
}): L.Popup {
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;

marker.bindPopup([headerContent, content].filter((x) => x).join('<br>'), { ...otherOptions, ...rawOptions });
Expand All @@ -89,11 +81,17 @@ export default class extends AbstractMapController<
}

this.map.fitBounds(
this.markers.map((marker: Marker) => {
this.markers.map((marker: L.Marker) => {
const position = marker.getLatLng();

return [position.lat, position.lng];
})
);
}

protected augmentEventPayload(payload: Record<string, unknown>): Record<string, unknown> {
payload.L = L;

return payload;
}
}

0 comments on commit df1748b

Please sign in to comment.