Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
Height="600px">
</MapLibre>

<button @onclick="AddMarkerAsync" style="margin-top: 10px;">Add a random marker</button>
<div style="padding: 10px; gap: 10px;">
<div style="display: flex; flex-direction: row; align-items: center; gap: 15px;">
<button @onclick="AddMarkerAsync">Add a random marker</button>
<p style="margin: 0;">Try drag a marker!</p>
</div>
<p>@DragInfo</p>
</div>

@code {
private MapLibre _mapListener { get; set; } = new MapLibre();

public string DragInfo { get; set; } = "";
private readonly MapOptions _mapOptions = new()
{
Style = "https://demotiles.maplibre.org/style.json"
Expand All @@ -24,6 +30,8 @@
{
var options = new MarkerOptions
{
Draggable = true,
OnDragEndAsync = OnMarkerDragEnd,
Extensions = new MarkerOptionsExtensions
{
HtmlContent = "<div><img src='https://i.pravatar.cc/300' width='50' height='50' class='border border-white border-3 rounded-circle shadow-lg'/></div>",
Expand All @@ -33,5 +41,14 @@

await _mapListener.AddMarker(options, new LngLat(Random.Shared.NextDouble() * 180 - 90, Random.Shared.Next(-90, 90)));
}

private Task OnMarkerDragEnd(MarkerEvent arg)
{
DragInfo = $"Lat: {arg.LngLat.Latitude}, Lng: {arg.LngLat.Longitude}";
Console.WriteLine($"Marker dragged to: Lat={arg.LngLat.Latitude}, Lng={arg.LngLat.Longitude}");
StateHasChanged();

return Task.CompletedTask;
}

}
83 changes: 77 additions & 6 deletions src/Community.Blazor.MapLibre/MapLibre.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ await JsRuntime.InvokeAsync<IJSObjectReference>("import",
Options.Container = MapId;

// Initialize the MapLibre map
_mapObject = await _jsModule.InvokeAsync<IJSObjectReference>("initializeMap", Options, _dotNetObjectReference);
_mapObject =
await _jsModule.InvokeAsync<IJSObjectReference>("initializeMap", Options, _dotNetObjectReference);

// Load the plugins after the map has been initialized
foreach (var plugin in _plugins)
Expand Down Expand Up @@ -239,7 +240,7 @@ public Task<Listener> OnClick(string? layerId, Action<MapMouseEvent> handler) =>
/// <param name="handler">The asynchronous callback.</param>
/// <returns>A task of type <see cref="Listener"/>.</returns>
public Task<Listener> OnClick(string? layerId, Func<MapMouseEvent, Task> handler) =>
AddAsyncListener("click", handler, layerId);
AddAsyncListener("click", handler, layerId);

#endregion

Expand Down Expand Up @@ -276,6 +277,7 @@ public async ValueTask AddImage(string id, string url, StyleImageMetadata? optio
_bulkTransaction.Add("addImage", id, url, options);
return;
}

await _jsModule.InvokeVoidAsync("addImage", MapId, id, url, options);
}

Expand All @@ -292,6 +294,7 @@ public async ValueTask AddLayer(Layer layer, string? beforeId = null)
_bulkTransaction.Add("addLayer", layer, beforeId);
return;
}

await _jsModule.InvokeVoidAsync("addLayer", MapId, layer, beforeId);
}

Expand All @@ -308,6 +311,7 @@ public async ValueTask AddSource(string id, ISource source)
_bulkTransaction.Add("addSource", id, source);
return;
}

await _jsModule.InvokeVoidAsync("addSource", MapId, id, source);
}

Expand All @@ -324,6 +328,7 @@ public async ValueTask SetSourceData(string id, GeoJsonSource source)
_bulkTransaction.Add("setSourceData", id, dataNode);
return;
}

await _jsModule.InvokeVoidAsync("setSourceData", MapId, id, dataNode);
}

Expand All @@ -341,6 +346,7 @@ public async ValueTask AddSprite(string id, string url, StyleSetterOptions? opti
_bulkTransaction.Add("addSprite", id, url, options);
return;
}

await _jsModule.InvokeVoidAsync("addSprite", MapId, id, url, options);
}

Expand Down Expand Up @@ -390,7 +396,8 @@ await _jsModule.InvokeAsync<CameraOptions>("calculateCameraOptionsFromTo", MapId
/// <param name="bounds">The geographical bounding box to be fitted.</param>
/// <param name="options">Optional parameters to customize the calculation.</param>
/// <returns>A task that represents the asynchronous operation, containing the resulting center, zoom, and bearing.</returns>
public async ValueTask<CenterZoomBearing> CameraForBounds(LngLatBounds bounds, CameraForBoundsOptions? options = null) =>
public async ValueTask<CenterZoomBearing> CameraForBounds(LngLatBounds bounds,
CameraForBoundsOptions? options = null) =>
await _jsModule.InvokeAsync<CenterZoomBearing>("cameraForBounds", MapId, bounds, options);

/// <summary>
Expand Down Expand Up @@ -939,7 +946,8 @@ public async ValueTask<object[]> QueryRenderedFeatures(object query, object? opt
/// });
/// </code>
/// </example>
public async ValueTask<SimpleFeature[]> QuerySourceFeatures(string sourceId, QuerySourceFeatureOptions parameters) =>
public async ValueTask<SimpleFeature[]>
QuerySourceFeatures(string sourceId, QuerySourceFeatureOptions parameters) =>
await _jsModule.InvokeAsync<SimpleFeature[]>("querySourceFeatures", MapId, sourceId, parameters);

/// <summary>
Expand Down Expand Up @@ -983,6 +991,7 @@ public async ValueTask RemoveControl(object control)
_bulkTransaction.Add("removeControl", control);
return;
}

await _jsModule.InvokeVoidAsync("removeControl", MapId, control);
}

Expand Down Expand Up @@ -1036,6 +1045,7 @@ public async ValueTask RemoveFeatureState(FeatureIdentifier target, string? key
_bulkTransaction.Add("removeFeatureState", target, key);
return;
}

await _jsModule.InvokeVoidAsync("removeFeatureState", MapId, target, key);
}

Expand All @@ -1050,6 +1060,7 @@ public async ValueTask RemoveImage(string id)
_bulkTransaction.Add("removeImage", id);
return;
}

await _jsModule.InvokeVoidAsync("removeImage", MapId, id);
}

Expand All @@ -1064,6 +1075,7 @@ public async ValueTask RemoveLayer(string id)
_bulkTransaction.Add("removeLayer", id);
return;
}

await _jsModule.InvokeVoidAsync("removeLayer", MapId, id);
}

Expand All @@ -1078,6 +1090,7 @@ public async ValueTask RemoveSource(string id)
_bulkTransaction.Add("removeSource", id);
return;
}

await _jsModule.InvokeVoidAsync("removeSource", MapId, id);
}

Expand All @@ -1092,6 +1105,7 @@ public async ValueTask RemoveSprite(string id)
_bulkTransaction.Add("removeSprite", id);
return;
}

await _jsModule.InvokeVoidAsync("removeSprite", MapId, id);
}

Expand Down Expand Up @@ -1316,6 +1330,31 @@ public async Task<Guid> AddMarker(MarkerOptions options, LngLat position, Guid?
{
var id = markerId ?? Guid.NewGuid();
await _jsModule.InvokeVoidAsync("createMarker", MapId, id, options, position);
// Register event handlers if provided
if (options.OnClick != null)
await AddMarkerListener(id, "click", options.OnClick);

if (options.OnClickAsync != null)
await AddAsyncMarkerListener(id, "click", options.OnClickAsync);

if (options.OnDragStart != null)
await AddMarkerListener(id, "dragstart", options.OnDragStart);

if (options.OnDragStartAsync != null)
await AddAsyncMarkerListener(id, "dragstart", options.OnDragStartAsync);

if (options.OnDrag != null)
await AddMarkerListener(id, "drag", options.OnDrag);

if (options.OnDragAsync != null)
await AddAsyncMarkerListener(id, "drag", options.OnDragAsync);

if (options.OnDragEnd != null)
await AddMarkerListener(id, "dragend", options.OnDragEnd);

if (options.OnDragEndAsync != null)
await AddAsyncMarkerListener(id, "dragend", options.OnDragEndAsync);

return id;
}

Expand All @@ -1335,6 +1374,39 @@ public async Task RemoveMarker(Guid markerId)
public async Task MoveMarker(Guid markerId, LngLat position)
=> await _jsModule.InvokeVoidAsync("moveMarker", markerId, position);

/// <summary>
/// Registers a synchronous event listener for a marker.
/// </summary>
/// <typeparam name="T">The type of the event payload (typically <see cref="MarkerEvent"/>).</typeparam>
/// <param name="markerId">The id of the marker.</param>
/// <param name="eventName">The name of the event to listen for (e.g., "click", "dragstart", "drag", "dragend").</param>
/// <param name="handler">The synchronous callback action to execute when the event occurs.</param>
/// <returns>A <see cref="Listener"/> instance that allows removal of the registered listener.</returns>
public Task<Listener> AddMarkerListener<T>(Guid markerId, string eventName, Action<T> handler) =>
AddMarkerListenerInternal<T>(markerId, eventName, handler);

/// <summary>
/// Registers an asynchronous event listener for a marker.
/// </summary>
/// <typeparam name="T">The type of the event payload (typically <see cref="MarkerEvent"/>).</typeparam>
/// <param name="markerId">The id of the marker.</param>
/// <param name="eventName">The name of the event to listen for (e.g., "click", "dragstart", "drag", "dragend").</param>
/// <param name="handler">The asynchronous callback action to execute when the event occurs.</param>
/// <returns>A <see cref="Listener"/> instance that allows removal of the registered listener.</returns>
public Task<Listener> AddAsyncMarkerListener<T>(Guid markerId, string eventName, Func<T, Task> handler) =>
AddMarkerListenerInternal<T>(markerId, eventName, handler);

private async Task<Listener> AddMarkerListenerInternal<T>(Guid markerId, string eventName, Delegate handler)
{
var callback = new CallbackHandler(_jsModule, eventName, handler, typeof(T));
var reference = DotNetObjectReference.Create(callback);
_references.TryAdd(Guid.NewGuid(), reference);

await _jsModule.InvokeVoidAsync("onMarker", markerId, eventName, reference);

return new Listener(callback);
}

#endregion

#region Bulk Transaction
Expand Down Expand Up @@ -1370,5 +1442,4 @@ public async ValueTask Commit()
}

#endregion

}
}
58 changes: 57 additions & 1 deletion src/Community.Blazor.MapLibre/MapLibre.razor.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function initializeMap(options, dotnetReference) {
map.on('load', function () {
dotnetReference.invokeMethodAsync("OnLoadCallback")
});

return map;
}

Expand All @@ -65,6 +65,7 @@ export function on(container, eventType, dotnetReference, layerIds) {
})
}
}

/**
* Adds a specified control to the given map container.
*
Expand Down Expand Up @@ -1231,6 +1232,10 @@ export function createMarker(container, markerId, options, position) {
markerInstances[markerId] = new maplibregl.Marker(options)
.setLngLat([position.lng, position.lat])
.addTo(mapInstances[container]);

if (!markerInstances[markerId]._clickListeners) {
markerInstances[markerId]._clickListeners = [];
}

if (options.extensions) {
const extensions = options.extensions;
Expand Down Expand Up @@ -1269,6 +1274,57 @@ export function moveMarker(markerId, position) {
marker.setLngLat([position.lng, position.lat]);
}

/**
* Attaches an event listener to a specified marker instance.
*
* @param {string} markerId - The marker ID.
* @param {string} eventType - The type of event to listen for (e.g., "click", "dragstart", "drag", "dragend").
* @param {object} dotnetReference - A reference to a .NET object used for invoking asynchronous methods.
*/
export function onMarker(markerId, eventType, dotnetReference) {
const marker = markerInstances[markerId];

if (!marker) {
console.warn(`Marker with id ${markerId} not found`);
return;
}
const createEventData = () => {
const lngLat = marker.getLngLat();
return {
type: eventType,
lngLat: {
lng: lngLat.lng,
lat: lngLat.lat
}
};
};

if (eventType === 'click') {
const clickHandler = (e) => {
e.stopPropagation(); // Prevent event from bubbling to map
const eventData = createEventData();
const result = JSON.stringify(eventData);
dotnetReference.invokeMethodAsync('Invoke', result);
};

marker.getElement().addEventListener('click', clickHandler);

// Store listener for cleanup
if (!marker._clickListeners) {
marker._clickListeners = [];
}
marker._clickListeners.push({handler: clickHandler, dotnetRef: dotnetReference});
} else if (['dragstart', 'drag', 'dragend'].includes(eventType)) {
marker.on(eventType, function (e) {
const eventData = createEventData();
const result = JSON.stringify(eventData);
dotnetReference.invokeMethodAsync('Invoke', result);
});
} else {
console.warn(`Event type '${eventType}' is not supported for markers`);
}
}

/**
* Perform all applied bulk transactions.
* The only purpose of bulk transaction send multiple transactions in one message, reducing the roundtrip time.
Expand Down
21 changes: 21 additions & 0 deletions src/Community.Blazor.MapLibre/Models/Marker/MarkerEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Community.Blazor.MapLibre.Models.Marker;

using System.Text.Json.Serialization;

/// <summary>
/// Represents an event that occurred on a marker.
/// </summary>
public class MarkerEvent
{
/// <summary>
/// The type of event (e.g., "click", "dragstart", "drag", "dragend").
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;

/// <summary>
/// The geographical position of the marker when the event occurred.
/// </summary>
[JsonPropertyName("lngLat")]
public LngLat LngLat { get; set; } = new();
}
Loading
Loading