Error executing template "Designs/Swift/Paragraph/Swift_ProductListGridView_EA.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
at CompiledRazorTemplates.Dynamic.RazorEngine_6ea607fe4ba94c958b6f87de90340bb8.Execute() in D:\dynamicweb.net\Solutions\ContextAnd\Plus_Prod\Files\Templates\Designs\Swift\Paragraph\Swift_ProductListGridView_EA.cshtml:line 2056
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.ParagraphViewModel>
2 @using System
3 @using System.Collections.Generic
4 @using Dynamicweb.Ecommerce.ProductCatalog
5 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites
6 @using System.Linq
7 @using Dynamicweb.Core
8 @using Dynamicweb.Ecommerce
9 @using Dynamicweb.Environment
10 @using Plus.CustomModules.ExcelGenerator.Operations
11 @using Plus.CustomModules.Helpers
12
13 @functions
14 {
15 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsLazyLoadingForProductInfoEnabled;
16 string liveInfoClass = "";
17 string productInfoFeed = "";
18
19 string showPricesWithVat = "";
20 bool neverShowVat = false;
21
22 bool isDetailPage = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID"));
23
24 ProductListViewModel productList = new ProductListViewModel();
25 }
26
27 @{
28 if (Dynamicweb.Context.Current.Items.Contains("ProductList"))
29 {
30 productList = (ProductListViewModel)Dynamicweb.Context.Current.Items["ProductList"];
31 }
32
33 showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
34 neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
35
36 if (isLazyLoadingForProductInfoEnabled)
37 {
38 if (Dynamicweb.Context.Current.Items.Contains("ProductInfoFeed"))
39 {
40 productInfoFeed = Dynamicweb.Context.Current.Items["ProductInfoFeed"]?.ToString();
41 if (!string.IsNullOrEmpty(productInfoFeed))
42 {
43 productInfoFeed = $"data-product-info-feed=\"{productInfoFeed}\"";
44 }
45 }
46 liveInfoClass = "js-live-info";
47 }
48
49 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
50 string themePadding = theme != string.Empty ? "px-0 py-3" : string.Empty;
51 var CartId = Dynamicweb.Ecommerce.Common.Context.Cart == null ? "" : Dynamicweb.Ecommerce.Common.Context.Cart.Id;
52 var CartValue = "";
53 var CartUrl = "";
54 var order = Dynamicweb.Ecommerce.Services.Orders.GetById(CartId);
55
56 }
57
58 @if (!isDetailPage)
59 {
60 var productCount = productList.Group != null ? Dynamicweb.Ecommerce.Services.ProductGroups?.GetGroup(productList.Group.Id).Products.Count : 1;
61
62 FacetsInfo.GetFacetsInfo(productList, out var facetsFound, out var selectedFacetsCount);
63
64 //var group = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(productList.Group.Id);
65 //var productCount = group.Products.Count;
66
67 if (!string.IsNullOrEmpty(theme))
68 {
69 if (productCount > 0 || selectedFacetsCount > 0)
70 {
71 <div class="h-100@(theme) @themePadding item_@Model.Item.SystemName.ToLower()" @productInfoFeed>
72 @{@RenderProductList()}
73 </div>
74 }
75
76 }
77 else
78 {
79 if (productCount > 0)
80 {
81 <div class="@themePadding item_@Model.Item.SystemName.ToLower() @productCount" @productInfoFeed>
82 @{@RenderProductList()}
83 </div>
84 }
85
86 }
87 }
88
89 @helper RenderProductList()
90 {
91 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
92 bool anonymousUser = Pageview.User == null;
93 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable();
94
95 string productTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ProductTheme")) ? " theme " + Model.Item.GetRawValueString("ProductTheme").Replace(" ", "").Trim().ToLower() : "";
96 string productThemePadding = productTheme != string.Empty ? "p-3" : string.Empty;
97
98 string url = Dynamicweb.Context.Current.Request.RawUrl;
99 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false;
100 bool showFavoritesSelectorMasterProduct = !string.IsNullOrEmpty(Model.Item.GetString("ShowFavoritesSelectorMasterProduct")) ? Model.Item.GetBoolean("ShowFavoritesSelectorMasterProduct") : false;
101 string staticVariantsLayout = Model.Item.GetRawValueString("StaticVariantsLayout", "hide");
102
103 string groupId = productList?.Group?.Id != null ? productList.Group.Id : "";
104
105 var badgeParms = new Dictionary<string, object>();
106 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
107 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
108 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
109 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
110 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
111
112 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
113 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
114
115 string googleAnalyticsTrackingID = Pageview.AreaSettings.GetString("GoogleAnalyticsTrackingID");
116 string googleAnalyticsMeasurementID = Pageview.AreaSettings.GetString("GoogleAnalyticsMeasurementID");
117 var cookieOptInLevel = CookieManager.GetCookieOptInLevel();
118 bool allowTracking = cookieOptInLevel == CookieOptInLevel.All || (cookieOptInLevel == CookieOptInLevel.Functional && CookieManager.GetCookieOptInCategories().Contains("Statistical"));
119 var bagdeItems = Model.Item?.GetItems("Bagdes") ?? Enumerable.Empty<Dynamicweb.Frontend.ItemViewModel>().ToList();
120 var mobileGrid = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GridLayoutMobile", "grid-1")) ? Model.Item.GetRawValueString("GridLayoutMobile", "grid-1") : "grid-1";
121
122 var favoriteParameters = new Dictionary<string, object>();
123 if (!anonymousUser && !hideFavoritesSelector)
124 {
125 int defaultFavoriteListId = 0;
126
127 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists();
128 if (favoreiteLists.Count() == 1)
129 {
130 foreach (FavoriteList list in favoreiteLists)
131 {
132 defaultFavoriteListId = list.ListId;
133 }
134 }
135
136 favoriteParameters.Add("ListId", defaultFavoriteListId);
137 }
138
139 if (productList.TotalProductsCount > 0)
140 {
141 if (Pageview.IsVisualEditorMode)
142 {
143 <div class="alert alert-dark m-0" role="alert">
144 <span>@Translate("Product list: The list will be shown here, if any")</span>
145 </div>
146 }
147 else
148 {
149
150 int pageSizeSetting = 30;
151 int pageSize = productList.PageSize;
152 pageSize += pageSizeSetting;
153
154 int loadedProducts = productList.PageSize > productList.TotalProductsCount ? productList.TotalProductsCount : productList.PageSize;
155
156 //indsæt check på Grid colums felt på mobile når itemtype er opdateret.
157
158 <div class="grid @mobileGrid grid-md-2 grid-lg-3 grid-xl-4">
159
160 @foreach (ProductViewModel product in productList.Products)
161 {
162 string variantIdForLink = !string.IsNullOrEmpty(product.VariantId) ? $"&VariantID={product.VariantId}" : "";
163 variantIdForLink = string.IsNullOrEmpty(variantIdForLink) && !string.IsNullOrEmpty(product.DefaultVariantId) ? $"&VariantID={product.DefaultVariantId}" : variantIdForLink;
164
165 string link = "Default.aspx?ID=" + GetPageIdByNavigationTag("Shop");
166 link += $"&GroupID={product.PrimaryOrDefaultGroup.Id}";
167 link += $"&ProductID={product.Id}";
168 link += variantIdForLink;
169 link = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(link);
170
171 string imagePath = product?.DefaultImage?.Value ?? "";
172 imagePath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath);
173
174 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
175 ratio = ratio != "0" ? ratio : "";
176 string ratioCssClass = ratio != "" ? " ratio" : "";
177 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
178
179 string imagePathXs = "/Admin/Public/GetImage.ashx?width=" + 480 + "&image=" + imagePath + "&format=webp";
180 string imagePathS = "/Admin/Public/GetImage.ashx?width=" + 640 + "&image=" + imagePath + "&format=webp";
181 string imagePathFallBack = "/Admin/Public/GetImage.ashx?width=" + 640 + "&image=" + imagePath + "&format=webp";
182
183 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
184 string imageThemePadding = imageTheme != string.Empty ? "px-0 py-3" : string.Empty;
185 string imageOutlineStyle = imageTheme == string.Empty ? "style=\"border: 1px solid transparent\"" : string.Empty;
186
187 string imageId = "ProductImage_" + product.Id + product.VariantId;
188 string priceId = "ProductPrice_" + product.Id + product.VariantId;@* Alternative image *@ var supportedImageFormats = new string[] { ".jpg", ".webp", ".png", ".gif" };
189 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
190 var selectedAssetCategories = Model.Item.GetRawValueString("AlternativeImageAssets");
191 IEnumerable<MediaViewModel> alternativeImagesList = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
192
193 if (alternativeImagesList.FirstOrDefault() != null)
194 {
195 alternativeImagesList = alternativeImagesList.OrderByDescending(x => x.Value.Equals(defaultImage));
196
197 if (alternativeImagesList.First().Value == defaultImage)
198 {
199 alternativeImagesList = alternativeImagesList.Skip(1);
200 }
201 }
202
203 string alternativeImage = alternativeImagesList.FirstOrDefault() != null ? alternativeImagesList.FirstOrDefault().Value : "";
204 alternativeImage = !string.IsNullOrEmpty(alternativeImage) ? "/Admin/Public/GetImage.ashx?width=" + 640 + "&image=" + alternativeImage + "&format=webp" : ""; @* Badges *@ DateTime createdDate = product.Created.Value;
205 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
206 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
207 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; @* Main features *@ IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList();
208 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>();
209
210 foreach (var selection in selectedDisplayGroups)
211 {
212 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values)
213 {
214 if (selection == group.Id)
215 {
216 mainFeatures.Add(group);
217 }
218 }
219 }
220
221 FieldValueViewModel pageContent;
222 product.ProductFields.TryGetValue("EFA_PageContentID", out pageContent);
223 var pageContentID = !string.IsNullOrEmpty(pageContent.Value.ToString()) ? Convert.ToInt32(pageContent.Value) : 0;
224
225 if (pageContentID != 0)
226 {
227 var paragraphs = Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(pageContentID);
228
229 if (paragraphs.Count() <= 1)
230 {
231 <article class="position-relative@(productTheme) product-list-item js-product @liveInfoClass" data-product-id="@product.Id">
232 @{
233 foreach (var paragraph in paragraphs)
234 {
235 <div class="d-block h-100 align-items-stretch px-0 py-3">
236 @RenderParagraphContent(paragraph.ID)
237 </div>
238 };
239 }
240 </article>
241 }
242 }
243 else
244 {
245 <article class="position-relative@(productTheme) product-list-item js-product @liveInfoClass" data-product-id="@product.Id" itemscope itemtype="https://schema.org/Product">
246 @if (!anonymousUser)
247 {
248 if (!hideFavoritesSelector && product.VariantInfo.VariantInfo == null)
249 {
250 <div class="position-absolute top-0 end-0 my-3" style="z-index: 2">
251 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
252 </div>
253 }
254 else if (showFavoritesSelectorMasterProduct)
255 {
256 <div class="position-absolute top-0 end-0 my-3" style="z-index: 2">
257 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
258 </div>
259 }
260 }
261
262 @if (showBadges)
263 {
264 <div class="position-absolute top-0 left-0 p-1 p-lg-2 ps-0 ps-lg-0" style="z-index: 2">
265 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
266 </div>
267 }
268
269 <div class="d-flex flex-column d-block h-100 align-items-stretch">
270 @{
271 string clickProductLink = string.Empty;
272 if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking)
273 {
274 clickProductLink = "onclick=\"return clickProductLink('" + product.Id + "', '" + product.Name + "', '" + product.VariantName + "', '" + product.Price.CurrencyCode + "', '" + product.Price.Price + "')\"";
275 }
276 }
277 <a href="@link" class="text-decoration-none d-flex flex-column j h-100" @clickProductLink>
278 @if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking)
279 {
280 <script>
281 function clickProductLink(productId, productName, productVariant, productCurrency, productPrice) {
282 if (typeof gtag !== "undefined") {
283 gtag("event", "select_item", {
284 item_list_id: "product_list_gridview",
285 item_list_name: "Product list (Gridview)",
286 items: [
287 {
288 item_id: productId,
289 item_name: productName,
290 currency: productCurrency,
291 item_list_id: "product_list_gridview",
292 item_list_name: "Product list (Gridview)",
293 item_variant: productVariant,
294 price: productPrice
295 }
296 ]
297 });
298 }
299 }
300 </script>
301 }
302
303 <div class="px-0 order-2">
304 <div class="flex-grow-1">
305 <h3 class="h6 mb-0 text-break" itemprop="name">
306 @product.Name
307 @if (!string.IsNullOrEmpty(product.VariantName))
308 {<text>(@product.VariantName)</text>}
309 </h3>
310 @if (!Model.Item.GetBoolean("HideProductNumber"))
311 {
312 <p class="fs-7 opacity-85 mb-2">@product.Number</p>
313 }
314 @if (mainFeatures.Count > 0)
315 {
316 <ul class="p-0 lh-sm opacity-75" style="list-style-position: inside">
317 @foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
318 {
319 foreach (var fieldViewModel in mainFeatureGroup.Fields)
320 {
321 var field = fieldViewModel.Value;
322 string fieldValue = field?.Value is object ? field.Value.ToString() : "";
323
324 if (fieldValue != "")
325 {
326 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
327 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
328
329 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
330 {
331 fieldValue = "";
332
333 foreach (FieldOptionValueViewModel option in field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>)
334 {
335 fieldValue = option.Name;
336 }
337 }
338
339 bool isColor = false;
340 if (fieldValue.Contains("#") && (Translate(field.Name) == Translate("Color") || Translate(field.Name) == Translate("Colour")))
341 {
342 isColor = true;
343 }
344
345 if (!string.IsNullOrEmpty(fieldValue))
346 {
347 if (!isColor)
348 {
349 <li>@(field.Name): @fieldValue</li>
350 }
351 else
352 {
353 <li class="position-relative">
354 <span class="colorbox-sm" style="background-color: @fieldValue"></span>
355 </li>
356 }
357 }
358 }
359 }
360 }
361 </ul>
362
363 }
364 </div>
365
366 @if (product.VariantInfo.VariantInfo != null && staticVariantsLayout == "swatches")
367 {
368 var optionCount = product.VariantInfo.VariantInfo.Count();
369 var showMaxVariants = 5;
370
371 <div class="d-flex flex-row gap-1 align-items-center">
372 @foreach (VariantInfoViewModel variant in product.VariantInfo.VariantInfo.Take(showMaxVariants))
373 {
374 <span class="colorbox colorbox-sm rounded-circle border me-1" style="background-color: @variant.OptionColor"></span>
375
376 }
377 @if (optionCount > showMaxVariants)
378 {
379 int left = optionCount - showMaxVariants;
380 <span class="ms-2">+@left</span>
381
382 }
383 </div>
384 }
385 </div>
386
387 <div class="overflow-hidden order-1 @(imageTheme) pb-2">
388 <div class="d-flex justify-content-center align-items-center">
389 @{
390 FieldValueViewModel plusDesignerIndikator;
391 product.ProductFields.TryGetValue("PlusDesignerLinkEA", out plusDesignerIndikator);
392 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") ? "target=\"_blank\"" : string.Empty;
393 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") ? "rel=\"noopener\"" : string.Empty;
394
395 if (!String.IsNullOrEmpty(plusDesignerIndikator.Value.ToString()))
396 {
397 <div class="plus-designer__container" style="position: absolute; left: 0; margin-bottom: 55%; background: #fff; border: 1px solid #228B64; border-left: none; z-index: 100; width: 75%; max-width: 200px; ">
398 <object>
399 <a href="@plusDesignerIndikator.Value" @target @rel class="text-decoration-none">
400 <div class="plus-designer__inner p-1 ps-3">
401 <p class="m-0 opacity-75 fs-7">Try me in</p>
402 <div class="plus-designer__image-container d-flex w-100">
403 <img class="" src="/Files/Templates/Designs/Swift/Assets/Images/plusdesignerlogo.svg" alt="Alternate Text" style="width:90%" />
404 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right" style="width:10%"><polyline points="9 18 15 12 9 6"></polyline></svg>
405 </div>
406 </div>
407 </a>
408 </object>
409 </div>
410
411 }
412 FieldValueViewModel plusBadge;
413 product.ProductFields.TryGetValue("Art", out plusBadge);
414 FieldValueViewModel fscBadge;
415 product.ProductFields.TryGetValue("FSC", out fscBadge);
416 FieldValueViewModel extraBadgeListObject;
417 product.ProductFields.TryGetValue("ExtraPlusBadges", out extraBadgeListObject);
418 FieldValueViewModel fscBadgeLink;
419 product.ProductFields.TryGetValue("FSC_link", out fscBadgeLink);
420 FieldValueViewModel pefcBadgeLink;
421 product.ProductFields.TryGetValue("PEFC_link", out pefcBadgeLink);
422 <div></div>
423 if (!String.IsNullOrEmpty(plusBadge.Value.ToString()) || !String.IsNullOrEmpty(fscBadge.Value.ToString()) || extraBadgeListObject.Value != null)
424 {
425 foreach (var badge in bagdeItems)
426 {
427 if (fscBadge.Value.ToString().Contains(badge.GetString("Title")))
428 {
429
430 <div class="plus-badge d-flex justify-content-center w-100 fsc-badge-list efa-mod ">
431 <object class="w-100">
432 <div class="d-flex justify-content-start w-75 h-75 efa-mod m-auto">
433 @if (fscBadge.Value.ToString().Contains("FSC"))
434 {
435 <a href="@fscBadgeLink.Value.ToString()" target="_blank" class="w-25"><img class="plus-badge_img fsc-badge_img w-100 efa-mod" srcset="@badge.GetString("Image")" src="" alt="@fscBadge.Value.ToString()" /></a>
436 }
437 @if (fscBadge.Value.ToString().Contains("PEFC"))
438 {
439 <a href="@pefcBadgeLink.Value.ToString()" target="_blank" class="w-25"><img class="plus-badge_img fsc-badge_img w-100 efa-mod" srcset="@badge.GetString("Image")" src="" alt="@fscBadge.Value.ToString()" /></a>
440 }
441 </div>
442 </object>
443 </div>
444 }
445 var badgeTitle = badge.GetString("Title");
446
447 switch (product.LanguageId)
448 {
449 case "LANG3":
450 {
451 if (badgeTitle.StartsWith(plusBadge.Value.ToString()) && badgeTitle.EndsWith("_DE"))
452 {
453 @RenderBadge(badge.GetString("Image"), plusBadge.Value.ToString())
454 }
455 break;
456 }
457 case "LANG4":
458 {
459 if (badgeTitle.StartsWith(plusBadge.Value.ToString()) && badgeTitle.EndsWith("_SE"))
460 {
461 @RenderBadge(badge.GetString("Image"), plusBadge.Value.ToString())
462 }
463 break;
464 }
465 case "LANG5":
466 {
467 if (badgeTitle.StartsWith(plusBadge.Value.ToString()) && badgeTitle.EndsWith("_NO"))
468 {
469 @RenderBadge(badge.GetString("Image"), plusBadge.Value.ToString())
470 }
471 break;
472 }
473 default:
474 {
475 if (badgeTitle.StartsWith(plusBadge.Value.ToString()) && !badgeTitle.EndsWith("_NO") && !badgeTitle.EndsWith("_SE") && !badgeTitle.EndsWith("_DE"))
476 {
477 @RenderBadge(badge.GetString("Image"), plusBadge.Value.ToString())
478 }
479 break;
480 }
481 }
482
483 if (extraBadgeListObject.Value is List<FieldOptionValueViewModel>)
484 {
485 var extraBadgeList = (List<FieldOptionValueViewModel>)extraBadgeListObject.Value;
486 foreach (var extraBadge in extraBadgeList)
487 {
488 var extraBadgeTitle = extraBadge.Value;
489
490 switch (product.LanguageId)
491 {
492 case "LANG3":
493 {
494 if (badgeTitle.StartsWith(extraBadgeTitle) && badgeTitle.EndsWith("_DE"))
495 {
496 @RenderExtraBadge(System.Web.HttpUtility.UrlPathEncode(badge.GetString("Image")), extraBadge.Value.ToString())
497 }
498 break;
499 }
500 case "LANG4":
501 {
502 if (badgeTitle.StartsWith(extraBadgeTitle) && badgeTitle.EndsWith("_SE"))
503 {
504 @RenderExtraBadge(System.Web.HttpUtility.UrlPathEncode(badge.GetString("Image")), extraBadge.Value.ToString())
505 }
506 break;
507 }
508 case "LANG5":
509 {
510 if (badgeTitle.StartsWith(extraBadgeTitle) && badgeTitle.EndsWith("_NO"))
511 {
512 @RenderExtraBadge(System.Web.HttpUtility.UrlPathEncode(badge.GetString("Image")), extraBadge.Value.ToString())
513 }
514 break;
515 }
516 default:
517 {
518 if (badgeTitle.StartsWith(extraBadgeTitle) && !badgeTitle.EndsWith("_NO") && !badgeTitle.EndsWith("_SE") && !badgeTitle.EndsWith("_DE"))
519 {
520 @RenderExtraBadge(System.Web.HttpUtility.UrlPathEncode(badge.GetString("Image")), extraBadge.Value.ToString())
521 }
522 break;
523 }
524 }
525 }
526 }
527 }
528 }
529
530 }
531 @if (string.IsNullOrEmpty(alternativeImage))
532 {
533 <img id="@imageId" itemprop="image"
534 srcset="
535 @imagePathXs 480w,
536 @imagePathS 640w"
537 sizes="(min-width: 992px) 33vw, 50vw"
538 src="@imagePathFallBack"
539 loading="lazy"
540 decoding="async"
541 class="w-75 mw-100 mh-100 @imageThemePadding"
542 alt="@product.Name">
543 }
544 else
545 {
546 <img id="@imageId" itemprop="image"
547 src="@imagePathFallBack"
548 loading="lazy"
549 decoding="async"
550 class="w-75 mw-100 mh-100 @imageThemePadding"
551 alt="@product.Name"
552 onmouseover="this.src='@alternativeImage'"
553 onmouseout="this.src='@imagePathFallBack'">
554
555 }
556 </div>
557
558 @if (product.VariantInfo.VariantInfo != null && staticVariantsLayout == "images")
559 {
560 int variantGroupCount = 0;
561 int showMaxVariantGroups = 2;
562 int showMaxVariants = 3;
563 var productVariantTheme = productTheme != "" ? productTheme : "bg-white";
564
565 <div class="position-relative">
566 <div id="StaticVariants_@product.Id" class="static-variants w-100 d-none d-lg-block position-absolute left-0 bottom-0 @productTheme" style="pointer-events: none;">
567
568 @foreach (var variantGroup in product.VariantGroups())
569 {
570 int variantsCount = 0;
571
572 <div class="d-flex gap-2 mb-2">
573 @foreach (var variant in variantGroup.Options)
574 {
575 if (variantGroupCount < showMaxVariantGroups)
576 {
577 var optionsCount = variantGroup.Options.Count();
578
579 if (variantsCount < showMaxVariants)
580 {
581 string optionWidth = !string.IsNullOrEmpty(variant.Color) ? "w-25" : "";
582
583 <article class="static-variants-option @optionWidth @(productVariantTheme)" title="@product.Name @variant.Name" style="pointer-events: initial;">
584 @if (!string.IsNullOrEmpty(variant.Color))
585 {
586 string defaultProductImage = Dynamicweb.Context.Current.Server.UrlEncode(product.DefaultImage.Value);
587 string variantImage = Dynamicweb.Context.Current.Server.UrlEncode(variant.Image.Value);
588 string defaultPrice = !hidePrice ? product.Price.PriceFormatted : "0";
589 string variantPrice = !hidePrice ? product.Price.PriceFormatted : "0";
590
591 if (isLazyLoadingForProductInfoEnabled)
592 {
593 <figure class="w-100 d-block m-0" data-price-formatted="" onmouseover="swift.StaticVariants.SwitchProduct(event, '@product.Id', this.getAttribute('data-price-formatted'), '@variantImage')" onmouseout="swift.StaticVariants.SwitchProduct(event, '@product.Id', this.getAttribute('data-price-formatted'), '@defaultProductImage')">
594 <div class="d-flex align-items-center justify-content-center">
595 <img src="/admin/public/GetImage.ashx?image=@variantImage&width=75&height=75&crop=5&FillCanvas=true&format=webp&Quality=70" height="75" width="75" class="p-1 text-small" loading="lazy" decoding="async" alt="@product.Name, @variant.Name">
596 </div>
597 </figure>
598 }
599 else
600 {
601 <figure class="w-100 d-block m-0" onmouseover="swift.StaticVariants.SwitchProduct(event, '@product.Id', '@defaultPrice', '@variantImage')" onmouseout="swift.StaticVariants.SwitchProduct(event, '@product.Id', '@variantPrice', '@defaultProductImage')">
602 <div class="d-flex align-items-center justify-content-center">
603 <img src="/admin/public/GetImage.ashx?image=@variantImage&width=75&height=75&crop=5&FillCanvas=true&format=webp&Quality=70" height="75" width="75" class="p-1 text-small" loading="lazy" decoding="async" alt="@product.Name, @variant.Name">
604 </div>
605 </figure>
606 }
607 }
608 else
609 {
610 <div class="d-flex align-items-center justify-content-center">
611 @variant.Name
612 </div>
613 }
614 <div class="visually-hidden">
615 <h4>@product.Name, @variant.Name</h4>
616 @if (!hidePrice)
617 {
618 if (isLazyLoadingForProductInfoEnabled)
619 {
620 <span class="text-price js-text-price"></span>
621 }
622 else
623 {
624 <span class="text-price">@product.Price.PriceFormatted</span>
625
626 }
627 }
628 </div>
629 </article>
630 }
631
632 variantsCount++;
633
634 if (variantsCount == showMaxVariants && optionsCount != showMaxVariants)
635 {
636 int left = optionsCount - showMaxVariants;
637 <div class="variant-option ms-1 d-flex justify-content-center align-items-center">
638 <span>+@left</span>
639 </div>
640 }
641 }
642 }
643 </div>
644 variantGroupCount++;
645 }
646 </div>
647 </div>
648 }
649 </div>
650 </a>
651 @if (!hidePrice)
652 {
653 string priceMin = "";
654 string priceMax = "";
655 <div itemprop="offers" itemscope itemtype="https://schema.org/Offer">
656 <div class="px-0">
657 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span>
658
659 @if (showPricesWithVat == "false" && !neverShowVat)
660 {
661 if (isLazyLoadingForProductInfoEnabled)
662 {
663 <span itemprop="price" content="" class="d-none"></span>
664 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span> }
665 else
666 {
667 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted;
668 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span>
669 if (product.Price.Price != product.PriceBeforeDiscount.Price)
670 {
671 <span class="text-decoration-line-through opacity-75 me-3 text-price campaign-price-efa">@beforePrice</span> }
672 }
673
674 }
675 else
676 {
677
678 if (isLazyLoadingForProductInfoEnabled)
679 {
680 <span itemprop="price" content="" class="d-none"></span>
681 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span>
682 }
683 else
684 {
685 string beforePrice = product.PriceBeforeDiscount.PriceFormatted;
686 string googlepriceraw = product.Price.PriceWithVat.ToString();
687 string googlepriceformatted = googlepriceraw.Replace(',', '.');
688 var priceStandardFieldValueConverted = CampaignPrice.GetConvertedPriceWithVat(product);
689 bool campaignPrice = CampaignPrice.CheckCampaign(product, priceStandardFieldValueConverted);
690
691 <span itemprop="price" content="@googlepriceformatted" class="d-none"></span>
692 if (product.Price.Price != product.PriceBeforeDiscount.Price & campaignPrice == false)
693 {
694 <span class="text-decoration-line-through opacity-75 me-3 text-price campaign-price-efa">@beforePrice</span>
695
696 }
697 }
698 }
699
700 @if (showPricesWithVat == "false" && !neverShowVat)
701 {
702 if (isLazyLoadingForProductInfoEnabled)
703 {
704 <span class="text-price js-text-price"><span class="spinner-border" role="status"></span></span> }
705 else
706 {
707 string price = product.Price.PriceWithoutVatFormatted;
708 if (product?.VariantInfo?.VariantInfo != null)
709 {
710 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : "";
711 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : "";
712 }
713 if (priceMin != priceMax)
714 {
715 price = priceMin + " - " + priceMax;
716 }
717 <span class="text-price">@price</span>
718 }
719 }
720 else
721 {
722 if (isLazyLoadingForProductInfoEnabled)
723 {
724 <span class="text-price js-text-price"><span class="spinner-border" role="status"></span></span>
725 }
726 else
727 {
728 string price = product.Price.PriceFormatted;
729 var priceStandardFieldValueConverted = CampaignPrice.GetConvertedPriceWithVat(product);
730 bool campaignPrice = CampaignPrice.CheckCampaign(product, priceStandardFieldValueConverted);
731 var priceBefore = CampaignPrice.PriceBeforeWithVat(product, priceStandardFieldValueConverted);
732
733 if (product?.VariantInfo?.VariantInfo != null)
734 {
735 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : "";
736 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : "";
737 }
738 if (priceMin != priceMax)
739 {
740 price = priceMin + " - " + priceMax;
741 }
742
743 <div class="d-flex flex-wrap">
744 @if (campaignPrice)
745 {
746 <span class="text-decoration-line-through opacity-75 me-3 text-price w-100 campaign-price-efa">@priceBefore</span>
747 }
748 <span class="text-price fw-bold w-100">@price</span>
749 </div>
750
751 string ecomCountryCode = !string.IsNullOrEmpty(Pageview.Area.EcomCountryCode) ? Pageview.Area.EcomCountryCode : "";
752 var countryVat = Services.Countries.GetCountry(ecomCountryCode).Vat > 0 ? Services.Countries.GetCountry(ecomCountryCode).Vat : 0;
753 var productToShow = Services.Products.GetProductById(product.Id, product.VariantId, product.LanguageId);
754 var currency = Dynamicweb.Ecommerce.Common.Context.Currency.Code;
755 string shipId = ecomCountryCode.IsNotNullOrEmpty() ? ShippingBasedOnProductSetting.GetShippingId(ecomCountryCode, productToShow) : "";
756 product.ProductFields.TryGetValue("ProductNextBackorderDate", out FieldValueViewModel backInStockDateValue);
757 DateTime backInstockDate = backInStockDateValue.Value != null ? DateTime.Parse(backInStockDateValue.Value.ToString()) : DateTime.Today.AddDays(-1);
758 bool hasBackInstockDate = backInStockDateValue != null && backInstockDate >= DateTime.Today;
759 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery >= DateTime.Today;
760 string expectedDeliveryDate = hasBackInstockDate ? backInstockDate.ToShortDateString() : product.ExpectedDelivery?.ToShortDateString() ?? "";
761
762 <div class="fs-8 mt-1 mb-2 text-start">
763 @if (!string.IsNullOrWhiteSpace(product.StockStatus))
764 {
765 <p class="mb-1">@product.StockStatus</p>
766 if (!string.IsNullOrWhiteSpace(product.StockDeliveryText))
767 {
768 <p class="mb-1">@product.StockDeliveryText</p>
769 }
770 }
771 @if (hasExpectedDelivery || hasBackInstockDate)
772 {
773 <div class="mb-1">
774 <span>@Translate("Expected in stock"): </span>
775 <span>@expectedDeliveryDate</span>
776 </div>
777 }
778 </div>
779
780 @*if (!string.IsNullOrWhiteSpace(product.StockStatus)) {
781 <div class="">@product.StockStatus</div>
782 <div class="">@product.StockDeliveryText</div>
783 {
784 <div>
785 <span>@Translate("Expected in stock"): </span>
786 <span>@expectedDeliveryDate</span>
787 </div>
788 }
789 } else {
790
791 if (hasExpectedDelivery || hasBackInstockDate)
792 {
793 <div>
794 <span>@Translate("Expected in stock"): </span>
795 <span>@expectedDeliveryDate</span>
796 </div>
797 }
798 }*@
799
800 @*if (!string.IsNullOrEmpty(ecomCountryCode) && !hidePrice && product.Id != null && shipId != null)
801 {
802 var shippingFeeAmount = ShippingBasedOnProductSetting.GetShippingAmount(shipId, countryVat, productToShow, currency);
803
804
805
806 if (shippingFeeAmount != null)
807 {
808 <div class="fs-8 text-black-50 text-start">
809 <p>@Translate("Levering fra") @currency @shippingFeeAmount</p>
810 </div>
811 }
812 }*@
813 }
814 }
815
816 @if (showPricesWithVat == "false" && !neverShowVat)
817 {
818 if (isLazyLoadingForProductInfoEnabled)
819 {
820 <div class="fs-7 opacity-85 text-price js-text-price-with-vat d-none" data-suffix="@Translate("Incl. VAT")"></div>
821 }
822 else
823 {
824 string price = product.Price.PriceWithVatFormatted;
825 if (product?.VariantInfo?.VariantInfo != null)
826 {
827 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : "";
828 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : "";
829 }
830 if (priceMin != priceMax)
831 {
832 price = priceMin + " - " + priceMax;
833 }
834 <div class="fs-7 opacity-85 text-price">@price @Translate("Incl. VAT")</div>
835
836 }
837 }
838 </div>
839 </div>
840 }
841 @RenderAddToCart(product, link, clickProductLink)
842 </div>
843 </article>
844 }
845
846 }
847 </div>
848
849 <div class="my-3" id="LoadMoreButton">
850 <div class="text-center d-flex flex-column gap-3">
851 <div class="opacity-85">@loadedProducts @Translate("out of") @productList.TotalProductsCount @Translate("products")</div>
852 @if (productList.PageCount != 1)
853 {
854 string sortBySelection = Dynamicweb.Context.Current.Request?.Form["SortBy"] ?? "";
855 sortBySelection = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("SortBy")) ? Dynamicweb.Context.Current.Request.QueryString.Get("SortBy") : sortBySelection;
856
857 string searchQuery = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("q")) ? Dynamicweb.Context.Current.Request.QueryString.Get("q") : "";
858 string searchLayout = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("SearchLayout")) ? Dynamicweb.Context.Current.Request.QueryString.Get("SearchLayout") : "";
859
860 <form method="get" action="@url" data-response-target-element="content" class="w-100">
861 @foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
862 {
863 foreach (FacetViewModel facetItem in facetGroup.Facets)
864 {
865 foreach (FacetOptionViewModel facetOption in facetItem.Options)
866 {
867 if (facetOption.Selected)
868 {
869 <input type="hidden" name="@facetItem.QueryParameter" value="[@facetOption.Value]">
870 }
871 }
872 }
873 }
874
875 <input type="hidden" name="PageSize" value="@pageSize">
876 <input type="hidden" name="SortBy" value="@sortBySelection">
877 <input type="hidden" name="RequestType" value="UpdateList">
878
879 @if (!string.IsNullOrEmpty(searchQuery))
880 {
881 <input type="hidden" name="q" value="@searchQuery">
882 <input type="hidden" name="SearchLayout" value="@searchLayout">
883 }
884
885 @{
886 string nextPageLink = "/Default.aspx?ID=" + Pageview.Page.ID + "&PageSize=" + pageSize + "&SortBy=" + sortBySelection;
887
888 foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
889 {
890 foreach (FacetViewModel facetItem in facetGroup.Facets)
891 {
892 foreach (FacetOptionViewModel facetOption in facetItem.Options)
893 {
894 if (facetOption.Selected)
895 {
896 nextPageLink += "&" + facetItem.QueryParameter + "=[" + facetOption.Value + "]";
897 }
898 }
899 }
900 }
901
902 nextPageLink += !string.IsNullOrEmpty(searchQuery) ? "&q=" + searchQuery : "";
903 }
904
905 <a href="@nextPageLink" class="btn btn-primary" onclick="swift.ProductList.Update(event)" id="LoadMoreButton_@Model.ID">@Translate("Load more products")</a>
906 </form>
907
908 }
909 </div>
910 </div>
911 }
912 }
913 else
914 {
915 <div class="alert alert-dark m-0">
916 @Translate("We did not find anything matching your search result")
917 </div>
918 }
919 }
920
921 @helper RenderBadge(string badge, string plusBadge)
922 {
923 <div class="plus-badge d-flex justify-content-center w-100 efa-mod">
924 <div class="d-flex justify-content-end w-75 h-75">
925 <img class="plus-badge_img efa-mod" srcset="@badge" src="" alt="@plusBadge" />
926 </div>
927 </div>
928 }
929
930 @helper RenderExtraBadge(string badge, string extraBadge)
931 {
932 <div class="plus-badge d-flex justify-content-center w-100 efa-mod">
933 <div class="d-flex justify-content-start w-75 h-75">
934 <img class="extra-plus-badge_img efa-mod" srcset="@System.Web.HttpUtility.UrlPathEncode(badge)" src="" alt="@extraBadge" />
935 </div>
936 </div>
937 }
938
939
940 @helper RenderViewMore(string link, string clickProductLink)
941 {
942 <a href="@link" class="btn btn-secondary flex-fill" @clickProductLink>@Translate("View more")</a>
943 }
944
945 @helper RenderAddToCart(ProductViewModel product, string link, string clickProductLink)
946 {
947 string iconPath = "/Files/icons/";
948 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
949 if (!url.Contains("LayoutTemplate"))
950 {
951 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
952 }
953
954 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
955 bool anonymousUser = Pageview.User == null;
956
957 bool hideAddToCart = !string.IsNullOrEmpty(Model.Item.GetString("HideAddToCart")) ? Model.Item.GetBoolean("HideAddToCart") : false;
958 hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable() ? true : hideAddToCart;
959 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("QuantitySelector")) ? Model.Item.GetBoolean("QuantitySelector") : false;
960
961 bool isDiscontinued = product.Discontinued;
962 bool IsNeverOutOfStock = product.NeverOutOfstock;
963 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : "";
964 disableAddToCart = isDiscontinued ? "disabled" : disableAddToCart;
965 disableAddToCart = IsNeverOutOfStock ? "" : disableAddToCart;
966 disableAddToCart = isLazyLoadingForProductInfoEnabled ? "disabled" : disableAddToCart;
967
968 string ecomCountryCode = !string.IsNullOrEmpty(Pageview.Area.EcomCountryCode) ? Pageview.Area.EcomCountryCode : "";
969 var countryVat = Services.Countries.GetCountry(ecomCountryCode).Vat > 0 ? Services.Countries.GetCountry(ecomCountryCode).Vat : 0;
970 var productToShow = Services.Products.GetProductById(product.Id, product.VariantId, product.LanguageId);
971 var currency = Dynamicweb.Ecommerce.Common.Context.Currency.Code;
972 string shipId = ecomCountryCode.IsNotNullOrEmpty() ? ShippingBasedOnProductSetting.GetShippingId(ecomCountryCode, productToShow) : "";
973 var shippingFeeAmount = string.Empty;
974
975 if (!string.IsNullOrEmpty(ecomCountryCode) && shipId != null)
976 {
977 shippingFeeAmount = ShippingBasedOnProductSetting.GetShippingAmount(shipId, countryVat, productToShow, currency);
978 }
979
980 var addToCartLabel = Model.Item.GetRawValueString("GridLayoutMobile", "grid-1") == "grid-2" ? "" : Translate("Add to cart");
981 string hideOnMobile = Model.Item.GetRawValueString("GridLayoutMobile") == "grid-2" ? "hide-on-mobile" : "";
982 string viewMoreIcon = Model.Item.GetRawValueString("ViewMoreIcon");
983
984 var deliveryCost = Translate("Levering fra") + " " + currency + " " + shippingFeeAmount;
985 var deliveryName = ShippingBasedOnProductSetting.GetShippingName(shipId, product.LanguageId);
986
987 if (!hideAddToCart)
988 {
989 if (product.VariantInfo.VariantInfo == null)
990 {
991 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\"";
992 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1";
993 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty;
994 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : "";
995 string cartUrl = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
996 string imagePathHidden = product?.DefaultImage.Value.ToString() ?? "";
997 imagePathHidden = "/Admin/Public/GetImage.ashx?image=" + imagePathHidden + "&width=" + 350 + "&Format=WebP&Quality=70";
998 <div class="px-0 pb-3">
999 <form method="post" action="@url">
1000 <input type="hidden" name="redirect" value="false">
1001 <input type="hidden" name="MainProductId" value="None">
1002 <input type="hidden" name="ProductId" value="@product.Id">
1003 <input type="hidden" name="ProductName" value="@product.Name">
1004 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code">
1005 <input type="hidden" name="ProductReferer" value="product_list_listview">
1006 <input type="hidden" name="ProductPrice" value="@product.Price.PriceFormatted">
1007 <input type="hidden" name="cartcmd" value="add">
1008 @* #38 EA: sbj *@
1009 <input type="hidden" name="ProductPriceFormatted" value="@product.Price.PriceFormatted">
1010 <input type="hidden" name="ProductUnit" value="@Translate("stk.", "stk.")">
1011 <input type="hidden" name="ProductTotalText" value="@Translate("I alt", "I alt")">
1012 <input type="hidden" name="ProductImage" value="@imagePathHidden">
1013 <input type="hidden" name="ProductDeliveryCost" value="@deliveryCost">
1014 <input type="hidden" name="ProductDeliveryName" value="@deliveryName">
1015 <input type="hidden" name="cartpage" value="@cartUrl">
1016
1017 @if (!string.IsNullOrEmpty(product.VariantId))
1018 {
1019 <input type="hidden" name="VariantId" value="@product.VariantId">}
1020
1021 @if (quantitySelector)
1022 {
1023 <div class="input-group input-primary-button-group">
1024 <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 100px" type="number" onkeydown="swift.Cart.UpdateOnEnterKey(event)" @disableAddToCart>
1025 <div class="d-flex gap-2">
1026 @RenderViewMore(link, clickProductLink)
1027 @*<a href="@link" class="btn btn-secondary flex-fill" @clickProductLink>@Translate("View more")</a>*@
1028 @if (disableAddToCart == "disabled")
1029 {
1030 <button name="notify-cookie" type="button" data-href="@link" onclick="addCookie('ProductNotifier',true,1,'@link');" class="btn btn-primary flex-fill js-add-to-cart-button" title="@Translate("Add to cart")" id="AddCookie@(product.Id)">
1031 <span class="icon-2 d-flex gap-2 align-items-center justify-content-center">@ReadFile(iconPath + "envelope.svg") @Translate("Produkt notifikations label")</span>
1032 </button>
1033 }
1034 else
1035 {
1036 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">
1037 <span class="icon-2 d-flex gap-2 align-items-center justify-content-center">@ReadFile(iconPath + "shopping-cart.svg") @Translate("Add to cart")</span>
1038 </button>
1039 }
1040 </div>
1041 </div>
1042 if (stepQty != "1")
1043 {
1044 <div class="invalid-feedback d-none">
1045 @Translate("Please select a quantity that is dividable by") @stepQty
1046 </div>
1047
1048 }
1049 <label for="Quantity_@(product.Id)_@product.VariantId" class="visually-hidden">@Translate("Quantity")</label>
1050 }
1051 else
1052 {
1053 <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart>
1054 <div class="d-flex gap-2">
1055 <a href="@link" class="btn btn-secondary flex-fill view-more-btn px-0" @clickProductLink><div class="@hideOnMobile">@ReadFile(viewMoreIcon)<span>@Translate("View more")</span></div></a>
1056 @if (disableAddToCart == "disabled")
1057 {
1058 <button name="notify-cookie" type="button" data-href="@link" class="btn btn-primary flex-fill js-add-to-cart-button" title="@Translate("Produkt notifikations label")" id="AddCookie@(product.Id)">
1059 <span class="icon-2 d-flex gap-2 align-items-center justify-content-center @hideOnMobile">@ReadFile(iconPath + "envelope.svg") <span class="add-to-cart-label"> @Translate("Produkt notifikations label")</span></span>
1060 </button>
1061 }
1062 else
1063 {
1064 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button px-0" title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">
1065 <span class="icon-2 d-flex gap-2 align-items-center justify-content-center @hideOnMobile">@ReadFile(iconPath + "shopping-cart.svg") <span class="add-to-cart-label">@Translate("Add to cart")</span></span>
1066 @*<span class="icon-2 d-flex gap-2 align-items-center justify-content-center d-md-none">@ReadFile(iconPath + "shopping-cart.svg")</span>*@
1067 </button>
1068 }
1069 </div>
1070
1071 }
1072 </form>
1073 </div>
1074 }
1075 else
1076 {
1077 string buttonWidth = quantitySelector ? "calc(var(--swift-button-primary-padding-x) * 2 + 1rem + 7rem)" : "calc(var(--swift-button-primary-padding-x) * 2 + 1rem)"; @* Set the width of the container to: button-primary-padding-x × 2 + 1rem for the icon + 7rem for the quantity input *@ string buttonText = quantitySelector ? Translate("Select") : "<span class=\"icon-2\">" + @ReadFile(iconPath + "shopping-cart.svg") + "</span>";
1078
1079 string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : "";
1080 variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString();
1081
1082 string disableVariantSelector = isLazyLoadingForProductInfoEnabled ? "disabled" : "";
1083 <div class="px-0 pb-3">
1084 <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" class="d-inline-block">
1085 <input type="hidden" name="ProductID" value="@product.Id">
1086 <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()">
1087 <input type="hidden" name="HideInventory" value="@Model.Item.GetBoolean("HideInventory").ToString()">
1088 <input type="hidden" name="HideStockState" value="@Model.Item.GetBoolean("HideStockState").ToString()">
1089 <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId">
1090 <input type="hidden" name="ViewType" value="ModalContent">
1091 <button type="button" onclick="swift.PageUpdater.Update(event)" class="btn btn-primary" style="width: @buttonWidth" @disableVariantSelector @disableVariantSelector title="@Translate("Select")" data-bs-toggle="modal" data-bs-target="#DynamicModal" id="OpenVariantSelectorModal@(product.Id)_@Pageview.CurrentParagraph.ID">@buttonText</button>
1092 </form>
1093 </div>
1094
1095 }
1096 }
1097 @RenderModal(product)
1098
1099 }
1100
1101 @helper RenderProduct(ProductInfoViewModel relatedProduct, string productId)
1102 {
1103 var relProduct = relatedProduct.GetProduct();
1104 var mainProductId = productId.IsNotNullOrEmpty() ? productId : "None";
1105 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
1106 bool anonymousUser = Pageview.User == null;
1107 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable();
1108 bool showFavoritesSelectorMasterProduct = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ShowFavoritesSelectorMasterProduct")) ? Convert.ToBoolean(Dynamicweb.Context.Current.Request.Form.Get("ShowFavoritesSelectorMasterProduct")) : false;
1109
1110 string ratio = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio") : "";
1111 string ratioCssClass = ratio != "" ? "ratio" : "";
1112 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
1113
1114 string theme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("Theme")) ? Dynamicweb.Context.Current.Request.Form.Get("Theme") : "";
1115 string themePadding = theme != string.Empty ? "p-3" : string.Empty;
1116 string imageTheme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageTheme")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageTheme") : "";
1117 string imageOutlineStyle = imageTheme == string.Empty ? "border: 1px solid transparent;" : string.Empty;
1118 string imageThemePadding = imageTheme != string.Empty ? "p-3" : string.Empty;
1119 string ContentPadding = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ContentPadding")) ? Dynamicweb.Context.Current.Request.Form.Get("ContentPadding") : "";
1120
1121 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
1122 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
1123
1124 string variantIdForLink = !string.IsNullOrEmpty(relProduct.VariantId) ? $"&VariantID={relProduct.VariantId}" : "";
1125 variantIdForLink = string.IsNullOrEmpty(variantIdForLink) && !string.IsNullOrEmpty(relProduct.DefaultVariantId) ? $"&VariantID={relProduct.DefaultVariantId}" : variantIdForLink;
1126
1127 string link = "Default.aspx?ID=" + GetPageIdByNavigationTag("Shop");
1128 link += $"&GroupID={relProduct.PrimaryOrDefaultGroup.Id}";
1129 link += $"&ProductID={relProduct.Id}";
1130 link += variantIdForLink;
1131 link = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(link);
1132
1133 string imagePath = relProduct?.DefaultImage.Value.ToString() ?? "";
1134 imagePath = "/Admin/Public/GetImage.ashx?image=" + imagePath + "&width=" + 350 + "&Format=WebP&Quality=70";
1135
1136 string saleBadgeType = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType") : "";
1137 string saleBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName") : "";
1138 string newBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName") : "";
1139 int newPublicationDays = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) ? Convert.ToInt32(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) : 0;
1140 string campaignBadgesValues = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues")) ? Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues") : "";
1141
1142 var badgeParms = new Dictionary<string, object>();
1143 badgeParms.Add("saleBadgeType", saleBadgeType);
1144 badgeParms.Add("saleBadgeCssClassName", saleBadgeCssClassName);
1145 badgeParms.Add("newBadgeCssClassName", newBadgeCssClassName);
1146 badgeParms.Add("campaignBadgesValues", campaignBadgesValues);
1147 badgeParms.Add("newPublicationDays", newPublicationDays);
1148
1149 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(saleBadgeCssClassName) && saleBadgeCssClassName != "none" ? true : false;
1150 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(newBadgeCssClassName) && newBadgeCssClassName != "none" ? true : false;
1151 DateTime createdDate = relProduct.Created.Value;
1152 bool showBadges = saleBadgeEnabled && relProduct.Discount.Price != 0 ? true : false;
1153 showBadges = (newBadgeEnabled && newPublicationDays == 0) || (newBadgeEnabled && (createdDate.AddDays(newPublicationDays) > DateTime.Now)) ? true : showBadges;
1154 showBadges = !string.IsNullOrEmpty(campaignBadgesValues) ? true : showBadges;
1155
1156 string disableAddToCart = (relProduct.StockLevel <= 0) ? "disabled" : "";
1157 bool isNeverOutOfStock = relProduct.NeverOutOfstock;
1158 disableAddToCart = isNeverOutOfStock ? "" : disableAddToCart;
1159
1160 string iconPath = "/Files/icons/";
1161 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "shopping-cart.svg");
1162 string addToCartLabel = !addToCartIcon.Contains("_none") ? "<span class=\"icon-2\">" + ReadFile(addToCartIcon) + "</span>" : "";
1163 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : "";
1164 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? Translate("Add to cart") : "";
1165 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : "";
1166 string buttonSize = Model.Item.GetRawValueString("ButtonSize", "regular");
1167 string inputSize = string.Empty;
1168
1169 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
1170 if (!url.Contains("LayoutTemplate"))
1171 {
1172 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
1173 }
1174 string cartUrl = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
1175 string imagePathHidden = relProduct?.DefaultImage.Value.ToString() ?? "";
1176 imagePathHidden = "/Admin/Public/GetImage.ashx?image=" + imagePathHidden + "&width=" + 350 + "&Format=WebP&Quality=70";
1177
1178 switch (buttonSize)
1179 {
1180 case "small":
1181 inputSize = " input-group-sm";
1182 buttonSize = " btn-sm";
1183 break;
1184 case "regular":
1185 buttonSize = string.Empty;
1186 break;
1187 case "large":
1188 inputSize = " input-group-lg";
1189 buttonSize = " btn-lg";
1190 break;
1191 }
1192
1193 string ecomCountryCode = !string.IsNullOrEmpty(Pageview.Area.EcomCountryCode) ? Pageview.Area.EcomCountryCode : "";
1194 var countryVat = Services.Countries.GetCountry(ecomCountryCode).Vat > 0 ? Services.Countries.GetCountry(ecomCountryCode).Vat : 0;
1195 var productToShow = Services.Products.GetProductById(relProduct.Id, relProduct.VariantId, relProduct.LanguageId);
1196 var currency = Dynamicweb.Ecommerce.Common.Context.Currency.Code;
1197 string shipId = ecomCountryCode.IsNotNullOrEmpty() ? ShippingBasedOnProductSetting.GetShippingId(ecomCountryCode, productToShow) : "";
1198 var shippingFeeAmount = string.Empty;
1199
1200 if (!string.IsNullOrEmpty(ecomCountryCode) && shipId != null)
1201 {
1202 shippingFeeAmount = ShippingBasedOnProductSetting.GetShippingAmount(shipId, countryVat, productToShow, currency);
1203 }
1204
1205 var deliveryCost = Translate("Levering fra") + " " + currency + " " + shippingFeeAmount;
1206 var deliveryName = ShippingBasedOnProductSetting.GetShippingName(shipId, relProduct.LanguageId);
1207
1208 <div class="text-decoration-none d-block h-100">
1209 <div class="h-100 d-flex flex-column justify-content-between@(theme)">
1210 @{
1211 FieldValueViewModel plusDesignerModalIndikator;
1212 relProduct.ProductFields.TryGetValue("PlusDesignerLinkEA", out plusDesignerModalIndikator);
1213 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") ? "target=\"_blank\"" : string.Empty;
1214 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") ? "rel=\"noopener\"" : string.Empty;
1215
1216 if (!string.IsNullOrEmpty(plusDesignerModalIndikator.Value.ToString()))
1217 {
1218 <div class="plus-designer__container" style="position: absolute; left: 0; bottom: 10px; background: #fff; border: 1px solid #228B64; border-left: none; z-index: 100; width: 75%; max-width: 200px; ">
1219 <object>
1220 <a href="@plusDesignerModalIndikator.Value" @target @rel class="text-decoration-none">
1221 <div class="plus-designer__inner p-1 ps-3">
1222 <p class="m-0 opacity-75 fs-7">Try me in</p>
1223 <div class="plus-designer__image-container d-flex w-100">
1224 <img class="" src="/Files/Templates/Designs/Swift/Assets/Images/plusdesignerlogo.svg" alt="Alternate Text" style="width:90%" />
1225 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right" style="width:10%"><polyline points="9 18 15 12 9 6"></polyline></svg>
1226 </div>
1227 </div>
1228 </a>
1229 </object>
1230 </div>
1231
1232 }
1233
1234 }
1235 <div class="@(imageTheme)" style="@imageOutlineStyle">
1236 <div class="@(ratioCssClass) position-relative m-auto mw200 efa-mod" style="@ratioVariable">
1237 @if (showBadges)
1238 {
1239 <div class="position-absolute top-0 left-0 p-1 p-lg-2" style="z-index: 2">
1240 @{
1241 @RenderPartial("Components/EcommerceBadge.cshtml", relProduct, badgeParms)
1242 }
1243 </div>
1244 }
1245 <img loading="lazy" decoding="async" src="@imagePath" class="h-100 w-100 @(imageThemePadding)" style="object-fit: contain;" alt="@relProduct.Name">
1246 </div>
1247 </div>
1248 <div class="flex-fill p-3 pb-0 d-flex flex-column justify-content-between @themePadding">
1249 <h3 class="h6 opacity-85">@relProduct.Name @relProduct.VariantName</h3>
1250
1251 @if (!hidePrice)
1252 {
1253 <div>
1254 <p class="h6 m-0">
1255 @if (showPricesWithVat == "false" && !neverShowVat)
1256 {
1257 if (relProduct.Price.Price != relProduct.PriceBeforeDiscount.Price)
1258 {
1259 <span class="text-decoration-line-through opacity-75 me-1">
1260 @relProduct.PriceBeforeDiscount.PriceWithoutVatFormatted
1261 </span>
1262 }
1263 }
1264 else
1265 {
1266 if (relProduct.Price.Price != relProduct.PriceBeforeDiscount.Price)
1267 {
1268 <span class="text-decoration-line-through opacity-75 me-1">
1269 @relProduct.PriceBeforeDiscount.PriceFormatted
1270 </span>
1271 }
1272 }
1273
1274 @if (showPricesWithVat == "false" && !neverShowVat)
1275 {
1276 <span class="text-price fw-bold">@relProduct.Price.PriceWithoutVatFormatted</span>
1277 }
1278 else
1279 {
1280 <span class="text-price fw-bold">@relProduct.Price.PriceFormatted</span>
1281 }
1282 </p>
1283 @if (shippingFeeAmount != null)
1284 {
1285 <div class="fs-8 text-black-50 text-start">
1286 <p>@deliveryCost</p>
1287 </div>
1288 }
1289
1290
1291 @if (showPricesWithVat == "false" && !neverShowVat)
1292 {
1293 <small class="opacity-85 fst-normal">@relProduct.Price.PriceWithVatFormatted @Translate("Incl. VAT")</small>
1294 }
1295
1296 </div>
1297 }
1298 </div>
1299
1300 <div class="d-flex gap-4 p-3 pt-0">
1301 @{
1302 string stepQty = relProduct.PurchaseQuantityStep > 1 ? relProduct.PurchaseQuantityStep.ToString() : "1";
1303 string valueQty = relProduct.PurchaseMinimumQuantity > relProduct.PurchaseQuantityStep ? relProduct.PurchaseMinimumQuantity.ToString() : stepQty;
1304
1305 }
1306 <form method="post" action="@url">
1307 <input type="hidden" name="redirect" value="false">
1308 <input type="hidden" name="MainProductId" value="@mainProductId">
1309 <input type="hidden" name="ProductId" value="@relProduct.Id">
1310 <input type="hidden" name="ProductName" value="@relProduct.Name">
1311 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code">
1312 <input type="hidden" name="ProductReferer" value="product_list_listview">
1313 <input type="hidden" name="ProductPrice" value="@relProduct.Price.PriceFormatted">
1314 <input type="hidden" name="cartcmd" value="add">
1315 @* #38 EA: sbj *@
1316 <input type="hidden" name="ProductPriceFormatted" value="@relProduct.Price.PriceFormatted">
1317 <input type="hidden" name="ProductUnit" value="@Translate("stk.", "stk.")">
1318 <input type="hidden" name="ProductTotalText" value="@Translate("I alt", "I alt")">
1319 <input type="hidden" name="ProductImage" value="@imagePathHidden">
1320 <input type="hidden" name="ProductDeliveryCost" value="@deliveryCost">
1321 <input type="hidden" name="ProductDeliveryName" value="@deliveryName">
1322 <input type="hidden" name="cartpage" value="@cartUrl">
1323
1324 <input id="Quantity_@(relProduct.Id)_@relProduct.VariantId" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart>
1325 <div class="d-flex gap-3 p-3 pt-0">
1326 <a href="@link" class="btn btn-secondary flex-fill">@Translate("View more")</a>
1327 <a onclick="swift.Cart.Update(event)" class="btn btn-primary @(buttonSize) @flexFill flex-fill js-add-to-cart-button" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(relProduct.Id)_@Pageview.CurrentParagraph.ID">
1328 @if (!Model.Item.GetBoolean("HideButtonText"))
1329 {
1330 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2">
1331 @addToCartLabel
1332 </span>
1333 }
1334 else
1335 {
1336 @addToCartLabel
1337 }
1338 </a>
1339 </div>
1340 </form>
1341 </div>
1342
1343 </div>
1344 </div>
1345 @*@RenderModalRelatedProducts(relProduct)*@
1346 }
1347
1348 @helper RenderModal(ProductViewModel product)
1349 {
1350 <div id="cartNotificationModal_@(product.Id)" class="modal" tabindex="-1" aria-labelledby="cartNotificationModalTitel" aria-hidden="true">
1351 @{
1352 string modelId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ModelID")) ? Dynamicweb.Context.Current.Request.Form.Get("ModelID") : product.Id;
1353
1354 string scrollBarForceMobile = Dynamicweb.Context.Current.Request.Form.Get("NavigationShowScrollbar") != string.Empty ? "--swiffy-slider-track-height:0.5rem !important;" : string.Empty;
1355 bool hideSliderNavigation = false;
1356
1357 int itemsShown = 3;
1358
1359 string imageTheme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageTheme")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageTheme") : "";
1360 string imageOutlineStyle = imageTheme == string.Empty ? "border: 1px solid transparent;" : string.Empty;
1361 string imageThemePadding = imageTheme != string.Empty ? "p-3" : string.Empty;
1362
1363 string imagePath = product?.DefaultImage.Value.ToString() ?? "";
1364 imagePath = "/Admin/Public/GetImage.ashx?image=" + imagePath + "&width=" + 350 + "&Format=WebP&Quality=70";
1365
1366 string ratio = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio") : "";
1367 string ratioCssClass = ratio != "" ? "ratio" : "";
1368 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
1369
1370 string saleBadgeType = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType") : "";
1371 string saleBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName") : "";
1372 string newBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName") : "";
1373 int newPublicationDays = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) ? Convert.ToInt32(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) : 0;
1374 string campaignBadgesValues = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues")) ? Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues") : "";
1375
1376 var badgeParms = new Dictionary<string, object>();
1377 badgeParms.Add("saleBadgeType", saleBadgeType);
1378 badgeParms.Add("saleBadgeCssClassName", saleBadgeCssClassName);
1379 badgeParms.Add("newBadgeCssClassName", newBadgeCssClassName);
1380 badgeParms.Add("campaignBadgesValues", campaignBadgesValues);
1381 badgeParms.Add("newPublicationDays", newPublicationDays);
1382
1383 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(saleBadgeCssClassName) && saleBadgeCssClassName != "none" ? true : false;
1384 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(newBadgeCssClassName) && newBadgeCssClassName != "none" ? true : false;
1385 DateTime createdDate = product.Created.Value;
1386
1387 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
1388 showBadges = (newBadgeEnabled && newPublicationDays == 0) || (newBadgeEnabled && (createdDate.AddDays(newPublicationDays) > DateTime.Now)) ? true : showBadges;
1389 showBadges = !string.IsNullOrEmpty(campaignBadgesValues) ? true : showBadges;
1390 var productCounter = 1;
1391
1392 var shopPageId = GetPageIdByNavigationTag("Shop");
1393
1394 bool relatedExist = false;
1395 string modalProductCon = "";
1396 string modalProductSize = "";
1397 foreach (var relatedGroup in product.RelatedGroups)
1398 {
1399 if (relatedGroup.Id == "Addon")
1400 {
1401 if (relatedGroup.Products.Count > 0)
1402 {
1403 relatedExist = true;
1404 modalProductCon = "col-xl-7";
1405 }
1406 }
1407 }
1408 if (relatedExist)
1409 {
1410 modalProductSize = "xl";
1411 }
1412 else
1413 {
1414 modalProductSize = "lg";
1415 }
1416
1417 }
1418 @*<script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script>
1419 <script type="module">
1420 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css');
1421 </script>*@
1422 <div class="modal-dialog theme plus-primary modal-@modalProductSize">
1423 <div class="modal-content rounded-0 container">
1424 <div class="row">
1425 <div class="col-12 @modalProductCon p-0 border-bottom border-lg-none">
1426 <div class="modal-header w-100 text-center">
1427 <h3 class="modal-title w-100" id="cartNotificationModalTitel">@Translate("Følgende er tilføjet til indkøbslisten", "Følgende er tilføjet til indkøbslisten")</h3>
1428 <button type="button" class="btn-close position-absolute m-0 end-0 me-3 d-block d-xl-none" data-bs-dismiss="modal" aria-label="Close"></button>
1429 </div>
1430 <div id="cartNotificationModalBody" class="modal-body">
1431 <div class="container-fluid">
1432 <div class="row">
1433 <div class="@(imageTheme) col-12" style="@imageOutlineStyle">
1434 <div class="@(ratioCssClass) col-4 col-xl-6 m-auto position-relative" style="@ratioVariable">
1435 @if (showBadges)
1436 {
1437 <div class="position-absolute top-0 left-0 p-1 p-lg-2" style="z-index: 2">
1438 @{
1439 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
1440 }
1441 </div>
1442 }
1443 <img loading="lazy" decoding="async" src="@imagePath" class="h-100 w-100 @(imageThemePadding)" style="object-fit: contain;" alt="@product.Name" id="cartNotificationModal_Image">
1444 </div>
1445 </div>
1446 <div class="d-flex flex-wrap py-3 col-12 gap-md-3">
1447 <div class="col-12 col-md-auto info">
1448 <div class="fc-dark efa-mod"><strong id="cartNotificationModal_Name"></strong></div>
1449 <div class="fs-5 fw-bold fc-dark efa-mod" id="cartNotificationModal_Price"></div>
1450 <div class="fs-8 fc-grey efa-mod" id="cartNotificationModal_DeliveryCost"></div>
1451 <div class="fs-8 fc-grey efa-mod" id="cartNotificationModal_DeliveryName"></div>
1452 <div id="cartNotificationModal_Quantity"></div>
1453 </div>
1454 <div class="col-12 col-lg action align-self-end">
1455 <div class="col-12 d-flex mt-3">
1456 <a id="cartNotificationModal_Button" class="btn btn-primary flex-fill" href="">@Translate("Gå til indkøbslisten", "Gå til indkøbslisten")</a>
1457 </div>
1458 </div>
1459 </div>
1460 </div>
1461 </div>
1462 </div>
1463 </div>
1464 @if (relatedExist)
1465 {
1466 <div class="col-12 col-xl-5 p-0">
1467 <div class="modal-header w-100 text-center">
1468 <h3 class="modal-title text-center w-100">@Translate("Add-on products", "Tilbehør")</h3>
1469 <button type="button" class="btn-close position-absolute m-0 end-0 me-3 d-none d-xl-block" data-bs-dismiss="modal" aria-label="Close"></button>
1470 </div>
1471 <div class="modal-body">
1472 <div id="addonsList_@product.Id" class="list row gy-3 overflow-auto">
1473 @{
1474 foreach (RelatedGroupViewModel relatedGroup in product.RelatedGroups.Where(x => x.Id == "Addon"))
1475 {
1476 foreach (ProductInfoViewModel relatedProduct in relatedGroup.Products)
1477 {
1478 <div class="elements col-6 col-xl-12 align-self-end">
1479 @RenderAddons(relatedProduct, productCounter, product.Id)
1480 </div>
1481 productCounter += 1;
1482 }
1483 }
1484
1485 }
1486 </div>
1487 </div>
1488 </div>
1489 }
1490 </div>
1491 </div>
1492 </div>
1493 </div>
1494 }
1495
1496 @helper RenderAddons(ProductInfoViewModel relatedProduct, int productCounter, string productID)
1497 {
1498 var relProduct = relatedProduct.GetProduct();
1499 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
1500 bool anonymousUser = Pageview.User == null;
1501 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable();
1502 bool showFavoritesSelectorMasterProduct = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ShowFavoritesSelectorMasterProduct")) ? Convert.ToBoolean(Dynamicweb.Context.Current.Request.Form.Get("ShowFavoritesSelectorMasterProduct")) : false;
1503
1504 string ratio = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio") : "";
1505 string ratioCssClass = ratio != "" ? "ratio" : "";
1506 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
1507
1508 string theme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("Theme")) ? Dynamicweb.Context.Current.Request.Form.Get("Theme") : "";
1509 string themePadding = theme != string.Empty ? "p-3" : string.Empty;
1510 string imageTheme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageTheme")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageTheme") : "";
1511 string imageOutlineStyle = imageTheme == string.Empty ? "border: 1px solid transparent;" : string.Empty;
1512 string imageThemePadding = imageTheme != string.Empty ? "p-3" : string.Empty;
1513 string ContentPadding = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ContentPadding")) ? Dynamicweb.Context.Current.Request.Form.Get("ContentPadding") : "";
1514
1515 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
1516 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
1517
1518 string variantIdForLink = !string.IsNullOrEmpty(relProduct.VariantId) ? $"&VariantID={relProduct.VariantId}" : "";
1519 variantIdForLink = string.IsNullOrEmpty(variantIdForLink) && !string.IsNullOrEmpty(relProduct.DefaultVariantId) ? $"&VariantID={relProduct.DefaultVariantId}" : variantIdForLink;
1520
1521 string link = "Default.aspx?ID=" + GetPageIdByNavigationTag("Shop");
1522 link += $"&GroupID={relProduct.PrimaryOrDefaultGroup.Id}";
1523 link += $"&ProductID={relProduct.Id}";
1524 link += variantIdForLink;
1525 link = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(link);
1526
1527 string imagePath = relProduct?.DefaultImage.Value.ToString() ?? "";
1528 imagePath = "/Admin/Public/GetImage.ashx?image=" + imagePath + "&width=" + 350 + "&Format=WebP&Quality=70";
1529
1530 string saleBadgeType = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType") : "";
1531 string saleBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName") : "";
1532 string newBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName") : "";
1533 int newPublicationDays = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) ? Convert.ToInt32(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) : 0;
1534 string campaignBadgesValues = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues")) ? Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues") : "";
1535
1536 var badgeParms = new Dictionary<string, object>();
1537 badgeParms.Add("saleBadgeType", saleBadgeType);
1538 badgeParms.Add("saleBadgeCssClassName", saleBadgeCssClassName);
1539 badgeParms.Add("newBadgeCssClassName", newBadgeCssClassName);
1540 badgeParms.Add("campaignBadgesValues", campaignBadgesValues);
1541 badgeParms.Add("newPublicationDays", newPublicationDays);
1542
1543 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(saleBadgeCssClassName) && saleBadgeCssClassName != "none" ? true : false;
1544 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(newBadgeCssClassName) && newBadgeCssClassName != "none" ? true : false;
1545 DateTime createdDate = relProduct.Created.Value;
1546 bool showBadges = saleBadgeEnabled && relProduct.Discount.Price != 0 ? true : false;
1547 showBadges = (newBadgeEnabled && newPublicationDays == 0) || (newBadgeEnabled && (createdDate.AddDays(newPublicationDays) > DateTime.Now)) ? true : showBadges;
1548 showBadges = !string.IsNullOrEmpty(campaignBadgesValues) ? true : showBadges;
1549
1550 string disableAddToCart = (relProduct.StockLevel <= 0) ? "disabled" : "";
1551 bool isNeverOutOfStock = relProduct.NeverOutOfstock;
1552 disableAddToCart = isNeverOutOfStock ? "" : disableAddToCart;
1553
1554 string iconPath = "/Files/icons/";
1555 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "shopping-cart.svg");
1556 string addToCartLabel = !addToCartIcon.Contains("_none") ? "<span class=\"icon-2\">" + ReadFile(addToCartIcon) + "</span>" : "";
1557 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : "";
1558 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? Translate("Add to cart") : "";
1559 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : "";
1560 string buttonSize = Model.Item.GetRawValueString("ButtonSize", "regular");
1561 string inputSize = string.Empty;
1562 string viewMoreIcon = Model.Item.GetRawValueString("ViewMoreIcon");
1563
1564 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
1565 if (!url.Contains("LayoutTemplate"))
1566 {
1567 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
1568 }
1569 string cartUrl = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("EasyFlowCart"));
1570 string imagePathHidden = relProduct?.DefaultImage.Value.ToString() ?? "";
1571 imagePathHidden = "/Admin/Public/GetImage.ashx?image=" + imagePathHidden + "&width=" + 350 + "&Format=WebP&Quality=70";
1572
1573 switch (buttonSize)
1574 {
1575 case "small":
1576 inputSize = " input-group-sm";
1577 buttonSize = " btn-sm";
1578 break;
1579 case "regular":
1580 buttonSize = string.Empty;
1581 break;
1582 case "large":
1583 inputSize = " input-group-lg";
1584 buttonSize = " btn-lg";
1585 break;
1586 }
1587
1588 string ecomCountryCode = !string.IsNullOrEmpty(Pageview.Area.EcomCountryCode) ? Pageview.Area.EcomCountryCode : "";
1589 var countryVat = Services.Countries.GetCountry(ecomCountryCode).Vat > 0 ? Services.Countries.GetCountry(ecomCountryCode).Vat : 0;
1590 var productToShow = Services.Products.GetProductById(relProduct.Id, relProduct.VariantId, relProduct.LanguageId);
1591 var currency = Dynamicweb.Ecommerce.Common.Context.Currency.Code;
1592 string shipId = ecomCountryCode.IsNotNullOrEmpty() ? ShippingBasedOnProductSetting.GetShippingId(ecomCountryCode, productToShow) : "";
1593 var shippingFeeAmount = string.Empty;
1594
1595 if (!string.IsNullOrEmpty(ecomCountryCode) && shipId != null)
1596 {
1597 shippingFeeAmount = ShippingBasedOnProductSetting.GetShippingAmount(shipId, countryVat, productToShow, currency);
1598 }
1599
1600 var deliveryCost = Translate("Levering fra") + " " + currency + " " + shippingFeeAmount;
1601 var deliveryName = ShippingBasedOnProductSetting.GetShippingName(shipId, relProduct.LanguageId);
1602 string stepQty = relProduct.PurchaseQuantityStep > 1 ? relProduct.PurchaseQuantityStep.ToString() : "1";
1603 string valueQty = relProduct.PurchaseMinimumQuantity > relProduct.PurchaseQuantityStep ? relProduct.PurchaseMinimumQuantity.ToString() : stepQty;
1604
1605 relProduct.ProductFields.TryGetValue("ProductNextBackorderDate", out FieldValueViewModel backInStockDateValue);
1606 DateTime backInstockDate = backInStockDateValue.Value != null ? DateTime.Parse(backInStockDateValue.Value.ToString()) : DateTime.Today.AddDays(-1);
1607 bool hasBackInstockDate = backInStockDateValue != null && backInstockDate >= DateTime.Today;
1608 bool hasExpectedDelivery = relProduct.ExpectedDelivery != null && relProduct.ExpectedDelivery >= DateTime.Today;
1609 string expectedDeliveryDate = hasBackInstockDate ? backInstockDate.ToShortDateString() : relProduct.ExpectedDelivery?.ToShortDateString() ?? "";
1610
1611 <div class="element w-100 d-flex">
1612
1613 <div class="w-100 row m-0 align-items-end">
1614 <div class="product-info col ps-lg-0">
1615 <div class="col-6 col-xl-3 m-auto m-xl-0 image-container pe-2">
1616 <img class="h-100 w-100" src="@imagePath" alt="" style="object-fit: cover;" />
1617 </div>
1618 <div>
1619 <div><strong>@relProduct.Name</strong></div>
1620 @if (!hidePrice)
1621 {
1622 <div>
1623 <p class="h6 m-0">
1624 @if (showPricesWithVat == "false" && !neverShowVat)
1625 {
1626 if (relProduct.Price.Price != relProduct.PriceBeforeDiscount.Price)
1627 {
1628 <span class="text-decoration-line-through opacity-75 me-1">
1629 @relProduct.PriceBeforeDiscount.PriceWithoutVatFormatted
1630 </span>
1631 }
1632 }
1633 else
1634 {
1635 if (relProduct.Price.Price != relProduct.PriceBeforeDiscount.Price)
1636 {
1637 <span class="text-decoration-line-through opacity-75 me-1">
1638 @relProduct.PriceBeforeDiscount.PriceFormatted
1639 </span>
1640 }
1641 }
1642
1643 @if (showPricesWithVat == "false" && !neverShowVat)
1644 {
1645 <span class="text-price fw-bold">@relProduct.Price.PriceWithoutVatFormatted</span>
1646 }
1647 else
1648 {
1649 <span class="text-price fw-bold">@relProduct.Price.PriceFormatted</span>
1650 }
1651 </p>
1652
1653 <div class="fs-8 mt-1 mb-2 text-start">
1654 @if (!string.IsNullOrWhiteSpace(relProduct.StockStatus))
1655 {
1656 <p class="mb-1">@relProduct.StockStatus</p>
1657 if (!string.IsNullOrWhiteSpace(relProduct.StockDeliveryText))
1658 {
1659 <p class="mb-1">@relProduct.StockDeliveryText</p>
1660 }
1661 }
1662 @if (hasExpectedDelivery || hasBackInstockDate)
1663 {
1664 <div class="mb-1">
1665 <span>@Translate("Expected in stock"): </span>
1666 <span>@expectedDeliveryDate</span>
1667 </div>
1668 }
1669 </div>
1670
1671 @*<div class="fs-8 text-black-50 text-start">
1672 <p class="m-0">@Translate("Levering fra") @currency @shippingFeeAmount</p>
1673 </div>*@
1674 @if (showPricesWithVat == "false" && !neverShowVat)
1675 {
1676 <small class="opacity-85 fst-normal">@relProduct.Price.PriceWithVatFormatted @Translate("Incl. VAT")</small>
1677 }
1678
1679 </div>
1680 }
1681 </div>
1682 </div>
1683 <div class="action col-auto px-0 ps-lg-0 w-100 w-lg-auto">
1684 <form method="post" action="@url">
1685 <input type="hidden" name="redirect" value="false">
1686 <input type="hidden" name="ProductId" value="@relProduct.Id">
1687 <input type="hidden" name="ProductName" value="@relProduct.Name">
1688 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code">
1689 <input type="hidden" name="ProductReferer" value="product_list_listview">
1690 <input type="hidden" name="ProductPrice" value="@relProduct.Price.PriceFormatted">
1691 <input type="hidden" name="cartcmd" value="add">
1692 @* #38 EA: sbj *@
1693 <input type="hidden" name="ProductPriceFormatted" value="@relProduct.Price.PriceFormatted">
1694 <input type="hidden" name="ProductUnit" value="@Translate("stk.", "stk.")">
1695 <input type="hidden" name="ProductTotalText" value="@Translate("I alt", "I alt")">
1696 <input type="hidden" name="ProductDeliveryCost" value="@deliveryCost">
1697 <input type="hidden" name="ProductDeliveryName" value="@deliveryName">
1698 <input type="hidden" name="ProductImage" value="@imagePathHidden">
1699 <input type="hidden" name="cartpage" value="@cartUrl">
1700 @if (relProduct.StockLevel > 0)
1701 {
1702 <div class="d-flex flex-nowrap w-100 mb-2 bg-light efa-mod" style="height:40px;">
1703
1704 <div class="d-flex align-items-center justify-content-center btn border-0" id="minus-btn" onclick="quantityBtn('Quantity_@(productID)_@(relProduct.Id)_@relProduct.VariantId', 'minus')" style="width:calc(100%/3);">
1705 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dash-lg" viewBox="0 0 16 16">
1706 <path fill-rule="evenodd" d="M2 8a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 2 8Z" />
1707 </svg>
1708 </div>
1709 <div class="d-flex align-items-center justify-content-center" style="width:calc(100%/3);">
1710 <input id="Quantity_@(productID)_@(relProduct.Id)_@relProduct.VariantId" name="Quantity" value="1" step="1" min="1" class="form-control p-0 bg-light border-0 fc-green quantity_selector efa-mod" style="max-width:40px;" type="number" onkeydown="swift.Cart.UpdateOnEnterKey(event)" @disableAddToCart>
1711 </div>
1712 <div class="d-flex align-items-center justify-content-center btn border-0" id="plus-btn" onclick="quantityBtn('Quantity_@(productID)_@(relProduct.Id)_@relProduct.VariantId', 'plus')" style="width:calc(100%/3);">
1713 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16">
1714 <path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z" />
1715 </svg>
1716 </div>
1717 <script type="text/javascript">
1718 function quantityBtn(id, symbol) {
1719 console.log(document.getElementById(id));
1720 var idValue = document.getElementById(id).value;
1721 var value = parseInt(idValue, 10);
1722 var step = 1;
1723 value = isNaN(value) ? 0 : value;
1724 switch (symbol) {
1725 case "minus":
1726 value = value - step;
1727 break;
1728 case "plus":
1729 value = value + step;
1730 break;
1731 }
1732 console.log(value);
1733 if (value > 0) {
1734 document.getElementById(id).value = value;
1735 }
1736 //UpdateQuantity();
1737 }
1738 </script>
1739
1740 </div>
1741 <a onclick="swift.Cart.Update(event)" data-container="productRel" class="btn btn-primary @(buttonSize) @flexFill w-100 w-lg-auto flex-fill js-add-to-cart-button addToCart" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(relProduct.Id)_@Pageview.CurrentParagraph.ID">
1742 @if (!Model.Item.GetBoolean("HideButtonText"))
1743 {
1744 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2">
1745 @addToCartLabel
1746 </span>
1747 }
1748 else
1749 {
1750 @addToCartLabel
1751 }
1752 </a>
1753 }
1754 else
1755 {
1756 <div class="w-100 efa-mod" style="height:40px;">
1757 @*<button name="notify-cookie" data-href="@link" type="button" class="btn btn-primary flex-fill h-100 js-add-to-cart-button" title="@Translate("Produkt notifikations label")" id="AddCookie@(relProduct.Id)">
1758 <span class="icon-2 d-flex gap-2 align-items-center justify-content-center">@ReadFile(iconPath + "envelope.svg")</span>
1759 </button>*@
1760 <a href="@link" class="btn btn-secondary flex-fill view-more-btn w-100"><div class="">@ReadFile(viewMoreIcon)<span>@Translate("View more")</span></div></a>
1761 </div>
1762 }
1763 </form>
1764 </div>
1765 </div>
1766 </div>
1767 }
1768
1769 @*@helper RenderModalRelatedProducts(ProductViewModel product)
1770 {
1771 <div id="cartNotificationModal_@(product.Id)" class="modal" tabindex="-1" aria-labelledby="cartNotificationModalTitel" aria-hidden="true">
1772 @{
1773 string modelId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ModelID")) ? Dynamicweb.Context.Current.Request.Form.Get("ModelID") : product.Id;
1774
1775 string scrollBarForceMobile = Dynamicweb.Context.Current.Request.Form.Get("NavigationShowScrollbar") != string.Empty ? "--swiffy-slider-track-height:0.5rem !important;" : string.Empty;
1776 bool hideSliderNavigation = false;
1777
1778 int itemsShown = 3;
1779
1780 string imageTheme = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageTheme")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageTheme") : "";
1781 string imageOutlineStyle = imageTheme == string.Empty ? "border: 1px solid transparent;" : string.Empty;
1782 string imageThemePadding = imageTheme != string.Empty ? "p-3" : string.Empty;
1783
1784 string imagePath = product?.DefaultImage.Value.ToString() ?? "";
1785 imagePath = "/Admin/Public/GetImage.ashx?image=" + imagePath + "&width=" + 350 + "&Format=WebP&Quality=70";
1786
1787 string ratio = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio")) ? Dynamicweb.Context.Current.Request.Form.Get("ImageAspectRatio") : "";
1788 string ratioCssClass = ratio != "" ? "ratio" : "";
1789 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
1790
1791 string saleBadgeType = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeType") : "";
1792 string saleBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("SaleBadgeCssClassName") : "";
1793 string newBadgeCssClassName = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName")) ? Dynamicweb.Context.Current.Request.Form.Get("NewBadgeCssClassName") : "";
1794 int newPublicationDays = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) ? Convert.ToInt32(Dynamicweb.Context.Current.Request.Form.Get("NewPublicationDays")) : 0;
1795 string campaignBadgesValues = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues")) ? Dynamicweb.Context.Current.Request.Form.Get("CampaignBadgesValues") : "";
1796
1797 var badgeParms = new Dictionary<string, object>();
1798 badgeParms.Add("saleBadgeType", saleBadgeType);
1799 badgeParms.Add("saleBadgeCssClassName", saleBadgeCssClassName);
1800 badgeParms.Add("newBadgeCssClassName", newBadgeCssClassName);
1801 badgeParms.Add("campaignBadgesValues", campaignBadgesValues);
1802 badgeParms.Add("newPublicationDays", newPublicationDays);
1803
1804 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(saleBadgeCssClassName) && saleBadgeCssClassName != "none" ? true : false;
1805 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(newBadgeCssClassName) && newBadgeCssClassName != "none" ? true : false;
1806 DateTime createdDate = product.Created.Value;
1807
1808 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
1809 showBadges = (newBadgeEnabled && newPublicationDays == 0) || (newBadgeEnabled && (createdDate.AddDays(newPublicationDays) > DateTime.Now)) ? true : showBadges;
1810 showBadges = !string.IsNullOrEmpty(campaignBadgesValues) ? true : showBadges;
1811
1812 var shopPageId = GetPageIdByNavigationTag("Shop");
1813
1814 bool relatedExist = false;
1815 foreach (var relatedGroup in product.RelatedGroups)
1816 {
1817 if (relatedGroup.Id == "Addon")
1818 {
1819 if (relatedGroup.Products.Count > 0)
1820 {
1821 relatedExist = true;
1822 }
1823 }
1824 }
1825
1826 }
1827 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script>
1828 <script type="module">
1829 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css');
1830 </script>
1831 <div class="modal-dialog theme plus-primary modal-xl">
1832 <div class="modal-content rounded-0">
1833 <div class="modal-header w-100 text-center">
1834 <h3 class="modal-title w-100" id="cartNotificationModalTitel">@Translate("Følgende er tilføjet til indkøbslisten", "Følgende er tilføjet til indkøbslisten")</h3>
1835 <button type="button" class="btn-close position-absolute m-0 end-0 me-3" data-bs-dismiss="modal" aria-label="Close"></button>
1836 </div>
1837 <div id="cartNotificationModalBody" class="modal-body">
1838 <div class="container-fluid">
1839 <div class="row">
1840 <div class="col-12 col-md-6 col-lg-3 m-auto me-lg-0 @(imageTheme)" style="@imageOutlineStyle">
1841 <div class="@(ratioCssClass) position-relative" style="@ratioVariable">
1842 @if (showBadges)
1843 {
1844 <div class="position-absolute top-0 left-0 p-1 p-lg-2" style="z-index: 2">
1845 @{
1846 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
1847 }
1848 </div>
1849 }
1850 <img loading="lazy" decoding="async" src="" class="h-100 w-100 @(imageThemePadding)" style="object-fit: contain;" alt="" id="cartNotificationModal_Image">
1851 </div>
1852 </div>
1853 <div class="col-12 col-md-6 col-lg-4 m-auto ms-lg-0 d-flex align-content-between flex-wrap">
1854 <div class="col-12 ms-auto fc-dark efa-mod"><strong id="cartNotificationModal_Name"></strong></div>
1855 <div class="col-12 ms-auto">
1856 <div class="col-12 ms-auto fs-5 fw-bold fc-dark efa-mod" id="cartNotificationModal_Price"></div>
1857 <div class="fs-8 fc-grey efa-mod text-start">
1858 <p class="" id="cartNotificationModal_DeliveryCost"></p>
1859 </div>
1860
1861 <div class="col-12 d-flex flex-wrap mb-3 ms-auto" id="cartNotificationModal_Quantity"></div>
1862 <div class="col-12 d-flex mb-2">
1863 <a id="cartNotificationModal_Button" class="btn btn-primary flex-fill" href="">@Translate("Gå til indkøbslisten", "Gå til indkøbslisten")</a>
1864 </div>
1865 <div class="col-12 d-flex">
1866 <div class="btn btn-secondary col-12" data-bs-dismiss="modal">@Translate("Continue shopping")</div>
1867 </div>
1868 </div>
1869 </div>
1870 </div>
1871 </div>
1872 </div>
1873
1874 @if (relatedExist)
1875 {
1876 <div class="modal-footer d-none d-md-block d-lg-block d-xl-block" id="cartNotificationModal_Related">
1877 <div class="modal-title w-100 text-center">
1878 <h3>@Translate("Add-on products", "Tilbehør")</h3>
1879 </div>
1880 <div class="container-fluid">
1881 <div class="row">
1882 <div class="col-12 m-auto">
1883 <div id="slider_@(modelId)" class="swiffy-slider slider-item-show@(itemsShown) slider-nav-visible" style="--swiffy-slider-nav-light:var(--swift-foreground-color); --swiffy-slider-nav-dark:var(--swift-background-color); @(scrollBarForceMobile)">
1884 <ul class="slider-container">
1885 @{
1886 foreach (RelatedGroupViewModel relatedGroup in product.RelatedGroups.Where(x => x.Id == "Addon"))
1887 {
1888 foreach (ProductInfoViewModel relatedProduct in relatedGroup.Products)
1889 {
1890 <li>
1891 @RenderProduct(relatedProduct, product.Id)
1892 </li>
1893 }
1894 }
1895 }
1896
1897 </ul>
1898 <button type="button" title="@Translate("Previous slide")" class="slider-nav" style="z-index: 2;">
1899 <span class="visually-hidden">@Translate("Previous slide")</span>
1900 </button>
1901 <button type="button" title="@Translate("Next slide")" class="slider-nav slider-nav-next" style="z-index: 2;">
1902 <span class="visually-hidden">@Translate("Next slide")</span>
1903 </button>
1904
1905 </div>
1906 <script type="module">
1907 swiffyslider.initSlider(document.querySelector('#slider_@(modelId)'));
1908 </script>
1909 </div>
1910 </div>
1911 </div>
1912 </div>
1913 }
1914 </div>
1915 </div>
1916 </div>
1917 }*@
1918
1919 <script type="text/javascript">
1920
1921 var notifyCookieBtn = document.getElementsByName("notify-cookie");
1922 if (notifyCookieBtn) {
1923 for (let i = 0; i < notifyCookieBtn.length; i++) {
1924 notifyCookieBtn[i].addEventListener("click", function (event) {
1925 let link = notifyCookieBtn[i].getAttribute("data-href"),
1926 cName = "ProductNotifier",
1927 cValue = true,
1928 date = new Date(),
1929 expDays = 1;
1930 date.setTime(date.getTime() + (expDays * 24 * 60 * 60 * 1000));
1931 const expires = "expires=" + date.toUTCString();
1932 document.cookie = cName + "=" + cValue + "; " + expires + "; path=/";
1933
1934 window.location.href = link;
1935 });
1936 }
1937 }
1938
1939 document.addEventListener("update.swift.cart", function (event) {
1940 var data = Object.fromEntries(event.detail.formData.entries());
1941 var mainProductId = data.MainProductId;
1942
1943 if (mainProductId != "None") {
1944 var openqs = '#cartNotificationModal_' + mainProductId;
1945
1946 var prodId = data.ProductId;
1947
1948 var price = data.ProductPrice;
1949 if (price.includes(",")) {
1950 price = price.replace(/,/g, ".");
1951 price = parseFloat(price).toFixed(2);
1952 }
1953
1954 var quantity = data.Quantity;
1955 var detailsPrice = data.ProductPrice;
1956 var totalprice = data.ProductPrice.includes(",") ? data.ProductPrice : detailsPrice.concat("", ",00");;
1957 if (quantity > 1) {
1958 var total = price * quantity;
1959 var totalpriceCalc = parseFloat(total).toFixed(2);
1960 totalpriceCalc = totalpriceCalc.toString();
1961 if (totalpriceCalc.includes(".")) {
1962 totalprice = totalpriceCalc.replace(".", ",");
1963 }
1964 else {
1965 totalprice = totalpriceCalc.concat("", ",00");
1966 }
1967 }
1968
1969 var details = data.ProductPrice.includes(",") ? data.ProductPrice : detailsPrice.concat("", ",00");
1970
1971 document.querySelector(openqs).querySelector("#cartNotificationModal_Button").href = data.cartpage;
1972 document.querySelector(openqs).querySelector("#cartNotificationModal_Quantity").innerHTML = data.Quantity + " " + data.ProductUnit;
1973 document.querySelector(openqs).querySelector("#cartNotificationModal_Name").innerHTML = data.ProductName;
1974 document.querySelector(openqs).querySelector("#cartNotificationModal_Price").innerHTML = details;
1975 document.querySelector(openqs).querySelector("#cartNotificationModal_Image").src = data.ProductImage;
1976 document.querySelector(openqs).querySelector("#cartNotificationModal_Image").alt = data.ProductName;
1977 document.querySelector(openqs).querySelector("#cartNotificationModal_DeliveryCost").innerHTML = data.ProductDeliveryCost;
1978 document.querySelector(openqs).querySelector("#cartNotificationModal_DeliveryName").innerHTML = data.ProductDeliveryName;
1979 document.querySelector(openqs).querySelector("#cartNotificationModal_Related").classList.remove('d-md-block', 'd-lg-block', 'd-xl-block');
1980 }
1981 else {
1982 var prodId = data.ProductId;
1983 mainProductId = prodId;
1984
1985 var price = data.ProductPrice;
1986 if (price.includes(",")) {
1987 price = price.replace(/,/g, ".");
1988 price = parseFloat(price).toFixed(2);
1989 }
1990
1991 var quantity = data.Quantity;
1992 var detailsPrice = data.ProductPrice;
1993 var totalprice = data.ProductPrice.includes(",") ? data.ProductPrice : detailsPrice.concat("", ",00");;
1994 if (quantity > 1) {
1995 var total = price * quantity;
1996 var totalpriceCalc = parseFloat(total).toFixed(2);
1997 totalpriceCalc = totalpriceCalc.toString();
1998 if (totalpriceCalc.includes(".")) {
1999 totalprice = totalpriceCalc.replace(".", ",");
2000 }
2001 else {
2002 totalprice = totalpriceCalc.concat("", ",00");
2003 }
2004 }
2005
2006 var qs = "#cartNotificationModal_" + prodId;
2007 const myModalAlternative = new bootstrap.Modal(qs)
2008
2009 var details = data.ProductPrice.includes(",") ? data.ProductPrice : detailsPrice.concat("", ",00");
2010
2011 document.querySelector(qs).querySelector("#cartNotificationModal_Button").href = data.cartpage;
2012 document.querySelector(qs).querySelector("#cartNotificationModal_Quantity").innerHTML = data.Quantity + " " + data.ProductUnit;
2013 document.querySelector(qs).querySelector("#cartNotificationModal_Name").innerHTML = data.ProductName;
2014 document.querySelector(qs).querySelector("#cartNotificationModal_Price").innerHTML = details;
2015 document.querySelector(qs).querySelector("#cartNotificationModal_Image").src = data.ProductImage;
2016 document.querySelector(qs).querySelector("#cartNotificationModal_Image").alt = data.ProductName;
2017 document.querySelector(qs).querySelector("#cartNotificationModal_DeliveryCost").innerHTML = data.ProductDeliveryCost;
2018 document.querySelector(qs).querySelector("#cartNotificationModal_DeliveryName").innerHTML = data.ProductDeliveryName;
2019 if (document.querySelector(qs).querySelector("#cartNotificationModal_Related")) {
2020 if (!document.querySelector(qs).querySelector("#cartNotificationModal_Related").classList.contains('d-md-block', 'd-lg-block', 'd-xl-block')) {
2021 document.querySelector(qs).querySelector("#cartNotificationModal_Related").classList.add('d-md-block', 'd-lg-block', 'd-xl-block');
2022 }
2023 }
2024
2025 myModalAlternative.show();
2026 }
2027 let addonsListHeight = 0;
2028 console.log(mainProductId);
2029 let addonList = document.querySelector("#addonsList_" + mainProductId);
2030 console.log(addonList);
2031 let addonListElems = addonList.children;
2032 console.log(addonListElems);
2033 if (addonListElems.length > 3) {
2034 for (var i = 0; 3 > i; i++) {
2035 console.log(addonListElems[i].clientHeight);
2036 addonsListHeight = addonsListHeight + addonListElems[i].clientHeight + 16;
2037 }
2038 addonList.style.maxHeight = addonsListHeight + "px";
2039 }
2040
2041 });
2042
2043 </script>
2044 @* #38 EA: end *@
2045
2046
2047 <script>
2048 dataLayer = window.dataLayer || [];
2049 dataLayer.push({ ecommerce: null });
2050 dataLayer.push({
2051 event: 'view_item_list',
2052 ecommerce: {
2053 currency: '@Dynamicweb.Ecommerce.Common.Context.Currency.Code',
2054 ecomm_pagetype: 'category',
2055 items: [
2056 @foreach (ProductViewModel product in productList.Products)
2057 {
2058 <text>{
2059 item_name: '@product.Name',
2060 item_id: '@product.Number',
2061 price: @product.Price.Price,
2062 quantity: 1,
2063 item_category: '@product.PrimaryOrDefaultGroup.Name',
2064 item_brand: 'PLUS',
2065 item_variant: ''
2066 },</text>
2067 }
2068 ]
2069 },
2070 user_data: {
2071 first_name: '@(string.IsNullOrEmpty(CartId) ? "" : order?.DeliveryName)',
2072 last_name: '',
2073 email_address: '@(string.IsNullOrEmpty(CartId) ? "" : order?.CustomerEmail)',
2074 phone_number: '@(string.IsNullOrEmpty(CartId) ? "" : order?.CustomerPhone)',
2075 }
2076 });
2077 </script>
2078