Error executing template "Designs/Swift/Swift_Page.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
at CompiledRazorTemplates.Dynamic.RazorEngine_315c39c7ac1c4b6c9cf6b42be86b10a0.ExecuteAsync()
at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel>
2 @using System
3 @using Dynamicweb
4 @using Dynamicweb.Core
5 @using Dynamicweb.Environment
6 @using Dynamicweb.Frontend
7
8 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@
9
10 @{
11 var brandingPageId = Model.Area.Item?.GetInt32("BrandingPage") ?? 0;
12 var themePageId = Model.Area.Item?.GetInt32("ThemesPage") ?? 0;
13 var cssPageId = Model.Area.Item?.GetInt32("CssPage") ?? 0;
14 var brandingPage = brandingPageId != 0 ? Dynamicweb.Content.Services.Pages?.GetPage(brandingPageId) ?? null : null;
15 var themesParagraphs = themePageId != 0 ? Dynamicweb.Content.Services.Paragraphs?.GetParagraphsByPageId(themePageId) ?? null : null;
16 var cssParagraphs = cssPageId != 0 ? Dynamicweb.Content.Services.Paragraphs?.GetParagraphsByPageId(cssPageId) ?? null : null;
17 }
18
19 @functions
20 {
21 //CUSTOM - TODO: Move into project
22 private Dynamicweb.Frontend.PageInfoViewModel? GetClosestPageByItemType(Dynamicweb.Content.Page page, string itemType)
23 {
24 if (page != null)
25 {
26 if (page.ItemType == itemType)
27 {
28 return Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(page);
29 }
30 else if (page.Parent != null)
31 {
32 return GetClosestPageByItemType(page.Parent, itemType);
33 }
34 }
35 return null;
36 }
37 //--CUSTOM
38 }
39
40 @if (themesParagraphs != null || brandingPage != null)
41 {
42 string swiftVersion = ReadFile("/Files/Templates/Designs/Swift/swift_version.txt");
43 bool renderAsResponsive = Model.Area.Item.GetString("DeviceRendering", "responsive").Equals("responsive", StringComparison.OrdinalIgnoreCase);
44 bool renderMobile = Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Mobile || Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Tablet;
45 string responsiveClassDesktop = string.Empty;
46 string responsiveClassMobile = string.Empty;
47 if (renderAsResponsive)
48 {
49 responsiveClassDesktop = " d-none d-xl-block";
50 responsiveClassMobile = " d-block d-xl-none";
51 }
52
53 //CUSTOM
54 var urlParamRefPageID = Dynamicweb.Context.Current?.Request.GetInt32("RefPageID") ?? 0;
55 var clinicPageView = null as Dynamicweb.Frontend.PageInfoViewModel;
56 var clinicBodyClass = string.Empty;
57
58 if (urlParamRefPageID > 0)
59 {
60 var refClinicPageView = GetClosestPageByItemType(Dynamicweb.Content.Services.Pages.GetPage(urlParamRefPageID), "Swift_Custom_Clinic");
61 if (refClinicPageView != null)
62 {
63 clinicPageView = refClinicPageView;
64 clinicBodyClass = "custom-clinic-visit";
65
66 if (!Model.MetaTags.Contains("<meta name=\"robots\""))
67 {
68 Pageview.Meta.AddTag($"<meta name=\"robots\" content=\"noindex,nofollow\">");
69 }
70
71 if (!Model.MetaTags.Contains("<link rel=\"canonical\""))
72 {
73 Pageview.Meta.AddTag($"<link rel=\"canonical\" href=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(Pageview.Page.ID)}\">");
74 }
75 }
76 }
77
78 if (clinicPageView == null)
79 {
80 clinicPageView = GetClosestPageByItemType(Pageview.Page.Parent, "Swift_Custom_Clinic");
81 }
82
83 var headerDesktopLink = clinicPageView?.Item.GetLink("HeaderDesktop") ?? Model.Area.Item?.GetLink("HeaderDesktop") ?? null;
84 var headerMobileLink = clinicPageView?.Item.GetLink("HeaderMobile") ?? Model.Area.Item?.GetLink("HeaderMobile") ?? null;
85 var footerDesktopLink = clinicPageView?.Item.GetLink("FooterDesktop") ?? Model.Area.Item?.GetLink("FooterDesktop") ?? null;
86 var footerMobileLink = clinicPageView?.Item.GetLink("FooterMobile") ?? Model.Area.Item?.GetLink("FooterMobile") ?? null;
87 //--CUSTOM
88
89 var disableWideBreakpoints = Model.Area?.Item?.GetRawValueString("DisableWideBreakpoints", "default");
90
91 string customHeaderInclude = !string.IsNullOrEmpty(Model.Area.Item.GetRawValueString("CustomHeaderInclude")) ? (Model.Area.Item.GetFile("CustomHeaderInclude")?.Name ?? string.Empty) : string.Empty; //CUSTOM: Prevent error if file does not exist
92
93 var themesParagraphLastChanged = Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(themePageId).OrderByDescending(p => p.Audit.LastModifiedAt).FirstOrDefault();
94 var cssLastModified = brandingPage.Audit.LastModifiedAt > themesParagraphLastChanged.Audit.LastModifiedAt ? brandingPage.Audit.LastModifiedAt : themesParagraphLastChanged.Audit.LastModifiedAt;
95
96 var cssThemeAndBrandingStyleFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath($"/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_{Model.Area.ID}.min.css"));
97
98
99 if (cssPageId != 0)
100 {
101 var cssFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath($"/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_css_styles_{Model.Area.ID}.css"));
102 var cssParagraphLastChanged = Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(cssPageId).OrderByDescending(p => p.Audit.LastModifiedAt).FirstOrDefault();
103 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < cssParagraphLastChanged.Audit.LastModifiedAt)
104 {
105 var cssPageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(cssPageId);
106 cssPageview.Redirect = false;
107 cssPageview.Output();
108 }
109 }
110
111 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < brandingPage.Audit.LastModifiedAt)
112 {
113 //Branding page has been saved or the file is missing. Rewrite the file to disc.
114 if (brandingPageId > 0)
115 {
116 var brandingPageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(brandingPageId);
117 brandingPageview.Redirect = false;
118 brandingPageview.Output();
119 }
120 }
121
122 if (!cssThemeAndBrandingStyleFileInfo.Exists || cssThemeAndBrandingStyleFileInfo.LastWriteTime < themesParagraphLastChanged.Audit.LastModifiedAt)
123 {
124 //Branding page has been saved or the file is missing. Rewrite the file to disc.
125 if (themePageId > 0)
126 {
127 var themePageview = Dynamicweb.Frontend.PageView.GetPageviewByPageID(themePageId);
128 themePageview.Redirect = false;
129 themePageview.Output();
130 }
131 }
132
133 // Schema.org details for PDP
134 bool isProductDetailsPage = Dynamicweb.Context.Current.Request.QueryString.AllKeys.Contains("ProductID");
135 bool isArticlePage = Model.ItemType == "Swift_Article";
136 string schemaOrgType = string.Empty;
137
138 if (isProductDetailsPage)
139 {
140 schemaOrgType = "itemscope=\"\" itemtype=\"https://schema.org/Product\"";
141 }
142
143 if (isArticlePage)
144 {
145 schemaOrgType = "itemscope=\"\" itemtype=\"https://schema.org/Article\"";
146 }
147
148
149 var cssStyleFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath("/Files/Templates/Designs/Swift/Assets/css/styles.css"));
150 var jsFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath("/Files/Templates/Designs/Swift/Assets/js/scripts.js"));
151
152 string masterTheme = !string.IsNullOrWhiteSpace(Model.Area.Item.GetRawValueString("Theme")) ? " theme " + Model.Area.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
153
154 string favicon = Model.Area.Item.GetRawValueString("Favicon", "/Files/Templates/Designs/Swift/Assets/Images/favicon.png");
155 string appleTouchIcon = Model.Area.Item.GetRawValueString("AppleTouchIcon", "/Files/Templates/Designs/Swift/Assets/Images/apple-touch-icon.png");
156
157 string headerCssClass = "sticky-top";
158 bool movePageBehind = false;
159
160 if (Model.PropertyItem != null)
161 {
162 headerCssClass = Model.PropertyItem.GetRawValueString("MoveThisPageBehindTheHeader", "sticky-top");
163 movePageBehind = headerCssClass == "fixed-top" && !Pageview.IsVisualEditorMode ? true : false;
164 }
165
166 headerCssClass = headerCssClass == "" ? "sticky-top" : headerCssClass;
167 headerCssClass = Pageview.IsVisualEditorMode ? "" : headerCssClass;
168
169 string googleTagManagerID = Model.Area.Item.GetString("GoogleTagManagerID");
170 string googleAnalyticsMeasurementID = Model.Area.Item.GetString("GoogleAnalyticsMeasurementID");
171
172 bool allowTracking = true;
173 if (CookieManager.IsCookieManagementActive)
174 {
175 var cookieOptInLevel = CookieManager.GetCookieOptInLevel();
176 allowTracking = cookieOptInLevel == CookieOptInLevel.All || (cookieOptInLevel == CookieOptInLevel.Functional && CookieManager.GetCookieOptInCategories().Contains("Statistical"));
177 }
178
179 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/Assets/css/styles.css?{cssStyleFileInfo.LastWriteTime.Ticks}>; rel=preload; as=style;");
180 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_{Model.Area.ID}.min.css?{cssLastModified.Ticks}>; rel=preload; as=style;");
181 Dynamicweb.Context.Current.Response.AddHeader("link", $"</Files/Templates/Designs/Swift/Assets/js/scripts.js?{jsFileInfo.LastWriteTime.Ticks}>; rel=preload; as=script;");
182
183 SetMetaTags();
184
185 List<Dynamicweb.Content.Page> languages = new List<Dynamicweb.Content.Page>();
186
187 var masterPage = Pageview.Area.IsMaster ? Pageview.Page : Pageview.Page.MasterPage;
188 languages.Add(masterPage);
189 if (masterPage?.Languages != null)
190 {
191 foreach (var language in masterPage.Languages)
192 {
193 languages.Add(language);
194 }
195 }
196
197 Uri url = Dynamicweb.Context.Current.Request.Url;
198 string hostName = url.Host;
199
200 <!doctype html>
201 <html lang="@Pageview.Area.CultureInfo.TwoLetterISOLanguageName">
202 <head>
203 <!-- @swiftVersion -->
204 @* Required meta tags *@
205 <meta charset="utf-8">
206 <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0">
207 <link rel="shortcut icon" href="@favicon">
208 <link rel="apple-touch-icon" href="@appleTouchIcon">
209
210 @Model.MetaTags
211
212 @{
213 var alreadyWrittenTwoletterIsos = new List<string>();
214 @* Languages meta data *@
215 foreach (var language in languages)
216 {
217 hostName = url.Host;
218 if (language?.Area != null)
219 {
220 if (language.Area?.MasterArea != null && !string.IsNullOrEmpty(language.Area.MasterArea.DomainLock))
221 {
222 hostName = language.Area.MasterArea.DomainLock; //dk.domain.com or dk-domain.dk
223 }
224 if (language != null && language.Published && language.Area.Active && language.Area.Published)
225 {
226 if (!string.IsNullOrEmpty(language.Area.DomainLock))
227 {
228 hostName = language.Area.DomainLock; //dk.domain.com or dk-domain.dk
229 }
230 string querystring = $"Default.aspx?ID={language.ID}";
231 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["GroupID"]))
232 {
233 querystring += $"&GroupID={Dynamicweb.Context.Current.Request.QueryString["GroupID"]}";
234 }
235 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["ProductID"]))
236 {
237 querystring += $"&ProductID={Dynamicweb.Context.Current.Request.QueryString["ProductID"]}";
238 }
239 if (!string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["VariantID"]))
240 {
241 querystring += $"&VariantID={Dynamicweb.Context.Current.Request.QueryString["VariantID"]}";
242 }
243
244 string friendlyUrl = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(querystring);
245 if (language.Area.RedirectFirstPage && language.ParentPageId == 0 && language.Sort == 1)
246 {
247 friendlyUrl = "/";
248 }
249 string href = $"{url.Scheme}://{hostName}{friendlyUrl}";
250
251
252 <link rel="alternate" hreflang="@language.Area.CultureInfo.Name.ToLower()" href="@href">
253 if (!alreadyWrittenTwoletterIsos.Contains(language.Area.CultureInfo.TwoLetterISOLanguageName))
254 {
255 <link rel="alternate" hreflang="@language.Area.CultureInfo.TwoLetterISOLanguageName.ToLower()" href="@href">
256 }
257 }
258 }
259 }
260 }
261
262 <title>@Model.Title</title>
263 @* Bootstrap + Swift stylesheet *@
264 <link href="/Files/Templates/Designs/Swift/Assets/css/styles.css?@cssStyleFileInfo.LastWriteTime.Ticks" rel="stylesheet" media="all" type="text/css">
265
266 @if (disableWideBreakpoints != "disableBoth")
267 {
268 <style>
269 @@media ( min-width: 1600px ) {
270 .container-xxl,
271 .container-xl,
272 .container-lg,
273 .container-md,
274 .container-sm,
275 .container {
276 max-width: 1520px;
277 }
278 }
279 </style>
280
281
282
283 if (disableWideBreakpoints != "disableUltraWideOnly")
284 {
285 <style>
286 @@media ( min-width: 1920px ) {
287 .container-xxl,
288 .container-xl,
289 .container-lg,
290 .container-md,
291 .container-sm,
292 .container {
293 max-width: 1820px;
294 }
295 }
296 </style>
297 }
298 }
299
300 @* Branding and Themes min stylesheet *@
301 <link href="/Files/Templates/Designs/Swift/_parsed/Swift_css/Swift_styles_@(Model.Area.ID).min.css?@cssLastModified.Ticks" rel="stylesheet" media="all" type="text/css" data-last-modified-content="@cssLastModified">
302 <script src="/Files/Templates/Designs/Swift/Assets/js/scripts.js?@jsFileInfo.LastWriteTime.Ticks"></script>
303 <script type="module">
304 swift.Scroll.hideHeadersOnScroll();
305 swift.Scroll.handleAlternativeTheme();
306
307 //Only load if AOS
308 const aosColumns = document.querySelectorAll('[data-aos]');
309 if (aosColumns.length > 0) {
310 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/js/aos.js?@jsFileInfo.LastWriteTime.Ticks', 'js');
311 document.addEventListener('load.swift.assetloader', function () {
312 AOS.init({ duration: 400, delay: 100, easing: 'ease-in-out', mirror: false, disable: window.matchMedia('(prefers-reduced-motion: reduce)') });
313 });
314 }
315 </script>
316
317 @* Google tag manager *@
318 @if (!string.IsNullOrWhiteSpace(googleTagManagerID) && allowTracking)
319 {
320 <script>
321 (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
322 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
323 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
324 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
325 })(window, document, 'script', 'dataLayer', '@(googleTagManagerID)');
326
327 function gtag() { dataLayer.push(arguments); }
328 </script>
329 }
330
331 @if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking)
332 {
333 var GoogleAnalyticsDebugMode = "";
334
335 if (Model.Area.Item.GetBoolean("EnableGoogleAnalyticsDebugMode"))
336 {
337 GoogleAnalyticsDebugMode = ", {'debug_mode': true}";
338 }
339
340 <script async src="https://www.googletagmanager.com/gtag/js?id=@googleAnalyticsMeasurementID"></script>
341 <script>
342 window.dataLayer = window.dataLayer || [];
343 function gtag() { dataLayer.push(arguments); }
344 gtag('js', new Date());
345 gtag('config', '@googleAnalyticsMeasurementID'@GoogleAnalyticsDebugMode);
346 </script>
347 }
348
349 @if (allowTracking)
350 {
351 //CUSTOM
352 <script>
353 const CustomDataLayerPromise = async (arguments) => {
354 return new Promise((resolve, reject) => {
355 try {
356 window.dataLayer = window.dataLayer || [];
357 window.dataLayer.push(arguments);
358 resolve(true);
359 } catch (error) {
360 reject(error);
361 }
362 });
363 };
364
365 const CustomDataLayer = async (arguments) => {
366 try {
367 await CustomDataLayerPromise(arguments);
368 return true;
369 } catch (error) {
370 return false;
371 }
372 }
373
374 @if (clinicPageView != null)
375 {
376 <text>
377 const CustomDataLayerEvent = async (event, arguments) => {
378 return await CustomDataLayer({
379 ...{
380 'event': event,
381 'clinic_name': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(clinicPageView?.Item.GetString("DataLayerClinicName"))',
382 'clinic_type': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(clinicPageView?.Item.GetString("DataLayerClinicType"))',
383 'clinic_city': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(clinicPageView?.Item.GetString("DataLayerClinicCity"))',
384 'clinic_online_booking': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(clinicPageView?.Item.GetString("DataLayerClinicOnlineBooking"))',
385 },
386 ...arguments
387 });
388 }
389 </text>
390
391 if (Pageview.Page.Parent.ItemType == "Swift_Custom_Clinic" && Pageview.Page.Sort == 1)
392 {
393 <text>
394 CustomDataLayerEvent('view_clinic');
395 </text>
396 }
397 }
398
399 @if (Model.Item.GetString("Custom_DataLayerTreatmentTreatmentType").IsNotNullOrEmpty())
400 {
401 <text>
402 CustomDataLayer({
403 'event': 'view_treatment',
404 'treatment_name': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Pageview.Page.MenuText)',
405 'treatment_type': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentTreatmentType"))',
406 'urgent': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentUrgent"))',
407 'clinic_type': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentClinicType"))',
408 'regulated_price': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentRegulatedPrice"))',
409 'professional': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentProfessional"))',
410 'price_category': '@Dynamicweb.Core.Encoders.HtmlEncoder.JavaScriptStringEncode(Model.Item.GetString("Custom_DataLayerTreatmentPriceCategory"))'
411 });
412 </text>
413 }
414 </script>
415 //--CUSTOM
416 }
417
418 @if (!string.IsNullOrWhiteSpace(customHeaderInclude))
419 {
420 @RenderPartial($"Components/Custom/{customHeaderInclude}")
421 }
422 </head>
423 <body class="brand @(masterTheme) @(clinicBodyClass)" id="page@(Model.ID)"> @*//CUSTOM*@
424
425 @* Google tag manager *@
426 @if (!string.IsNullOrWhiteSpace(googleTagManagerID) && allowTracking)
427 {
428 <noscript>
429 <iframe src="https://www.googletagmanager.com/ns.html?id=@(googleTagManagerID)"
430 height="0" width="0" style="display:none;visibility:hidden"></iframe>
431 </noscript>
432 }
433
434 @if (renderAsResponsive || !renderMobile)
435 {
436 <header class="page-header @headerCssClass top-0@(responsiveClassDesktop)" id="page-header-desktop">
437 @if (headerDesktopLink != null)
438 {
439 @RenderGrid(headerDesktopLink.PageId)
440 }
441 </header>
442 }
443
444 @if ((renderAsResponsive || renderMobile))
445 {
446 <header class="page-header @headerCssClass top-0@(responsiveClassMobile)" id="page-header-mobile">
447 @if (headerMobileLink != null)
448 {
449 @RenderGrid(headerMobileLink.PageId)
450 }
451 </header>
452 }
453
454 <div data-intersect></div>
455
456 <main id="content" @(schemaOrgType)>
457 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel>
458 @using System
459 @using Dynamicweb.Ecommerce.ProductCatalog
460
461
462 @{
463 string productIdFromUrl = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("ProductID") : string.Empty;
464 bool isProductDetail = !string.IsNullOrEmpty(productIdFromUrl) && Pageview.Page.NavigationTag.ToLower() == "shop";
465
466 bool isArticlePagePage = Model.ItemType == "Swift_Article";
467 bool isArticleListPage = Model.ItemType == "Swift_ArticleListPage";
468 string schemaOrgProp = string.Empty;
469 if(isArticlePagePage)
470 {
471 schemaOrgProp = "itemprop=\"articleBody\"";
472 }
473
474 string theme = "";
475 string gridContent = "";
476
477 if (Model.PropertyItem != null)
478 {
479 theme = !string.IsNullOrWhiteSpace(Model.PropertyItem.GetRawValueString("Theme")) ? "theme " + Model.PropertyItem.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
480 }
481
482 if (Model.Item != null || Pageview.IsVisualEditorMode)
483 {
484 if (!isProductDetail)
485 {
486 gridContent = Model.Grid("Grid", "Grid", "default:true;sort:1", "Page");
487 }
488 else
489 {
490 var productObject = Dynamicweb.Ecommerce.Services.Products.GetProductById(productIdFromUrl, "", Pageview.Area.EcomLanguageId);
491 var detailPage = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(productObject.PrimaryGroupId)?.Meta.PrimaryPage ?? string.Empty;
492 var detailPageId = detailPage != string.Empty ? Convert.ToInt16(detailPage.Substring(detailPage.LastIndexOf('=') + 1)) : GetPageIdByNavigationTag("ProductDetailPage");
493
494 @RenderGrid(detailPageId)
495 }
496 }
497
498 bool doNotRenderPage = false;
499
500 //Check if we are on the poduct detail page, and if there is data to render
501 ProductViewModel product = new ProductViewModel();
502 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
503 {
504 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
505 if (string.IsNullOrEmpty(product.Id)) {
506 doNotRenderPage = true;
507 }
508 }
509
510 //Render the page
511 if (!doNotRenderPage) {
512 string itemIdentifier = Model?.Item?.SystemName != null ? "item_" + Model.Item.SystemName.ToLower() : "item_Swift_Page";
513
514 if (Pageview.IsVisualEditorMode) {
515 @Model.Placeholder("dwcontent", "content", "default:true;sort:1")
516 }
517
518 <div class="@theme @itemIdentifier" @schemaOrgProp>
519 @if (isArticleListPage)
520 {
521 var hx = $"hx-get=\"{Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(Model.ID)}\" hx-select=\"#content\" hx-target=\"#content\" hx-swap=\"outerHTML\" hx-trigger=\"change\" hx-headers='{{\"feed\": \"true\"}}' hx-push-url=\"true\" hx-indicator=\"#ArticleFacetForm\"";
522
523 <form @hx id="ArticleFacetForm">
524 @gridContent
525 </form>
526 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/htmx.js"></script>
527 <script type="module">
528 document.addEventListener('htmx:confirm', (event) => {
529 let filters = event.detail.elt.querySelectorAll('select');
530 for (var i = 0; i < filters.length; i++) {
531 let input = filters[i];
532 if (input.name && !input.value) {
533 input.name = '';
534 }
535 }
536 });
537
538 document.addEventListener('htmx:beforeOnLoad', (event) => {
539 swift.Scroll.stopIntersectionObserver();
540 });
541
542 document.addEventListener('htmx:afterOnLoad', () => {
543 swift.Scroll.hideHeadersOnScroll();
544 swift.Scroll.handleAlternativeTheme();
545 });
546 </script>
547 }
548 else
549 {
550 @gridContent
551 }
552 </div>
553
554 } else {
555 <div class="container">
556 <div class="alert alert-info" role="alert">@Translate("Sorry. There is nothing to view here")</div>
557 </div>
558 }
559
560 if (!Model.IsCurrentUserAllowed)
561 {
562 int signInPage = GetPageIdByNavigationTag("SignInPage");
563 int dashboardPage = GetPageIdByNavigationTag("MyAccountDashboardPage");
564
565 if (!Pageview.IsVisualEditorMode)
566 {
567 if (signInPage != 0)
568 {
569 if (signInPage != Model.ID) {
570 Dynamicweb.Context.Current.Response.Redirect("/Default.aspx?ID=" + signInPage);
571 } else {
572 if (dashboardPage != 0) {
573 Dynamicweb.Context.Current.Response.Redirect("/Default.aspx?ID=" + dashboardPage);
574 } else {
575 Dynamicweb.Context.Current.Response.Redirect("/");
576 }
577 }
578 }
579 else
580 {
581 <div class="alert alert-dark m-0" role="alert">
582 <span>@Translate("You do not have access to this page")</span>
583 </div>
584 }
585 }
586 else
587 {
588 <div class="alert alert-dark m-0" role="alert">
589 <span>@Translate("To work on this page, you must be signed in, in the frontend")</span>
590 </div>
591 }
592 }
593 }
594
595 </main>
596
597 @if (renderAsResponsive || !renderMobile)
598 {
599 <footer class="page-footer@(responsiveClassDesktop)" id="page-footer-desktop">
600 @if (footerDesktopLink != null)
601 {
602 @RenderGrid(footerDesktopLink.PageId)
603 }
604 </footer>
605 }
606
607 @if (renderAsResponsive || renderMobile)
608 {
609 <footer class="page-footer@(responsiveClassMobile)" id="page-footer-mobile">
610 @if (footerMobileLink != null)
611 {
612 @RenderGrid(footerMobileLink.PageId)
613 }
614 </footer>
615 }
616
617 @* Render any offcanvas menu here *@
618 @RenderSnippet("offcanvas")
619
620 @{
621 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Context.Current.Items["IsWebServiceConnectionAvailable"]);
622 }
623
624 @* Language selector modal *@
625 <div class="modal fade" id="PreferencesModal" tabindex="-1" aria-hidden="true">
626 <div class="modal-dialog modal-dialog-centered modal-sm" id="PreferencesModalContent">
627 @* The content here comes from an external request *@
628 </div>
629 </div>
630
631 @* Favorite toast *@
632 <div aria-live="polite" aria-atomic="true">
633 <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
634 <div id="favoriteNotificationToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
635 <div class="toast-header">
636 <strong class="me-auto">@Translate("Favorite list updated")</strong>
637 <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
638 </div>
639 <div class="toast-body d-flex gap-3">
640 <div id="favoriteNotificationToast_Image"></div>
641 <div id="favoriteNotificationToast_Text"></div>
642 </div>
643 </div>
644 </div>
645 </div>
646
647 @* Modal for dynamic content *@
648 <div class="modal fade js-product" id="DynamicModal" tabindex="-1" aria-hidden="true">
649 <div class="modal-dialog modal-dialog-centered modal-md">
650 <div class="modal-content theme light" id="DynamicModalContent">
651 @* The content here comes from an external request *@
652 </div>
653 </div>
654 </div>
655
656 @* Offcanvas for dynamic content *@
657 <div class="offcanvas offcanvas-end theme light" tabindex="-1" id="DynamicOffcanvas" style="width: 30rem">
658 @* The content here comes from an external request *@
659 </div>
660
661 @if (Model.Area.Item.GetBoolean("ShowErpDownMessage") && !Dynamicweb.Core.Converter.ToBoolean(Context.Current.Items["IsWebServiceConnectionAvailable"]))
662 {
663 string erpDownMessageTheme = !string.IsNullOrWhiteSpace(Model.Area.Item.GetRawValueString("ErpDownMessageTheme")) ? " theme " + Model.Area.Item.GetRawValueString("ErpDownMessageTheme").Replace(" ", "").Trim().ToLower() : "theme light";
664
665 <div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1040">
666 <div class="toast fade show border-0 @erpDownMessageTheme" role="alert" aria-live="assertive" aria-atomic="true">
667 <div class="toast-header">
668 <strong class="me-auto">@Translate("Connection down")</strong>
669 <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
670 </div>
671 <div class="toast-body">
672 @Translate("We are experiencing some connectivity issues. Not all features may be available to you.")
673 </div>
674 </div>
675 </div>
676 }
677 </body>
678 </html>
679 } else if (Pageview.IsVisualEditorMode) {
680 <head>
681 <title>@Model.Title</title>
682 @* Bootstrap + Swift stylesheet *@
683 <link href="/Files/Templates/Designs/Swift/Assets/css/styles.css" rel="stylesheet" media="all" type="text/css">
684 </head>
685 <body class="p-3">
686 <div class="alert alert-danger" role="alert">
687 @Translate("Basic Swift setup is needed!")
688 </div>
689
690 @if (brandingPage == null) {
691 <div class="alert alert-warning" role="alert">
692 @Translate("Please add a Branding page and reference it in website settings")
693 </div>
694 }
695
696 @if (themesParagraphs == null) {
697 <div class="alert alert-warning" role="alert">
698 @Translate("Please add a Themes collection page and reference it in website settings")
699 </div>
700 }
701 </body>
702 }
703
704
705 @functions {
706 void SetMetaTags()
707 {
708 //Verification Tokens
709 string siteVerificationGoogle = Model.Area.Item.GetString("Google_Site_Verification") != null ? Model.Area.Item.GetString("Google_Site_Verification") : "";
710
711 //Generic Site Values
712 string openGraphFacebookAppID = Model.Area.Item.GetString("Fb_app_id") != null ? Model.Area.Item.GetString("Fb_app_id") : "";
713 string openGraphType = Model.Area.Item.GetString("Open_Graph_Type") != null ? Model.Area.Item.GetString("Open_Graph_Type") : "";
714 string openGraphSiteName = Model.Area.Item.GetString("Open_Graph_Site_Name") != null ? Model.Area.Item.GetString("Open_Graph_Site_Name") : "";
715
716 string twitterCardSite = Model.Area.Item.GetString("Twitter_Site") != null ? Model.Area.Item.GetString("Twitter_Site") : "";
717
718 //Page specific values
719 string openGraphSiteTitle = Model.Area.Item.GetString("Open_Graph_Title") != null ? Model.Area.Item.GetString("Open_Graph_Title") : "";
720 FileViewModel openGraphImage = Model.Area.Item.GetFile("Open_Graph_Image");
721 string openGraphImageALT = Model.Area.Item.GetString("Open_Graph_Image_ALT") != null ? Model.Area.Item.GetString("Open_Graph_Image_ALT") : "";
722 string openGraphDescription = Model.Area.Item.GetString("Open_Graph_Description") != null ? Model.Area.Item.GetString("Open_Graph_Description") : "";
723
724 string twitterCardURL = Model.Area.Item.GetString("Twitter_URL") != null ? Model.Area.Item.GetString("Twitter_URL") : "";
725 string twitterCardTitle = Model.Area.Item.GetString("Twitter_Title") != null ? Model.Area.Item.GetString("Twitter_Title") : "";
726 string twitterCardDescription = Model.Area.Item.GetString("Twitter_Description") != null ? Model.Area.Item.GetString("Twitter_Description") : "";
727 FileViewModel twitterCardImage = Model.Area.Item.GetFile("Twitter_Image");
728 string twitterCardImageALT = Model.Area.Item.GetString("Twitter_Image_ALT") != null ? Model.Area.Item.GetString("Twitter_Image_ALT") : "";
729
730 if (string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString["ProductID"]))
731 {
732 if (!string.IsNullOrEmpty(Model.Description))
733 {
734 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{Model.Description}\">");
735 }
736 else
737 {
738 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{openGraphDescription}\">");
739 }
740
741 if (!string.IsNullOrEmpty(Pageview.Page.TopImage))
742 {
743 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}/Files{Pageview.Page.TopImage}\">");
744 Pageview.Meta.AddTag($"<meta property=\"og:image:secure_url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}/Files{Pageview.Page.TopImage}\">");
745 }
746 else if (openGraphImage != null)
747 {
748 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}\">");
749 Pageview.Meta.AddTag($"<meta property=\"og:image:secure_url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}\">");
750 }
751
752 if (!string.IsNullOrEmpty(openGraphImageALT))
753 {
754 Pageview.Meta.AddTag($"<meta property=\"og:image:alt\" content=\"{openGraphImageALT}\">");
755 }
756 if (!string.IsNullOrEmpty(twitterCardDescription))
757 {
758 Pageview.Meta.AddTag("twitter:description", twitterCardDescription);
759 }
760
761 if (!string.IsNullOrEmpty(Pageview.Page.TopImage))
762 {
763 Pageview.Meta.AddTag("twitter:image", $"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}/Files{Pageview.Page.TopImage}");
764 }
765 else if (twitterCardImage != null)
766 {
767 Pageview.Meta.AddTag("twitter:image", $"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{openGraphImage.Path}");
768 }
769
770 if (!string.IsNullOrEmpty(twitterCardImageALT))
771 {
772 Pageview.Meta.AddTag("twitter:image:alt", twitterCardImageALT);
773 }
774 }
775
776 if (!string.IsNullOrEmpty(siteVerificationGoogle))
777 {
778 Pageview.Meta.AddTag("google-site-verification", siteVerificationGoogle);
779 }
780
781 if (!string.IsNullOrEmpty(openGraphFacebookAppID))
782 {
783 Pageview.Meta.AddTag($"<meta property=\"fb:app_id\" content=\"{openGraphFacebookAppID}\">");
784 }
785
786 if (!string.IsNullOrEmpty(openGraphType))
787 {
788 Pageview.Meta.AddTag($"<meta property=\"og:type\" content=\"{openGraphType}\">");
789 }
790
791 if (!string.IsNullOrEmpty(openGraphSiteName))
792 {
793 Pageview.Meta.AddTag($"<meta property=\"og:url\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Pageview.SearchFriendlyUrl}\">");
794 }
795
796 if (!string.IsNullOrEmpty(openGraphSiteName))
797 {
798 Pageview.Meta.AddTag($"<meta property=\"og:site_name\" content=\"{openGraphSiteName}\">");
799 }
800
801 if (!string.IsNullOrEmpty(Model.Title))
802 {
803 Pageview.Meta.AddTag($"<meta property=\"og:title\" content=\"{Model.Title}\">");
804 }
805 else
806 {
807 Pageview.Meta.AddTag($"<meta property=\"og:title\" content=\"{openGraphSiteTitle}\">");
808 }
809
810 if (!string.IsNullOrEmpty(twitterCardSite))
811 {
812 Pageview.Meta.AddTag("twitter:site", twitterCardSite);
813 }
814
815 if (!string.IsNullOrEmpty(twitterCardURL))
816 {
817 Pageview.Meta.AddTag("twitter:url", twitterCardURL);
818 }
819
820 if (!string.IsNullOrEmpty(twitterCardTitle))
821 {
822 Pageview.Meta.AddTag("twitter:title", twitterCardTitle);
823 }
824 }
825 }
826