Error executing template "/Designs/Rotadrill/Paragraph/RotadrillDealerLocatorParagraph.cshtml" System.AggregateException: One or more errors occurred. ---> System.Data.Entity.Core.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. ---> System.Data.SqlClient.SqlException: The SELECT permission was denied on the object 'vw_rotadrill_verkooppunten', database 'EXT', schema 'access_cms_rota_nl'. at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__180_0(Task`1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.<ExecuteStoreCommandsAsync>d__26.MoveNext() --- End of inner exception stack trace --- at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.<ExecuteStoreCommandsAsync>d__26.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan.<ExecuteAsync>d__10`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteInTransactionAsync>d__156`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<ExecuteAsyncImplementation>d__6`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Core.Objects.ObjectQuery`1.<GetResultsAsync>d__43.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Internal.LazyAsyncEnumerator`1.<FirstMoveNextAsync>d__9.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.<ForEachAsync>d__2`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Hoenderdaal.Core.Services.DealerlocatorService.<GetDealersAsync>d__4.MoveNext() in C:\Users\PetervandenDungen\source\repos\hoenderdaal\Hoenderdaal.Core\Services\DealerlocatorService.cs:line 102 --- End of inner exception stack trace --- at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at CompiledRazorTemplates.Dynamic.RazorEngine_8ded517a7bd3415784e4af9e706f8dec.Execute() in D:\inetpub\wwwroot\www.hoenderdaal-fasteners.nl\Files\Templates\Designs\Rotadrill\Paragraph\RotadrillDealerLocatorParagraph.cshtml:line 53 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate() ---> (Inner Exception #0) System.Data.Entity.Core.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. ---> System.Data.SqlClient.SqlException: The SELECT permission was denied on the object 'vw_rotadrill_verkooppunten', database 'EXT', schema 'access_cms_rota_nl'. at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__180_0(Task`1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.<ExecuteStoreCommandsAsync>d__26.MoveNext() --- End of inner exception stack trace --- at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition.<ExecuteStoreCommandsAsync>d__26.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan.<ExecuteAsync>d__10`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteInTransactionAsync>d__156`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<ExecuteAsyncImplementation>d__6`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Core.Objects.ObjectQuery`1.<GetResultsAsync>d__43.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Utilities.TaskExtensions.CultureAwaiter`1.GetResult() at System.Data.Entity.Internal.LazyAsyncEnumerator`1.<FirstMoveNextAsync>d__9.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.<ForEachAsync>d__2`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Hoenderdaal.Core.Services.DealerlocatorService.<GetDealersAsync>d__4.MoveNext() in C:\Users\PetervandenDungen\source\repos\hoenderdaal\Hoenderdaal.Core\Services\DealerlocatorService.cs:line 102<---
1 @using System.Text; 2 @using System.Web.Mvc; 3 @using Hoenderdaal.Core.Extensions; 4 @using Hoenderdaal.Core.Interfaces.Services; 5 @using Hoenderdaal.Models.Services.Dealerlocator; 6 7 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 8 @{ 9 var item = Model.Item; 10 if (item != null && Model.ItemType.Equals(Hoenderdaal.Items.Constants.Rotadrill.ItemConstants.Item_DealerLocatorParagraph)) 11 { 12 // globals 13 var pageView = Dynamicweb.Frontend.PageView.Current(); 14 var areaId = pageView.Area.ID; 15 var culture = pageView.GlobalTags.GetTagByName("Global:Area.LongLang").Value; 16 var baseUrl = "/Files/Templates/Designs/" + Pageview.Layout.Design.Folder.Name.TrimEnd('/'); 17 var showDealerIds = Dynamicweb.Context.Current.Request.QueryString.Get("showdealerids"); 18 19 // content 20 var googleMapsID = item.GetString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsID)); 21 if (string.IsNullOrEmpty(googleMapsID)) 22 { 23 googleMapsID = "766970dc8e26c170"; //default value 24 } 25 var mapZoom = item.GetString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsZoom)); 26 var mapsLongitude = item.GetRawValueString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsLongitude)).Replace(",", "."); 27 var mapLatitude = item.GetRawValueString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsLatitude)).Replace(",", "."); 28 var mapRegion = item.GetString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsRegion)); 29 if (string.IsNullOrEmpty(mapRegion)) 30 { 31 mapRegion = "nl"; 32 } 33 34 // settings for search filter 35 var mapZoomSearch = item.GetString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsZoomSearch)); 36 var mapZoomSearchRange = item.GetString(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.GoogleMapsSearchRange)); 37 if (string.IsNullOrEmpty(mapZoomSearchRange) || mapZoomSearchRange == "0") 38 { 39 mapZoomSearchRange = "25"; 40 } 41 42 // map 43 var markerGrouped = item.GetFile(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.MarkerGrouped)); 44 var marker = item.GetFile(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.Marker)); 45 var markerCertified = item.GetFile(nameof(Hoenderdaal.Items.Paragraphs.Rotadrill.RotadrillDealerLocatorParagraph.CertifiedMarker)); 46 47 // dealers 48 var dealerlocatorService = DependencyResolver.Current.GetService<IDealerlocatorService>(); 49 var dealers = new List<DealerDto>(); 50 51 if (dealerlocatorService != null) 52 { 53 dealers = dealerlocatorService.GetDealersAsync().Result.ToList(); 54 } 55 56 <div id="@($"dealer-locator-{Model.ID}")" class="dealer-locator"> 57 <div class="row align-items-center"> 58 <div class="col-12 col-md-4 dealer-locator-sidebar"> 59 <div class="dealer-list"> 60 61 <div class="dealer-search"> 62 <input type="search" id="dealer-search-filter-@Model.ID" placeholder="@Translate("Rotadrill:DealerLocator:SearchPlaceholder", "Plaats of postcode")"> 63 <button class="search-btn" onclick="filterMarkers()"><svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg"> 64 <g clip-path="url(#clip0_637_2373)"> 65 <path d="M49.0153 13.5416V0L3.8763 0V13.5416L49.0153 13.5416Z" fill="#000000"/> 66 <path d="M35.4736 45.1396H49.0153L49.0153 0.00069046H35.4736L35.4736 45.1396Z" fill="#000000"/> 67 <path d="M0.00311582 39.4422L9.57849 49.0176L41.4966 17.0995L31.9212 7.52414L0.00311582 39.4422Z" fill="#000000"/> 68 </g> 69 <defs> 70 <clipPath id="clip0_637_2373"> 71 <rect width="50" height="50" fill="#000000"/> 72 </clipPath> 73 </defs> 74 </svg> 75 </button> 76 </div> 77 78 @foreach (var dealer in dealers) 79 { 80 <div class="dealer-container"> 81 <a href="#" class="dealer-link" data-id="@dealer.Id"> 82 <div class="dealer-name">@(!string.IsNullOrEmpty(showDealerIds) ? dealer.Id : string.Empty) @dealer.Name</div> 83 84 @if (Convert.ToBoolean(dealer.Certified) == true) 85 { 86 <div class="dealer-certified"><span class="certified-star"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> 87 <path d="M11.6975 5.49191L9.28541 7.64948L10.0203 10.8761C10.0608 11.0513 10.0504 11.235 9.9903 11.404C9.93019 11.573 9.8231 11.7197 9.68257 11.8257C9.54204 11.9316 9.37437 11.992 9.20074 11.9993C9.02711 12.0065 8.85531 11.9602 8.70704 11.8663L6.00011 10.1393L3.29157 11.8663C3.14332 11.9597 2.97172 12.0055 2.79839 11.998C2.62506 11.9905 2.45774 11.93 2.3175 11.8242C2.17727 11.7183 2.07038 11.5718 2.01031 11.4031C1.95023 11.2344 1.93966 11.051 1.97991 10.8761L2.71748 7.64948L0.305367 5.49191C0.174201 5.3744 0.0793386 5.21944 0.032628 5.04637C-0.0140826 4.8733 -0.0105715 4.68981 0.0427227 4.5188C0.096017 4.34779 0.196733 4.19684 0.332291 4.08481C0.46785 3.97278 0.632244 3.90464 0.804944 3.88888L3.9675 3.62439L5.18749 0.563902C5.25353 0.397107 5.36592 0.254435 5.51038 0.154024C5.65484 0.0536135 5.82484 0 5.99877 0C6.1727 0 6.3427 0.0536135 6.48716 0.154024C6.63162 0.254435 6.74401 0.397107 6.81004 0.563902L8.0295 3.62439L11.1921 3.88888C11.3651 3.90405 11.53 3.97182 11.666 4.08369C11.8021 4.19557 11.9033 4.34657 11.9569 4.51779C12.0105 4.68901 12.0142 4.87282 11.9675 5.0462C11.9208 5.21959 11.8258 5.37483 11.6943 5.49247L11.6975 5.49191Z" fill="#D03234"/> 88 </svg> 89 </span>@Translate("Rotadrill:DealerLocator:CertifiedDealer", "Gecertificeerde dealer")</div> 90 } 91 92 <div class="dealer-address"> 93 @($"{(string.IsNullOrEmpty(dealer.Address) ? string.Empty : dealer.Address)}" + 94 $"{(string.IsNullOrEmpty(dealer.PostalCode) ? string.Empty : ", " + dealer.PostalCode)}" + 95 $"{(string.IsNullOrEmpty(dealer.City) ? string.Empty : ", " + dealer.City.Substring(0, 1).ToUpper() + dealer.City.Substring(1).ToLower())}") 96 </div> 97 <div class="dealer-status" id="dealer-status-@dealer.Id"> 98 <div class="status-placeholder"> 99 <!-- These elements will be updated by JavaScript --> 100 <span class="open-status"></span> 101 <span class="status-delimiter"> • </span> 102 <span class="close-time"></span> 103 <div class="dealer-closed"></div> 104 </div> 105 </div> 106 </a> 107 </div> 108 } 109 110 <div class="dealer-container-empty" style="display: none;"> 111 <span class="text">@Translate("Rotadrill:DealerLocator:NoDealersFound", $"Er konden geen dealers gevonden worden, binnen een straal van {mapZoomSearchRange} km.")</span> 112 </div> 113 114 </div> 115 </div> 116 <div class="col-12 col-md-8 dealer-locator__container"> 117 <div id="map_@(Model.ID)" class="map_canvas" style="width: 100%; height: 900px;"></div> 118 <div id="loader-@Model.ID" class="loader" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="200" height="200" style="shape-rendering: auto; display: block; background: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g transform="rotate(0 50 50)"> 119 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 120 <animate repeatCount="indefinite" begin="-0.9166666666666666s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 121 </rect> 122 </g><g transform="rotate(30 50 50)"> 123 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 124 <animate repeatCount="indefinite" begin="-0.8333333333333334s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 125 </rect> 126 </g><g transform="rotate(60 50 50)"> 127 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 128 <animate repeatCount="indefinite" begin="-0.75s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 129 </rect> 130 </g><g transform="rotate(90 50 50)"> 131 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 132 <animate repeatCount="indefinite" begin="-0.6666666666666666s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 133 </rect> 134 </g><g transform="rotate(120 50 50)"> 135 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 136 <animate repeatCount="indefinite" begin="-0.5833333333333334s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 137 </rect> 138 </g><g transform="rotate(150 50 50)"> 139 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 140 <animate repeatCount="indefinite" begin="-0.5s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 141 </rect> 142 </g><g transform="rotate(180 50 50)"> 143 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 144 <animate repeatCount="indefinite" begin="-0.4166666666666667s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 145 </rect> 146 </g><g transform="rotate(210 50 50)"> 147 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 148 <animate repeatCount="indefinite" begin="-0.3333333333333333s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 149 </rect> 150 </g><g transform="rotate(240 50 50)"> 151 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 152 <animate repeatCount="indefinite" begin="-0.25s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 153 </rect> 154 </g><g transform="rotate(270 50 50)"> 155 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 156 <animate repeatCount="indefinite" begin="-0.16666666666666666s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 157 </rect> 158 </g><g transform="rotate(300 50 50)"> 159 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 160 <animate repeatCount="indefinite" begin="-0.08333333333333333s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 161 </rect> 162 </g><g transform="rotate(330 50 50)"> 163 <rect fill="#0197D2" height="12" width="6" ry="6" rx="3" y="24" x="47"> 164 <animate repeatCount="indefinite" begin="0s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity"></animate> 165 </rect> 166 </g><g></g></g><!-- [ldio] generated by https://loading.io --></svg></div> <!-- The loader --> 167 </div> 168 </div> 169 </div> 170 171 @SnippetStart("JavaScript") 172 <script> 173 window.onload = async function () { 174 175 const initialValues = construct(); 176 let dealers = initialValues.dealers; 177 let mapId = initialValues.mapId; 178 let userLatitude = initialValues.userLatitude; 179 let userLongitude = initialValues.userLongitude; 180 let zoomCenter = initialValues.zoomCenter; 181 182 const searchInput = document.getElementById(`dealer-search-filter-${@(Model.ID)}`); 183 184 searchInput.addEventListener("keypress", function (event) { 185 if (event.key === "Enter") { 186 event.preventDefault(); 187 filterMarkers(); 188 } 189 }); 190 191 if (typeof google !== "undefined") { 192 if ("geolocation" in navigator) { 193 // Async function to handle geolocation 194 const getLocation = async () => { 195 try { 196 const position = await new Promise((resolve, reject) => { 197 navigator.geolocation.getCurrentPosition(resolve, reject); 198 }); 199 200 if (position.coords.latitude && position.coords.longitude) { 201 filterMarkers(position.coords.latitude, position.coords.longitude); 202 } else { 203 loadMap(dealers, mapId, zoomCenter, userLatitude, userLongitude); 204 } 205 } catch (error) { 206 console.error("Error getting location: ", error); 207 loadMap(dealers, mapId, zoomCenter, userLatitude, userLongitude); 208 } 209 }; 210 211 getLocation(); 212 } else { 213 loadMap(dealers, mapId, zoomCenter, userLatitude, userLongitude); 214 } 215 } else { 216 console.error("Google Maps API failed to load."); 217 } 218 }; 219 220 document.addEventListener("DOMContentLoaded", async () => { 221 loader(true); 222 }); 223 224 function construct() { 225 return { 226 dealers: JSON.parse(`@dealers.ToJson().Replace("\"", "\\\"")`), 227 mapId: '@Model.ID', 228 userLatitude: parseFloat('@mapLatitude'.replace(",", ".")) === 0 ? 52.0842635 : parseFloat('@mapLatitude'.replace(",", ".")), 229 userLongitude: parseFloat('@mapsLongitude'.replace(",", ".")) === 0 ? 5.0000917 : parseFloat('@mapsLongitude'.replace(",", ".")), 230 zoomCenter: parseInt('@mapZoom') === 0 ? 7 : parseInt('@mapZoom'), 231 zoomCenterSearch: parseInt('@mapZoomSearch') === 0 ? 12 : parseInt('@mapZoomSearch'), 232 searchRange: parseInt('@mapZoomSearchRange') === 0 ? 25 : parseInt('@mapZoomSearchRange'), 233 }; 234 } 235 236 async function filterMarkers(overrideLatitude, overrideLongitude) { 237 238 const initialValues = construct(); 239 let dealers = initialValues.dealers; 240 let mapId = initialValues.mapId; 241 var userLatitude = initialValues.userLatitude; 242 var userLongitude = initialValues.userLongitude; 243 var zoomCenter = initialValues.zoomCenter; 244 var zoomCenterSearch = initialValues.zoomCenterSearch; 245 var searchRange = initialValues.searchRange; 246 247 //add class for mobile visibility of the sidebar 248 document.querySelector('.dealer-locator-sidebar')?.classList.add('visible'); 249 250 let value = document.getElementById(`dealer-search-filter-${@(Model.ID)}`)?.value ?? ''; 251 252 if (value.trim() === '') { 253 resetDealerList(); 254 loadMap(dealers, mapId, zoomCenter, userLatitude, userLongitude); 255 return; 256 } 257 258 loader(true); 259 260 try { 261 var responseText = ''; 262 var response = ''; 263 var match = false; 264 const apiUrl = '/hfwebapi/geocoding/filter-markers/@areaId'; 265 266 if (overrideLatitude === undefined || overrideLongitude === undefined) { 267 268 const region = getRegion(value); 269 270 response = await fetch(apiUrl, { 271 method: "POST", 272 headers: { 273 "Content-Type": "application/json", 274 }, 275 body: JSON.stringify({ 276 filter: value, 277 region: region, 278 }), 279 }); 280 281 if (!response.ok) { 282 console.log('Failed to fetch coordinates'); 283 } 284 285 responseText = await response.text(); 286 } 287 else { 288 responseText = "{ Lat = " + overrideLatitude + ", Lng = " + overrideLongitude + " }"; 289 } 290 291 match = responseText.match(/Lat\s*=\s*([\d.,-]+),\s*Lng\s*=\s*([\d.,-]+)/); 292 293 if (match) { 294 const Lat = parseFloat(match[1].replace(',', '.')); // Replace commas with dots for valid float 295 const Lng = parseFloat(match[2].replace(',', '.')); 296 297 const maxDistance = searchRange; 298 const filteredDealers = dealers 299 .filter(dealer => { 300 301 // Ensure that each dealer has Latitude and Longitude properties 302 if (dealer.Latitude && dealer.Longitude) { 303 // Calculate the distance between the search location and dealer's location 304 const distance = calculateDistance(Lat, Lng, dealer.Latitude, dealer.Longitude); 305 306 dealer.distance = distance; // Attach the distance to the dealer object 307 return distance <= maxDistance; // Only include dealers within the search range 308 } 309 return false; // Exclude dealers without valid coordinates 310 }) 311 .sort((a, b) => { 312 313 314 if (a.distance !== undefined && b.distance !== undefined) { 315 return a.distance - b.distance; // Sort dealers by distance (ascending) 316 } 317 return 0; // If no distance, don't change the order 318 }); 319 320 321 322 323 // Load the filtered dealers on the map 324 loadMap(filteredDealers, mapId, zoomCenterSearch, Lat, Lng); 325 326 updateDealerList(filteredDealers, response.status); 327 328 // update dealer status during filtering 329 if (value !== '') { 330 for (let i = 0; i < filteredDealers.length; i++) { 331 332 const details = await getPlaceDetails(filteredDealers[i].Latitude, filteredDealers[i].Longitude); 333 334 await updateDealerStatus(filteredDealers[i], i, details); 335 } 336 } 337 338 } else { 339 340 updateDealerList([], response.status); // Call with empty array on non-200 status, to show empty list of dealers. 341 loader(false); 342 343 console.error("Invalid format in response:", responseText); 344 } 345 } catch (error) { 346 347 updateDealerList([]); // Call with empty array on non-200 status, to show empty list of dealers. 348 loader(false); 349 350 console.error("Error fetching coordinates:", error); 351 } 352 } 353 354 async function loadMap(dealers, mapId, zoom, latitude, longitude) { 355 356 const mapOptions = { 357 zoom, 358 center: new google.maps.LatLng(latitude || '@mapLatitude', longitude || '@mapsLongitude'), 359 mapTypeId: google.maps.MapTypeId.TERRAIN, 360 mapTypeControl: false, 361 mapId: '@googleMapsID', // Map ID is required for advanced markers. 362 }; 363 364 const map = new google.maps.Map(document.getElementById(`map_${mapId}`), mapOptions); 365 var infowindow; 366 var markers = []; 367 368 try { 369 370 for (var i = 0; i < dealers.length; i++) { 371 372 const coordinates = { 373 latitude: dealers[i].Latitude, 374 longitude: dealers[i].Longitude 375 }; 376 377 dealers[i].Coordinates = coordinates; 378 379 const certified = dealers[i]["Certified"]; 380 381 const markerContent = document.createElement("div"); 382 markerContent.innerHTML = `<img src="${certified === 'true' ? '@marker.Path' : '@markerCertified.Path'}">`; 383 384 var marker = new google.maps.marker.AdvancedMarkerElement({ 385 position: new google.maps.LatLng(dealers[i].Latitude, dealers[i].Longitude), 386 map: map, 387 title: dealers[i].Name, 388 content: markerContent, 389 }); 390 391 marker.id = dealers[i].Id; 392 393 markers.push(marker); 394 395 // Add a click listener to the marker to load details on demand 396 (function (marker, dealer, i) { 397 marker.addListener('click', async () => { 398 if (infowindow) { 399 infowindow.close(); 400 } 401 infowindow = new google.maps.InfoWindow(); 402 403 try { 404 405 const details = await getPlaceDetails(dealers[i].Latitude, dealers[i].Longitude); 406 const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${dealers[i].Latitude},${dealers[i].Longitude}`; 407 408 infowindow.setContent(` 409 <div class="info-window"> 410 <h5>${dealer.Name}</h5> 411 <p class="certified"> 412 ${certified === 'true' ? ` 413 <span class="certified-star"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> 414 <path d="M11.6975 5.49191L9.28541 7.64948L10.0203 10.8761C10.0608 11.0513 10.0504 11.235 9.9903 11.404C9.93019 11.573 9.8231 11.7197 9.68257 11.8257C9.54204 11.9316 9.37437 11.992 9.20074 11.9993C9.02711 12.0065 8.85531 11.9602 8.70704 11.8663L6.00011 10.1393L3.29157 11.8663C3.14332 11.9597 2.97172 12.0055 2.79839 11.998C2.62506 11.9905 2.45774 11.93 2.3175 11.8242C2.17727 11.7183 2.07038 11.5718 2.01031 11.4031C1.95023 11.2344 1.93966 11.051 1.97991 10.8761L2.71748 7.64948L0.305367 5.49191C0.174201 5.3744 0.0793386 5.21944 0.032628 5.04637C-0.0140826 4.8733 -0.0105715 4.68981 0.0427227 4.5188C0.096017 4.34779 0.196733 4.19684 0.332291 4.08481C0.46785 3.97278 0.632244 3.90464 0.804944 3.88888L3.9675 3.62439L5.18749 0.563902C5.25353 0.397107 5.36592 0.254435 5.51038 0.154024C5.65484 0.0536135 5.82484 0 5.99877 0C6.1727 0 6.3427 0.0536135 6.48716 0.154024C6.63162 0.254435 6.74401 0.397107 6.81004 0.563902L8.0295 3.62439L11.1921 3.88888C11.3651 3.90405 11.53 3.97182 11.666 4.08369C11.8021 4.19557 11.9033 4.34657 11.9569 4.51779C12.0105 4.68901 12.0142 4.87282 11.9675 5.0462C11.9208 5.21959 11.8258 5.37483 11.6943 5.49247L11.6975 5.49191Z" fill="#D03234"/> 415 </svg> 416 </span> 417 @Translate("Rotadrill:DealerLocator:CertifiedDealer", "Gecertificeerde dealer") 418 ` : ''} 419 </p> 420 <p> 421 ${details.address && details.address.trim() ? formatPopupAddress(details.address) : ''} 422 <br/>${details.postalCode && details.postalCode.trim() ? formatPopupAddress(details.postalCode) : ''} 423 ${details.city && details.city.trim() ? formatPopupAddress(details.city) : ''} 424 ${details.phoneNumber && details.phoneNumber.trim() ? `<a href="tel:${details.phoneNumber}"><svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> 425 <path d="M27.6531 32C27.6446 32 27.6361 32 27.6276 32C23.5278 31.8658 18.7249 27.8922 15.4158 24.5813C12.1023 21.2703 8.12831 16.4658 8.00046 12.3858C7.95359 10.954 11.4673 8.40794 11.5036 8.38237C12.4156 7.74745 13.4277 7.9733 13.8432 8.54856C14.1245 8.93846 16.788 12.9738 17.0778 13.4319C17.3783 13.907 17.3335 14.6144 16.9585 15.3238C16.7518 15.718 16.0657 16.9239 15.7439 17.4864C16.0912 17.9807 17.0096 19.193 18.9061 21.0892C20.8046 22.9855 22.015 23.9059 22.5114 24.2532C23.074 23.9314 24.28 23.2454 24.6742 23.0387C25.3731 22.668 26.0763 22.6211 26.5558 22.9152C27.0459 23.2156 31.071 25.8916 31.4418 26.1494C31.7529 26.3688 31.9532 26.7438 31.9936 27.1806C32.032 27.6216 31.8956 28.0882 31.6122 28.4952C31.5888 28.5293 29.0723 32 27.6531 32Z" fill="currentColor"/> 426 </svg> 427 @Translate("Rotadrill:DealerLocator:PhoneNumber", "Telefoonnummer") </a>` : ''} 428 ${details.website && details.website.trim() ? `<a href="${details.website}" target="_blank"><svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"> 429 <path d="M19.6939 8C13.2459 8 8 13.2459 8 19.6939C8 26.142 13.2459 31.3879 19.6939 31.3879C20.0892 31.3879 20.4786 31.3671 20.8633 31.3285V30.2185V25.5409C20.8633 24.8942 20.3395 24.3715 19.6939 24.3715H18.5246V22.7339C18.5246 22.4745 18.4224 22.2353 18.2665 22.0327H20.8633V19.6939H16.1858V17.3552H17.3552C18.0007 17.3552 18.5246 16.8324 18.5246 16.1858V13.8744L20.8816 13.8561C22.1609 13.8479 23.2021 12.7978 23.2021 11.5173V11.0263C26.6263 12.4174 29.0491 15.7764 29.0491 19.6939C29.0491 19.708 29.0468 19.721 29.0468 19.7351L31.1869 21.7975C31.312 21.1134 31.3879 20.412 31.3879 19.6939C31.3879 13.2459 26.142 8 19.6939 8ZM19.6939 10.3388C20.0909 10.3388 20.4792 10.3705 20.8633 10.4187V11.5173L17.346 11.5447C16.704 11.5494 16.1858 12.0721 16.1858 12.7141V15.0164H15.0164C14.3709 15.0164 13.847 15.5391 13.847 16.1858V17.5721L11.4853 15.2105C13.076 12.3098 16.159 10.3388 19.6939 10.3388ZM23.2021 17.3552V29.0491L25.8607 26.5207L28.2383 32L30.81 30.8306L28.4142 25.5409H31.7077L23.2021 17.3552ZM10.5786 17.611L16.1858 23.2181V24.3715C16.1858 25.6614 17.2347 26.7103 18.5246 26.7103V28.9692C13.9176 28.3906 10.3388 24.4552 10.3388 19.6939C10.3388 18.9771 10.4252 18.282 10.5786 17.611Z" fill="currentColor"/> 430 </svg> 431 @Translate("Rotadrill:DealerLocator:Website", "Website")</a>` : ''} 432 ${`<a href="${googleMapUrl}" target="_blank"><?xml version="1.0" encoding="utf-8"?> 433 <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> 434 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 435 <path fill-rule="evenodd" clip-rule="evenodd" d="M6.14214 6.14214C8.90356 3.38071 10.2843 2 12 2C13.7157 2 15.0964 3.38071 17.8579 6.14214C20.6193 8.90356 22 10.2843 22 12C22 13.7157 20.6193 15.0964 17.8579 17.8579C15.0964 20.6193 13.7157 22 12 22C10.2843 22 8.90356 20.6193 6.14214 17.8579C3.38071 15.0964 2 13.7157 2 12C2 10.2843 3.38071 8.90356 6.14214 6.14214ZM13.8463 8.45285C13.5441 8.16955 13.0695 8.18486 12.7862 8.48704C12.5029 8.78923 12.5182 9.26386 12.8204 9.54715L14.1034 10.75H10.6667C10.1116 10.75 9.28861 10.9003 8.5804 11.3784C7.83208 11.8835 7.25 12.7345 7.25 14C7.25 14.4142 7.58579 14.75 8 14.75C8.41421 14.75 8.75 14.4142 8.75 14C8.75 13.2655 9.05681 12.8665 9.41961 12.6216C9.8225 12.3497 10.3329 12.25 10.6667 12.25H14.1034L12.8204 13.4528C12.5182 13.7361 12.5029 14.2108 12.7862 14.513C13.0695 14.8151 13.5441 14.8305 13.8463 14.5472L16.513 12.0472C16.6642 11.9054 16.75 11.7073 16.75 11.5C16.75 11.2927 16.6642 11.0946 16.513 10.9528L13.8463 8.45285Z" fill="#1C274C"/> 436 </svg> @Translate("Rotadrill:DealerLocator:NavigateTo", "Navigeer naar")</a><br>`} 437 ${details.information ? ` 438 <ul> 439 ${["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].map(day => { 440 const hours = details.information[day]; 441 442 const translation = { 443 "Monday": "@Translate("Rotadrill:DealerLocator:Monday", "Maandag")", 444 "Tuesday": "@Translate("Rotadrill:DealerLocator:Tuesday", "Dinsdag")", 445 "Wednesday": "@Translate("Rotadrill:DealerLocator:Wednesday", "Woensdag")", 446 "Thursday": "@Translate("Rotadrill:DealerLocator:Thursday", "Donderdag")", 447 "Friday": "@Translate("Rotadrill:DealerLocator:Friday", "Vrijdag")", 448 "Saturday": "@Translate("Rotadrill:DealerLocator:Saturday", "Zaterdag")", 449 "Sunday": "@Translate("Rotadrill:DealerLocator:Sunday", "Zondag")" 450 }[day]; 451 452 const displayHours = hours?.toLowerCase() === "closed" 453 ? "@Translate("Rotadrill:DealerLocator:Closed", "Gesloten")" 454 : hours; 455 456 return `<li>${translation}: ${displayHours ? displayHours : "@Translate("Rotadrill:DealerLocator:NotAvailable", "n/a")"}</li>`; 457 }).join('')} 458 </ul> 459 ` : ''} 460 </p> 461 </div> 462 `); 463 infowindow.open(map, marker); 464 } catch (error) { 465 console.error("Error fetching details: ", error); 466 } 467 }); 468 })(marker, dealers[i], i); 469 } 470 } catch (error) { 471 console.error("Error loading map: ", error); 472 } 473 474 document.querySelectorAll('.dealer-link').forEach((dealerLink) => { 475 476 dealerLink.addEventListener('click', function (event) { 477 event.preventDefault(); 478 const dealerId = this.getAttribute('data-id'); 479 480 const marker = markers.find(m => m.id.toString() === dealerId); 481 482 if (marker) { 483 map.setZoom(15); 484 map.panTo(marker.position); // Access the position directly from 'marker.position' 485 google.maps.event.trigger(marker, 'click'); 486 } 487 }); 488 }); 489 490 const renderer = { 491 render: ({ count, position }) => { 492 // Create a div to hold the custom marker content 493 const markerContent = document.createElement("div"); 494 markerContent.className = "clustered-marker"; 495 496 markerContent.innerHTML = ` 497 <img src="${'@markerGrouped.Path'}" class="clustered-marker-icon" alt="Cluster Marker"> 498 <span class="clustered-marker-label">${count}</span> 499 `; 500 501 return new google.maps.marker.AdvancedMarkerElement({ 502 position, 503 zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count, 504 content: markerContent, // Use `content` instead of `icon` and `label` 505 }); 506 } 507 }; 508 509 new markerClusterer.MarkerClusterer({ markers: markers, map: map, renderer: renderer }); 510 511 loader(false); 512 } 513 514 function resetDealerList() { 515 document.querySelector('.dealer-container')?.classList.remove('visible'); 516 document.querySelector('.dealer-locator-sidebar')?.classList.remove('visible'); 517 518 document.querySelectorAll(".dealer-container").forEach(dealerElement => { 519 dealerElement.style.display = ""; 520 }); 521 } 522 523 function updateDealerList(filteredDealers, statusCode) { 524 const dealerContainers = document.querySelectorAll(".dealer-container"); 525 const emptyStateElement = document.querySelector(".dealer-container-empty"); 526 const parentElement = document.querySelector(".dealer-list"); 527 528 let visibleDealersCount = 0; 529 530 // Sort filtered dealers by distance 531 filteredDealers.sort((a, b) => a.distance - b.distance); 532 533 // Build a map of existing dealer DOM elements by ID 534 const dealerElementMap = new Map(); 535 dealerContainers.forEach(el => { 536 const id = el.querySelector(".dealer-link")?.getAttribute("data-id"); 537 if (id) dealerElementMap.set(id, el); 538 539 // Hide all by default 540 el.style.display = "none"; 541 el.classList.remove("visible"); 542 }); 543 544 // Show + reorder only filtered dealers 545 filteredDealers.forEach(dealer => { 546 const dealerId = String(dealer.Id); 547 const element = dealerElementMap.get(dealerId); 548 549 if (element) { 550 element.style.display = ""; 551 element.classList.add("visible"); 552 parentElement.appendChild(element); // Reorders in DOM! 553 visibleDealersCount++; 554 } else { 555 console.warn("Could not find dealer element in DOM for ID:", dealerId); 556 } 557 }); 558 559 // Empty state handling 560 if (emptyStateElement) { 561 if (statusCode === 429) { 562 emptyStateElement.querySelector('.text').textContent = 563 '@Translate("Rotadrill:DealerLocator:ToManyRequests", $"Het filteren kan maar eens per minuut.")'; 564 } else { 565 emptyStateElement.querySelector('.text').textContent = 566 `@Translate("Rotadrill:DealerLocator:NoDealersFound", $"Er konden geen dealers gevonden worden, binnen een straal van ${mapZoomSearchRange} km.")`; 567 } 568 569 emptyStateElement.style.display = visibleDealersCount === 0 ? "block" : "none"; 570 } 571 } 572 573 function updateDealerStatus(dealer, index, details) { 574 const dealerStatusElement = document.getElementById(`dealer-status-${dealer.Id}`); 575 576 if (dealerStatusElement) { 577 const statusPlaceholder = dealerStatusElement.querySelector(".status-placeholder"); 578 statusPlaceholder.classList.remove("status-open", "status-closed"); 579 580 if (details.isOpen && details.isOpenUntilTime !== '') { 581 const openStatusText = "@Translate("Rotadrill:DealerLocator:Open", "Geopend")"; 582 583 const closeTimeText = `@Translate("Rotadrill:DealerLocator:ClosedAt", "Sluit om") ${details.isOpenUntilTime}`; 584 @*: "@Translate("Rotadrill:DealerLocator:NoClosingTime", "Geen sluitingstijd beschikbaar")";*@ 585 586 statusPlaceholder.classList.add("status-open"); 587 statusPlaceholder.querySelector(".open-status").innerText = openStatusText; 588 statusPlaceholder.querySelector(".close-time").innerText = closeTimeText; 589 } else { 590 const closedStatusText = "@Translate("Rotadrill:DealerLocator:Closed", "Gesloten")"; 591 statusPlaceholder.classList.add("status-closed"); 592 statusPlaceholder.querySelector(".dealer-closed").innerText = closedStatusText; 593 } 594 } 595 } 596 597 async function getPlaceDetails(latitude, longitude) { 598 const apiUrl = "/hfwebapi/geocoding/get-place-details/@areaId/@culture.Substring(0, 2)"; 599 600 try { 601 const response = await fetch(apiUrl, { 602 method: "POST", 603 headers: { 604 "Content-Type": "application/json", 605 }, 606 body: JSON.stringify({ 607 Longitude: longitude, 608 Latitude: latitude, 609 }), 610 }); 611 612 if (!response.ok) { 613 throw new Error(`API error: ${response.statusText}`); 614 } 615 616 const placeDetails = await response.json(); 617 618 return { 619 city: placeDetails.City || '', 620 phoneNumber: placeDetails.PhoneNumber || '', 621 website: placeDetails.Website || '', 622 address: placeDetails.Address || '', 623 postalCode: placeDetails.PostalCode || '', 624 information: placeDetails.Information, 625 isOpen: placeDetails.IsOpen || '', 626 certified: placeDetails.Certified || false, 627 isOpen: placeDetails.IsOpen || false, 628 isOpenUntilTime: placeDetails.IsOpenUntilTime || '', 629 }; 630 } catch (error) { 631 console.error("Error fetching place details:", error); 632 throw error; 633 } 634 } 635 636 function loader(visible) { 637 document.getElementById(`loader-${@(Model.ID)}`).style.display = visible === true ? 'block' : 'none'; 638 } 639 640 function getQueryStringParam(param) { 641 const urlParams = new URLSearchParams(window.location.search); 642 return urlParams.get(param)?.toLowerCase() || ""; 643 } 644 645 function calculateDistance(lat1, lon1, lat2, lon2) { 646 647 648 const R = 6371; 649 const dLat = toRad(lat2 - lat1); 650 const dLon = toRad(lon2 - lon1); 651 const a = 652 Math.sin(dLat / 2) * Math.sin(dLat / 2) + 653 Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * 654 Math.sin(dLon / 2) * Math.sin(dLon / 2); 655 const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 656 return R * c; 657 } 658 659 function toRad(value) { 660 return value * Math.PI / 180; 661 } 662 663 function formatPopupAddress(address) { 664 // Updated regex to handle street names with spaces and extra characters like "T" 665 const regex = /^(.*\d+[A-Za-z]*)\s*,\s*(\d{4}\s?[A-Z]{2})\s([a-zA-Z\s]+),\s*[^,]+$/; 666 const match = address.match(regex); 667 668 if (match) { 669 const street = match[1]; // Street and number (including "T" or other characters) 670 const zip = match[2]; // Zip code 671 const city = match[3]; // City 672 // The country part is ignored 673 674 // Return the formatted address with line breaks 675 return `${street}<br>${zip} ${city}`; 676 } 677 return address; // If no match found, return the address as-is 678 } 679 680 function isDutchZipCode(value) { 681 const dutchZipCodeRegex = /^\d{4}\s?[A-Za-z]{2}$/; 682 return dutchZipCodeRegex.test(value); 683 } 684 685 function isBelgianZipCode(value) { 686 const belgianZipCodeRegex = /^\d{4}$/; 687 return belgianZipCodeRegex.test(value); 688 } 689 690 function getRegion(value) { 691 if (isDutchZipCode(value)) { 692 return 'nl'; 693 } else if (isBelgianZipCode(value)) { 694 return 'be'; 695 } 696 return 'nl'; 697 } 698 </script> 699 @SnippetEnd("JavaScript") 700 } 701 }
