Error executing template "/Designs/Swift/Paragraph/Custom_Swift_ProductDetailsInfo.cshtml"
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
   at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at CompiledRazorTemplates.Dynamic.RazorEngine_ad35f60a210041e29d0db303d18dcb76.Execute() in D:\dynamicweb.net\Solutions\Novicell\danitech.cloud.dynamicweb-cms.com\Files\Templates\\Designs\Swift\Paragraph\Custom_Swift_ProductDetailsInfo.cshtml:line 232
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites 4 @using Dynamicweb.Ecommerce.Products.FieldDisplayGroups 5 @using Dynamicweb.Frontend 6 @using System.Drawing 7 @using System.Text.RegularExpressions 8 @using Danitech.Website.CustomModules.Helpers 9 @using System.Configuration; 10 @using Newtonsoft.Json 11 12 @functions { 13 //Find contrast color (white, black) 14 public static string GetContrastColor(string hexString) 15 { 16 System.Drawing.Color bg = System.Drawing.ColorTranslator.FromHtml(hexString); 17 18 int nThreshold = 105; 19 int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + 20 (bg.B * 0.114)); 21 22 string foreColor = (255 - bgDelta < nThreshold) ? "#333" : "#fff"; 23 return foreColor; 24 } 25 } 26 27 @functions{ 28 private dynamic GetQuantityDiscount(string productNumber) 29 { 30 if (Dynamicweb.Environment.CookieManager.GetCookie(ConfigurationManager.AppSettings["DanitechAPIJWTTokenName"]) == null) 31 { 32 return null; 33 } 34 35 var client = new System.Net.Http.HttpClient(); 36 var request = new System.Net.Http.HttpRequestMessage 37 { 38 Method = System.Net.Http.HttpMethod.Get, 39 RequestUri = new Uri(ConfigurationManager.AppSettings["DanitechAPIURL"] + "User/discount/getquantitydiscounts?productNumbers=" + productNumber), 40 Headers = 41 { 42 { "Authorization", $"Bearer {Dynamicweb.Environment.CookieManager.GetCookie(ConfigurationManager.AppSettings["DanitechAPIJWTTokenName"]).Value}" }, 43 }, 44 }; 45 46 string result = ""; 47 using (var response = client.SendAsync(request).Result) 48 { 49 if (response.IsSuccessStatusCode) 50 { 51 result = response.Content.ReadAsStringAsync().Result; 52 } 53 } 54 55 if (string.IsNullOrEmpty(result)) 56 { 57 return null; 58 } 59 else 60 { 61 return JsonConvert.DeserializeObject<dynamic>(result); 62 } 63 } 64 } 65 66 @functions{ 67 private string PriceFormatted(string price) 68 { 69 if (decimal.TryParse(price, out decimal result)) 70 { 71 return result.ToString("N2"); 72 } 73 74 return "-"; 75 } 76 } 77 78 @{ 79 ProductViewModel product = new ProductViewModel(); 80 81 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 82 { 83 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 84 } 85 86 if (Pageview.User == null || string.IsNullOrEmpty(Pageview.User.CustomerNumber)) 87 { 88 StockHelper stockHelper = new StockHelper(); 89 double? stock = stockHelper.GetStock(product.Number).GetAwaiter().GetResult(); 90 91 if (stock != null) 92 { 93 product.StockLevel = stock; 94 } 95 } 96 97 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 98 bool anonymousUser = Pageview.User == null; 99 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser; 100 hideAddToCart = product.VariantInfo.VariantInfo != null && Model.Item.GetBoolean("HideVariantSelector") ? true : hideAddToCart; 101 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser; 102 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false; 103 104 bool isDiscontinued = product.Discontinued; 105 bool IsNeverOutOfStock = product.NeverOutOfstock; 106 string[] variantId = product.VariantId.Split('.'); 107 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : ""; 108 disableAddToCart = isDiscontinued ? "disabled" : disableAddToCart; 109 disableAddToCart = IsNeverOutOfStock ? "" : disableAddToCart; 110 111 // Does product has a expected delivery data 112 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now; 113 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? ""; 114 115 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); 116 if (!url.Contains("LayoutTemplate")) 117 { 118 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; 119 } 120 121 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList(); 122 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>(); 123 124 foreach (var selection in selectedDisplayGroups) 125 { 126 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 127 { 128 if (selection == group.Id) 129 { 130 mainFeatures.Add(group); 131 } 132 } 133 } 134 135 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 136 137 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6"); 138 139 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 140 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding; 141 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding; 142 143 string quantityPricesLayout = Model.Item.GetRawValueString("QuantityPricesLayout", "list"); 144 145 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\""; 146 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1"; 147 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty; 148 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : ""; 149 150 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 151 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 152 153 string priceMin = ""; 154 string priceMax = ""; 155 156 var favoriteParameters = new Dictionary<string, object>(); 157 if (!anonymousUser && !hideFavoritesSelector) 158 { 159 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists(); 160 int defaultFavoriteListId = 0; 161 162 if (favoreiteLists.Count() == 1) 163 { 164 foreach (FavoriteList list in favoreiteLists) 165 { 166 defaultFavoriteListId = list.ListId; 167 } 168 } 169 170 favoriteParameters.Add("ListId", defaultFavoriteListId); 171 } 172 173 var badgeParms = new Dictionary<string, object>(); 174 badgeParms.Add("size", "h7"); 175 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 176 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 177 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 178 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 179 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 180 181 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 182 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 183 DateTime createdDate = product.Created.Value; 184 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 185 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 186 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 187 188 dynamic quantityDiscounts = null; 189 bool quantityDiscountsHasValues = false; 190 191 if (!anonymousUser) 192 { 193 quantityDiscounts = GetQuantityDiscount(product.Number); 194 quantityDiscountsHasValues = quantityDiscounts != null && quantityDiscounts.productDiscounts != null && quantityDiscounts.productDiscounts.HasValues && quantityDiscounts.productDiscounts[0].quantityDiscounts.Count > 0; 195 } 196 } 197 <div class="h-100 @(contentPadding) @(theme) item_@Model.Item.SystemName.ToLower()"> 198 <div class="d-flex flex-column gap-4 js-product"> 199 @if (showBadges) 200 { 201 <div class="swift_badge-collection"> 202 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 203 </div> 204 } 205 206 @RenderProductNameAndManufacturerLogo(product, titleFontSize, Model.Item.GetBoolean("HideProductNumber")) 207 208 @if (!hidePrice && !isDiscontinued) 209 { 210 <div> 211 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer"> 212 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span> 213 214 <div>@Translate("SRP")</div> 215 216 @if (showPricesWithVat == "false" && !neverShowVat) 217 { 218 var itemPropPrice = 0d; 219 var srpPriceFormatted = "-"; 220 221 if (product.Prices.Count > 1) 222 { 223 var minPrice = product.Prices.Min(p => p.Price); 224 var maxPrice = product.Prices.Max(p => p.Price); 225 226 srpPriceFormatted = minPrice != maxPrice ? minPrice.PriceWithoutVatFormatted + " - " + maxPrice.PriceWithoutVatFormatted : minPrice.PriceWithoutVatFormatted; 227 228 itemPropPrice = minPrice.PriceWithoutVat; 229 } 230 else 231 { 232 srpPriceFormatted = product.Prices[0].Price.PriceWithoutVatFormatted; 233 234 itemPropPrice = product.Prices[0].Price.PriceWithoutVat; 235 } 236 237 <span itemprop="price" content="@itemPropPrice" class="d-none"></span> 238 <span class="text-price">@srpPriceFormatted</span> 239 } 240 else 241 { 242 var itemPropPrice = 0d; 243 var srpPriceFormatted = "-"; 244 245 if (product.Prices.Count > 1) 246 { 247 var minPrice = product.Prices.Min(p => p.Price); 248 var maxPrice = product.Prices.Max(p => p.Price); 249 250 srpPriceFormatted = minPrice != maxPrice ? minPrice.PriceFormatted + " - " + maxPrice.PriceFormatted : minPrice.PriceFormatted; 251 itemPropPrice = minPrice.Price; 252 } 253 else 254 { 255 srpPriceFormatted = product.Prices[0].Price.PriceFormatted; 256 itemPropPrice = product.Prices[0].Price.Price; 257 } 258 259 <span itemprop="price" content="@itemPropPrice" class="d-none"></span> 260 <span class="text-price">@srpPriceFormatted</span> 261 } 262 </div> 263 @if (showPricesWithVat == "false" && !neverShowVat) 264 { 265 var price = "-"; 266 267 if (product.Prices.Count > 1) 268 { 269 var minPrice = product.Prices.Min(p => p.Price); 270 var maxPrice = product.Prices.Max(p => p.Price); 271 272 price = minPrice != maxPrice ? minPrice.PriceWithVatFormatted + " - " + maxPrice.PriceWithVatFormatted : minPrice.PriceWithVatFormatted; 273 } 274 else 275 { 276 price = product.Prices[0].Price.PriceWithVatFormatted; 277 } 278 279 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small> 280 } 281 282 @if (quantityDiscountsHasValues) 283 { 284 <div class="standalone__quantitydiscount"> 285 <h2>@Translate("Your prices")</h2> 286 287 @if (quantityPricesLayout == "list") 288 { 289 <div class="mt-3"> 290 @foreach (var quantityPrice in quantityDiscounts.productDiscounts[0].quantityDiscounts) 291 { 292 string quantityLabel = Translate("PCS"); 293 294 <small class="d-block opacity-75"><span>@quantityPrice.minimumQuantity @quantityLabel</span> - <span>@quantityPrice.discountPercent<text>%</text></span> - <span class="fw-bold">@PriceFormatted(quantityPrice.discountedPrice.ToString()) @quantityPrice.currencyCode</span></small> 295 } 296 </div> 297 } 298 else if (quantityPricesLayout == "table") 299 { 300 <div class="grid"> 301 <table class="table table-striped mt-3 g-col-12 g-col-lg-6"> 302 <thead> 303 <tr> 304 <th>@Translate("Min quantity")</th> 305 <th>@Translate("Discount percent")</th> 306 <th>@Translate("PCS price excl VAT")</th> 307 </tr> 308 </thead> 309 <tbody> 310 @foreach (var quantityPrice in quantityDiscounts.productDiscounts[0].quantityDiscounts) 311 { 312 <tr> 313 <td>@quantityPrice.minimumQuantity</td> 314 <td>@quantityPrice.discountPercent<text>%</text></td> 315 <td>@quantityPrice.discountedPrice @quantityPrice.currencyCode</td> 316 </tr> 317 } 318 </tbody> 319 </table> 320 </div> 321 } 322 </div> 323 } 324 </div> 325 } 326 327 @if (!string.IsNullOrEmpty(product.ShortDescription)) 328 { 329 <div class="mb-0-last-child" itemprop="disambiguatingDescription"> 330 @product.ShortDescription 331 </div> 332 } 333 334 @if (mainFeatures.Count > 0) 335 { 336 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures) 337 { 338 <dl class="grid gap-0"> 339 @foreach (var field in mainFeatureGroup.Fields) 340 { 341 @RenderField(field.Value) 342 } 343 </dl> 344 } 345 } 346 347 @if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector")) 348 { 349 int groupNumber = 1; 350 351 <form class="mb-3 js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())" id="VariantSelector_@Model.ID"> 352 <input type="hidden" name="variantid" /> 353 354 @foreach (var variantGroup in product.VariantGroups()) 355 { 356 VariantGroupViewModel group = variantGroup; 357 358 <h3 class="h6">@group.Name</h3> 359 <div class="mb-3 js-variant-group" data-group-id="@groupNumber"> 360 @foreach (var option in group.Options) 361 { 362 string active = variantId.Contains(option.Id) ? "active" : ""; 363 364 if (!string.IsNullOrEmpty(option.Color)) 365 { 366 string contrastColor = GetContrastColor(option.Color); 367 <button type="button" class="btn colorbox rounded-circle me-1 mb-2 d-inline-block variant-option border js-variant-option @active" style="background-color: @option.Color --variantoption-check-color: @contrastColor" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID"></button> 368 } 369 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value)) 370 { 371 <button type="button" class="btn p-0 d-inline-block mb-2 variant-option border js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID"> 372 <img src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" /> 373 </button> 374 } 375 else 376 { 377 <button type="button" class="btn btn-secondary d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID"> 378 @option.Name 379 </button> 380 } 381 } 382 </div> 383 384 groupNumber++; 385 } 386 </form> 387 } 388 389 <div class="d-flex flex-row flex-nowrap gap-2" id="AddToCart_@Model.ID"> 390 @if (!hideAddToCart && !isDiscontinued) 391 { 392 <form method="post" action="@url" class="flex-fill"> 393 <input type="hidden" name="redirect" value="false" /> 394 <input type="hidden" name="ProductId" value="@product.Id" /> 395 <input type="hidden" name="cartcmd" value="add" /> 396 397 @if (!string.IsNullOrEmpty(product.VariantId)) 398 { 399 <input type="hidden" name="VariantId" value="@product.VariantId" /> 400 } 401 @if (!Model.Item.GetBoolean("QuantitySelector")) 402 { 403 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" type="hidden"> 404 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary w-100 js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Add to cart")</button> 405 } 406 else 407 { 408 <div class="input-group input-primary-button-group js-input-group d-flex flex-row flex-nowrap"> 409 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label> 410 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 96px; min-width:64px;" type="number"> 411 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Add to cart")</button> 412 </div> 413 414 if (stepQty != "1") 415 { 416 <div class="invalid-feedback d-none"> 417 @Translate("Please select a quantity that is dividable by") @stepQty 418 </div> 419 } 420 } 421 </form> 422 if (!anonymousUser && !hideFavoritesSelector) 423 { 424 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters) 425 } 426 } 427 else if (!anonymousUser && !hideFavoritesSelector && !isDiscontinued) 428 { 429 <div class="flex-fill" id="AddToFavorites_@Model.ID"> 430 @Translate("Add to favorites") @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters) 431 </div> 432 } 433 434 @if (isDiscontinued && product.ReplacementProduct != null) 435 { 436 List<ProductInfoViewModel> replacementProductList = new List<ProductInfoViewModel>(); 437 replacementProductList.Add(product.ReplacementProduct); 438 var replacementProduct = replacementProductList.GetProducts().FirstOrDefault(); 439 440 if ((product.DiscontinuedAction == 0 || product.DiscontinuedAction == 1) && product?.ReplacementProduct.ProductId != null) 441 { 442 var parms = new Dictionary<string, object>(); 443 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 444 parms.Add("fullwidth", true); 445 parms.Add("columns", Model.GridRowColumnCount); 446 447 string imagePath = replacementProduct?.DefaultImage?.Value != null ? replacementProduct.DefaultImage.Value : ""; 448 449 var defaultGroupId = replacementProduct.PrimaryOrDefaultGroup.Id; 450 var selectedDetailPage = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(defaultGroupId)?.Meta.PrimaryPage ?? string.Empty; 451 452 string link = string.IsNullOrEmpty(selectedDetailPage) ? $"/Default.aspx?ID={Pageview.Page.ID}&groupid={defaultGroupId}" : selectedDetailPage; 453 link += "&productid=" + replacementProduct.Id; 454 link += !string.IsNullOrEmpty(replacementProduct.VariantId) ? "&variantid=" + replacementProduct.VariantId : ""; 455 456 <div class="w-100"> 457 <div class="fw-bold w-100">@Translate("Sorry, this product is no longer available").</div> 458 <div>@Translate("We recommend this replacement product instead"):</div> 459 460 <a href="@link"> 461 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 462 </a> 463 464 <div>@replacementProduct.Name</div> 465 466 @if (!hidePrice) 467 { 468 <div class="mb-3"> 469 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer"> 470 @if (showPricesWithVat == "false" && !neverShowVat) 471 { 472 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted; 473 474 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span> 475 if (product.Price.Price != product.PriceBeforeDiscount.Price) 476 { 477 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span> 478 } 479 } 480 else 481 { 482 string beforePrice = product.PriceBeforeDiscount.PriceFormatted; 483 484 <span itemprop="price" content="@product.Price.Price" class="d-none"></span> 485 if (product.Price.Price != product.PriceBeforeDiscount.Price) 486 { 487 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span> 488 } 489 } 490 491 @if (showPricesWithVat == "false" && !neverShowVat) 492 { 493 string price = product.Price.PriceWithoutVatFormatted; 494 if (product?.VariantInfo?.VariantInfo != null) 495 { 496 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : ""; 497 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : ""; 498 } 499 if (priceMin != priceMax) 500 { 501 price = priceMin + " - " + priceMax; 502 } 503 <span class="text-price">@price</span> 504 } 505 else 506 { 507 string price = product.Price.PriceFormatted; 508 if (product?.VariantInfo?.VariantInfo != null) 509 { 510 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : ""; 511 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : ""; 512 } 513 if (priceMin != priceMax) 514 { 515 price = priceMin + " - " + priceMax; 516 } 517 <span class="text-price">@price</span> 518 } 519 </div> 520 521 @if (showPricesWithVat == "false" && !neverShowVat) 522 { 523 string price = product.Price.PriceWithVatFormatted; 524 if (product?.VariantInfo?.VariantInfo != null) 525 { 526 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : ""; 527 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : ""; 528 } 529 if (priceMin != priceMax) 530 { 531 price = priceMin + " - " + priceMax; 532 } 533 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small> 534 } 535 </div> 536 } 537 538 <a href="@link" class="btn btn-primary w-100">@Translate("Go to the replacement")</a> 539 </div> 540 } 541 } 542 </div> 543 </div> 544 @if (!Model.Item.GetBoolean("HideStockState")) 545 { 546 if (!IsNeverOutOfStock && !isDiscontinued) 547 { 548 <div class="mt-3 js-stock-state"> 549 550 @if (product.StockLevel > 0) 551 { 552 if (!Model.Item.GetBoolean("HideInventory")) 553 { 554 <p class="small text-success m-0">@product.StockLevel @Translate("Products available in stock")</p> 555 } 556 else 557 { 558 <p class="small text-success m-0">@Translate("Available in stock")</p> 559 } 560 } 561 562 else 563 { 564 <p class="small text-danger m-0">@Translate("Out of Stock")</p> 565 } 566 567 @if (hasExpectedDelivery) 568 { 569 <p> 570 <span>@Translate("Expected back in stock:")</span> 571 <span>@expectedDeliveryDate</span> 572 </p> 573 } 574 575 </div> 576 } 577 } 578 </div> 579 580 @helper RenderField(FieldValueViewModel field) 581 { 582 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 583 bool noValues = false; 584 585 if (!string.IsNullOrEmpty(fieldValue)) 586 { 587 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 588 { 589 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 590 noValues = values.Count > 0 ? false : true; 591 } 592 } 593 594 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 595 { 596 <dt class="g-col-12 g-col-sm-4 g-col-lg-12 fw-bold m-0">@field.Name</dt> 597 <dd class="g-col-12 g-col-sm-8 g-col-lg-12 mb-3"> 598 @RenderFieldValue(field) 599 </dd> 600 } 601 } 602 603 @helper RenderFieldValue(FieldValueViewModel field) 604 { 605 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 606 607 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 608 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 609 610 bool isColor = false; 611 612 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 613 { 614 int valueCount = 0; 615 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 616 int totalValues = values.Count; 617 618 foreach (FieldOptionValueViewModel option in values) 619 { 620 if (option.Value.Substring(0, 1) == "#") 621 { 622 isColor = true; 623 } 624 625 if (!isColor) 626 { 627 @option.Name 628 } 629 else 630 { 631 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Value"></span> 632 } 633 634 if (valueCount != totalValues && valueCount < (totalValues - 1)) 635 { 636 if (isColor) 637 { 638 <text> </text> 639 } 640 else 641 { 642 <text>, </text> 643 } 644 } 645 valueCount++; 646 } 647 } 648 else 649 { 650 if (fieldValue.Substring(0, 1) == "#") 651 { 652 isColor = true; 653 } 654 655 if (!isColor) 656 { 657 @fieldValue 658 } 659 else 660 { 661 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 662 } 663 } 664 } 665 666 @if (product.VariantInfo.VariantInfo != null) 667 { 668 <script type="module"> 669 swift.VariantSelector.init(); 670 </script> 671 } 672 673 674 @helper RenderProductNameAndManufacturerLogo(ProductViewModel product, string titleFontSize, bool hideProductNumber) 675 { 676 <div> 677 @if (product.ProductFields != null && product.ProductFields.ContainsKey("BrandLogo")) 678 { 679 var brandLogo = product.ProductFields["BrandLogo"]; 680 if (brandLogo != null && !string.IsNullOrEmpty(brandLogo.Value.ToString()) && Regex.IsMatch(brandLogo.Value.ToString(), @"(.png|.jpg|.jpeg)$", RegexOptions.IgnoreCase)) 681 { 682 <div class="d-flex flex-row-reverse"> 683 <div class="p-2"> 684 <img src="/Admin/Public/GetImage.ashx?image=@brandLogo.Value&width=100&format=webp" /> 685 </div> 686 </div> 687 } 688 } 689 <div> 690 <h1 class="@titleFontSize" itemprop="name">@product.Name</h1> 691 @if (!hideProductNumber) 692 { 693 <div class="opacity-85">@product.Number</div> 694 } 695 </div> 696 </div> 697 }

Specifications

Variant feature selector TractorCon, Length
By clicking 'Accept All' you consent that we may collect information about you for various purposes, including: Functionality, Statistics and Marketing