Skip to content

[Map] Add "extra" data for markers and infowindows #2040

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

Merged
merged 1 commit into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Map/assets/dist/abstract_map_controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
title: string | null;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
rawOptions?: MarkerOptions;
extra: Record<string, unknown>;
};
export type InfoWindowDefinition<InfoWindowOptions> = {
headerContent: string | null;
Expand All @@ -23,6 +24,7 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
opened: boolean;
autoClose: boolean;
rawOptions?: InfoWindowOptions;
extra: Record<string, unknown>;
};
export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow> extends Controller<HTMLElement> {
static values: {
Expand Down
21 changes: 21 additions & 0 deletions src/Map/assets/src/abstract_map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ export type MarkerDefinition<MarkerOptions, InfoWindowOptions> = {
position: Point;
title: string | null;
infoWindow?: Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
/**
* Raw options passed to the marker constructor, specific to the map provider (e.g.: `L.marker()` for Leaflet).
*/
rawOptions?: MarkerOptions;
/**
* Extra data defined by the developer.
* They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners:
* - `ux:map:marker:before-create`
* - `ux:map:marker:after-create`
*/
extra: Record<string, unknown>;
};

export type InfoWindowDefinition<InfoWindowOptions> = {
Expand All @@ -23,7 +33,18 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
position: Point;
opened: boolean;
autoClose: boolean;
/**
* Raw options passed to the info window constructor,
* specific to the map provider (e.g.: `google.maps.InfoWindow()` for Google Maps).
*/
rawOptions?: InfoWindowOptions;
/**
* Extra data defined by the developer.
* They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners:
* - `ux:map:info-window:before-create`
* - `ux:map:info-window:after-create`
*/
extra: Record<string, unknown>;
};

export default abstract class<
Expand Down
20 changes: 16 additions & 4 deletions src/Map/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Configuration is done in your ``config/packages/ux_map.yaml`` file:

# config/packages/ux_map.yaml
ux_map:
renderer: '%env(UX_MAP_DSN)%'
renderer: '%env(resolve:default::UX_MAP_DSN)%'

The ``UX_MAP_DSN`` environment variable configure which renderer to use.

Expand Down Expand Up @@ -82,21 +82,33 @@ A map is created by calling ``new Map()``. You can configure the center, zoom, a
->zoom(6)
;

// 2. You can add markers, with an optional info window
// 2. You can add markers
$myMap
->addMarker(new Marker(
position: new Point(48.8566, 2.3522),
title: 'Paris'
))

// With an info window associated to the marker:
->addMarker(new Marker(
position: new Point(45.7640, 4.8357),
title: 'Lyon',
// With an info window
infoWindow: new InfoWindow(
headerContent: '<b>Lyon</b>',
content: 'The French town in the historic Rhône-Alpes region, located at the junction of the Rhône and Saône rivers.'
)
));
))

// You can also pass extra data, that you can later use in your custom Stimulus controller
// when listening to "ux:map:marker:before-create" event:
->addMarker(new Marker(
position: new Point(46.5074666, 6.633729),
title: 'Olympic Parc',
extra: [
'icon_mask_url' => 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/tree_pinlet.svg',
]
)
;

// 3. And inject the map in your template to render it
return $this->render('contact/index.html.twig', [
Expand Down
4 changes: 2 additions & 2 deletions src/Map/src/Bridge/Google/assets/dist/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class default_1 extends AbstractMapController {
});
}
doCreateMarker(definition) {
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;
const marker = new library.AdvancedMarkerElement({
position,
title,
Expand All @@ -39,7 +39,7 @@ class default_1 extends AbstractMapController {
return marker;
}
doCreateInfoWindow({ definition, marker, }) {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
const infoWindow = new library.InfoWindow({
headerContent: this.createTextOrElement(headerContent),
content: this.createTextOrElement(content),
Expand Down
4 changes: 2 additions & 2 deletions src/Map/src/Bridge/Google/assets/src/map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default class extends AbstractMapController<
protected doCreateMarker(
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>
): google.maps.marker.AdvancedMarkerElement {
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;

const marker = new library.AdvancedMarkerElement({
position,
Expand All @@ -114,7 +114,7 @@ export default class extends AbstractMapController<
>['infoWindow'];
marker: google.maps.marker.AdvancedMarkerElement;
}): google.maps.InfoWindow {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;

const infoWindow = new library.InfoWindow({
headerContent: this.createTextOrElement(headerContent),
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function provideTestRenderMap(): iterable
];

yield 'with markers and infoWindows' => [
'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-google-map--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;null,&quot;content&quot;&#x3A;&quot;Lyon&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true&#x7D;&#x7D;&#x5D;&#x7D;"></div>',
'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-google-map--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;null,&quot;content&quot;&#x3A;&quot;Lyon&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
Expand Down
4 changes: 2 additions & 2 deletions src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ class map_controller extends AbstractMapController {
return map$1;
}
doCreateMarker(definition) {
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
const { position, title, infoWindow, extra, rawOptions = {}, ...otherOptions } = definition;
const marker$1 = marker(position, { title, ...otherOptions, ...rawOptions }).addTo(this.map);
if (infoWindow) {
this.createInfoWindow({ definition: infoWindow, marker: marker$1 });
}
return marker$1;
}
doCreateInfoWindow({ definition, marker, }) {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
marker.bindPopup([headerContent, content].filter((x) => x).join('<br>'), { ...otherOptions, ...rawOptions });
if (definition.opened) {
marker.openPopup();
Expand Down
4 changes: 2 additions & 2 deletions src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default class extends AbstractMapController<
}

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

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

Expand All @@ -69,7 +69,7 @@ export default class extends AbstractMapController<
definition: MarkerDefinition['infoWindow'];
marker: Marker;
}): Popup {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;

marker.bindPopup([headerContent, content].filter((x) => x).join('<br>'), { ...otherOptions, ...rawOptions });
if (definition.opened) {
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function provideTestRenderMap(): iterable
];

yield 'with markers and infoWindows' => [
'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="&#x7B;&#x7D;" data-symfony--ux-leaflet-map--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;tileLayer&quot;&#x3A;&#x7B;&quot;url&quot;&#x3A;&quot;https&#x3A;&#x5C;&#x2F;&#x5C;&#x2F;tile.openstreetmap.org&#x5C;&#x2F;&#x7B;z&#x7D;&#x5C;&#x2F;&#x7B;x&#x7D;&#x5C;&#x2F;&#x7B;y&#x7D;.png&quot;,&quot;attribution&quot;&#x3A;&quot;&#x5C;u00a9&#x20;&lt;a&#x20;href&#x3D;&#x5C;&quot;https&#x3A;&#x5C;&#x2F;&#x5C;&#x2F;www.openstreetmap.org&#x5C;&#x2F;copyright&#x5C;&quot;&gt;OpenStreetMap&lt;&#x5C;&#x2F;a&gt;&quot;,&quot;options&quot;&#x3A;&#x7B;&#x7D;&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;null,&quot;content&quot;&#x3A;&quot;Lyon&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true&#x7D;&#x7D;&#x5D;&#x7D;"></div>',
'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="&#x7B;&#x7D;" data-symfony--ux-leaflet-map--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;tileLayer&quot;&#x3A;&#x7B;&quot;url&quot;&#x3A;&quot;https&#x3A;&#x5C;&#x2F;&#x5C;&#x2F;tile.openstreetmap.org&#x5C;&#x2F;&#x7B;z&#x7D;&#x5C;&#x2F;&#x7B;x&#x7D;&#x5C;&#x2F;&#x7B;y&#x7D;.png&quot;,&quot;attribution&quot;&#x3A;&quot;&#x5C;u00a9&#x20;&lt;a&#x20;href&#x3D;&#x5C;&quot;https&#x3A;&#x5C;&#x2F;&#x5C;&#x2F;www.openstreetmap.org&#x5C;&#x2F;copyright&#x5C;&quot;&gt;OpenStreetMap&lt;&#x5C;&#x2F;a&gt;&quot;,&quot;options&quot;&#x3A;&#x7B;&#x7D;&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;null,&quot;content&quot;&#x3A;&quot;Lyon&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;,&quot;extra&quot;&#x3A;&#x7B;&#x7D;&#x7D;&#x5D;&#x7D;"></div>',
'renderer' => new LeafletRenderer(new StimulusHelper(null)),
'map' => (clone $map)
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
Expand Down
5 changes: 5 additions & 0 deletions src/Map/src/InfoWindow.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
*/
final readonly class InfoWindow
{
/**
* @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
*/
public function __construct(
private ?string $headerContent = null,
private ?string $content = null,
private ?Point $position = null,
private bool $opened = false,
private bool $autoClose = true,
private array $extra = [],
) {
}

Expand All @@ -35,6 +39,7 @@ public function toArray(): array
'position' => $this->position?->toArray(),
'opened' => $this->opened,
'autoClose' => $this->autoClose,
'extra' => (object) $this->extra,
];
}
}
5 changes: 5 additions & 0 deletions src/Map/src/Marker.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
*/
final readonly class Marker
{
/**
* @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
*/
public function __construct(
private Point $position,
private ?string $title = null,
private ?InfoWindow $infoWindow = null,
private array $extra = [],
) {
}

Expand All @@ -31,6 +35,7 @@ public function toArray(): array
'position' => $this->position->toArray(),
'title' => $this->title,
'infoWindow' => $this->infoWindow?->toArray(),
'extra' => (object) $this->extra,
];
}
}
5 changes: 4 additions & 1 deletion src/Map/tests/InfoWindowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public function testToArray(): void
autoClose: false,
);

$array = $infoWindow->toArray();

self::assertSame([
'headerContent' => 'Paris',
'content' => 'Capitale de la France, est une grande ville européenne et un centre mondial de l\'art, de la mode, de la gastronomie et de la culture.',
Expand All @@ -36,6 +38,7 @@ public function testToArray(): void
],
'opened' => true,
'autoClose' => false,
], $infoWindow->toArray());
'extra' => $array['extra'],
], $array);
}
}
30 changes: 27 additions & 3 deletions src/Map/tests/MapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,41 @@ public function toArray(): array
[
'position' => ['lat' => 48.8566, 'lng' => 2.3522],
'title' => 'Paris',
'infoWindow' => ['headerContent' => '<b>Paris</b>', 'content' => 'Paris', 'position' => ['lat' => 48.8566, 'lng' => 2.3522], 'opened' => false, 'autoClose' => true],
'infoWindow' => [
'headerContent' => '<b>Paris</b>',
'content' => 'Paris',
'position' => ['lat' => 48.8566, 'lng' => 2.3522],
'opened' => false,
'autoClose' => true,
'extra' => $array['markers'][0]['infoWindow']['extra'],
],
'extra' => $array['markers'][0]['extra'],
],
[
'position' => ['lat' => 45.764, 'lng' => 4.8357],
'title' => 'Lyon',
'infoWindow' => ['headerContent' => '<b>Lyon</b>', 'content' => 'Lyon', 'position' => ['lat' => 45.764, 'lng' => 4.8357], 'opened' => true, 'autoClose' => true],
'infoWindow' => [
'headerContent' => '<b>Lyon</b>',
'content' => 'Lyon',
'position' => ['lat' => 45.764, 'lng' => 4.8357],
'opened' => true,
'autoClose' => true,
'extra' => $array['markers'][1]['infoWindow']['extra'],
],
'extra' => $array['markers'][1]['extra'],
],
[
'position' => ['lat' => 43.2965, 'lng' => 5.3698],
'title' => 'Marseille',
'infoWindow' => ['headerContent' => '<b>Marseille</b>', 'content' => 'Marseille', 'position' => ['lat' => 43.2965, 'lng' => 5.3698], 'opened' => true, 'autoClose' => true],
'infoWindow' => [
'headerContent' => '<b>Marseille</b>',
'content' => 'Marseille',
'position' => ['lat' => 43.2965, 'lng' => 5.3698],
'opened' => true,
'autoClose' => true,
'extra' => $array['markers'][2]['infoWindow']['extra'],
],
'extra' => $array['markers'][2]['extra'],
],
],
], $array);
Expand Down
Loading