Bezoek ons aan de Kantsingel 44 in Oss. Bekijk onze openingstijden hier.

Error executing template "Designs/GBIVanDijk_generated/eCom/Product/pdp.cshtml"
System.Xml.XmlException: An error occurred while parsing EntityName. Line 1, position 325.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.ParseEntityName()
   at System.Xml.XmlTextReaderImpl.ParseEntityReference()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace)
   at System.Xml.XmlLoader.LoadDocSequence(XmlDocument parentDoc)
   at System.Xml.XmlDocument.Load(XmlReader reader)
   at System.Xml.XmlDocument.LoadXml(String xml)
   at CompiledRazorTemplates.Dynamic.RazorEngine_12c159b19b15465580f6ecf69136bda5.AddSidebarPropertiesFromXML(String xml, List`1 specifications) in E:\Dynamicweb.NET\GBIvanDijk\Production\files\Templates\Designs\GBIVanDijk_generated\eCom\Product\pdp.cshtml:line 2498
   at CompiledRazorTemplates.Dynamic.RazorEngine_12c159b19b15465580f6ecf69136bda5.Execute() in E:\Dynamicweb.NET\GBIvanDijk\Production\files\Templates\Designs\GBIVanDijk_generated\eCom\Product\pdp.cshtml:line 2421
   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.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2 @using Dynamicweb; 3 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 4 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 5 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 6 @using System.Linq; 7 @using System.Globalization; 8 @using Dynamicweb.Content; 9 @using Bluedesk.DynamicWeb.ItemTypes.Settings.Configuration; 10 11 @{ 12 string listId = "product_detail"; 13 string listName = "Product detail"; 14 } 15 16 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 17 @using System.Globalization; 18 @using Dynamicweb; 19 @using Dynamicweb.Content.Items; 20 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 21 @using Bluedesk.DynamicWeb.ItemTypes.Settings.Configuration; 22 @using System.Runtime.Caching; 23 24 @{ 25 26 var colorService = new ColorSwatchService(); 27 28 /*** Masterconfig ***/ 29 30 Page master_configuration = null; 31 32 MemoryCache cache = MemoryCache.Default; 33 // store basket in cache 34 if (cache.Contains($"master_configuration_{Pageview.AreaID}")) 35 { 36 master_configuration = cache.Get($"master_configuration_{Pageview.AreaID}") as Page; 37 } 38 else 39 { 40 master_configuration = Dynamicweb.Services.Pages.GetPageByNavigationTag(Pageview.AreaID, "MasterConfiguration"); 41 cache.Set($"master_configuration_{Pageview.AreaID}", master_configuration, DateTimeOffset.UtcNow.AddMinutes(5)); 42 } 43 MemoryCache cacheMC = MemoryCache.Default; 44 MasterConfig mc = null; 45 46 if (cacheMC.Contains($"mc_{Pageview.AreaID}")) 47 { 48 mc = cacheMC.Get($"mc_{Pageview.AreaID}") as MasterConfig; 49 } 50 else 51 { 52 mc = master_configuration.Item.ToCodeFirstItem<MasterConfig>(); 53 cacheMC.Set($"mc_{Pageview.AreaID}", mc, DateTimeOffset.UtcNow.AddMinutes(5)); 54 } 55 56 EcomConfig EcommerceConfiguration = mc.EcomConfiguration; 57 ProductDetailConfig ProductDetailConfiguration = mc.ProductDetailConfiguration; 58 ProductOverviewConfig ProductOverviewConfiguration = mc.ProductOverviewConfiguration; 59 60 /*** BEGIN Webshop Variables ***/ 61 62 63 MemoryCache cachewebshopPage = MemoryCache.Default; 64 WebshopPage webshopPage = null; 65 66 if (cachewebshopPage.Contains($"webshopPage_{Pageview.AreaID}")) 67 { 68 webshopPage = cachewebshopPage.Get($"webshopPage_{Pageview.AreaID}") as WebshopPage; 69 } 70 else 71 { 72 webshopPage = Pageview.Page.Item.ToCodeFirstItem<WebshopPage>(); 73 cachewebshopPage.Set($"webshopPage_{Pageview.AreaID}", webshopPage, DateTimeOffset.UtcNow.AddMinutes(5)); 74 } 75 76 /*** END Webshop Variables ***/ 77 78 /*** BEGIN Configuration Bools ***/ 79 80 bool FormattedBool = EcommerceConfiguration.FormattedPrices; 81 bool WithVATBool = Pageview.Area.EcomPricesWithVat == "True"; 82 bool pricesWithoutVatForUsers = EcommerceConfiguration.ShowPricesWithoutVatForUsers; 83 bool pricesWithoutVatForValidVat = EcommerceConfiguration.ShowPricesWithoutVatWhenValidVatNumber; 84 if (pricesWithoutVatForUsers && !pricesWithoutVatForValidVat && Pageview.User != null) 85 { 86 WithVATBool = false; 87 } 88 if (pricesWithoutVatForValidVat && Pageview.User != null && !string.IsNullOrWhiteSpace(Pageview.User.VatRegNumber)) 89 { 90 WithVATBool = false; 91 } 92 bool hidePricesForGuests = EcommerceConfiguration.HidePricesForGuests; 93 bool hideShoppingCartForGuests = EcommerceConfiguration.HideShoppingCartForGuests; 94 bool hideStockForGuests = EcommerceConfiguration.HideStockForGuests; 95 bool hideZeroPrices = EcommerceConfiguration.HideZeroPrices; 96 bool enableAddToCartForZeroPrices = EcommerceConfiguration.AddToCartAllowZeroPrices; 97 bool enableAddToCartForOutOfStock = EcommerceConfiguration.AddToCartAllowOutOfStock; 98 string stockFormat = EcommerceConfiguration.StockFormat; 99 bool showReviewTab = ProductDetailConfiguration.ShowProductDetailReviewTab; 100 bool allowBackInStockNotifications = EcommerceConfiguration.AllowBackInStockNotifications; 101 bool allowBackInStockNotificationsForGuests = EcommerceConfiguration.AllowBackInStockNotificationsForGuests; 102 bool displayBackInStockNotifications = !allowBackInStockNotificationsForGuests && Pageview.User == null ? false : allowBackInStockNotifications; 103 104 105 /*** END Configuration Bools ***/ 106 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ID"), GetString("Ecom:Product.VariantID"), Pageview.Area.EcomLanguageId); 107 108 Dynamicweb.Context.Current.Session["productid"] = GetString("Ecom:Product.ID"); 109 110 var designRoot = "/Files/Templates/Designs/" + Pageview.Area.Layout.Design.Name; 111 112 bool isVariant = !string.IsNullOrWhiteSpace(GetString("Ecom:Product.VariantID")); 113 bool hasVariants = !isVariant ? GetInteger("Ecom:Product.VariantCount") > 0 : false; 114 115 string Manufacturer = GetString("Ecom:Manufacturer.Name"); 116 117 string productName = GetString("Ecom:Product.Name"); 118 Dynamicweb.Context.Current.Items["CurrentProductName"] = productName; 119 120 string productNumber = GetString("Ecom:Product.Number"); 121 122 string productShortDescription = GetString("Ecom:Product.ShortDescription"); 123 string productLongDescription = GetString("Ecom:Product.LongDescription"); 124 bool hasShortDescription = !string.IsNullOrWhiteSpace(productShortDescription); 125 bool hasLongDescription = !string.IsNullOrWhiteSpace(productLongDescription); 126 127 bool hasSpecifications = GetLoop("ProductCategories").Any(c => c.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))); 128 129 if (!hasSpecifications && (!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.CATpropertiesXML.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ERPpropertiesXML.Value")))) 130 { 131 hasSpecifications = true; 132 } 133 134 string manufacturerLogo = GetString("Ecom:Manufacturer.Logo"); 135 136 string price = GetString("Ecom:Product.Price"); 137 string priceFormatted = GetString("Ecom:Product.Price.PriceFormatted"); 138 bool hasDiscount = GetBoolean("Ecom:Product.HaveDiscount"); 139 bool pricezero = GetBoolean("Ecom:Product.Price.IsZero"); 140 141 string WithVATSuffix = WithVATBool ? "WithVAT" : "WithoutVAT"; 142 string FormattedSuffix = FormattedBool ? "Formatted" : ""; 143 144 string ProductdetailPriceSuffixWithVAT = Translate("Productdetail.Price.Suffix.WithVAT", "Incl. VAT"); 145 string ProductdetailPriceSuffixWithoutVAT = Translate("Productdetail.Price.Suffix.WithoutVAT", "Excl. VAT"); 146 147 string ProductdetailPriceSuffix = WithVATBool ? ProductdetailPriceSuffixWithVAT : ProductdetailPriceSuffixWithoutVAT; 148 149 string discountPriceValue = GetString("Ecom:Product.Discount.Price"); 150 double yourProfitValue = GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value"); 151 string yourProfitValueFormatted = GetString("Ecom:Product.Discount.TotalAmount.Price" + WithVATSuffix + FormattedSuffix); 152 153 double productRating = GetDouble("Comments.Rating"); 154 var Comments = GetLoop("Comments"); 155 int productRatingCount = GetInteger("Comments.Count"); 156 var productReviewPercentage = (productRating * 10) * 2 + "%"; 157 158 double stockSize = hasVariants ? GetLoop("VariantInfos").Sum(item => item.GetDouble("Stock")) : GetDouble("Ecom:Product.Stock"); 159 string stock = stockSize.ToString(stockSize % 1 == 0 ? "N0" : "N2", CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName)) ?? ""; 160 string stockText = GetString("Ecom:Product:Stock.Text"); 161 bool neverOutOfStock = GetBoolean("Ecom:Product.NeverOutOfStock"); 162 bool inStock = stockSize > 0 || neverOutOfStock; 163 string stockStateClass = inStock ? "pdp-stockstate--instock" : "pdp-stockstate--outofstock"; 164 165 string productTagline = GetString("Ecom:Product:Field.ProductTagline.Value"); 166 string productTaglineInfo = GetString("Ecom:Product:Field.ProductTaglineInfo.Value"); 167 string productDetailPageTagline = webshopPage.Tagline; 168 string productDetailPageTaglineInfo = webshopPage.TaglineInfo; 169 170 var productDetailUSPList = webshopPage.ProductDetailUSPs; 171 172 string defaultImage = Dynamicweb.Ecommerce.Services.ProductImages.GetImagePath(p); 173 var productid = GetString("Ecom:Product.ID"); 174 string groupid = GetString("Ecom:Product.PrimaryOrFirstGroupID"); 175 string productVariantId = GetString("Ecom:Product.VariantID"); 176 string addToCartLink = string.Format("/products/checkout?CartCmd=Add&ProductID={0}&Redirect=/products/checkout", productid); 177 List<LoopItem> detailImages = GetLoop("Details"); 178 List<LoopItem> productDiscounts = GetLoop("ProductDiscounts"); 179 List<string> productImages = new List<string>(); 180 181 if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ImageLarge.Clean"))) 182 { 183 productImages.Add(GetString("Ecom:Product.ImageLarge.Clean")); 184 } 185 else if (!string.IsNullOrWhiteSpace(defaultImage)) 186 { 187 productImages.Add(defaultImage); 188 } 189 190 string YoutubeProductVideo = GetString("Ecom:Product:Field.YoutubeProductVideo.Value"); 191 bool hasYoutubeVideo = !string.IsNullOrWhiteSpace(YoutubeProductVideo) && YoutubeProductVideo != "Files/"; 192 193 var PerfionThumbnails = GetString("Ecom:Product:Field.AdditionalImages.Value").Split(';'); 194 195 foreach (var PerfionImage in PerfionThumbnails) 196 { 197 if (!string.IsNullOrWhiteSpace(PerfionImage)) 198 { 199 productImages.Add("Files/" + PerfionImage); 200 } 201 } 202 203 foreach (var image in detailImages) 204 { 205 var isImageAsset = image.GetString("Ecom:Product:Detail.Image"); 206 var link = image.GetString("Ecom:Product:Detail.Image.Clean"); 207 if (!string.IsNullOrWhiteSpace(isImageAsset) && !string.IsNullOrWhiteSpace(link) && !productImages.Contains(link)) 208 { 209 productImages.Add(link); 210 } 211 } 212 213 Dynamicweb.Context.Current.Items["productDetailActive"] = true; 214 215 var productNumberLabel = Translate("Productdetail.ArticleNumber.Prefix", "Artikelnummer:"); 216 var originalPriceLabel = Translate("Productdetail.OriginalPriceLabel", "Adviesprijs"); 217 var yourProfitLabel = Translate("Productdetail.YourProfitLabel", "Uw voordeel:"); 218 var inShoppingCartLabel = Translate("ProductDetail.InShoppingCart", "In winkelwagen"); 219 220 // Payment provider logo's 221 var selectedPaymentLogos = Pageview.Area.Item["FooterPaymentLogos"]; 222 223 bool enableShoppingCart = hideShoppingCartForGuests && Pageview.User == null ? false : Pageview.Area.Item["ConfigModuleShoppingCart"] != null ? (bool)Pageview.Area.Item["ConfigModuleShoppingCart"] : false; 224 bool displayPrice = hidePricesForGuests ? Pageview.User != null : true; 225 bool enableProductCompare = Pageview.Area.Item["ConfigModuleProductCompare"] != null ? (bool)Pageview.Area.Item["ConfigModuleProductCompare"] : false; 226 bool enableProductFavorites = Pageview.User == null ? false : Pageview.Area.Item["ConfigModuleFavoriteLists"] != null ? (bool)Pageview.Area.Item["ConfigModuleFavoriteLists"] : false; 227 bool enableProductStock = hideStockForGuests ? Pageview.User != null : true; 228 229 bool enableProductShoppingCart = enableShoppingCart; 230 if (!enableAddToCartForZeroPrices && pricezero) 231 { 232 enableProductShoppingCart = false; 233 } 234 if (!enableAddToCartForOutOfStock && !inStock) 235 { 236 enableProductShoppingCart = false; 237 } 238 if (GetBoolean("Ecom:Product.Discontinued")) 239 { 240 enableProductShoppingCart = false; 241 } 242 243 string specifier = "G"; 244 CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US"); 245 246 string originalProductPrice = GetString("Ecom:Product.Price.Price" + WithVATSuffix + FormattedSuffix); 247 string discountProductPrice = GetString("Ecom:Product.Discount.Price.Price" + WithVATSuffix + FormattedSuffix); 248 double discountProductPriceDouble = GetDouble("Ecom:Product.Discount.Price.Price"); 249 string gtmPrice = GetDouble("Ecom:Product.Price.Price.Value").ToString(specifier, culture); 250 string gtmDiscount = GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value").ToString(specifier, culture); 251 string gtmValue = GetDouble("Ecom:Product.Discount.Price.Value").ToString(specifier, culture); 252 double discountPercentage = Math.Round((((GetDouble("Ecom:Product.Discount.Price.Price" + WithVATSuffix) - GetDouble("Ecom:Product.Price.Price" + WithVATSuffix)) / GetDouble("Ecom:Product.Price.Price" + WithVATSuffix)) * 100)); 253 254 string informativePrice = GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 255 if (EcommerceConfiguration.UseInformativePriceAsFromPrice && !string.IsNullOrWhiteSpace(informativePrice)) 256 { 257 double informativePriceValue = GetDouble("Ecom:Product.InformativePrice.Price" + WithVATSuffix); 258 hasDiscount = discountProductPriceDouble < informativePriceValue; 259 if (hasDiscount) 260 { 261 originalProductPrice = informativePrice; 262 discountPercentage = Math.Round((((GetDouble("Ecom:Product.Price.Price" + WithVATSuffix) - informativePriceValue) / informativePriceValue) * 100)); 263 yourProfitValue = informativePriceValue - GetDouble("Ecom:Product.Price.Price" + WithVATSuffix); 264 yourProfitValueFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = yourProfitValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = yourProfitValue }.PriceWithoutVATFormatted; 265 } 266 } 267 268 string retailPrice = ""; 269 bool displayRetailPrice = EcommerceConfiguration.DisplayRetailPrice; 270 if (EcommerceConfiguration.DisplayRetailPriceForUsers && Pageview.User == null) 271 { 272 displayRetailPrice = false; 273 } 274 if (displayRetailPrice) 275 { 276 string priceFieldName = EcommerceConfiguration.RetailPriceField; 277 if (EcommerceConfiguration.RetailPriceIsDbPrice) 278 { 279 PriceRaw customerPriceRaw = new PriceRaw(GetDouble("Ecom:Product.DBPrice"), Dynamicweb.Ecommerce.Common.Context.Currency); 280 PriceCalculated customerPrice = new Dynamicweb.Ecommerce.Prices.PriceCalculated(customerPriceRaw); 281 if (customerPrice.Price > 0) 282 { 283 retailPrice = WithVATBool ? customerPrice.PriceWithVATFormatted : customerPrice.PriceWithoutVATFormatted; 284 } 285 } 286 else if (!string.IsNullOrWhiteSpace(priceFieldName)) 287 { 288 double customerPriceValue = GetDouble($"Ecom:Product:Field.{priceFieldName}.Value"); 289 if (customerPriceValue > 0) 290 { 291 retailPrice = WithVATBool ? new PriceInfo { PriceWithVAT = customerPriceValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = customerPriceValue }.PriceWithoutVATFormatted; 292 } 293 } 294 } 295 296 PriceInfo minPrice = null; 297 string minPriceFormatted = ""; 298 PriceInfo maxPrice = null; 299 string maxPriceFormatted = ""; 300 301 if (hasVariants) 302 { 303 minPrice = p.VariantCombinations.OrderBy(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 304 maxPrice = p.VariantCombinations.OrderByDescending(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 305 minPriceFormatted = WithVATBool ? minPrice.PriceWithVATFormatted : minPrice.PriceWithoutVATFormatted; 306 maxPriceFormatted = WithVATBool ? maxPrice.PriceWithVATFormatted : maxPrice.PriceWithoutVATFormatted; 307 } 308 309 bool displayProductPrice = displayPrice; 310 if (hideZeroPrices && pricezero) 311 { 312 if ((hasVariants && !isVariant)) 313 { 314 if (minPrice.Price == 0 && maxPrice.Price == 0) 315 { 316 displayProductPrice = false; 317 } 318 } 319 else 320 { 321 displayProductPrice = false; 322 } 323 } 324 325 string productRibbon = GetString("Ecom:Product:Field.Product_Ribbon.Value"); 326 string productRibbonStyle = GetString("Ecom:Product:Field.RibbonStyle.Value"); 327 328 int QuotePageID = Bluedesk.Tools.DynamicWeb.Generic.PageHelper.GetPageIDByNavigationTag("QuoteForm", Pageview.AreaID); 329 var buttonIconClass = Pageview.Area.Item["Global_button_icon"] != null ? Pageview.Area.Item["Global_button_icon"].ToString().Replace("+", " ") : "fal fa-arrow-right"; 330 var minimumQuantity = GetDouble("Ecom:Product.PurchaseMinimumQuantity") == 0 ? 1 : GetDouble("Ecom:Product.PurchaseMinimumQuantity"); 331 var quantityStep = GetDouble("Ecom:Product.PurchaseQuantityStep") == 0 ? 1 : GetDouble("Ecom:Product.PurchaseQuantityStep"); 332 333 var productCategory = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Product.PrimaryOrFirstGroupID")); 334 string productCategoryName = productCategory != null ? productCategory.Name.Replace("''", "\\\"").Replace("'", "") : ""; 335 336 string AddToCartButtonBackgroundColor = EcommerceConfiguration.AddToCartButtonBackgroundColor; 337 if (!string.IsNullOrWhiteSpace(AddToCartButtonBackgroundColor)) 338 { 339 AddToCartButtonBackgroundColor = !AddToCartButtonBackgroundColor.Contains("#") ? colorService.GetHexColor(Pageview.AreaID, AddToCartButtonBackgroundColor) : AddToCartButtonBackgroundColor; 340 } 341 342 bool enableSpecificationsSidebar = !ProductDetailConfiguration.ShowSpecificationsAsTab; 343 344 bool showTitleAboveInfoBlock = ProductDetailConfiguration.ShowTitleAboveInfoBlock; 345 bool showVariantInfo = ProductDetailConfiguration.ShowVariantInfo; 346 347 string unitValue = GetString("Ecom:Product:Field.SamenstellingEenheid.Value"); 348 349 string productIconsValue = GetString("Ecom:Product:Field.ProductIcons.Value"); 350 string[] productIcons = productIconsValue.Split(';'); 351 } 352 353 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 354 @using Dynamicweb; 355 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 356 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 357 @using System.Linq; 358 @using System.Globalization; 359 @using Dynamicweb.Content; 360 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites; 361 @using System.Xml.Linq; 362 363 @helper RenderInfoContentElement(string ItemName, string header, string anchor) 364 { 365 if (!string.IsNullOrWhiteSpace(GetString(ItemName))) 366 { 367 <section id="@anchor" class="pdp-specifications-info__wrapper"> 368 @if (!string.IsNullOrWhiteSpace(header)) 369 { 370 <h2 class="pdp-specifications-info__header">@header</h2> 371 } 372 <section class="pdp-specifications-info__body"> 373 @GetString(ItemName) 374 </section> 375 <div class="pdp-specifications-info__showmore"> 376 <section class="pdp-specifications-info__showmoregradient"></section> 377 <button class="pdp-button pdp-button--inline pdp-button--showmore" type="button">@Translate("Productdetail.Info.ShowMore", "Show more")</button> 378 <button class="pdp-button pdp-button--inline pdp-button--showless" type="button" style="display: none;">@Translate("Productdetail.Info.ShowLess", "Show less")</button> 379 </div> 380 </section> 381 } 382 } 383 384 @helper RenderLongDescription() 385 { 386 string longDescription = GetString("Ecom:Product.LongDescription"); 387 string YoutubeProductVideo = GetString("Ecom:Product:Field.YoutubeProductVideo.Value"); 388 bool hasYoutubeVideo = !string.IsNullOrWhiteSpace(YoutubeProductVideo) && YoutubeProductVideo != "Files/"; 389 390 if (!string.IsNullOrWhiteSpace(longDescription)) 391 { 392 <section id="longdescription" class="pdp-specifications-info__wrapper"> 393 <section class="pdp-specifications-info__body"> 394 <div class="pdp-specifications-info__content"> 395 @longDescription 396 </div> 397 @if (hasYoutubeVideo) 398 { 399 <div class="pdp-specifications-info__video"> 400 <lite-youtube videoid="@YoutubeProductVideo" params="controls=1&loop=0&playlist=@YoutubeProductVideo&playsinline=1&modestbranding=1&mute=0&rel=0&enablejsapi=1& origin=@Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain()&disablekb=0"></lite-youtube> 401 </div> 402 } 403 </section> 404 <div class="pdp-specifications-info__showmore"> 405 <section class="pdp-specifications-info__showmoregradient"></section> 406 <button class="pdp-button pdp-button--inline pdp-button--showmore" type="button">@Translate("Productdetail.Info.ShowMore", "Show more")</button> 407 <button class="pdp-button pdp-button--inline pdp-button--showless" type="button" style="display: none;">@Translate("Productdetail.Info.ShowLess", "Show less")</button> 408 </div> 409 </section> 410 } 411 } 412 413 @helper renderReview(string name, string body, int Rating) 414 { 415 <li class="pdp-review__list-item"> 416 <div class="pdp-review-item__header"> 417 <p class="pdp-review-item__name">@name</p> 418 @renderReviewIndicator("#DB3125", Rating, "row", false, 0) 419 </div> 420 <p class="pdp-review-item__comment">@body</p> 421 </li> 422 } 423 424 @helper renderProduct(LoopItem product, string productdetailPriceSuffix, string WithVATSuffix, string FormattedSuffix, bool enableShoppingCart, bool displayPrice, bool enableProductStock, string listid, string listname, EcomConfig EcommerceConfiguration) 425 { 426 bool hideZeroPrices = EcommerceConfiguration.HideZeroPrices; 427 bool enableAddToCartForZeroPrices = EcommerceConfiguration.AddToCartAllowZeroPrices; 428 bool enableAddToCartForOutOfStock = EcommerceConfiguration.AddToCartAllowOutOfStock; 429 string stockFormat = EcommerceConfiguration.StockFormat; 430 431 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 432 433 string productName = product.GetString("Ecom:Product.Name"); 434 string productNumber = product.GetString("Ecom:Product.Number"); 435 string productId = product.GetString("Ecom:Product.ID"); 436 string productVariantId = product.GetString("Ecom:Product.VariantID"); 437 string productURL = product.GetString("Ecom:Product.VariantLinkGroup.Clean").Replace($"ID={Pageview.ID.ToString()}&", $"ID={productDetailPageId.ToString()}&"); 438 var p = Dynamicweb.Ecommerce.Services.Products.GetProductById(productId, productVariantId, Pageview.Area.EcomLanguageId); 439 string productImage = Dynamicweb.Ecommerce.Services.ProductImages.GetImagePath(p); 440 441 bool isVariant = !string.IsNullOrWhiteSpace(p.VariantId); 442 int variantCount = product.GetInteger("Ecom:Product.VariantCount"); 443 bool hasVariants = !isVariant ? variantCount > 0 : false; 444 445 var productCategory = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 446 string productCategoryName = productCategory != null ? productCategory.Name.Replace("''", "\\\"").Replace("'", "") : ""; 447 string productManufacturer = product.GetString("Ecom:Manufacturer.Name"); 448 449 string specifier = "G"; 450 CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US"); 451 452 string gtmDiscount = product.GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value").ToString(specifier, culture); 453 string gtmPrice = product.GetDouble("Ecom:Product.Price.Price.Value").ToString(specifier, culture); 454 455 bool isPriceZero = product.GetDouble("Ecom:Product.Price.Price.Value") <= 0; 456 bool hasDiscount = product.GetBoolean("Ecom:Product.HaveDiscount"); 457 string OriginalPrice = product.GetString("Ecom:Product.Price.Price" + WithVATSuffix + FormattedSuffix); 458 string Price = product.GetString("Ecom:Product.Discount.Price.Price" + WithVATSuffix + FormattedSuffix); 459 string yourDiscount = product.GetString("Ecom:Product.Discount.TotalAmount.Price" + WithVATSuffix + FormattedSuffix); 460 461 string informativePrice = product.GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 462 if (EcommerceConfiguration.UseInformativePriceAsFromPrice && !string.IsNullOrWhiteSpace(informativePrice)) 463 { 464 double informativePriceValue = product.GetDouble("Ecom:Product.InformativePrice.Price"); 465 hasDiscount = product.GetDouble("Ecom:Product.Price.Price") < informativePriceValue; 466 if (hasDiscount) 467 { 468 OriginalPrice = product.GetString("Ecom:Product.InformativePrice.Price" + WithVATSuffix + FormattedSuffix); 469 double yourProfitValue = product.GetDouble("Ecom:Product.InformativePrice.Price" + WithVATSuffix) - product.GetDouble("Ecom:Product.Price.Price" + WithVATSuffix); 470 yourDiscount = WithVATSuffix == "WithVAT" ? new PriceInfo { PriceWithVAT = yourProfitValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = yourProfitValue }.PriceWithoutVATFormatted; 471 } 472 } 473 474 string retailPrice = ""; 475 bool displayRetailPrice = EcommerceConfiguration.DisplayRetailPrice; 476 if (EcommerceConfiguration.DisplayRetailPriceForUsers && Pageview.User == null) 477 { 478 displayRetailPrice = false; 479 } 480 if (displayRetailPrice) 481 { 482 string priceFieldName = EcommerceConfiguration.RetailPriceField; 483 if (EcommerceConfiguration.RetailPriceIsDbPrice) 484 { 485 PriceRaw customerPriceRaw = new PriceRaw(product.GetDouble("Ecom:Product.DBPrice"), Dynamicweb.Ecommerce.Common.Context.Currency); 486 PriceCalculated customerPrice = new Dynamicweb.Ecommerce.Prices.PriceCalculated(customerPriceRaw); 487 if (customerPrice.Price > 0) 488 { 489 retailPrice = WithVATSuffix == "WithVAT" ? customerPrice.PriceWithVATFormatted : customerPrice.PriceWithoutVATFormatted; 490 } 491 } 492 else if (!string.IsNullOrWhiteSpace(priceFieldName)) 493 { 494 double customerPriceValue = product.GetDouble($"Ecom:Product:Field.{priceFieldName}.Value"); 495 if (customerPriceValue > 0) 496 { 497 retailPrice = WithVATSuffix == "WithVAT" ? new PriceInfo { PriceWithVAT = customerPriceValue }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = customerPriceValue }.PriceWithoutVATFormatted; 498 } 499 } 500 } 501 502 PriceInfo minPrice = null; 503 string minPriceFormatted = ""; 504 PriceInfo maxPrice = null; 505 string maxPriceFormatted = ""; 506 507 if (hasVariants) 508 { 509 minPrice = p.VariantCombinations.OrderBy(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 510 maxPrice = p.VariantCombinations.OrderByDescending(c => c.Product.Price.Price).FirstOrDefault().Product.Price; 511 minPriceFormatted = WithVATSuffix == "WithVAT" ? minPrice.PriceWithVATFormatted : minPrice.PriceWithoutVATFormatted; 512 maxPriceFormatted = WithVATSuffix == "WithVAT" ? maxPrice.PriceWithVATFormatted : maxPrice.PriceWithoutVATFormatted; 513 } 514 515 double stockSize = hasVariants ? product.GetLoop("VariantInfos").Sum(item => item.GetDouble("Stock")) : product.GetDouble("Ecom:Product.Stock"); 516 string stockText = product.GetString("Ecom:Product:Stock.Text"); 517 string stockSizeFormatted = stockSize.ToString(stockSize % 1 == 0 ? "N0" : "N2", System.Globalization.CultureInfo.GetCultureInfo(Pageview.Area.CultureInfo.TwoLetterISOLanguageName)); 518 bool neverOutOfStock = product.GetBoolean("Ecom:Product.NeverOutOfStock"); 519 bool isInStock = stockSize > 0 || neverOutOfStock; 520 string stockClass = isInStock ? "products-module__product-stock-state--instock" : "products-module__product-stock-state--outofstock"; 521 522 int productRating = product.GetInteger("Ecom:Product.Rating"); 523 int productCommentCount = product.GetInteger("Ecom:Product.CommentCount"); 524 string productRibbon = product.GetString("Ecom:Product:Field.Product_Ribbon.Value"); 525 string productRibbonStyle = product.GetString("Ecom:Product:Field.RibbonStyle.Value"); 526 527 string yourProfitLabel = Translate("Productdetail.YourProfitLabel", "Uw voordeel:"); 528 529 string manufacturerName = product.GetString("Ecom:Manufacturer.Name"); 530 531 bool enableProductShoppingCart = enableShoppingCart; 532 if (!enableAddToCartForZeroPrices && isPriceZero) 533 { 534 enableProductShoppingCart = false; 535 } 536 if (!enableAddToCartForOutOfStock && !isInStock) 537 { 538 enableProductShoppingCart = false; 539 } 540 if (product.GetBoolean("Ecom:Product.Discontinued")) 541 { 542 enableProductShoppingCart = false; 543 } 544 545 bool displayProductPrice = displayPrice; 546 if (hideZeroPrices && isPriceZero) 547 { 548 if ((hasVariants && !isVariant)) 549 { 550 if (minPrice.Price == 0 && maxPrice.Price == 0) 551 { 552 displayProductPrice = false; 553 } 554 } 555 else 556 { 557 displayProductPrice = false; 558 } 559 } 560 561 <li class="products-module__slider-cell"> 562 <div class="products-module products-module__slider-cell__innerwrapper"> 563 564 @if (!string.IsNullOrWhiteSpace(productRibbon)) 565 { 566 <p class="products-module__ribbon products-module__ribbon--@productRibbonStyle"> 567 <span class="products-module__ribbon-text">@productRibbon</span> 568 </p> 569 } 570 571 <figure class="products-module__image-container"> 572 @if (!string.IsNullOrWhiteSpace(productImage)) 573 { 574 <img src="/Admin/Public/GetImage.ashx?Image=@productImage&Crop=7&Format=webp&Quality=90&Compression=80&Height=200" alt="@productName" loading="lazy" height="200" width="300" /> 575 } 576 </figure> 577 578 <h3 class="products-module__product-name"> 579 @if (!string.IsNullOrWhiteSpace(manufacturerName)) 580 { 581 <span class="products-module__product-name--manufacturer">@manufacturerName</span> 582 } 583 <span class="products-module__product-name--product">@productName</span> 584 </h3> 585 586 @if (!string.IsNullOrWhiteSpace(productNumber)) 587 { 588 <p class="products-module__product-article-number">@productNumber</p> 589 } 590 591 @if (productCommentCount > 0) 592 { 593 <div class="products-module__product-review-indicator"> 594 @renderReviewIndicator("gold", productRating, "row", true, productCommentCount) 595 </div> 596 } 597 598 <div class="products-module__product-spacer"></div> 599 600 @if (hasVariants) 601 { 602 <div class="products-module__product-variant-info"> 603 @if (variantCount == 1) 604 { 605 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Single", "{0} variant"), variantCount)</p> 606 } 607 else 608 { 609 <p>@string.Format(Translate("ProductBlockVariantInfo.VariantCount.Multiple", "{0} variants"), variantCount)</p> 610 } 611 </div> 612 } 613 614 @if (displayPrice && displayProductPrice) 615 { 616 <div class="products-module__product-price-container"> 617 @if (hasVariants) 618 { 619 <section class="pdp-price__subcontainer"> 620 @if (minPrice != maxPrice) 621 { 622 <span class="products-module__product-price">@minPriceFormatted - @maxPriceFormatted</span> 623 } 624 else 625 { 626 <span class="products-module__product-price">@minPriceFormatted</span> 627 } 628 <span class="products-module__product-price-suffix">@productdetailPriceSuffix</span> 629 </section> 630 } 631 else 632 { 633 if (hasDiscount) 634 { 635 <span class="products-module__product-price-original">@OriginalPrice</span> 636 <span class="products-module__product-price">@Price</span> 637 } 638 else 639 { 640 <span class="products-module__product-price">@OriginalPrice</span> 641 } 642 <span class="products-module__product-price-suffix">@productdetailPriceSuffix</span> 643 if (!string.IsNullOrWhiteSpace(retailPrice)) 644 { 645 <p class="products-module__product-retail-price"> 646 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 647 </p> 648 } 649 if (hasDiscount) 650 { 651 <p class="products-module__product-your-discount">@Translate("Productdetail.YourProfitLabel", "Uw voordeel:") @yourDiscount</p> 652 } 653 } 654 </div> 655 } 656 657 <section class="products-module__product-actions"> 658 @if (enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 659 { 660 if (stockFormat == "text") 661 { 662 if (!string.IsNullOrWhiteSpace(stockText)) 663 { 664 <p class="products-module__product-stock-state @stockClass">@string.Format(stockText, stockSize)</p> 665 } 666 } 667 else 668 { 669 string translationTag = ""; 670 if (neverOutOfStock) 671 { 672 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 673 } 674 else if (stockSize == 1) 675 { 676 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 677 } 678 else if (stockSize > 1) 679 { 680 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 681 } 682 else if (!isInStock) 683 { 684 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 685 } 686 <p class="products-module__product-stock-state @stockClass">@string.Format(translationTag, stockSizeFormatted)</p> 687 } 688 } 689 690 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) 691 { 692 <add-to-cart class="app-addtocart" 693 data-prodid="@productId" 694 data-variantid="@productVariantId" 695 data-show-text="False" 696 data-min-quantity="@product.GetDouble("Ecom:Product.PurchaseMinimumQuantity")" 697 data-step="@product.GetDouble("Ecom:Product.PurchaseQuantityStep")" 698 data-list-id="@listid" 699 data-list-name="@listname"> 700 </add-to-cart> 701 } 702 </section> 703 <a href="@productURL" class="products-module__link" data-id="@productId" data-variantid="@productVariantId" data-number="@productNumber" data-name='@productName.Replace("''", "\\\"").Replace("'", "")' data-position="@product.GetInteger("Products.LoopCounter")" data-brand='@productManufacturer.Replace("''", "\\\"").Replace("'", "")' data-category='@productCategoryName' data-listid="@listid" data-listname="@listname" data-gtmprice="@gtmPrice" data-gtmdiscount="@gtmDiscount" aria-label="@productName"></a> 704 </div> 705 </li> 706 } 707 708 @helper renderProductListEnhancedEcom(string listId, string listName, List<LoopItem> products, string WithVATSuffix) 709 { 710 string specifier = "G"; 711 CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US"); 712 713 <text> 714 <script> 715 if(window.dataLayer) { 716 dataLayer.push({ 717 'event': 'view_item_list', 718 'ecommerce': { 719 'item_list_id': '@listId', 720 'item_list_name': '@listName', 721 'items': [ 722 @foreach (var product in products) { 723 var relCategoryObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 724 string relCategoryName = relCategoryObject != null ? relCategoryObject.Name.Replace("''", "\\\"").Replace("'", "") : ""; 725 <text> 726 { 727 'item_id': '@product.GetString("Ecom:Product.ID")', 728 'item_name': '@product.GetString("Ecom:Product.Name").Replace("''", "\\\"").Replace("'", "")', 729 'item_number': '@product.GetString("Ecom:Product.Number")', 730 'discount': @product.GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value").ToString(specifier, culture), 731 'index': @product.GetInteger("Products.LoopCounter"), 732 'item_brand': '@product.GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 733 'item_category': '@relCategoryName', 734 'item_list_id': '@listId', 735 'item_list_name': '@listName', 736 'item_variant': '@product.GetString("Ecom:Product.VariantID")', 737 'price': @product.GetDouble("Ecom:Product.Price.Price.Value").ToString(specifier, culture), 738 'quantity': 1, 739 }, 740 </text> 741 } 742 ] 743 } 744 }); 745 } 746 </script> 747 </text> 748 } 749 750 @helper renderReviewIndicator(string progressbarColor, int value, string flexDirection, bool boolReviews, int CommentCount) 751 { 752 int ratingPercentage = (100 / 5) * value; 753 754 <section style="display: flex; flex-direction: @flexDirection; position: relative; align-items: center;"> 755 <div class="reviews__indicator" style="display: flex; flex-direction: row;"> 756 <div class="reviews__indicator-progressbar" style="width: @ratingPercentage.ToString()%; background-color: @progressbarColor;"></div> 757 <ul class="reviews__indicator-star-list"> 758 <li class="reviews__indicator-star-list-item"></li> 759 <li class="reviews__indicator-star-list-item"></li> 760 <li class="reviews__indicator-star-list-item"></li> 761 <li class="reviews__indicator-star-list-item"></li> 762 <li class="reviews__indicator-star-list-item"></li> 763 </ul> 764 </div> 765 <span style="display: flex; position: relative; font-size: 12px; line-height: initial;"> 766 @value / 5 767 @if (boolReviews) 768 {<text>(@CommentCount reviews)</text>} 769 </span> 770 </section> 771 772 } 773 774 @helper RenderProductUSP(string USP, string USPInfo) 775 { 776 if (!string.IsNullOrWhiteSpace(USP)) 777 { 778 <li class="pdp-usplist__item pdp-usplist__item--product"> 779 <i class="fal fa-check"></i> 780 @USP 781 @if (!string.IsNullOrWhiteSpace(USPInfo)) 782 { 783 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 784 <i class="fal fa-info-circle"></i> 785 </span> 786 } 787 </li> 788 } 789 } 790 791 @helper RenderProductDetailUSP(string USP, string USPInfo) 792 { 793 if (!string.IsNullOrWhiteSpace(USP)) 794 { 795 <li class="pdp-usplist__item pdp-usplist__item--page"> 796 <i class="fal fa-dot-circle"></i> 797 @USP 798 @if (!string.IsNullOrWhiteSpace(USPInfo)) 799 { 800 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 801 <i class="fal fa-info-circle"></i> 802 </span> 803 } 804 </li> 805 } 806 } 807 808 @helper RenderProductModuleUSP(string USP, string USPInfo) 809 { 810 if (!string.IsNullOrWhiteSpace(USP)) 811 { 812 <li class="pdp-usplist__item pdp-usplist__item--product"> 813 <i class="fal fa-check"></i> 814 @USP 815 @if (!string.IsNullOrWhiteSpace(USPInfo)) 816 { 817 <span data-tippy-content="@USPInfo" class="pdp-usplist__infoicon"> 818 <i class="fal fa-info-circle"></i> 819 </span> 820 } 821 </li> 822 } 823 } 824 825 @* @helper RenderPDPTabs() 826 { 827 string downloadProductsheet = GetString("Ecom:Product:Field.DownloadProductsheet.Clean"); 828 829 <div class="pdp-specifications-navigation__container"> 830 831 <nav class="pdp-specifications-navigation"> 832 <a href="#information" class="pdp-specifications-navigation__tab pdp-specifications-navigation__tab--active">Productinformatie</a> 833 <a href="javascript:void(0);" class="pdp-specifications-navigation__tab toggle-of-canvas-menu" data-offcanvas-target="specifications">Specificaties</a> 834 @if (GetLoop("Comments").Count > 0) 835 { 836 <a href="#reviews" class="pdp-specifications-navigation__tab">Reviews</a> 837 } 838 </nav> 839 840 @if (!string.IsNullOrWhiteSpace(downloadProductsheet)) 841 { 842 <a class="productdetails__download btn btn--small ml-auto" href="@downloadProductsheet" target="_blank"> 843 <span class="btn__text">@Translate("Productdetail.DownloadProductsheetLabel", "Download productsheet")</span> 844 <i class="btn__icon fal fa-file-pdf"></i> 845 </a> 846 } 847 848 </div> 849 } *@ 850 851 @helper renderProductCompareButton(string productId, string name, string thumbnail) 852 { 853 string title = Translate("ProductBlockAction.Compare", "Add to product compare"); 854 855 <button class="po-block__action-btn po-block__action-btn--compare" aria-label="@title" title="@title" data-prodid="@productId" data-imgsmall="@thumbnail" data-prodname="@name"> 856 <i class="fas fa-exchange-alt"></i> 857 </button> 858 } 859 860 @helper renderProductFavoriteButton(string productId, string variantId, string productName, string productNumber, string gtmDiscount, string gtmPrice, string gtmValue, string productCategoryName, string productManufacturer, string listId, string listName) 861 { 862 if (string.IsNullOrWhiteSpace(variantId)) 863 { 864 variantId = ""; 865 } 866 867 string title = Translate("ProductBlockAction.Wishlist", "Add to wish list"); 868 bool isActive = Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites.UserExtensions.IsProductInAnyFavoriteList(Pageview.User, productId, variantId); 869 870 <button class="po-block__action-btn po-block__action-btn--wishlist" aria-label="@title" title="@title" data-productid="@productId" data-variantid="@variantId" data-offcanvas-target="favoritelist" data-productid="@productId" data-variantid="@variantId" data-number="@productNumber" data-name='@productName' data-position="1" data-brand='@productManufacturer' data-category='@productCategoryName' data-listid="@listId" data-listname="@listName" data-gtmprice="@gtmPrice" data-gtmdiscount="@gtmDiscount" data-gtmvalue="@gtmValue" data-currencycode="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> 871 @if (isActive) 872 { 873 <i class="fas fa-heart"></i> 874 } 875 else 876 { 877 <i class="far fa-heart"></i> 878 } 879 </button> 880 881 } 882 883 @helper renderReviewDistrubition(List<LoopItem> comments) 884 { 885 int totalCount = comments.Count; 886 @renderReviewDistrubitionLine(5, comments.Count(c => c.GetInteger("Rating") == 5), totalCount) 887 @renderReviewDistrubitionLine(4, comments.Count(c => c.GetInteger("Rating") == 4), totalCount) 888 @renderReviewDistrubitionLine(3, comments.Count(c => c.GetInteger("Rating") == 3), totalCount) 889 @renderReviewDistrubitionLine(2, comments.Count(c => c.GetInteger("Rating") == 2), totalCount) 890 @renderReviewDistrubitionLine(1, comments.Count(c => c.GetInteger("Rating") == 1), totalCount) 891 } 892 893 @helper renderReviewDistrubitionLine(int stars, int amount, int total) 894 { 895 double progress = ((double)amount / (double)total) * 100; 896 897 <div class="review-distribution"> 898 <span class="review-distribution__star"> 899 @for (int i = 0; i < stars; i++) 900 { 901 <i class="fa fa-star" aria-hidden="true"></i> 902 903 } 904 </span> 905 <div class="review-distribution__progress"> 906 <div class="review-distribution__progress-background"></div> 907 <div class="review-distribution__progress-fill" style="width: @progress%;"></div> 908 </div> 909 <span class="review-distribution__count">@amount reviews</span> 910 </div> 911 } 912 913 @helper RenderPdpTabs(bool showReviewTab, bool enableSpecificationsSidebar) 914 { 915 string productLongDescription = GetString("Ecom:Product.LongDescription"); 916 string downloadProductsheet = GetString("Ecom:Product:Field.DownloadProductsheet.Clean"); 917 918 string xmlString = GetString("Ecom:Product:Field.ProductDownloadProductSheets.Value.Clean"); 919 var downloadProductsheets = new List<string> { }; 920 921 if (!string.IsNullOrWhiteSpace(xmlString)) 922 { 923 var xml = XDocument.Parse(xmlString); 924 foreach (var attachment in xml.Root.Elements("attachment")) 925 { 926 string filename = attachment.Element("OriginalFileName")?.Value; 927 string url = $"/Files/ProductData/documents/{attachment.Element("Name")?.Value}/{attachment.Element("ConstructedFileName")?.Value}"; 928 string combined = $"{filename},{url}"; 929 downloadProductsheets.Add(combined); 930 } 931 } 932 933 bool hasDescription = !string.IsNullOrWhiteSpace(productLongDescription); 934 bool hasSpecifications = GetLoop("ProductCategories").Any(c => c.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))); 935 bool hasReviews = showReviewTab; 936 bool hasDownloads = false; 937 bool hasProductSheet = !string.IsNullOrWhiteSpace(downloadProductsheet); 938 bool hasProductSheets = downloadProductsheets.Any(); 939 940 string activeItem = hasDescription ? "description" : "reviews"; 941 if (Dynamicweb.Context.Current.Request["reviewcmd"] != null && Dynamicweb.Context.Current.Request["reviewcmd"] == "created") 942 { 943 activeItem = "reviews"; 944 } 945 946 if(!hasSpecifications && (!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.CATpropertiesXML.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ERPpropertiesXML.Value")))) 947 { 948 hasSpecifications = true; 949 } 950 951 if (hasDescription || hasReviews || hasDownloads) 952 { 953 <section id="pdp-tabs" class="pdp-tabs"> 954 <div class="container pdp-tabs__container"> 955 956 <div class="pdp-tabs__navigation"> 957 @if (hasDescription) 958 { 959 <a class='pdp-tabnavigation__item @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 960 @Translate("Productdetail.DescriptionTab", "Description") 961 </a> 962 } 963 @if (hasSpecifications) 964 { 965 if (enableSpecificationsSidebar) 966 { 967 <a class="pdp-tabnavigation__item toggle-of-canvas-menu" href="javascript:void(0);" data-offcanvas-target="specifications"> 968 @Translate("Productdetail.SpecificationsTab", "Specifications") 969 </a> 970 } 971 else 972 { 973 <a class='pdp-tabnavigation__item @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 974 @Translate("Productdetail.SpecificationsTab", "Specifications") 975 </a> 976 } 977 } 978 @if (hasReviews) 979 { 980 <a class='pdp-tabnavigation__item @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 981 @Translate("Productdetail.ReviewsTab", "Reviews") 982 </a> 983 } 984 @if (hasDownloads) 985 { 986 <a class="pdp-tabnavigation__item" href="javascript:void(0);" data-tab="downloads"> 987 @Translate("Productdetail.DownloadsTab", "Downloads") 988 </a> 989 } 990 @if (hasProductSheet) 991 { 992 <a class="pdp-tabnavigation__productsheetdownload btn btn__secondary" href="@downloadProductsheet" target="_blank"> 993 <span class="btn__text">@Translate("Productdetail.DownloadProductsheetLabel", "Download productsheet")</span> 994 <i class="btn__icon fal fa-file-pdf"></i> 995 </a> 996 } 997 @if (hasProductSheets) 998 { 999 <a class="pdp-tabnavigation__item" href="javascript:void(0);" data-tab="productsheets"> 1000 @Translate("Productdetail.ProductsheetsTab", "Productsheets") 1001 </a> 1002 } 1003 </div> 1004 1005 <div class="pdp-tabs__content"> 1006 @if (hasDescription) 1007 { 1008 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "description" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="description"> 1009 @Translate("Productdetail.DescriptionTab", "Description") 1010 </a> 1011 <section class='pdp-tabcontent__item @(activeItem == "description" ? "pdp-tabcontent__item--active" : "")' data-tab-content="description"> 1012 @RenderLongDescription() 1013 </section> 1014 } 1015 @if (hasSpecifications && !enableSpecificationsSidebar) 1016 { 1017 string[] hiddenFields = { }; 1018 Dictionary<string, string> specificationFields = new Dictionary<string, string>(); 1019 1020 string ERPpropertiesXML = GetString("Ecom:Product:Field.ERPpropertiesXML.Value"); 1021 string CATpropertiesXML = GetString("Ecom:Product:Field.CATpropertiesXML.Value"); 1022 List<dynamic> propertiesXML = new List<dynamic>(); 1023 1024 if (!string.IsNullOrWhiteSpace(ERPpropertiesXML)) 1025 { 1026 AddPropertiesFromXML(ERPpropertiesXML, propertiesXML); 1027 } 1028 if (!string.IsNullOrWhiteSpace(CATpropertiesXML)) 1029 { 1030 AddPropertiesFromXML(CATpropertiesXML, propertiesXML); 1031 } 1032 1033 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "specifications" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="specifications"> 1034 @Translate("Productdetail.SpecificationsTab", "Specifications") 1035 </a> 1036 <section class='pdp-tabcontent__item @(activeItem == "specifications" ? "pdp-tabcontent__item--active" : "")' data-tab-content="specifications"> 1037 <div class="pdp-specifications-info__body" style="flex-direction: column;"> 1038 @if (propertiesXML.Count > 0) 1039 { 1040 <section class="product-specifications__category"> 1041 <ul class="product-specifications__list"> 1042 @foreach (var spec in propertiesXML) 1043 { 1044 <li class="product-specifications__list-item"> 1045 <span class="product-specifications__label">@spec.Caption</span> 1046 <span class="product-specifications__value">@spec.Value @spec.Unit</span> 1047 </li> 1048 } 1049 </ul> 1050 </section> 1051 } 1052 else 1053 { 1054 foreach (LoopItem categorie in GetLoop("ProductCategories")) 1055 { 1056 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 1057 1058 if (categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 1059 { 1060 foreach (var categoryField in categorie.GetLoop("ProductCategoryFields")) 1061 { 1062 if (!hiddenFields.Contains(categoryField.GetString("Ecom:Product.CategoryField.TemplateTag")) && !string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.Value")) && categoryField.GetString("Ecom:Product.CategoryField.Value") != "False" && categoryField.GetString("Ecom:Product.CategoryField.Value") != "0") 1063 { 1064 specificationFields[categoryField.GetString("Ecom:Product.CategoryField.Label")] = categoryField.GetString("Ecom:Product.CategoryField.Value"); 1065 } 1066 } 1067 1068 <section class="product-specifications__category"> 1069 <ul class="product-specifications__list"> 1070 @foreach (KeyValuePair<string, string> specificationField in specificationFields.OrderBy(s => s.Key)) 1071 { 1072 Match unitMatch = valueRegex.Match(specificationField.Key); 1073 string unit = unitMatch.Groups[1].Value; 1074 string value = specificationField.Value; 1075 1076 <li class="product-specifications__list-item"> 1077 <span class="product-specifications__label">@valueRegex.Replace(specificationField.Key, "")</span> 1078 <span class="product-specifications__value"> 1079 @if (value == "True") 1080 { 1081 <i class="fas fa-check" title="@Translate(value, value)"></i> 1082 } 1083 else 1084 { 1085 @value @unit 1086 } 1087 </span> 1088 </li> 1089 } 1090 </ul> 1091 </section> 1092 } 1093 } 1094 } 1095 </div> 1096 </section> 1097 } 1098 @if (hasReviews) 1099 { 1100 <a class='pdp-tabnavigation__item pdp-tabnavigation__item--mobile @(activeItem == "reviews" ? "pdp-tabnavigation__item--active" : "")' href="javascript:void(0);" data-tab="reviews"> 1101 @Translate("Productdetail.ReviewsTab", "Reviews") 1102 </a> 1103 <section class='pdp-tabcontent__item @(activeItem == "reviews" ? "pdp-tabcontent__item--active" : "")' data-tab-content="reviews"> 1104 <div class="pdp-specifications-info__body"> 1105 @RenderSnippet("PdpReviews") 1106 </div> 1107 </section> 1108 } 1109 @if (hasDownloads) 1110 { 1111 <a class="pdp-tabnavigation__item pdp-tabnavigation__item--mobile" href="javascript:void(0);" data-tab="downloads"> 1112 @Translate("Productdetail.DownloadsTab", "Downloads") 1113 </a> 1114 <section class="pdp-tabcontent__item" data-tab-content="downloads"> 1115 <div class="pdp-specifications-info__body"> 1116 <h3>Downloads</h3> 1117 </div> 1118 </section> 1119 } 1120 @if (hasProductSheets) 1121 { 1122 <a class="pdp-tabnavigation__item pdp-tabnavigation__item--mobile" href="javascript:void(0);" data-tab="productsheets"> 1123 @Translate("Productdetail.ProductsheetsTab", "Productsheets") 1124 </a> 1125 <section class="pdp-tabcontent__item" data-tab-content="productsheets"> 1126 <div class="pdp-specifications-info__body"> 1127 <ul class="pdp-productsheets__list"> 1128 @foreach (var item in downloadProductsheets) 1129 { 1130 string[] files = item.Split(','); 1131 string filename = files[0]; 1132 string url = files[1]; 1133 <li> 1134 <a target="_blank" href="@url"><i class="fas fa-file-pdf"></i> @filename</a> 1135 </li> 1136 } 1137 </ul> 1138 </div> 1139 </section> 1140 } 1141 </div> 1142 1143 </div> 1144 </section> 1145 } 1146 } 1147 1148 @functions { 1149 public void AddPropertiesFromXML(string xml, List<dynamic> specifications) 1150 { 1151 var xmlDoc = new System.Xml.XmlDocument(); 1152 xmlDoc.LoadXml(xml); 1153 1154 foreach (System.Xml.XmlNode fieldNode in xmlDoc.SelectNodes("//Field")) 1155 { 1156 specifications.Add(new 1157 { 1158 Id = fieldNode.Attributes["id"]?.InnerText, 1159 Caption = fieldNode.SelectSingleNode("Caption")?.InnerText, 1160 Value = fieldNode.SelectSingleNode("Value")?.InnerText, 1161 Unit = fieldNode.SelectSingleNode("Unit")?.InnerText 1162 }); 1163 } 1164 } 1165 } 1166 1167 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 1168 @using Dynamicweb; 1169 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 1170 @using Bluedesk.DynamicWeb.ItemTypes.BaseSolution; 1171 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 1172 @using System.Linq; 1173 @using Dynamicweb.Content; 1174 @using Dynamicweb.Ecommerce.Prices; 1175 @using System.Text.RegularExpressions; 1176 @using System.Xml.Linq; 1177 1178 @SnippetStart("PdpHeader") 1179 <header class="pdp-header container"> 1180 @if (!showTitleAboveInfoBlock) 1181 { 1182 <h1 class="pdp-header__title"> 1183 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 1184 { 1185 <span class="pdp-header__manufacturer">@Manufacturer </span> 1186 } 1187 <span class="pdp-header__productname">@productName</span> 1188 </h1> 1189 } 1190 1191 @* Snippet PdpReviewIndicator *@ 1192 @if (GetInteger("Comments.TotalCount") > 0) 1193 { 1194 <div class="pdp-review-indicator"> 1195 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 1196 </div> 1197 } 1198 </header> 1199 @SnippetEnd("PdpHeader") 1200 1201 @SnippetStart("PdpInfo") 1202 <section class="pdp-info"> 1203 @* Snippet PdpActionButtons *@ 1204 @if (enableProductCompare || enableProductFavorites) 1205 { 1206 <div class="pdp-actions po-block__actions"> 1207 @if (enableProductCompare) 1208 { 1209 @renderProductCompareButton(productid, productName, defaultImage) 1210 } 1211 @if (enableProductFavorites) 1212 { 1213 @renderProductFavoriteButton(productid, productVariantId, productName, productNumber, gtmDiscount, gtmPrice, gtmValue, productCategoryName, Manufacturer, listId, listName) 1214 } 1215 </div> 1216 } 1217 1218 @if (showTitleAboveInfoBlock) 1219 { 1220 <h1 class="pdp-header__title"> 1221 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 1222 { 1223 <span class="pdp-header__manufacturer">@Manufacturer </span> 1224 } 1225 <span class="pdp-header__productname">@productName</span> 1226 </h1> 1227 } 1228 1229 1230 @* Snippet PdpProductNumber *@ 1231 @if (!string.IsNullOrWhiteSpace(productNumber)) 1232 { 1233 <p class="pdp-articlenumber"> 1234 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 1235 </p> 1236 } 1237 1238 @if (showVariantInfo && hasVariants) 1239 { 1240 <p class="pdp-variant-info"> 1241 <span class="pdp-variant-info__label"><strong>@Translate("ProductDetailVariantInfo.Variants", "Variants:")</strong></span> 1242 <span class="pdp-variant-info__amount"><strong>@Translate("ProductDetailVariantInfo.Amount", "Amount")</strong> @GetInteger("Ecom:Product.VariantCount")</span> 1243 </p> 1244 } 1245 1246 @* Snippet PdpPriceBlock *@ 1247 @if (displayPrice && displayProductPrice) 1248 { 1249 <div class="pdp-price__container"> 1250 @if (hasVariants) 1251 { 1252 <section class="pdp-price__subcontainer"> 1253 @if (minPrice != maxPrice) 1254 { 1255 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 1256 } 1257 else 1258 { 1259 <span class="pdp-price">@minPriceFormatted</span> 1260 } 1261 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1262 </section> 1263 } 1264 else 1265 { 1266 1267 if (hasDiscount) 1268 { 1269 <p class="pdp-price--original">@originalProductPrice</p> 1270 <section class="pdp-price__subcontainer"> 1271 <span class="pdp-price">@discountProductPrice</span> 1272 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1273 <span class="pdp-price__percentage">@discountPercentage%</span> 1274 </section> 1275 } 1276 else 1277 { 1278 <section class="pdp-price__subcontainer"> 1279 <span class="pdp-price">@originalProductPrice</span> 1280 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 1281 </section> 1282 } 1283 if (!string.IsNullOrWhiteSpace(unitValue)) 1284 { 1285 <div class="pdp-price__units"> 1286 <span>@unitValue</span> 1287 </div> 1288 } 1289 if (!string.IsNullOrWhiteSpace(retailPrice)) 1290 { 1291 <p class="pdp-price__retail-price"> 1292 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 1293 </p> 1294 } 1295 if (yourProfitValue > 0) 1296 { 1297 <p class="pdp-price__yourprofit"> 1298 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 1299 </p> 1300 } 1301 } 1302 </div> 1303 } 1304 1305 @* Snippet PdpStockState *@ 1306 @if (enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 1307 { 1308 <div class="pdp-stockstate__wrapper"> 1309 @if (stockFormat == "text") 1310 { 1311 if (!string.IsNullOrWhiteSpace(stockText)) 1312 { 1313 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 1314 } 1315 } 1316 else 1317 { 1318 string translationTag = ""; 1319 if (neverOutOfStock) 1320 { 1321 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 1322 } 1323 else if(stockSize > 0) 1324 { 1325 if(stockSize == 1) 1326 { 1327 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 1328 } 1329 else 1330 { 1331 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 1332 } 1333 } 1334 else if (!inStock) 1335 { 1336 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 1337 } 1338 1339 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 1340 } 1341 </div> 1342 } 1343 1344 @* Snippet BackInStockNotification *@ 1345 @if (!inStock && displayBackInStockNotifications) 1346 { 1347 bool notificationRegistered = GetBoolean("Ecom:Product.NotificationRegistered"); 1348 1349 <div class="pdp-back-in-stock-notification"> 1350 @if (!notificationRegistered && Pageview.User != null) 1351 { 1352 <a class="btn default-btn" href="/Default.aspx?ID=@GetString("Ecom:Product:Page.ID")&ProductID=@GetString("Ecom:Product.ID")&VariantID=@GetString("Ecom:Product.VariantID")&LanguageID=@GetString("Ecom:Product.LanguageID")&CartCmd=createnotificationforthisproduct"> 1353 <span class="btn__text">@Translate("BackInStock.EmailMe", "Email me when back in stock")</span> 1354 <i class="btn__icon far fa-bell"></i> 1355 </a> 1356 } 1357 else if (!notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) 1358 { 1359 <form name='@GetString("Ecom:Product.ID")' id='NotificationForm_@GetString("Ecom:Product.ID")' method='post' class="no-validate default-contact-form" action='/Default.aspx?ID=@GetString("Ecom:Product:Page.ID")'> 1360 <h3 class="pdp-back-in-stock-notification__header">@Translate("BackInStock.FormTitle", "Email me when back in stock")</h3> 1361 <input type="hidden" name="ProductID" id="ProductID" value='@GetString("Ecom:Product.ID")' /> 1362 <input type="hidden" name="VariantID" id="VariantID" value='@GetString("Ecom:Product.VariantID")' /> 1363 <input type="hidden" name="LanguageID" id="LanguageID" value='@GetString("Ecom:Product.LanguageID")' /> 1364 <input type="hidden" name="CartCmd" id="CartCmd" value="createnotificationforthisproduct" /> 1365 1366 <section class="form__item"> 1367 <div class="input__group form-item__input form-group"> 1368 <label class="form__input-label input__label" for="NotificationEmail">@Translate("BackInStock.Email", "Email *")</label> 1369 <input name="NotificationEmail" 1370 type="email" 1371 class="form__input input input--text" 1372 id="NotificationEmail" 1373 required /> 1374 </div> 1375 </section> 1376 1377 <button type="submit" class="btn default-btn"> 1378 <span class="btn__text">@Translate("BackInStock.CreateNotification", "Create notification")</span> 1379 <i class="btn__icon far fa-bell"></i> 1380 </button> 1381 </form> 1382 } 1383 else if (notificationRegistered && Pageview.User != null) 1384 { 1385 <p class="pdp-back-in-stock-notification__success"> 1386 <i class="far fa-bell-on"></i> 1387 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1388 </p> 1389 } 1390 else if (notificationRegistered && (Pageview.User == null && allowBackInStockNotificationsForGuests)) 1391 { 1392 <p class="pdp-back-in-stock-notification__success"> 1393 <i class="far fa-bell-on"></i> 1394 @Translate("BackInStock.NotificationSuccess", "You will be notified when this product is back in stock.") 1395 </p> 1396 } 1397 </div> 1398 } 1399 1400 @* Snippet PdpTaglines *@ 1401 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 1402 { 1403 <div class="pdp-tagline__container"> 1404 @if (!string.IsNullOrWhiteSpace(productTagline)) 1405 { 1406 <p class="pdp-tagline"> 1407 @productTagline 1408 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 1409 { 1410 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 1411 <i class="fal fa-info-circle"></i> 1412 </span> 1413 } 1414 </p> 1415 } 1416 1417 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 1418 { 1419 <p class="pdp-tagline"> 1420 @productDetailPageTagline 1421 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 1422 { 1423 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 1424 <i class="fal fa-info-circle"></i> 1425 </span> 1426 } 1427 </p> 1428 } 1429 </div> 1430 } 1431 1432 @* Snippet PdpVariantSelector *@ 1433 @if (GetLoop("VariantGroups").Count > 0) 1434 { 1435 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 1436 string variantSelection = productVariantId.Replace(".", ","); 1437 1438 var variantCombinationsObject = new List<Array>(); 1439 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 1440 { 1441 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 1442 variantCombinationsObject.Add(combinations); 1443 } 1444 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 1445 1446 var variantGroupsObject = new List<List<String>>(); 1447 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1448 { 1449 var variantsObject = new List<String>(); 1450 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 1451 { 1452 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 1453 } 1454 variantGroupsObject.Add(variantsObject); 1455 } 1456 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 1457 1458 <div class="pdp-variants"> 1459 <div class="product-variants__wrapper"> 1460 <div class="js-variants" data-total-variant-groups="@GetLoop("VariantGroups").Count" data-combinations="@combinationsJson" data-variants="@variantsJson" data-current-page-variant="@variantSelection" data-variant-selections="@variantSelection" data-page-id="@pageId" data-product-id="@productid" data-group-id="@groupid"> 1461 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 1462 { 1463 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 1464 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 1465 1466 <div class="product-variants__block product-variants__block--@groupId"> 1467 @if (containsImage) 1468 { 1469 if (!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 1470 { 1471 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 1472 } 1473 1474 <div class="product-variants__options-wrapper"> 1475 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions").OrderBy(option => option.GetString("Ecom:VariantOption.Name"))) 1476 { 1477 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1478 1479 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 1480 { 1481 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 1482 1483 <div data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn product-variants__btn--image @selected"> 1484 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 1485 </div> 1486 } 1487 else 1488 { 1489 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn @selected">@variantOption.GetString("Ecom:VariantOption.Name")</button> 1490 } 1491 } 1492 </div> 1493 } 1494 else 1495 { 1496 if (!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 1497 { 1498 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 1499 } 1500 1501 <div class="product-variants__dropdown"> 1502 <div class="product-variants__dropdown-wrapper"> 1503 <button class="product-variants__toggle"> 1504 <span data-original="@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")">@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")</span> 1505 <i class="fal fa-chevron-down"></i> 1506 </button> 1507 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 1508 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions").OrderBy(option => option.GetString("Ecom:VariantOption.Name"))) 1509 { 1510 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 1511 1512 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 1513 @variantOption.GetString("Ecom:VariantOption.Name") 1514 </button> 1515 } 1516 </div> 1517 </div> 1518 </div> 1519 } 1520 </div> 1521 } 1522 </div> 1523 </div> 1524 </div> 1525 } 1526 1527 @* Snippet PdpVolumePrices *@ 1528 @if (displayPrice && displayProductPrice && GetLoop("Product.Prices").Any()) 1529 { 1530 var priceList = new List<object>(); 1531 1532 foreach (LoopItem volumePrice in GetLoop("Product.Prices")) 1533 { 1534 int volumePriceQuantity = volumePrice.GetInteger("Ecom:Product.Prices.Quantity"); 1535 1536 if (volumePriceQuantity != 0) 1537 { 1538 double newVolumePrice = Math.Round(volumePrice.GetDouble("Ecom:Product.Prices.Price" + WithVATSuffix) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1539 string newVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = newVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = newVolumePrice }.PriceWithoutVATFormatted; 1540 1541 double diffVolumePrice = Math.Round((discountProductPriceDouble - volumePrice.GetDouble("Ecom:Product.Prices.Price")) * volumePrice.GetDouble("Ecom:Product.Prices.Quantity"), 2); 1542 string diffVolumePriceFormatted = WithVATBool ? new PriceInfo { PriceWithVAT = diffVolumePrice }.PriceWithVATFormatted : new PriceInfo { PriceWithoutVAT = diffVolumePrice }.PriceWithoutVATFormatted; 1543 1544 if (!FormattedBool) 1545 { 1546 newVolumePriceFormatted = newVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1547 diffVolumePriceFormatted = diffVolumePriceFormatted.Replace(volumePrice.GetString("Ecom:Product.Prices.Currency.Symbol"), ""); 1548 } 1549 1550 var priceObj = new 1551 { 1552 discountId = volumePrice.GetValue("Product.Prices.LoopCounter"), 1553 newVolumePriceFormatted = newVolumePriceFormatted, 1554 diffVolumePriceFormatted = diffVolumePriceFormatted, 1555 quantity = volumePriceQuantity 1556 }; 1557 priceList.Add(priceObj); 1558 } 1559 } 1560 1561 string pricesJson = Newtonsoft.Json.JsonConvert.SerializeObject(priceList); 1562 1563 <div class="app-volumeprices" data-price-list='@pricesJson'></div> 1564 } 1565 1566 @* Snippet PdpAddToCartBox *@ 1567 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) 1568 { 1569 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 1570 <add-to-cart class="app-addtocart" 1571 data-prodid="@productid" 1572 data-variantid="@productVariantId" 1573 data-min-quantity="@minimumQuantity" 1574 data-step="@quantityStep" 1575 data-list-id="product_detail" 1576 data-list-name="Product detail"> 1577 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 1578 </add-to-cart> 1579 </div> 1580 } 1581 else 1582 { 1583 if (QuotePageID > 0) 1584 { 1585 <div class="pdp-request-quote__container"> 1586 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 1587 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 1588 <i class="btn__icon @buttonIconClass"></i> 1589 </a> 1590 </div> 1591 } 1592 1593 if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 1594 { 1595 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 1596 1597 if (repProd != null) 1598 { 1599 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 1600 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 1601 if (!string.IsNullOrWhiteSpace(repProd.VariantId)) 1602 { 1603 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 1604 } 1605 1606 <div class="pdp-replacement__container"> 1607 @if (!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 1608 { 1609 <p class="pdp-replacement__intro">@string.Format(Translate("ProductDetail.ReplacementProd.Intro", "This product is not available anymore. Please take a look at our selected replacement product."), repProd.Name)</p> 1610 } 1611 <a href="@productUrl" class="btn default-btn"> 1612 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 1613 <i class="btn__icon @buttonIconClass"></i> 1614 </a> 1615 </div> 1616 } 1617 } 1618 } 1619 1620 @* Snippet PdpPaymentLogos *@ 1621 @if (selectedPaymentLogos != null) 1622 { 1623 <section class="pdp-paymentlogos paymentlogos--small"> 1624 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 1625 </section> 1626 } 1627 1628 @* Snippet PdpPageUspList *@ 1629 @if (productDetailUSPList.Any()) 1630 { 1631 <ul class="pdp-usplist"> 1632 @foreach (ProductDetailUSP item in productDetailUSPList) 1633 { 1634 @RenderProductDetailUSP(item.USP, item.Tooltip) 1635 } 1636 </ul> 1637 } 1638 1639 @* Snippet PdpProductUspList *@ 1640 @if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP1.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP2.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP3.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP4.Value")) || !string.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ProductUSP5.Value"))) 1641 { 1642 <ul class="pdp-usplist"> 1643 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP1.Value"), GetString("Ecom:Product:Field.ProductUSP1Info.Value")) 1644 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP2.Value"), GetString("Ecom:Product:Field.ProductUSP2Info.Value")) 1645 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP3.Value"), GetString("Ecom:Product:Field.ProductUSP3Info.Value")) 1646 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP4.Value"), GetString("Ecom:Product:Field.ProductUSP4Info.Value")) 1647 @RenderProductUSP(GetString("Ecom:Product:Field.ProductUSP5.Value"), GetString("Ecom:Product:Field.ProductUSP5Info.Value")) 1648 </ul> 1649 } 1650 1651 @* Snippet PdpProductIcons *@ 1652 @if (productIcons[0] != "" && productIcons.Length != 0) 1653 { 1654 <div class="pdp-producticons"> 1655 @foreach (var item in productIcons) { 1656 <img src="/Files/@item" /> 1657 } 1658 </div> 1659 <style> 1660 .pdp-producticons { 1661 display: flex; 1662 flex-wrap: wrap; 1663 gap: 10px; 1664 margin-bottom: 1rem; 1665 } 1666 1667 .pdp-producticons img { 1668 width: 50px; 1669 height: 50px; 1670 object-fit: contain; 1671 } 1672 </style> 1673 } 1674 1675 @* Snippet PdpProductShortDescription *@ 1676 @if (hasShortDescription) 1677 { 1678 <div class="pdp-short-description"> 1679 @productShortDescription 1680 </div> 1681 } 1682 1683 @if ((hasShortDescription && hasLongDescription) || hasSpecifications) 1684 { 1685 <div class="pdp-action-buttons"> 1686 @if (hasShortDescription && hasLongDescription) 1687 { 1688 <p class="pdp-short-description__readmore"> 1689 <a href="#pdp-tabs">@Translate("Productdetail.Readmore", "Read more")</a> 1690 </p> 1691 } 1692 @if (hasSpecifications && enableSpecificationsSidebar) 1693 { 1694 <a href="javascript:void(0);" class="toggle-of-canvas-menu" data-offcanvas-target="specifications"> 1695 @Translate("Productdetail.ViewSpecifications", "View product specifications") 1696 </a> 1697 } 1698 </div> 1699 } 1700 1701 </section> 1702 @SnippetEnd("PdpInfo") 1703 1704 @SnippetStart("PdpImage") 1705 <section class="product-detailpage__images pdp__images-container"> 1706 1707 <section class="productimages pdp__images"> 1708 1709 @{ 1710 int videoThumbPosition = 1; 1711 var SetViaThumbInt = 0; 1712 var SetViaImageInt = 0; 1713 } 1714 1715 @if (productImages.Count > 1 || hasYoutubeVideo) 1716 { 1717 1718 if (videoThumbPosition > productImages.Count) 1719 { 1720 videoThumbPosition = productImages.Count; 1721 } 1722 1723 <section class="productimages__wrapper"> 1724 1725 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1726 { 1727 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1728 } 1729 1730 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1731 { 1732 <img src="/Admin/Public/GetImage.ashx?Image=/Files/@manufacturerLogo&Crop=7&Format=webp&Quality=90&Compression=80&width=150" class="manufacturer__logo" alt="Manufacturer" width="150" height="50" /> 1733 } 1734 1735 <div class="productimages__carousel-big hidden"> 1736 1737 @foreach (var image in productImages) 1738 { 1739 1740 SetViaImageInt++; 1741 1742 <div class="productimages__item"> 1743 <img class="w-auto" src="/Admin/Public/GetImage.ashx?Image=@image&Format=webp&Quality=-1&width=800&height=800" alt="@productName" /> 1744 </div> 1745 1746 if (SetViaImageInt == videoThumbPosition && hasYoutubeVideo) 1747 { 1748 <div class="productimages__item"> 1749 <div class="productimages__carousel__video-container"> 1750 <lite-youtube videoid="@YoutubeProductVideo" params="controls=1&loop=0&playlist=@YoutubeProductVideo&playsinline=1&modestbranding=1&mute=0&rel=0&enablejsapi=1& origin=@Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain()&disablekb=0"></lite-youtube> 1751 </div> 1752 </div> 1753 } 1754 } 1755 1756 </div> 1757 1758 </section> 1759 1760 <div class="productimages__carousel-thumbnails hidden"> 1761 @foreach (var image in productImages) 1762 { 1763 SetViaThumbInt++; 1764 1765 <div class="productimages__thumbnail"> 1766 <img class="product-image" src="/Admin/Public/GetImage.ashx?Image=@image&Crop=5&Format=webp&Quality=90&Compression=80&width=200&height=200" alt="Thumbnail @productName" width="100" height="100" /> 1767 </div> 1768 1769 if (SetViaThumbInt == videoThumbPosition && hasYoutubeVideo) 1770 { 1771 <div class="productimages__thumbnail productimages__thumbnail--video"> 1772 <img src="https://i.ytimg.com/vi_webp/@YoutubeProductVideo/hqdefault.webp" alt="Video preview @productName" width="100" height="100" /> 1773 <span class="video-icon"> 1774 <i class="fas fa-play"></i> 1775 </span> 1776 </div> 1777 } 1778 } 1779 </div> 1780 } 1781 else if (productImages.Count == 1) 1782 { 1783 var img = productImages.First(); 1784 img = !string.IsNullOrWhiteSpace(img) ? $"/Admin/Public/GetImage.ashx?Image={img}&Format=webp&Quality=-1&width=800&height=800" : "https://via.placeholder.com/800x400/?text=No%20Image%20Found"; 1785 1786 <div id="productimages__big" class="productimages__big"> 1787 @if (!string.IsNullOrWhiteSpace(productRibbon)) 1788 { 1789 <p class="product-detailpage__ribbon product-detailpage__ribbon--big product-detailpage__ribbon--@productRibbonStyle"><span>@productRibbon</span></p> 1790 } 1791 @if (!string.IsNullOrWhiteSpace(manufacturerLogo)) 1792 { 1793 <img src="/Admin/Public/GetImage.ashx?Image=/Files/@manufacturerLogo&Crop=7&Format=webp&Quality=90&Compression=80&width=150" class="manufacturer__logo" alt="Manufacturer" width="150" height="50" /> 1794 } 1795 <img id="product-image" class="product-image" src="@img" alt="@productName" /> 1796 </div> 1797 } 1798 </section> 1799 </section> 1800 @SnippetEnd("PdpImage") 1801 1802 @SnippetStart("PdpHelp") 1803 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody) || !string.IsNullOrWhiteSpace(webshopPage.HelpBannerBody)) 1804 { 1805 <section class="pdp-paragraph pdp-paragraph__container"> 1806 <div class="container pdp-paragraph__innerwrapper"> 1807 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 1808 { 1809 <figure class="pdp-paragraph__image"> 1810 <img src="/Admin/Public/GetImage.ashx?Image=@webshopPage.HelpBannerImage&Crop=7&Format=webp&Quality=90&Compression=80&Width=600" alt="" loading="lazy" width="600" height="300" /> 1811 </figure> 1812 } 1813 <div class="pdp-paragraph__body"> 1814 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerHeader)) 1815 { 1816 <h2 class="pdp-paragraph__header">@webshopPage.HelpBannerHeader</h2> 1817 } 1818 <div class="pdp-paragraph__content"> 1819 @webshopPage.HelpBannerBody 1820 </div> 1821 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerCTAButtonLink)) 1822 { 1823 <a href="@webshopPage.HelpBannerCTAButtonLink" class="btn default-btn"> 1824 <span class="btn__text">@webshopPage.HelpBannerCTAButtonLabel</span> 1825 <i class="btn__icon fa-chevron-right"></i> 1826 </a> 1827 } 1828 </div> 1829 </div> 1830 </section> 1831 } 1832 @SnippetEnd("PdpHelp") 1833 1834 @SnippetStart("PdpBanner") 1835 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerHeader) || !string.IsNullOrWhiteSpace(webshopPage.BannerBody) || !string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLabel)) 1836 { 1837 <section class="pdp-paragraph pdp-paragraph--image-right pdp-paragraph__container"> 1838 <div class="container pdp-paragraph__innerwrapper"> 1839 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerImage)) 1840 { 1841 <figure class="pdp-paragraph__image"> 1842 <img src="/Admin/Public/GetImage.ashx?Image=@webshopPage.BannerImage&Crop=7&Format=webp&Quality=90&Compression=80&Width=600" alt="" loading="lazy" width="600" height="300" /> 1843 </figure> 1844 } 1845 <div class="pdp-paragraph__body"> 1846 @if (!string.IsNullOrWhiteSpace(webshopPage.HelpBannerImage)) 1847 { 1848 <h2 class="pdp-paragraph__header">@webshopPage.BannerHeader</h2> 1849 } 1850 <div class="pdp-paragraph__content"> 1851 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerBody)) 1852 { 1853 @webshopPage.BannerBody 1854 } 1855 </div> 1856 @if (!string.IsNullOrWhiteSpace(webshopPage.BannerCTAButtonLink)) 1857 { 1858 <a href="@webshopPage.BannerCTAButtonLink" class="btn default-btn"> 1859 <span class="btn__text">@webshopPage.BannerCTAButtonLabel</span> 1860 <i class="btn__icon fa-chevron-right"></i> 1861 </a> 1862 } 1863 </div> 1864 </div> 1865 </section> 1866 } 1867 @SnippetEnd("PdpBanner") 1868 1869 @* @SnippetStart("PdpSpecification") 1870 <section class="pdp-specifications" id="specs"> 1871 <div class="container pdp-specifications__container"> 1872 1873 @RenderPDPTabs() 1874 1875 <div class="pdp-specifications__innerwrapper"> 1876 <div class="pdp-specifications__long-description" id="information"> 1877 @RenderInfoContentElement("Ecom:Product.LongDescription", "Productinformatie", "productinformation") 1878 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailSpecs", "Specificaties", "specifications") 1879 @RenderInfoContentElement("Ecom:Product:Field.ProductDetailDownloads", "Downloads", "downloads") 1880 </div> 1881 @if (hasYoutubeVideo) 1882 { 1883 <div class="pdp-specifications__video-wrapper"> 1884 <iframe class="pdp-specifications__video" width="100%" height="315" src="https://www.youtube.com/embed/@YoutubeProductVideo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> 1885 </div> 1886 } 1887 </div> 1888 </div> 1889 </section> 1890 @SnippetEnd("PdpSpecification") *@ 1891 1892 @SnippetStart("PdpReviews") 1893 <div id="reviews" class="review-paragraph"> 1894 <div class="container review-paragraph__container"> 1895 <div class="review-paragraph__summary"> 1896 <div class="review-summary__column"> 1897 <h2 class="review-summary__title">@Translate("Reviews.Title", "Customer reviews")</h2> 1898 <div class="review-summary__container"> 1899 <div class="review-summary__indicator"> 1900 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.TotalCount")) 1901 </div> 1902 </div> 1903 </div> 1904 1905 <div class="review-summary__column"> 1906 @renderReviewDistrubition(GetLoop("Comments")) 1907 </div> 1908 1909 <div class="review-summary__column"> 1910 @if (Dynamicweb.Context.Current.Request["reviewcmd"] != null && Dynamicweb.Context.Current.Request["reviewcmd"] == "created") 1911 { 1912 <h2 class="review-summary__title">@Translate("Reviews.Submitted.Title", "Thanks for your review")</h2> 1913 <p>@Translate("Reviews.Submitted.Text", "Thank you for submitting your review.")</p> 1914 } 1915 else 1916 { 1917 <h2 class="review-summary__title">@Translate("Reviews.Create.Text", "Write a review")</h2> 1918 <p>@Translate("Reviews.Create.Text", "Share your thoughts about this product with other customers.")</p> 1919 <div class="review-summary__btn-wrapper"> 1920 <a href="#" class="btn btn__primary toggle-of-canvas-menu" data-offcanvas-target="reviewcreate"> 1921 <span class="btn__text">@Translate("Reviews.Create.Button", "Write a review")</span> 1922 <i class="btn__icon fal fa-plus"></i> 1923 </a> 1924 </div> 1925 } 1926 </div> 1927 </div> 1928 <div class="review-paragraph__comments"> 1929 @foreach (LoopItem Comment in GetLoop("Comments.Newfirst").Take(3)) 1930 { 1931 <section class="review-comment__list-item"> 1932 <div class="review-comment__header"> 1933 <div class="review-comment__rating-wrapper"> 1934 @renderReviewIndicator("#86C440", Comment.GetInteger("Rating"), "row", false, 0) 1935 </div> 1936 </div> 1937 <div class="review-comment__message"> 1938 <p>@Comment.GetString("Text")</p> 1939 </div> 1940 <p class="review-comment__meta">@Comment.GetString("Name") | @Comment.GetDate("CreatedDate").ToString("d MMMM yyyy")</p> 1941 </section> 1942 } 1943 @if (GetLoop("Comments.Newfirst").Count > 3) 1944 { 1945 <a href="#" class="toggle-of-canvas-menu" data-offcanvas-target="reviews">@string.Format(Translate("Reviews.ReadMore", "Read all {0} reviews"), GetLoop("Comments.Newfirst").Count)</a> 1946 } 1947 </div> 1948 </div> 1949 </div> 1950 @SnippetEnd("PdpReviews") 1951 1952 @SnippetStart("PdpRelatedGroups") 1953 @foreach (var relatedGroup in GetLoop("ProductRelatedGroups")) 1954 { 1955 string relatedGroupId = relatedGroup.GetString("Ecom:Product:RelatedGroup.GroupID"); 1956 string relatedGroupName = relatedGroup.GetString("Ecom:Product:RelatedGroup.Name"); 1957 int RelatedGroupsInt = relatedGroup.GetLoop("Products").Count; 1958 string ClassIgniteCarousel = RelatedGroupsInt > 4 ? "products-module__container--carousel" : ""; 1959 1960 if (relatedGroupId != "RELGRP6") 1961 { 1962 <section class="products-module__container @ClassIgniteCarousel" id="@relatedGroupId"> 1963 <div class="container"> 1964 1965 <header class="products-module__header"> 1966 <h2 class="products-module__title">@Translate("ProductRelatedGroup." + relatedGroupName.Replace(" ", ""), relatedGroupName)</h2> 1967 </header> 1968 1969 <ul class="products-module__slider"> 1970 @foreach (var Product in relatedGroup.GetLoop("Products")) 1971 { 1972 @renderProduct(Product, ProductdetailPriceSuffix, WithVATSuffix, FormattedSuffix, enableShoppingCart, displayPrice, enableProductStock, relatedGroupId, relatedGroupName, EcommerceConfiguration); 1973 } 1974 </ul> 1975 1976 </div> 1977 </section> 1978 1979 @renderProductListEnhancedEcom(relatedGroupId, relatedGroupName, relatedGroup.GetLoop("Products"), WithVATSuffix); 1980 } 1981 } 1982 @SnippetEnd("PdpRelatedGroups") 1983 1984 @SnippetStart("PdpSecondaryInfo") 1985 <section class="pdp-secondary-info"> 1986 <div class="container pdp-secondary-info__container"> 1987 <div class="pdp-secondary-info__column"> 1988 @if (!string.IsNullOrWhiteSpace(defaultImage)) 1989 { 1990 <figure class="pdp-secondary-info__image-container"> 1991 <img class="pdp-secondary-info__image" src="/Admin/Public/GetImage.ashx?Image=@defaultImage&Crop=7&Format=webp&Quality=90&Compression=80&Height=400" alt="@productName" loading="lazy" height="400" width="600" /> 1992 </figure> 1993 } 1994 </div> 1995 <div class="pdp-secondary-info__column"> 1996 1997 <h2 class="pdp-header__title"> 1998 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 1999 { 2000 <span class="pdp-header__manufacturer">@Manufacturer</span> 2001 } 2002 <span class="pdp-header__productname"> 2003 @productName 2004 </span> 2005 </h2> 2006 2007 @if (GetInteger("Comments.TotalCount") > 0) 2008 { 2009 <div class="pdp-review-indicator"> 2010 @renderReviewIndicator("#86C440", GetInteger("Comments.Rating"), "row", true, GetInteger("Comments.Count")) 2011 </div> 2012 } 2013 2014 @if (!string.IsNullOrWhiteSpace(productNumber)) 2015 { 2016 <p class="pdp-articlenumber"> 2017 @Translate("Productdetail.ArticleNumber.Prefix", "Article number:") @productNumber 2018 </p> 2019 } 2020 2021 @if (enableProductStock && !string.IsNullOrWhiteSpace(stockFormat)) 2022 { 2023 <div class="pdp-stockstate__wrapper"> 2024 @if (stockFormat == "text") 2025 { 2026 if (!string.IsNullOrWhiteSpace(stockText)) 2027 { 2028 <p class="pdp-stockstate @stockStateClass">@string.Format(stockText, stock)</p> 2029 } 2030 } 2031 else 2032 { 2033 string translationTag = ""; 2034 if (neverOutOfStock) 2035 { 2036 translationTag = Translate("ProductBlockStockInfo.AmountInStock", "In stock"); 2037 } 2038 else if(stockSize > 0) 2039 { 2040 if(stockSize == 1) 2041 { 2042 translationTag = Translate("ProductBlockStockInfo.AmountInStockSingle", "{0} product in stock"); 2043 } 2044 else 2045 { 2046 translationTag = Translate("ProductBlockStockInfo.AmountInStockMultiple", "{0} products in stock"); 2047 } 2048 } 2049 else if (!inStock) 2050 { 2051 translationTag = Translate("ProductBlockStockInfo.AmountOutOfStock", "Out of stock"); 2052 } 2053 2054 <p class="pdp-stockstate @stockStateClass">@string.Format(translationTag, stock)</p> 2055 } 2056 </div> 2057 } 2058 2059 @if (!string.IsNullOrWhiteSpace(productTagline) || !string.IsNullOrWhiteSpace(productDetailPageTagline)) 2060 { 2061 <div class="pdp-tagline__container"> 2062 @if (!string.IsNullOrWhiteSpace(productTagline)) 2063 { 2064 <p class="pdp-tagline"> 2065 @productTagline 2066 @if (!string.IsNullOrWhiteSpace(productTaglineInfo)) 2067 { 2068 <span class="pdp-tagline__infoicon" data-tippy-content="@productTaglineInfo"> 2069 <i class="fal fa-info-circle"></i> 2070 </span> 2071 } 2072 </p> 2073 } 2074 2075 @if (!string.IsNullOrWhiteSpace(productDetailPageTagline)) 2076 { 2077 <p class="pdp-tagline"> 2078 @productDetailPageTagline 2079 @if (!string.IsNullOrWhiteSpace(productDetailPageTaglineInfo)) 2080 { 2081 <span class="pdp-tagline__infoicon" data-tippy-content="@productDetailPageTaglineInfo"> 2082 <i class="fal fa-info-circle"></i> 2083 </span> 2084 } 2085 </p> 2086 } 2087 </div> 2088 } 2089 </div> 2090 <div class="pdp-secondary-info__column"> 2091 2092 @if (displayPrice && displayProductPrice) 2093 { 2094 <div class="pdp-price__container"> 2095 @if (hasVariants) 2096 { 2097 <section class="pdp-price__subcontainer"> 2098 @if (minPrice != maxPrice) 2099 { 2100 <span class="pdp-price">@minPriceFormatted - @maxPriceFormatted</span> 2101 } 2102 else 2103 { 2104 <span class="pdp-price">@minPriceFormatted</span> 2105 } 2106 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2107 </section> 2108 } 2109 else 2110 { 2111 if (hasDiscount) 2112 { 2113 <p class="pdp-price--original">@originalProductPrice</p> 2114 <section class="pdp-price__subcontainer"> 2115 <span class="pdp-price">@discountProductPrice</span> 2116 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2117 <span class="pdp-price__percentage">@discountPercentage%</span> 2118 </section> 2119 } 2120 else 2121 { 2122 <section class="pdp-price__subcontainer"> 2123 <span class="pdp-price">@originalProductPrice</span> 2124 <span class="pdp-price--suffix">@ProductdetailPriceSuffix</span> 2125 </section> 2126 } 2127 if (!string.IsNullOrWhiteSpace(retailPrice)) 2128 { 2129 <p class="pdp-price__retail-price"> 2130 @String.Format(Translate("ProductBlockTitle.RetailPrice", "Retail price: {0}"), retailPrice) 2131 </p> 2132 } 2133 if (yourProfitValue > 0) 2134 { 2135 <p class="pdp-price__yourprofit"> 2136 @Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted 2137 </p> 2138 } 2139 } 2140 2141 @if (!string.IsNullOrWhiteSpace(unitValue)) 2142 { 2143 <div class="pdp-price__units"> 2144 <span>@unitValue</span> 2145 </div> 2146 } 2147 </div> 2148 } 2149 2150 @if (GetLoop("VariantGroups").Count > 0) 2151 { 2152 string pageId = GetGlobalValue("Global:Page.ID").ToString(); 2153 string variantSelection = productVariantId.Replace(".", ","); 2154 2155 var variantCombinationsObject = new List<Array>(); 2156 foreach (LoopItem variantcomb in GetLoop("VariantStockCombinations")) 2157 { 2158 string[] combinations = variantcomb.GetString("Ecom:VariantStockCombination.VariantID").Split('.'); 2159 variantCombinationsObject.Add(combinations); 2160 } 2161 string combinationsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantCombinationsObject).Replace("\"", "\'"); 2162 2163 var variantGroupsObject = new List<List<String>>(); 2164 foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2165 { 2166 var variantsObject = new List<String>(); 2167 foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2168 { 2169 variantsObject.Add(variantOption.GetString("Ecom:VariantOption.ID")); 2170 } 2171 variantGroupsObject.Add(variantsObject); 2172 } 2173 string variantsJson = Newtonsoft.Json.JsonConvert.SerializeObject(variantGroupsObject).Replace("\"", "\'"); 2174 2175 <div class="pdp-variants"> 2176 <div class="product-variants__wrapper"> 2177 <div class="js-variants" data-total-variant-groups="@GetLoop("VariantGroups").Count" data-combinations="@combinationsJson" data-variants="@variantsJson" data-current-page-variant="@variantSelection" data-variant-selections="@variantSelection" data-page-id="@pageId" data-product-id="@productid" data-group-id="@groupid"> 2178 @foreach (LoopItem variantGroup in GetLoop("VariantGroups")) 2179 { 2180 bool containsImage = variantGroup.GetLoop("VariantAvailableOptions").Any(v => !string.IsNullOrEmpty(v.GetString("Ecom:VariantOption.ImgSmall.Clean"))); 2181 string groupId = variantGroup.GetString("Ecom:VariantGroup.ID"); 2182 2183 <div class="product-variants__block product-variants__block--@groupId"> 2184 @if (containsImage) 2185 { 2186 if (!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Label"))) 2187 { 2188 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Label")</p> 2189 } 2190 2191 <div class="product-variants__options-wrapper"> 2192 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions")) 2193 { 2194 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2195 2196 if (!string.IsNullOrEmpty(variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"))) 2197 { 2198 string variantImage = "/Files/" + variantOption.GetString("Ecom:VariantOption.ImgSmall.Clean"); 2199 <div data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn product-variants__btn--image @selected"> 2200 <img src="@variantImage" alt="@variantOption.GetString("Ecom:VariantOption.Name")" title="@variantOption.GetString("Ecom:VariantOption.Name")" /> 2201 </div> 2202 } 2203 else 2204 { 2205 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn @selected">@variantOption.GetString("Ecom:VariantOption.Name")</button> 2206 } 2207 } 2208 </div> 2209 } 2210 else 2211 { 2212 if (!string.IsNullOrWhiteSpace(variantGroup.GetString("Ecom:VariantGroup.Name"))) 2213 { 2214 <p class="product-variants__title">@variantGroup.GetString("Ecom:VariantGroup.Name")</p> 2215 } 2216 2217 <div class="product-variants__dropdown"> 2218 <div class="product-variants__dropdown-wrapper"> 2219 2220 <button class="product-variants__toggle"> 2221 <span data-original="@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")">@Translate(string.Format("VariantDropdown.Placeholder.{0}", variantGroup.GetString("Ecom:VariantGroup.Name")), "Select your option")</span> 2222 <i class="fal fa-chevron-down"></i> 2223 </button> 2224 <div class="product-variants__options-wrapper product-variants__options-wrapper--dropdown product-variants__dropdown-options-wrapper"> 2225 @foreach (LoopItem variantOption in variantGroup.GetLoop("VariantAvailableOptions").OrderBy(option => option.GetString("Ecom:VariantOption.Name"))) 2226 { 2227 string selected = variantOption.GetBoolean("Ecom:VariantOption.Selected") ? "product-variants__btn--checked" : ""; 2228 2229 <button type="button" data-variant-id="@variantOption.GetString("Ecom:VariantOption.ID")" data-variant-group="@groupId" class="js-variant-option product-variants__btn--dropdown @selected"> 2230 @variantOption.GetString("Ecom:VariantOption.Name") 2231 </button> 2232 } 2233 </div> 2234 </div> 2235 </div> 2236 } 2237 </div> 2238 } 2239 </div> 2240 </div> 2241 </div> 2242 } 2243 2244 @if (!hasVariants && enableShoppingCart && enableProductShoppingCart) 2245 { 2246 <div class="pdp-add-to-cart pdp-add-to-cart__container"> 2247 <add-to-cart class="app-addtocart" 2248 data-prodid="@productid" 2249 data-variantid="@productVariantId" 2250 data-min-quantity="@minimumQuantity" 2251 data-step="@quantityStep" 2252 data-list-id="product_detail" 2253 data-list-name="Product detail"> 2254 <a class="pdp__info-btn--add-to-shoppingcart">@inShoppingCartLabel<i class="btn__icon fal fa-shopping-cart"></i></a> 2255 </add-to-cart> 2256 </div> 2257 } 2258 else 2259 { 2260 if (QuotePageID > 0) 2261 { 2262 <div class="pdp-request-quote__container"> 2263 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2264 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2265 <i class="btn__icon @buttonIconClass"></i> 2266 </a> 2267 </div> 2268 } 2269 2270 if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product.ReplacementProductId"))) 2271 { 2272 var repProd = Dynamicweb.Ecommerce.Services.Products.GetProductById(GetString("Ecom:Product.ReplacementProductId"), GetString("Ecom:Product.ReplacementVariantId"), Pageview.Area.EcomLanguageId); 2273 2274 if (repProd != null) 2275 { 2276 int productDetailPageId = GetPageIdByNavigationTag("ProductOverview"); 2277 string productUrl = $"Default.aspx?ID={productDetailPageId}&GroupID={repProd.DefaultGroup.Id}&ProductID={repProd.Id}"; 2278 if (!string.IsNullOrWhiteSpace(repProd.VariantId)) 2279 { 2280 productUrl = $"{productUrl}?VariantID={repProd.VariantId}"; 2281 } 2282 2283 <div class="pdp-replacement__container"> 2284 @if (!string.IsNullOrWhiteSpace(Translate("ProductDetail.ReplacementProd.Intro"))) 2285 { 2286 <p class="pdp-replacement__intro">@string.Format(Translate("ProductDetail.ReplacementProd.Intro", "This product is not available anymore. Please take a look at our selected replacement product."), repProd.Name)</p> 2287 } 2288 <a href="@productUrl" class="btn default-btn"> 2289 <span class="btn__text">@string.Format(Translate("ProductDetail.ReplacementProd.Btn", "View replacement product"), repProd.Name)</span> 2290 <i class="btn__icon @buttonIconClass"></i> 2291 </a> 2292 </div> 2293 } 2294 } 2295 } 2296 2297 @if (selectedPaymentLogos != null) 2298 { 2299 <section class="pdp-paymentlogos paymentlogos--small"> 2300 <div class="footer-paymentoptions" data-paymentmethods="@selectedPaymentLogos"></div> 2301 </section> 2302 } 2303 </div> 2304 </div> 2305 </section> 2306 @SnippetEnd("PdpSecondaryInfo") 2307 2308 2309 <!-- ***** END PDP ***** --> 2310 2311 @SnippetStart("ProductDetailHeaderDesktop") 2312 <!-- BEGIN Stickymenu --> 2313 <section class="stickymenu__replaceable"> 2314 2315 <div class="stickymenu_product-info-wrapper"> 2316 @if (!string.IsNullOrWhiteSpace(defaultImage)) 2317 { 2318 <figure class="stickymenu__product-image"> 2319 <img src="/Admin/Public/GetImage.ashx?Image=@defaultImage&Crop=5&Format=webp&Quality=90&Compression=80&Height=60&Width=60" alt="@productName" width="60" height="60"> 2320 </figure> 2321 } 2322 <div class="stickymenu_product-info"> 2323 <p class="stickymenu__product-name"> 2324 @if (!string.IsNullOrWhiteSpace(Manufacturer)) 2325 { 2326 <span class="stickymenu__product-name--manufacturer">@Manufacturer </span> 2327 } 2328 <span class="stickymenu__product-name--product">@productName</span> 2329 </p> 2330 2331 @if (displayPrice && displayProductPrice) 2332 { 2333 if (hasDiscount) 2334 { 2335 <div class="stickymenu_product__price__wrapper"> 2336 <span class="stickymenu_product__price--old">@originalProductPrice</span> 2337 <span class="stickymenu_product__price--current">@discountProductPrice</span> 2338 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2339 <span class="stickymenu_product__price--profit">@Translate("Productdetail.YourProfitLabel", "Your profit:") @yourProfitValueFormatted</span> 2340 </div> 2341 } 2342 else 2343 { 2344 <div class="stickymenu_product__price__wrapper"> 2345 <span class="stickymenu_product__price--current">@originalProductPrice</span> 2346 <span class="stickymenu_product__price--suffix">@ProductdetailPriceSuffix</span> 2347 </div> 2348 } 2349 } 2350 2351 @if (!string.IsNullOrWhiteSpace(unitValue)) 2352 { 2353 <div class="pdp-price__units"> 2354 <span>@unitValue</span> 2355 </div> 2356 } 2357 </div> 2358 </div> 2359 2360 @if (enableShoppingCart && enableProductShoppingCart) 2361 { 2362 <add-to-cart class="app-addtocart" 2363 data-prodid="@productid" 2364 data-variantid="@productVariantId" 2365 data-min-quantity="@minimumQuantity" 2366 data-step="@quantityStep" 2367 data-list-id="product_detail" 2368 data-list-name="Product detail"> 2369 <!-- Fall Back button for Add to Cart--> 2370 <a class="pdp__add-to-cart-btn">@inShoppingCartLabel <i class="btn__icon fal fa-shopping-cart"></i></a> 2371 </add-to-cart> 2372 } 2373 else 2374 { 2375 if (QuotePageID > 0) 2376 { 2377 <div class="py-4"> 2378 <a href="/Default.aspx?ID=@QuotePageID&ProdID=@productid&VarID=@productVariantId" class="btn product-detailpage__info-btn--request-quote"> 2379 <span class="btn__text">@Translate("ProductDetail.QuoteButton.Text", "Vraag een offerte aan")</span> 2380 <i class="btn__icon @buttonIconClass"></i> 2381 </a> 2382 </div> 2383 } 2384 } 2385 2386 </section> 2387 <!-- END Stickymenu --> 2388 @SnippetEnd("ProductDetailHeaderDesktop") 2389 2390 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2391 @using Dynamicweb; 2392 @using Bluedesk.DynamicWeb.ItemTypes.Pages; 2393 @using Bluedesk.Tools.DynamicWeb.ExtensionMethods; 2394 @using System.Linq; 2395 @using Dynamicweb.Content; 2396 2397 @{ 2398 2399 } 2400 2401 <style> 2402 .products-module__product-btn--add-to-shoppingcart { 2403 background-color: @AddToCartButtonBackgroundColor; 2404 } 2405 </style> 2406 2407 2408 @using System.Text.RegularExpressions; 2409 2410 @{ 2411 string ERPpropertiesXML = GetString("Ecom:Product:Field.ERPpropertiesXML.Value"); 2412 string CATpropertiesXML = GetString("Ecom:Product:Field.CATpropertiesXML.Value"); 2413 List<dynamic> propertiesXML = new List<dynamic>(); 2414 2415 if (!string.IsNullOrWhiteSpace(ERPpropertiesXML)) 2416 { 2417 AddSidebarPropertiesFromXML(ERPpropertiesXML, propertiesXML); 2418 } 2419 if (!string.IsNullOrWhiteSpace(CATpropertiesXML)) 2420 { 2421 AddSidebarPropertiesFromXML(CATpropertiesXML, propertiesXML); 2422 } 2423 } 2424 2425 @if(enableSpecificationsSidebar) 2426 { 2427 <div class="offcanvas__backdrop" name="specifications"></div> 2428 2429 <aside class="offcanvas" name="specifications"> 2430 <header class="offcanvas__header"> 2431 <span class="offcanvas__title">@Translate("OffCanvasMenu.SpecificationsTitle", "Specifications")</span> 2432 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2433 <i class="fal fa-times"></i> 2434 </button> 2435 </header> 2436 <div class="offcanvas__body"> 2437 @if (propertiesXML.Count > 0) 2438 { 2439 <section class="product-specifications__category"> 2440 <button class="product-specifications__header product-specifications__toggle" aria-expanded="true" aria-controls="propertiesXml"> 2441 <p class="product-specifications__title">@Translate("SpecificationsList.PropertiesXML", "Specifications")</p> 2442 <i class="fal fa-chevron-down"></i> 2443 </button> 2444 <ul id="propertiesXml" class="product-specifications__list"> 2445 @foreach (var spec in propertiesXML) 2446 { 2447 <li class="product-specifications__list-item"> 2448 <span class="product-specifications__label">@spec.Caption</span> 2449 <span class="product-specifications__value">@spec.Value @spec.Unit</span> 2450 </li> 2451 } 2452 </ul> 2453 </section> 2454 } 2455 else 2456 { 2457 Regex valueRegex = new Regex(@"\[(.+?)\]", RegexOptions.IgnoreCase); 2458 2459 foreach(LoopItem categorie in GetLoop("ProductCategories")) 2460 { 2461 string prodCategoryId = categorie.GetString("Ecom:Product.Category.ID"); 2462 string prodCategoryName = categorie.GetString("Ecom:Product.Category.Name"); 2463 2464 if(categorie.GetLoop("ProductCategoryFields").Any(d => !string.IsNullOrWhiteSpace(d.GetString("Ecom:Product.CategoryField.Value")))) 2465 { 2466 <section class="product-specifications__category"> 2467 <button class="product-specifications__header product-specifications__toggle" aria-expanded="true" aria-controls="@prodCategoryId"> 2468 <p class="product-specifications__title">@Translate("SpecificationsList." + prodCategoryName.Replace(" ", ""), prodCategoryName)</p> 2469 <i class="fal fa-chevron-down"></i> 2470 </button> 2471 <ul id="@prodCategoryId" class="product-specifications__list"> 2472 @foreach (var categoryField in categorie.GetLoop("ProductCategoryFields")) 2473 { 2474 if (!string.IsNullOrWhiteSpace(categoryField.GetString("Ecom:Product.CategoryField.Value"))) 2475 { 2476 Match unitMatch = valueRegex.Match(categoryField.GetString("Ecom:Product.CategoryField.Label")); 2477 string unit = unitMatch.Groups[1].Value; 2478 2479 <li class="product-specifications__list-item"> 2480 <span class="product-specifications__label">@valueRegex.Replace(categoryField.GetString("Ecom:Product.CategoryField.Label"), "")</span> 2481 <span class="product-specifications__value">@categoryField.GetString("Ecom:Product.CategoryField.Value") @unit</span> 2482 </li> 2483 } 2484 } 2485 </ul> 2486 </section> 2487 } 2488 } 2489 } 2490 </div> 2491 </aside> 2492 } 2493 2494 @functions { 2495 public void AddSidebarPropertiesFromXML(string xml, List<dynamic> specifications) 2496 { 2497 var xmlDoc = new System.Xml.XmlDocument(); 2498 xmlDoc.LoadXml(xml); 2499 2500 foreach (System.Xml.XmlNode fieldNode in xmlDoc.SelectNodes("//Field")) 2501 { 2502 specifications.Add(new 2503 { 2504 Id = fieldNode.Attributes["id"]?.InnerText, 2505 Caption = fieldNode.SelectSingleNode("Caption")?.InnerText, 2506 Value = fieldNode.SelectSingleNode("Value")?.InnerText, 2507 Unit = fieldNode.SelectSingleNode("Unit")?.InnerText 2508 }); 2509 } 2510 } 2511 } 2512 @using System.Text.RegularExpressions; 2513 2514 <div class="offcanvas__backdrop" name="reviews"></div> 2515 2516 <aside class="offcanvas" name="reviews"> 2517 <header class="offcanvas__header"> 2518 <span class="offcanvas__title">@Translate("OffCanvasMenu.ReviewTitle", "Customer reviews") (@GetLoop("Comments.Newfirst").Count)</span> 2519 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2520 <i class="fal fa-times"></i> 2521 </button> 2522 </header> 2523 <div class="offcanvas__body"> 2524 @foreach (LoopItem Comment in GetLoop("Comments.Newfirst")) 2525 { 2526 <section class="review-comment__list-item"> 2527 <div class="review-comment__header"> 2528 <div class="review-comment__rating-wrapper"> 2529 @renderReviewIndicator("#86C440", Comment.GetInteger("Rating"), "row", false, 0) 2530 </div> 2531 </div> 2532 <div class="review-comment__message"> 2533 <p>@Comment.GetString("Text")</p> 2534 </div> 2535 <p class="review-comment__meta">@Comment.GetString("Name") | @Comment.GetDate("CreatedDate").ToString("d MMMM yyyy")</p> 2536 </section> 2537 } 2538 </div> 2539 </aside> 2540 @using System.Text.RegularExpressions; 2541 2542 @{ 2543 @* var pageView = Dynamicweb.Frontend.PageView.Current(); *@ 2544 var isLoggedIn = Dynamicweb.Security.UserManagement.User.IsExtranetUserLoggedIn(); 2545 var currentUser = Dynamicweb.Security.UserManagement.User.GetCurrentExtranetUser(); 2546 var ecomLanguageId = GetString("Ecom:Product.LanguageID"); 2547 2548 string namePrefill = currentUser != null ? currentUser.Name : ""; 2549 string emailPrefill = currentUser != null ? currentUser.Email : ""; 2550 } 2551 2552 <script> 2553 var errorMessagesJSON = 2554 { 2555 'StrErrorMsgTxtName': { 2556 '_default' : '@Translate("form.error.text", "It looks like this field is empty or too short")', 2557 '_validate' : '@Translate("form.error.text.notvalid", "It looks like this field is not valid")' 2558 }, 2559 'StrErrorMsgTxtEmail': { 2560 '_default' : '@Translate("form.error.emaildefault", "It seems that your e-mail address has not been completed")', 2561 '_validate' : '@Translate("form.error.emailnotvalid", "It seems that this is not a valid e-mail address")' 2562 } 2563 }; 2564 </script> 2565 2566 <div class="offcanvas__backdrop" name="reviewcreate"></div> 2567 2568 <aside class="offcanvas" name="reviewcreate"> 2569 2570 <header class="offcanvas__header"> 2571 <span class="offcanvas__title">@Translate("OffCanvasMenu.ReviewCreateTitle", "Rate this product")</span> 2572 <button class="offcanvas__close" aria-label="@Translate("OffCanvasMenu.Close", "Close menu")"> 2573 <i class="fal fa-times"></i> 2574 </button> 2575 </header> 2576 2577 <div class="offcanvas__body"> 2578 <form method="post" action="/Default.aspx?ID=@Pageview.ID" id="commentform" class="default-contact-form"> 2579 <input type="hidden" name="Comment.Command" id="Comment.Command" value="create" /> 2580 <input type="hidden" name="Comment.Active" value="false" /> 2581 <input type="hidden" name="Comment.ItemType" value="ecomProduct" /> 2582 <input type="hidden" name="Comment.ItemID" value="@productid" /> 2583 <input type="hidden" name="Comment.LangID" value="@ecomLanguageId" /> 2584 <input type="hidden" name="Comment.Continue" value="@Pageview.SearchFriendlyUrl?reviewcmd=created#pdp-tabs" /> 2585 2586 <div class="flex flex-row flex-wrap"> 2587 <div class="w-full"> 2588 @renderDefaultField("Name", "Review.Form", true, "Comment.Name", namePrefill, "text", 2589 new Dictionary<string, string>() { 2590 { "erroralias", "StrErrorMsgTxtName" }, 2591 { "verplicht", "verplicht" }, 2592 { "minchar", "1" }, 2593 } 2594 ) 2595 </div> 2596 </div> 2597 2598 <div class="flex flex-row flex-wrap"> 2599 <div class="w-full"> 2600 @renderDefaultField("Email", "Review.Form", true, "Comment.Email", emailPrefill, "email", 2601 new Dictionary<string, string>() { 2602 { "erroralias", "StrErrorMsgTxtEmail" }, 2603 { "verplicht", "verplicht" }, 2604 { "minchar", "1" }, 2605 } 2606 ) 2607 </div> 2608 </div> 2609 2610 <div class="flex flex-row flex-wrap"> 2611 <div class="w-full"> 2612 @renderStarRatingField("Rating", "Review.Form", true, "Comment.Rating") 2613 </div> 2614 </div> 2615 2616 <div class="flex flex-row flex-wrap"> 2617 <div class="w-full"> 2618 @renderTextArea("Comment", "Review.Form", true, "Comment.Text", "", 2619 new Dictionary<string, string>() { 2620 { "erroralias", "StrErrorMsgTxtName" }, 2621 { "verplicht", "verplicht" }, 2622 { "minchar", "1" }, 2623 { "rows", "10" } 2624 } 2625 ) 2626 </div> 2627 </div> 2628 2629 </form> 2630 </div> 2631 2632 <footer class="offcanvas__footer"> 2633 <button type="submit" form="commentform" class="btn btn__primary"> 2634 <span class="btn__text">@Translate("Review.Form.Submit", "Submit review")</span> 2635 <i class="far fa-chevron-right btn__icon"></i> 2636 </button> 2637 </footer> 2638 2639 </aside> 2640 2641 @helper renderInputLabel(string prefix, string label, bool isRequired, string inputId) { 2642 var labelCode = string.Format("{0}.{1}", prefix, label.Replace(" ", "_")); 2643 <label class="form__input-label" for="@inputId">@Translate(labelCode, label) @(isRequired ? "*" : "")</label> 2644 } 2645 2646 @helper renderDefaultField(string label, string labelPrefix, bool isRequired, string fieldId, string fieldValue, string fieldType, Dictionary<string, string> customattributes) 2647 { 2648 string attrs = ""; 2649 foreach (var item in customattributes) 2650 { 2651 attrs += item.Key + "=\"" + item.Value + "\" "; 2652 } 2653 2654 <section class="form__item form__item--default"> 2655 <div class="input__group form-item__input"> 2656 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2657 <input 2658 name="@fieldId" 2659 id="@fieldId" 2660 class="input form__input" 2661 type="@fieldType" 2662 value="@fieldValue" 2663 @attrs 2664 /> 2665 <i class="fal fa-check checkmark"></i> 2666 <i class="fal fa-times cross"></i> 2667 </div> 2668 <div class="errormessage"></div> 2669 </section> 2670 } 2671 2672 @helper renderTextArea(string label, string labelPrefix, bool isRequired, string fieldId, string fieldValue, Dictionary<string, string> customattributes) 2673 { 2674 string attrs = ""; 2675 foreach (var item in customattributes) 2676 { 2677 attrs += item.Key + "=\"" + item.Value + "\" "; 2678 } 2679 2680 <section class="form__item"> 2681 <div class="input__group form-item__input form-group"> 2682 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2683 <textarea class="input--text form__input form__input--textarea" 2684 id="@fieldId" 2685 name="@fieldId" 2686 @attrs 2687 >@fieldValue</textarea> 2688 </div> 2689 <div class="errormessage"></div> 2690 </section> 2691 } 2692 2693 @helper renderStarRatingField(string label, string labelPrefix, bool isRequired, string fieldId) { 2694 <section class="form__item form__item--default form__item--starrating"> 2695 @renderInputLabel(labelPrefix, label, isRequired, fieldId) 2696 <div class="star-rating"> 2697 <div class="star-rating__wrap"> 2698 <input class="star-rating__input" id="star-rating-5" type="radio" name="@fieldId" value="5"> 2699 <label class="star-rating__ico far fa-star" for="star-rating-5" title="5 out of 5 stars"></label> 2700 <input class="star-rating__input" id="star-rating-4" type="radio" name="@fieldId" value="4"> 2701 <label class="star-rating__ico far fa-star" for="star-rating-4" title="4 out of 5 stars"></label> 2702 <input class="star-rating__input" id="star-rating-3" type="radio" name="@fieldId" value="3"> 2703 <label class="star-rating__ico far fa-star" for="star-rating-3" title="3 out of 5 stars"></label> 2704 <input class="star-rating__input" id="star-rating-2" type="radio" name="@fieldId" value="2"> 2705 <label class="star-rating__ico far fa-star" for="star-rating-2" title="2 out of 5 stars"></label> 2706 <input class="star-rating__input" id="star-rating-1" type="radio" name="@fieldId" value="1"> 2707 <label class="star-rating__ico far fa-star" for="star-rating-1" title="1 out of 5 stars"></label> 2708 </div> 2709 </div> 2710 <div class="errormessage"></div> 2711 </section> 2712 } 2713 2714 2715 <script> 2716 window.globals.productId = '@productid'; 2717 window.globals.productVariantId = '@productVariantId'; 2718 </script> 2719 2720 @if(enableShoppingCart && enableProductShoppingCart) 2721 { 2722 <section class="w-full stickymenu__replaceable__wrapper"> 2723 <div class="container"> 2724 @RenderSnippet("ProductDetailHeaderDesktop") 2725 </div> 2726 </section> 2727 2728 <script> 2729 window.addEventListener('DOMContentLoaded', function (event) { 2730 var stickyAddToCartEl = document.querySelector('.stickymenu__replaceable__wrapper'); 2731 var pdpAddToCartElement = document.querySelector('.pdp-add-to-cart'); 2732 var scrollToTopEl = document.querySelector('#scroll-to-top'); 2733 2734 if(pdpAddToCartElement) { 2735 var observer = new IntersectionObserver((entries, observer) => { 2736 entries.forEach(entry => { 2737 if(entry.intersectionRatio != 1 && window.scrollY >= entry.boundingClientRect.top) { 2738 // Out of view 2739 stickyAddToCartEl.classList.add('stickymenu__replaceable__wrapper--active'); 2740 scrollToTopEl.classList.add('scroll-to-top--shown--lg'); 2741 scrollToTopEl.style.setProperty('--sticky-bottom-offset', stickyAddToCartEl.offsetHeight + 'px'); 2742 document.body.style.paddingBottom = stickyAddToCartEl.offsetHeight + 'px'; 2743 } else { 2744 // In view 2745 stickyAddToCartEl.classList.remove('stickymenu__replaceable__wrapper--active'); 2746 scrollToTopEl.classList.remove('scroll-to-top--shown--lg'); 2747 scrollToTopEl.style.setProperty('--sticky-bottom-offset', '0px'); 2748 document.body.style.paddingBottom = null; 2749 } 2750 }); 2751 }, { 2752 threshold: 1 2753 }); 2754 2755 observer.observe(pdpAddToCartElement); 2756 } 2757 2758 }); 2759 </script> 2760 } 2761 2762 <main class="pdp"> 2763 2764 <div class="container pdp-info__container"> 2765 <div class="pdp-info__imagecolumn"> 2766 @RenderSnippet("PdpHeader") 2767 @RenderSnippet("PdpImage") 2768 </div> 2769 @RenderSnippet("PdpInfo") 2770 </div> 2771 2772 @RenderSnippet("PdpHelp") 2773 @RenderPdpTabs(showReviewTab, enableSpecificationsSidebar) 2774 @* @RenderSnippet("PdpSpecification") *@ 2775 @RenderSnippet("PdpBanner") 2776 @* @RenderSnippet("PdpReviews") *@ 2777 @RenderSnippet("PdpRelatedGroups") 2778 @if(enableShoppingCart && enableProductShoppingCart) 2779 { 2780 @RenderSnippet("PdpSecondaryInfo"); 2781 } 2782 @RenderSnippet("PdpRecentViewedProducts") 2783 2784 </main> 2785 2786 @{ 2787 var httpdomain = Dynamicweb.Environment.Helpers.LinkHelper.GetHttpDomain(); 2788 var productAvailability = @GetInteger("Ecom:Product.Stock") > 0 ? "https://schema.org/InStock" : "https://schema.org/OutOfStock"; 2789 string productPrice = @GetBoolean("Ecom:Product.HaveDiscount") ? @GetString("Ecom:Product.Discount.Price.PriceWithVAT.Value") : @GetString("Ecom:Product.Price.PriceWithVAT.Value"); 2790 //Dynamicweb gives the values with comma notation, while google expects a dot notation and then converts it into a comma notation on frontend 2791 productPrice = productPrice.Replace(',', '.'); 2792 2793 //Rating is set at the website setting, not product setting. If it's not defined, the rating won't be rendered in the review 2794 double rating = @GetDouble("Ecom:Product.Rating"); 2795 bool renderRating = rating > 0; 2796 2797 //Must be at least 1, or reviews won't be rendered 2798 int reviewCount = Math.Max(@GetInteger("Comments.RepliesCount"), 1); 2799 } 2800 2801 <script type="application/ld+json"> 2802 { 2803 "@@context": "https://schema.org/", 2804 "@@type": "Product", 2805 "name": "@GetString("Ecom:Product.Name")", 2806 "image": "@httpdomain@GetString("Ecom:Product.ImageDefault.Clean")", 2807 "description": "@GetString("Ecom:Product.ShortDescription.Raw")", 2808 "sku": "@GetString("Ecom:Product:Field.EAN.Value.Clean")", 2809 "mpn": "@GetString("Ecom:Product.ID")", 2810 "url": "@httpdomain/@GetString("Ecom:Product.VariantLinkGroup.Clean")", 2811 @if(!string.IsNullOrWhiteSpace(GetString("Ecom:Manufacturer.Name"))) 2812 { 2813 <text> 2814 "brand": { 2815 "@@type": "Brand", 2816 "name": "@GetString("Ecom:Manufacturer.Name")" 2817 }, 2818 </text> 2819 } 2820 @if(renderRating) 2821 { 2822 <text> 2823 "aggregateRating": { 2824 "@@type": "AggregateRating", 2825 "ratingValue": "@rating", 2826 "reviewCount": "@reviewCount" 2827 }, 2828 </text> 2829 } 2830 "offers": { 2831 "@@type": "Offer", 2832 "availability": "@productAvailability", 2833 "itemCondition": "https://schema.org/NewCondition", 2834 "price": "@productPrice", 2835 "priceCurrency": "@GetString("Ecom:Product.Price.Currency.Code")", 2836 "url": "@httpdomain/@GetString("Ecom:Product.VariantLinkGroup.Clean")" 2837 } 2838 } 2839 </script> 2840 2841 2842 @SnippetStart("DataLayer") 2843 @{ 2844 var categoryObject = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Product.PrimaryOrFirstGroupID")); 2845 string categoryName = categoryObject != null ? categoryObject.Name : ""; 2846 } 2847 2848 <script> 2849 if(window.dataLayer) { 2850 dataLayer.push({ 2851 'event': 'view_item', 2852 'ecommerce': { 2853 'currency': '@Dynamicweb.Ecommerce.Common.Context.Currency.Code', 2854 'value': @GetDouble("Ecom:Product.Discount.Price.Price.Value").ToString(specifier, culture), 2855 'items': [{ 2856 'item_id': '@GetString("Ecom:Product.ID")', 2857 'item_name': '@GetString("Ecom:Product.Name").Replace("''", "\\\"").Replace("'", "")', 2858 'item_number': '@GetString("Ecom:Product.Number")', 2859 'discount': @GetDouble("Ecom:Product.Discount.TotalAmount.Price.Value").ToString(specifier, culture), 2860 'index': 1, 2861 'item_brand': '@GetString("Ecom:Manufacturer.Name").Replace("''", "\\\"").Replace("'", "")', 2862 'item_category': '@categoryName.Replace("''", "\\\"").Replace("'", "")', 2863 'item_list_id': '@listId', 2864 'item_list_name': '@listName', 2865 'item_variant': '@GetString("Ecom:Product.VariantID")', 2866 'price': @GetDouble("Ecom:Product.Price.Price.Value").ToString(specifier, culture), 2867 'quantity': 1 2868 }] 2869 } 2870 }); 2871 } 2872 </script> 2873 2874 @SnippetEnd("DataLayer") 2875 2876 2877 @SnippetStart("ProductDetailHeaderMobile") 2878 <!-- BEGIN Stickymenu --> 2879 <section class="stickymenu__replaceable flex flex-1 md:justify-between items-center"> 2880 <div class="stickymenu_product-info-wrapper flex items-center mr-auto"> 2881 @if (!string.IsNullOrWhiteSpace(defaultImage)) 2882 { 2883 <div class="stickymenu__product-image mr-2" style="background-image: url('@defaultImage');"></div> 2884 } 2885 <div class="flex flex-col leading-tight"> 2886 <p class="h5 sm:text-l md:text-xl font-bold m-0 stickymenu__product-name">@productName</p> 2887 2888 @if (displayPrice) 2889 { 2890 if (!pricezero) 2891 { 2892 if (hasDiscount) 2893 { 2894 <div class="stickymenu_product__price__wrapper flex items-center"> 2895 <span class="stickymenu_product__price--old">@priceFormatted</span> 2896 <span class="stickymenu_product__price--current">@discountPriceValue</span> 2897 <span class="stickymenu_product__price--profit">@yourProfitLabel @yourProfitValue</span> 2898 </div> 2899 } 2900 else 2901 { 2902 <p>@priceFormatted</p> 2903 } 2904 } 2905 } 2906 2907 </div> 2908 </div> 2909 </section> 2910 <!-- END Stickymenu --> 2911 2912 @SnippetEnd("ProductDetailHeaderMobile") 2913
Naar boven