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