diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMapDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMapDocumentation.razor index ce7813628..c3ee16df5 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMapDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMapDocumentation.razor @@ -46,11 +46,19 @@
-
Set the PinElement.Glyph option to an empty string to hide a marker's glyph.
+
Set the PinElement.GlyphText option to an empty string to hide a marker's glyph.
+
+
Use the PinElement.GlyphText option to show text in a marker pin.
+ +
+
+
Use the PinElement.GlyphSrc option to show an image in a marker pin.
+ +
-
Use the PinElement.UseIconFonts and PinElement.Glyph options to use the icon fonts.
+
Existing PinElement.UseIconFonts and legacy PinElement.Glyph configurations remain supported while existing applications migrate.
diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_01_Examples.razor index 16b50dc0d..dc41f940c 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_01_Examples.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_01_Examples.razor @@ -1,7 +1,18 @@ @inherits GoogleMapDemoComponentBase - \ No newline at end of file +
+
+ +
+
+ +
+
diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_E_Hide_the_glyph.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_E_Hide_the_glyph.razor index a80d26525..0735adb59 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_E_Hide_the_glyph.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_E_Hide_the_glyph.razor @@ -12,15 +12,15 @@ { new GoogleMapMarker() { - PinElement = new PinElement{ Glyph = "", }, + PinElement = new PinElement{ GlyphText = "", }, Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352) , Title = "Single family house with modern design", }, new GoogleMapMarker() { - PinElement = new PinElement{ Glyph = "", }, + PinElement = new PinElement{ GlyphText = "", }, Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727) , Title = "Townhouse with friendly neighbors", } }; -} \ No newline at end of file +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_F_Use_icon_fonts.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_F_Use_icon_fonts.razor index 8402fd1c3..6eb9f169f 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_F_Use_icon_fonts.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_F_Use_icon_fonts.razor @@ -8,6 +8,7 @@ Markers="markers" /> @code { +#pragma warning disable CS0618 // This demo intentionally exercises the legacy icon-font compatibility path. List markers = new() { new GoogleMapMarker() @@ -107,4 +108,5 @@ Title = "Heavy rain", } }; -} \ No newline at end of file +#pragma warning restore CS0618 +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_G_Use_text_glyphs.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_G_Use_text_glyphs.razor new file mode 100644 index 000000000..e214f8292 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_G_Use_text_glyphs.razor @@ -0,0 +1,26 @@ +@inherits GoogleMapDemoComponentBase + + + +@code { + List markers = new() + { + new GoogleMapMarker() + { + PinElement = new PinElement { Background = "#137333", GlyphColor = "white", GlyphText = "A" }, + Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352), + Title = "Marker A", + }, + new GoogleMapMarker() + { + PinElement = new PinElement { Background = "#1A73E8", GlyphColor = "white", GlyphText = "B" }, + Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727), + Title = "Marker B", + } + }; +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_H_Use_image_glyphs.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_H_Use_image_glyphs.razor new file mode 100644 index 000000000..f659d820d --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_03_Marker_Customization_H_Use_image_glyphs.razor @@ -0,0 +1,28 @@ +@inherits GoogleMapDemoComponentBase + + + +@code { + private const string MarkerImageUrl = "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png"; + + List markers = new() + { + new GoogleMapMarker() + { + PinElement = new PinElement { GlyphSrc = MarkerImageUrl }, + Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352), + Title = "Image marker one", + }, + new GoogleMapMarker() + { + PinElement = new PinElement { GlyphSrc = MarkerImageUrl }, + Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727), + Title = "Image marker two", + } + }; +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_05_Make_a_marker_clickable.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_05_Make_a_marker_clickable.razor index b6e5e6476..6f8ea8cfb 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_05_Make_a_marker_clickable.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_05_Make_a_marker_clickable.razor @@ -10,6 +10,7 @@ OnMarkerClick="OnGoogleMapMarkerClick" /> @code { +#pragma warning disable CS0618 // This demo intentionally exercises the legacy icon-font compatibility path. [Inject] public ToastService ToastService { get; set; } = default!; private void OnGoogleMapMarkerClick(GoogleMapMarker marker) @@ -116,4 +117,5 @@ Title = "Heavy rain", } }; -} \ No newline at end of file +#pragma warning restore CS0618 +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_06_Dynamic_markers.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_06_Dynamic_markers.razor index 447efeeec..77b7bc072 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_06_Dynamic_markers.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Demos/Maps/GoogleMap_Demo_06_Dynamic_markers.razor @@ -22,6 +22,7 @@ OnMarkerClick="OnGoogleMapMarkerClick" /> @code { +#pragma warning disable CS0618 // This demo intentionally exercises the legacy icon-font compatibility path. Random random = new Random(2000000000); GoogleMap googleMapRef = default!; @@ -161,4 +162,5 @@ Title = "Heavy rain", }; } -} \ No newline at end of file +#pragma warning restore CS0618 +} diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor b/blazorbootstrap/Components/Maps/GoogleMap.razor index f61c6d7e3..cc8b42c9b 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor @@ -4,9 +4,8 @@ + ScriptId="blazor-bootstrap-google-maps-script" + Source="@GoogleMapsJsFileUrl" />
diff --git a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs index 0eba255af..91382bf3e 100644 --- a/blazorbootstrap/Components/Maps/GoogleMap.razor.cs +++ b/blazorbootstrap/Components/Maps/GoogleMap.razor.cs @@ -10,6 +10,14 @@ public partial class GoogleMap : BlazorBootstrapComponentBase #region Methods + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + await SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef); + + await base.OnAfterRenderAsync(firstRender); + } + protected override async Task OnInitializedAsync() { objRef ??= DotNetObjectReference.Create(this); @@ -64,11 +72,6 @@ public ValueTask UpdateMarkersAsync(IEnumerable markers) return ValueTask.CompletedTask; } - private void OnScriptLoad() - { - Task.Run(() => SafeInvokeVoidAsync("window.blazorBootstrap.googlemaps.initialize", Id, Zoom, Center, Markers, Clickable, objRef)); - } - #endregion #region Properties, Indexers @@ -116,7 +119,7 @@ private void OnScriptLoad() [Parameter] public bool Clickable { get; set; } - private string? GoogleMapsJsFileUrl => $"https://maps.googleapis.com/maps/api/js?key={ApiKey}&libraries=maps,marker"; + private string? GoogleMapsJsFileUrl => $"https://maps.googleapis.com/maps/api/js?key={ApiKey}&libraries=maps,marker&loading=async&callback=blazorBootstrap.googlemaps.onApiLoaded"; /// /// Gets or sets the height of the . diff --git a/blazorbootstrap/Models/Maps/PinElement.cs b/blazorbootstrap/Models/Maps/PinElement.cs index 8152835e3..fce8740be 100644 --- a/blazorbootstrap/Models/Maps/PinElement.cs +++ b/blazorbootstrap/Models/Maps/PinElement.cs @@ -4,20 +4,99 @@ public class PinElement { #region Properties, Indexers - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// + /// Gets or sets the background color of the pin. + /// + /// Default value is . + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the background color of the pin.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Background { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// + /// Gets or sets the border color of the pin. + /// + /// Default value is . + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the border color of the pin.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? BorderColor { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// + /// Gets or sets the legacy glyph content for the pin. + /// + /// Default value is . + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the legacy glyph content for the pin.")] + [Obsolete("Use GlyphText or GlyphSrc instead.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object? Glyph { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// + /// Gets or sets the color of the pin glyph. + /// + /// Default value is . + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the color of the pin glyph.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? GlyphColor { get; set; } + /// + /// Gets or sets the URL of the image displayed in the pin glyph. + /// + /// Default value is . + /// + /// + [AddedVersion("4.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the URL of the image displayed in the pin glyph.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? GlyphSrc { get; set; } + + /// + /// Gets or sets the text displayed in the pin glyph. + /// + /// Default value is . + /// + /// + [AddedVersion("4.0.0")] + [DefaultValue(null)] + [Description("Gets or sets the text displayed in the pin glyph.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? GlyphText { get; set; } + + /// + /// Gets or sets the scale of the pin. + /// + /// Default value is 1.0. + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(1.0)] + [Description("Gets or sets the scale of the pin.")] public double Scale { get; set; } = 1.0; + /// + /// Gets or sets a value indicating whether the legacy glyph is an icon-font class. + /// + /// Default value is . + /// + /// + [AddedVersion("3.0.0")] + [DefaultValue(false)] + [Description("Gets or sets a value indicating whether the legacy glyph is an icon-font class.")] public bool UseIconFonts { get; set; } #endregion diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 31401f0fe..f2c3d6714 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -453,6 +453,7 @@ window.blazorBootstrap = { } }, googlemaps: { + apiLoaded: false, addMarker: (elementId, marker, dotNetHelper) => { let mapInstance = window.blazorBootstrap.googlemaps.get(elementId); if (mapInstance) { @@ -461,24 +462,42 @@ window.blazorBootstrap = { let _content; if (marker.pinElement) { - let _glyph; - if (marker.pinElement.useIconFonts) { - const icon = document.createElement("div"); - icon.innerHTML = ``; - _glyph = icon; + const content = document.createElement("div"); + const icon = document.createElement("i"); + const scale = marker.pinElement.scale ?? 1; + + content.classList.add("bb-google-marker-content"); + content.style.alignItems = "center"; + content.style.backgroundColor = marker.pinElement.background; + content.style.border = `2px solid ${marker.pinElement.borderColor ?? "transparent"}`; + content.style.borderRadius = "50%"; + content.style.boxSizing = "border-box"; + content.style.color = marker.pinElement.glyphColor; + content.style.display = "flex"; + content.style.height = `${48 * scale}px`; + content.style.justifyContent = "center"; + content.style.width = `${48 * scale}px`; + + icon.className = marker.pinElement.glyph ?? ""; + content.append(icon); + _content = content; } else { - _glyph = marker.pinElement.glyph; - } + const pinOptions = { + background: marker.pinElement.background, + borderColor: marker.pinElement.borderColor, + glyphColor: marker.pinElement.glyphColor, + scale: marker.pinElement.scale, + }; + + if (marker.pinElement.glyphSrc) { + pinOptions.glyphSrc = marker.pinElement.glyphSrc; + } else { + pinOptions.glyphText = marker.pinElement.glyphText ?? (typeof marker.pinElement.glyph === "string" ? marker.pinElement.glyph : undefined); + } - const pin = new google.maps.marker.PinElement({ - background: marker.pinElement.background, - borderColor: marker.pinElement.borderColor, - glyph: _glyph, - glyphColor: marker.pinElement.glyphColor, - scale: marker.pinElement.scale, - }); - _content = pin.element; + _content = new google.maps.marker.PinElement(pinOptions); + } } else if (marker.content) { _content = document.createElement("div"); @@ -488,18 +507,19 @@ window.blazorBootstrap = { const markerEl = new google.maps.marker.AdvancedMarkerElement({ map, - content: _content, position: marker.position, title: marker.title, gmpClickable: clickable }); + if (_content) + markerEl.append(_content); + window.blazorBootstrap.googlemaps.markerEls[elementId].push(markerEl); // add a click listener for each marker, and set up the info window. if (clickable) { - markerEl.addListener("click", ({ domEvent, latLng }) => { - const { target } = domEvent; + markerEl.addEventListener("gmp-click", () => { const infoWindow = new google.maps.InfoWindow(); infoWindow.close(); infoWindow.setContent(markerEl.title); @@ -522,10 +542,19 @@ window.blazorBootstrap = { return window.blazorBootstrap.googlemaps.instances[elementId]; }, initialize: (elementId, zoom, center, markers, clickable, dotNetHelper) => { + if (!window.blazorBootstrap.googlemaps.apiLoaded) { + window.blazorBootstrap.googlemaps.pendingInitializations[elementId] = { center, clickable, dotNetHelper, markers, zoom }; + return; + } + + const element = document.getElementById(elementId); + if (!element) + return; + window.blazorBootstrap.googlemaps.markerEls[elementId] = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; let mapOptions = { center: center, zoom: zoom, mapId: elementId }; - let map = new google.maps.Map(document.getElementById(elementId), mapOptions); + let map = new google.maps.Map(element, mapOptions); window.blazorBootstrap.googlemaps.create(elementId, map, zoom, center, markers, clickable); @@ -537,6 +566,17 @@ window.blazorBootstrap = { }, instances: {}, markerEls: {}, + onApiLoaded: () => { + window.blazorBootstrap.googlemaps.apiLoaded = true; + + const pendingInitializations = Object.entries(window.blazorBootstrap.googlemaps.pendingInitializations); + window.blazorBootstrap.googlemaps.pendingInitializations = {}; + + for (const [elementId, { center, clickable, dotNetHelper, markers, zoom }] of pendingInitializations) { + window.blazorBootstrap.googlemaps.initialize(elementId, zoom, center, markers, clickable, dotNetHelper); + } + }, + pendingInitializations: {}, updateMarkers: (elementId, markers, dotNetHelper) => { let markerEls = window.blazorBootstrap.googlemaps.markerEls[elementId] ?? []; @@ -783,6 +823,30 @@ window.blazorBootstrap = { return; } + let existingScriptEl = scriptId == null ? null : document.getElementById(scriptId); + + if (existingScriptEl?.tagName === 'SCRIPT') { + if (existingScriptEl.dataset.blazorBootstrapScriptLoaderStatus === 'loaded') { + dotNetHelper.invokeMethodAsync('OnLoadJS'); + return; + } + + if (existingScriptEl.dataset.blazorBootstrapScriptLoaderStatus === 'error') { + dotNetHelper.invokeMethodAsync('OnErrorJS', `An error occurred while loading the script: ${source}`); + return; + } + + existingScriptEl.addEventListener("error", (event) => { + dotNetHelper.invokeMethodAsync('OnErrorJS', `An error occurred while loading the script: ${source}`); + }, { once: true }); + + existingScriptEl.addEventListener("load", (event) => { + dotNetHelper.invokeMethodAsync('OnLoadJS'); + }, { once: true }); + + return; + } + let scriptEl = document.createElement('script'); scriptEl.async = async; @@ -799,10 +863,12 @@ window.blazorBootstrap = { scriptEl.type = type; scriptEl.addEventListener("error", (event) => { + scriptEl.dataset.blazorBootstrapScriptLoaderStatus = 'error'; dotNetHelper.invokeMethodAsync('OnErrorJS', `An error occurred while loading the script: ${source}`); }); scriptEl.addEventListener("load", (event) => { + scriptEl.dataset.blazorBootstrapScriptLoaderStatus = 'loaded'; dotNetHelper.invokeMethodAsync('OnLoadJS'); }); diff --git a/docs/docs/05-components/google-map.mdx b/docs/docs/05-components/google-map.mdx index 2b5d071a8..16c20e2d6 100644 --- a/docs/docs/05-components/google-map.mdx +++ b/docs/docs/05-components/google-map.mdx @@ -266,7 +266,7 @@ Use the **PinElement.GlyphColor** option to change the glyph color of a marker. ### Hide the glyph -Set the **PinElement.Glyph** option to an empty string to hide a marker's glyph. +Set the **PinElement.GlyphText** option to an empty string to hide a marker's glyph. Blazor Bootstrap: Google Map Component - Hide the glyph @@ -287,13 +287,13 @@ Set the **PinElement.Glyph** option to an empty string to hide a marker's glyph. { new GoogleMapMarker() { - PinElement = new PinElement{ Glyph = "", }, + PinElement = new PinElement{ GlyphText = "", }, Position = new GoogleMapMarkerPosition(37.50024109655184, -122.28528451834352) , Title = "Single family house with modern design", }, new GoogleMapMarker() { - PinElement = new PinElement{ Glyph = "", }, + PinElement = new PinElement{ GlyphText = "", }, Position = new GoogleMapMarkerPosition(37.44440882321596, -122.2160620727) , Title = "Townhouse with friendly neighbors", } @@ -303,9 +303,36 @@ Set the **PinElement.Glyph** option to an empty string to hide a marker's glyph. [See demo here](https://demos.blazorbootstrap.com/google-map#hide-the-glyph) +### Use text glyphs + +Use **PinElement.GlyphText** to show text in a marker pin. + +```cs {} showLineNumbers +PinElement = new PinElement +{ + GlyphText = "A", + GlyphColor = "white" +}; +``` + +[See demo here](https://demos.blazorbootstrap.com/google-map#use-text-glyphs) + +### Use image glyphs + +Use **PinElement.GlyphSrc** to show an image URL in a marker pin. + +```cs {} showLineNumbers +PinElement = new PinElement +{ + GlyphSrc = "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png" +}; +``` + +[See demo here](https://demos.blazorbootstrap.com/google-map#use-image-glyphs) + ### Use icon fonts -Use the **PinElement.UseIconFonts** and **PinElement.Glyph** options to use the icon fonts. +Existing **PinElement.UseIconFonts** and **PinElement.Glyph** configurations remain supported as a compatibility path and render as custom advanced-marker content. **Glyph** is obsolete, so use this path only while migrating existing icon-font configurations; use **GoogleMapMarker.Content** for new custom HTML marker content. Blazor Bootstrap: Google Map Component - Use icon fonts