Komponent editor til: EFA Combi group and product list - 2024

Error executing template "Designs/Swift/Paragraph/Swift_ProductListGridView_EA.cshtml"
System.NullReferenceException: Objektreferencen er ikke indstillet til en forekomst af et objekt.
   ved CompiledRazorTemplates.Dynamic.RazorEngine_1341d73997d14c989162535b1f6e2f7a.Execute() i C:\inetpub\wwwroot\plus-prod\Files\Templates\Designs\Swift\Paragraph\Swift_ProductListGridView_EA.cshtml:linje 2056
   ved RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   ved RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   ved RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   ved RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   ved Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   ved Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   ved 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

Tangent hegn og låger i træ

Leder du efter et hegn med rene linjer og smukke detaljer til din haveindretning?

Tangent er et super elegant og gennemtænkt træhegn, som giver rene linjer i dit hegnsforløb fra start til slut. Kombinationen af brede og smalle lister skaber en smuk effekt, hvor stolperne kommer til at indgå som et naturligt element i hegnsforløbet. Kombinerer du hegnet med de ligeafskårede stolper med en flad stolpehat på toppen, får hegnet et helt roligt udtryk, der vil klæde de fleste huse og haver uanset byggestil og haveindretning.

I Tangent serien finder du både hegn, enkeltlåger og dobbeltlåger i flere forskellige mål og dimensioner, så du kan bygge dit hegn, præcist som du ønsker. Hegnet er ideelt til brug som afskærmning og rumdeler i dit uderum og i et haveskel til en nabo eller sti, da hegnet med sin listeopbygning virker kamuflerende og skærmer for indkig.

Hegnet er fremstillet i certificeret grundmalet træ af højeste kvalitet, så du er sikker på, at din hegn holder i årevis – du skal blot huske at vedligeholde det.