diff --git a/ClientSideDemo/wwwroot/index.html b/ClientSideDemo/wwwroot/index.html index a29b3be3..a091034a 100644 --- a/ClientSideDemo/wwwroot/index.html +++ b/ClientSideDemo/wwwroot/index.html @@ -12,7 +12,6 @@ Loading... - diff --git a/GoogleMapsComponents/GoogleMap.razor b/GoogleMapsComponents/GoogleMap.razor index c93af004..5a786885 100644 --- a/GoogleMapsComponents/GoogleMap.razor +++ b/GoogleMapsComponents/GoogleMap.razor @@ -1,21 +1,13 @@ -@using System -@using Maps -@using System.Threading.Tasks -@using Microsoft.AspNetCore.Components +@using Maps @using Microsoft.JSInterop @inherits MapComponent -@implements IDisposable +@implements IAsyncDisposable @inject IJSRuntime JSRuntime
@code { - #nullable enable - // Load the module and keep a reference to it - // You need to use .AsTask() to convert the ValueTask to Task as it may be awaited multiple times - //private List moduleImports = new List(); - [Parameter] public string? Id { get; set; } @@ -47,17 +39,20 @@ private string StyleStr => $"height: {Height};"; private ElementReference Element { get; set; } + private IJSObjectReference? _scriptLoaderModule; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - //var tasks = new List>(); - //tasks.Add(JSRuntime.InvokeAsync("import", "./_content/BlazorGoogleMaps/js/objectManager.js").AsTask()); - //if(!string.IsNullOrWhiteSpace(ApiKey)) - // tasks.Add(JSRuntime.InvokeAsync("import", $"https://maps.googleapis.com/maps/api/js?key={ApiKey}&v=3").AsTask()); - - //moduleImports.AddRange(await Task.WhenAll(tasks.ToArray())); + try + { + await LoadScriptAsync("_content/BlazorGoogleMaps/js/objectManager.min.js"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } } await InitAsync(Element, Options); @@ -71,14 +66,27 @@ { return false; } + + private async Task GetJsModuleAsync() + { + _scriptLoaderModule ??= await JSRuntime.InvokeAsync("import", + "./_content/BlazorGoogleMaps/js/script-loader.min.js").AsTask(); + return _scriptLoaderModule; + } + + private async Task LoadScriptAsync(string scriptPath) + { + var jsModule = await GetJsModuleAsync(); + await jsModule.InvokeVoidAsync("BlazorGoogleMaps_LoadScript", scriptPath); + } + - void IDisposable.Dispose() + public async ValueTask DisposeAsync() { - //if(moduleImports != null && moduleImports.Count > 0) - //{ - // foreach (var mi in moduleImports) - // await mi.DisposeAsync(); - //} - base.Dispose(); + if (_scriptLoaderModule != null) + { + await _scriptLoaderModule.DisposeAsync(); + } + base.DisposeAsync(); } } \ No newline at end of file diff --git a/GoogleMapsComponents/wwwroot/js/objectManager.min.js b/GoogleMapsComponents/wwwroot/js/objectManager.min.js new file mode 100644 index 00000000..49471b1b --- /dev/null +++ b/GoogleMapsComponents/wwwroot/js/objectManager.min.js @@ -0,0 +1 @@ +window.blazorGoogleMaps=window.blazorGoogleMaps||function(){function stringToFunction(e){const t=e.split(".");let o=window||this;for(const e of t){if(void 0===o[e])throw new Error(`Property '${e}' not found`);o=o[e]}if("function"!=typeof o)throw new TypeError("Function not found");return o}const extendableStringify=(e,t,o)=>{const r=window?.blazorGoogleMapsBeforeStringify||this?.blazorGoogleMapsBeforeStringify;return"function"==typeof r&&(e=r(e)),JSON.stringify(e,t,o)};let mapObjects={},controlParents={};const dateFormat=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;function addMapObject(e,t){t&&"object"==typeof t&&"set"in t&&t.set("guidString",e),mapObjects[e]=t}const getCircularReplacer=()=>{const e=new WeakSet;return(t,o)=>{if("map"!=t&&"function"!=typeof o){if("object"==typeof o&&null!==o){if(e.has(o))return;e.add(o)}return o}}};function dateObjectReviver(e,t){return"string"==typeof t&&dateFormat.test(t)?new Date(t):t}function tryParseJson(item){if(null!==item&&"object"==typeof item&&"invokeMethodAsync"in item)return async function(...e){null==e&&await item.invokeMethodAsync("Invoke");const t=blazorGoogleMaps.objectManager.addObject(e[0]);if(1===e.length&&void 0!==e[0].marker){const o=e[0].marker;e[0].marker=null,await item.invokeMethodAsync("Invoke",extendableStringify(e,getCircularReplacer()),t),e[0].marker=o}else await item.invokeMethodAsync("Invoke",extendableStringify(e,getCircularReplacer()),t);blazorGoogleMaps.objectManager.disposeObject(t)};if("string"!=typeof item)return item;let parsedItem;try{parsedItem=JSON.parse(item,dateObjectReviver)}catch(e){try{parsedItem=eval(`(${item})`)}catch(e){return item}}if("string"==typeof item&&item.startsWith("google.maps.drawing.OverlayType")){const e={"google.maps.drawing.OverlayType.CIRCLE":google.maps.drawing.OverlayType.CIRCLE,"google.maps.drawing.OverlayType.MARKER":google.maps.drawing.OverlayType.MARKER,"google.maps.drawing.OverlayType.POLYGON":google.maps.drawing.OverlayType.POLYGON,"google.maps.drawing.OverlayType.POLYLINE":google.maps.drawing.OverlayType.POLYLINE,"google.maps.drawing.OverlayType.RECTANGLE":google.maps.drawing.OverlayType.RECTANGLE};return e[item]||item}if("object"==typeof parsedItem&&null!==parsedItem){if("guidString"in parsedItem)return mapObjects[parsedItem.guidString];for(let e in parsedItem){let t=parsedItem[e];if("string"==typeof t&&t.startsWith("google.maps.Animation")){const o={"google.maps.Animation.DROP":google.maps.Animation.DROP,"google.maps.Animation.BOUNCE":google.maps.Animation.BOUNCE};parsedItem[e]=o[t]||t}if("string"==typeof t&&t.startsWith("google.maps.CollisionBehavior")){const o={"google.maps.CollisionBehavior.REQUIRED":google.maps.CollisionBehavior.REQUIRED,"google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL":google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,"google.maps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY":google.maps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY};parsedItem[e]=o[t]||t}if("object"==typeof t&&null!==t&&void 0!==t.position&&(t.position=getGooglePositionFromString(t.position)),"renderingType"===e&&null!==t){const o={"google.maps.RenderingType.RASTER":google.maps.RenderingType.RASTER,"google.maps.RenderingType.UNINITIALIZED":google.maps.RenderingType.UNINITIALIZED,"google.maps.RenderingType.VECTOR":google.maps.RenderingType.VECTOR};parsedItem[e]=o[t]||google.maps.RenderingType.RASTER}if("object"==typeof t&&null!==t&&"drawingModes"in t){const e={"google.maps.drawing.OverlayType.CIRCLE":google.maps.drawing.OverlayType.CIRCLE,"google.maps.drawing.OverlayType.MARKER":google.maps.drawing.OverlayType.MARKER,"google.maps.drawing.OverlayType.POLYGON":google.maps.drawing.OverlayType.POLYGON,"google.maps.drawing.OverlayType.POLYLINE":google.maps.drawing.OverlayType.POLYLINE,"google.maps.drawing.OverlayType.RECTANGLE":google.maps.drawing.OverlayType.RECTANGLE};t.drawingModes=t.drawingModes.map((t=>e[t]||t))}"object"==typeof t&&null!==t&&"guidString"in t&&(parsedItem[e]=mapObjects[t.guidString])}return parsedItem}return item.replace(/['"]+/g,"")}function uuidv4(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,(e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)))}function cleanDirectionResult(e,t){let o=JSON.parse(extendableStringify(e));return o.routes.forEach((e=>{(null==t||t.stripOverviewPath)&&(e.overview_path=[]),(null==t||t.stripOverviewPolyline)&&(e.overview_polyline=""),e.legs.forEach((e=>{null==t||t.stripLegsSteps?e.steps=[]:e.steps.forEach((e=>{(null==t||t.stripLegsStepsLatLngs)&&(e.lat_lngs=[]),(null==t||t.stripLegsStepsPath)&&(e.path=[])}))}))})),o}function getGooglePositionFromString(e){return{BOTTOM_CENTER:google.maps.ControlPosition.BOTTOM_CENTER,BOTTOM_LEFT:google.maps.ControlPosition.BOTTOM_LEFT,BOTTOM_RIGHT:google.maps.ControlPosition.BOTTOM_RIGHT,LEFT_BOTTOM:google.maps.ControlPosition.LEFT_BOTTOM,LEFT_CENTER:google.maps.ControlPosition.LEFT_CENTER,LEFT_TOP:google.maps.ControlPosition.LEFT_TOP,RIGHT_BOTTOM:google.maps.ControlPosition.RIGHT_BOTTOM,RIGHT_CENTER:google.maps.ControlPosition.RIGHT_CENTER,RIGHT_TOP:google.maps.ControlPosition.RIGHT_TOP,TOP_CENTER:google.maps.ControlPosition.TOP_CENTER,TOP_LEFT:google.maps.ControlPosition.TOP_LEFT,TOP_RIGHT:google.maps.ControlPosition.TOP_RIGHT}[e]||google.maps.ControlPosition.BOTTOM_CENTER}let directionService={route:async function(e,t){let o=this,r=new Promise(((t,o)=>{(new google.maps.DirectionsService).route(e,((e,r)=>{"OK"==r?t(e):o(r)}))}));try{let e=await r;return"function"==typeof o.setDirections&&o.setDirections(e),extendableStringify(cleanDirectionResult(e,t))}catch(e){return console.log(e),e}}};function getAdvancedMarkerElementContent(e,t){if(("google.maps.marker.AdvancedMarkerView"===e||"google.maps.marker.AdvancedMarkerElement"===e)&&t){if("string"==typeof t&&t.startsWith("<")){let e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}{let e=new google.maps.marker.PinElement({background:t.background,borderColor:t.borderColor,glyphColor:t.glyphColor,scale:t.scale}),o=t.glyph;if(o){if("string"==typeof o&&o.startsWith("<")){let t=document.createElement("div");t.innerHTML=o.trim(),e.glyph=t}else e.glyph=o.startsWith("http")?new URL(o):o}return e.element}}return null}return{objectManager:{get mapObjects(){return mapObjects},initMap:async function(e){const t=e.libraries;var o,r,n,a,i,s,l,c,p,g,m,d,u;delete e.libraries,o=e,i="The Google Maps JavaScript API",s="google",l="importLibrary",c="__ib__",p=document,g=window,m=(g=g[s]||(g[s]={})).maps||(g.maps={}),d=new Set,u=new URLSearchParams,m[l]?console.warn(i+" only loads once. Ignoring:",o):m[l]=(e,...t)=>d.add(e)&&(r||(r=new Promise((async(e,t)=>{for(a in await(n=p.createElement("script")),u.set("libraries",[...d]+""),o)u.set(a.replace(/[A-Z]/g,(e=>"_"+e[0].toLowerCase())),o[a]);u.set("callback",s+".maps."+c),n.src=`https://maps.${s}apis.com/maps/api/js?`+u,m[c]=e,n.onerror=()=>r=t(Error(i+" could not load.")),n.nonce=p.querySelector("script[nonce]")?.nonce||"",p.head.append(n)})))).then((()=>m[l](e,...t)));for(var O=t.split(","),y=0;ytryParseJson(e))),o=e[1],r=getAdvancedMarkerElementContent(o,t.length>0?t[0].content:null);null!==r&&(t[0].content=r);let n=new(stringToFunction(o))(...t);addMapObject(e[0],n)},createMultipleObject:function(e){mapObjects=mapObjects||[];let t=e.slice(2).map((e=>tryParseJson(e))),o=e[1],r=stringToFunction(o),n=JSON.parse(e[0]);for(var a=0,i=t.length;a=0;o--)this.internalRemoveControlAt(e,t,o)},removeControl(e){const t=e[0],o=mapObjects[t],r=getGooglePositionFromString(e[1].replace(/"/g,"")),n=e[2].id,a=o.controls[r].getArray().findIndex((e=>e.id===n));this.internalRemoveControlAt(t,r,a)},removeControls(e){const t=e[0],o=getGooglePositionFromString(e[1].replace(/"/g,""));this.internalRemoveControls(t,o)},addImageLayer(e){let t=mapObjects[e[0]],o=mapObjects[e[1]];t.overlayMapTypes.push(o)},removeImageLayer(e){let t=mapObjects[e[0]],o=mapObjects[e[1]];for(var r=t.overlayMapTypes.getArray(),n=0,a=r.length;ntryParseJson(e))),o=mapObjects[e[0]],r=e[1],n=t.map((e=>e&&e.hasOwnProperty("lat")&&e.hasOwnProperty("lng")?new google.maps.LatLng(e.lat,e.lng):e)),a=getAdvancedMarkerElementContent("google.maps.marker.AdvancedMarkerElement","content"===r?n[0]:null);null!==a&&(n[0]=a);try{if(!(n.length>0)){const e=o[r];return e&&"object"==typeof e?JSON.parse(extendableStringify(e,getCircularReplacer())):e}o[r]=n[0]}catch(e){console.error(`Error invoking property: Function: ${r} Arguments: ${JSON.stringify(n)} Error: ${e}`)}},invoke:async function(e){const[t,o,...r]=e,n=mapObjects[t],a=r.map((e=>tryParseJson(e))),i=a.map((e=>e&&e.hasOwnProperty("lat")&&e.hasOwnProperty("lng")?new google.maps.LatLng(e.lat,e.lng):e));try{switch(o){case"blazorGoogleMaps.directionService.route":return await directionService.route.call(n,i[0],i[1]);case"setData":const e=new google.maps.MVCArray;return i[0].forEach((t=>{const o=t.hasOwnProperty("weight")?{location:new google.maps.LatLng(t.location.lat,t.location.lng),weight:t.weight}:new google.maps.LatLng(t.lat,t.lng);e.push(o)})),n.setData(e);case"getDirections":const a=i[0],s=n[o]();return extendableStringify(cleanDirectionResult(s,a));case"getProjection":const l=n[o](...i);return void addMapObject(r[0],l);case"createPath":const c=n.getPath();return void addMapObject(r[0],c);case"fromLatLngToPoint":return n[o](i[0]);case"google.maps.marker.PinElement":return new google.maps.marker.PinElement(i[0]);case"addListenerOnce":return new google.maps.event.addListenerOnce(n,i[0],i[1]);case"getPaths":return n[o](...i).getArray().map((e=>e.getArray()));case"removeAllFeatures":return n.forEach((e=>n.remove(e))),null;case"overrideStyle":const p=r[0].replace(/"/g,""),g=mapObjects[p],m=mapObjects[t],d=tryParseJson(r[1]);return m.overrideStyle(g,d);default:if(void 0!==google.maps.places&&n instanceof google.maps.places.AutocompleteService||void 0!==google.maps.places&&n instanceof google.maps.places.PlacesService||n instanceof google.maps.Geocoder)return new Promise(((e,t)=>{n[o](i[0],((t,o)=>{n instanceof google.maps.places.AutocompleteService?e({predictions:t,status:o}):e({results:t,status:o})}))}));{const e=n[o](...i);if(e&&"object"==typeof e)return e.hasOwnProperty("geocoded_waypoints")&&e.hasOwnProperty("routes")?extendableStringify(cleanDirectionResult(e)):"getArray"in e?e.getArray():"addListener"===o?e:"addGeoJson"===o?e.map((e=>this.addObject(e))):"get"in e?e.get("guidString"):"dotnetTypeName"in e?extendableStringify(e,getCircularReplacer()):JSON.parse(extendableStringify(e,getCircularReplacer()));if("remove"!==o)return e;this.disposeObject(t)}}}catch(e){console.log(e),console.log(`\nfunctionToInvoke: ${o}\nargs: ${JSON.stringify(a)}\n`)}},invokeMultiple:async function(e){const t=JSON.parse(e[0]),o=e[1],r=e.slice(2).map((e=>tryParseJson(e))),n={},a=(t.map((e=>mapObjects[e])),t.map(((e,t)=>{const a=[e,o,r[t]],i=blazorGoogleMaps.objectManager.invoke(a);return Promise.resolve(i).then((t=>{n[e]=t}))})));return await Promise.all(a),n},invokeWithReturnedObjectRef:async function(e){const t=await blazorGoogleMaps.objectManager.invoke(e),o=uuidv4();return addMapObject(o,t),o},drawingManagerOverlaycomplete:function(e){const[t,o]=e,r=mapObjects[t];google.maps.event.addListener(r,"overlaycomplete",(e=>{const r=uuidv4();addMapObject(r,e.overlay);const n=extendableStringify([{type:e.type,uuid:r.toString()}]);o.invokeMethodAsync("Invoke",n,t)}))},invokeMultipleWithReturnedObjectRef:function(e){const t=e[0],o=e.slice(1,-1),r=e[e.length-1],n={};for(let e=0;etryParseJson(e)));return await Promise.all(t.map(((e,t)=>{mapObjects[e];const n=Array.isArray(r[t])?r[t]:[r[t]],a=[e,"addListener",o,...n];return blazorGoogleMaps.objectManager.invoke(a)}))),!0},readObjectPropertyValue:function(e){const[t,o]=e,r=mapObjects[t];return r?.[o]},readObjectPropertyValueWithReturnedObjectRef:function(e){const[t,o]=e,r=mapObjects[t],n=r?.[o],a=uuidv4();return addMapObject(a,n),a},readObjectPropertyValueAndMapToArray:function(e){const[t,o,r]=e,n=mapObjects[t];let a=n?.[o];const i=tryParseJson(r);return a&&Array.isArray(a)&&i.forEach((e=>{a=a.map((t=>t?.[e]))})),a},createClusteringMarkers(e,t,o,r){const n={map:mapObjects[t],markers:o.map(((e,t)=>mapObjects[e.guid]))};if(r&&r.rendererObjectName){const e=r.rendererObjectName.split(".");try{let t=window[e[0]];for(i=1,len=e.length;i{});addMapObject(e,new markerClusterer.MarkerClusterer(n))},removeClusteringMarkers(e,t,o){const r=t.map(((e,t)=>mapObjects[e.guid]));mapObjects[e].removeMarkers(r,o)},addClusteringMarkers(e,t,o){const r=t.map(((e,t)=>mapObjects[e.guid]));mapObjects[e].addMarkers(r,o)}}}}(); \ No newline at end of file diff --git a/GoogleMapsComponents/wwwroot/js/script-loader.js b/GoogleMapsComponents/wwwroot/js/script-loader.js new file mode 100644 index 00000000..a6d1b734 --- /dev/null +++ b/GoogleMapsComponents/wwwroot/js/script-loader.js @@ -0,0 +1,19 @@ +export function BlazorGoogleMaps_LoadScript(path) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = path; + script.type = 'text/javascript'; + script.async = true; + + script.onload = () => { + resolve(); + }; + + script.onerror = (error) => { + console.error(`Error loading script: ${path}`, error); + reject(error); + }; + + document.head.appendChild(script); + }); +} \ No newline at end of file diff --git a/GoogleMapsComponents/wwwroot/js/script-loader.min.js b/GoogleMapsComponents/wwwroot/js/script-loader.min.js new file mode 100644 index 00000000..ad84b490 --- /dev/null +++ b/GoogleMapsComponents/wwwroot/js/script-loader.min.js @@ -0,0 +1 @@ +export function BlazorGoogleMaps_LoadScript(r){return new Promise(((o,e)=>{const t=document.createElement("script");t.src=r,t.type="text/javascript",t.async=!0,t.onload=()=>{o()},t.onerror=o=>{console.error(`Error loading script: ${r}`,o),e(o)},document.head.appendChild(t)}))} \ No newline at end of file diff --git a/README.md b/README.md index 8791bf10..0e6a7921 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,7 @@ OR (legacy - not recommended) Add google map script HEAD tag to wwwroot/index.ht ``` -2. Add path to project javascript functions file in wwwroot/index.html for Blazor WASM, or in _Host.cshtml or _HostLayout.cshtml for Blazor Server. -``` - -``` -If you want to use marker clustering add this script as well: +2. If you want to use marker clustering add this script in wwwroot/index.html for Blazor WASM, or in _Host.cshtml or _HostLayout.cshtml for Blazor Server ``` ```