Go to top
Error executing template "Designs/Dwsimple/eCom/Product/Product.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at Hounisen.Website.Helpers.GroupHelper.GetParentsRecursively(Group group, List`1 groupNames)
   at CompiledRazorTemplates.Dynamic.RazorEngine_5d1649a65b1a4dc49b6e2b41908d0219.Execute() in D:\web\hounisen\Hounisen.Website\Files\Templates\Designs\Dwsimple\eCom\Product\Product.cshtml:line 822
   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 @using System.Web; 2 @using System.Net; 3 @using System.Globalization; 4 @using System.Text.RegularExpressions; 5 @using Dynamicweb; 6 @using Dynamicweb.Rendering; 7 @using Dynamicweb.Security.UserManagement; 8 @using System.IO; 9 @using Dynamicweb.Content 10 @using Dynamicweb.Core; 11 @using Dynamicweb.Ecommerce.Products 12 @using Hounisen.Website.Helpers 13 @using HtmlAgilityPack 14 @using VestjyskMarketing.Models; 15 @using Website.Hounisen.Helpers; 16 17 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 18 19 @using System.Globalization 20 @using Dynamicweb.Content 21 @using Dynamicweb.Ecommerce 22 @using Dynamicweb.Ecommerce.Products 23 @using Dynamicweb.Security.UserManagement 24 @using Hounisen.Website.Helpers 25 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 26 27 @using System.Text.RegularExpressions 28 @using System.Web 29 30 31 @functions{ 32 public class WrapMethods 33 { 34 //Gets the contrasting color 35 public static string getContrastYIQ(string hexcolor) 36 { 37 if (hexcolor != "") 38 { 39 hexcolor = Regex.Replace(hexcolor, "[^0-9a-zA-Z]+", ""); 40 41 int r = Convert.ToByte(hexcolor.Substring(0, 2), 16); 42 int g = Convert.ToByte(hexcolor.Substring(2, 2), 16); 43 int b = Convert.ToByte(hexcolor.Substring(4, 2), 16); 44 int yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; 45 46 if (yiq >= 128) 47 { 48 return "black"; 49 } 50 else 51 { 52 return "white"; 53 } 54 } 55 else 56 { 57 return "black"; 58 } 59 } 60 61 62 //Truncate text 63 public static string Truncate (string value, int count, bool strip=true) 64 { 65 if (strip == true){ 66 value = StripHtmlTagByCharArray(value); 67 } 68 69 if (value.Length > count) 70 { 71 value = value.Substring(0, count - 1) + "..."; 72 } 73 74 return value; 75 } 76 77 78 //Strip text from HTML 79 public static string StripHtmlTagByCharArray(string htmlString) 80 { 81 char[] array = new char[htmlString.Length]; 82 int arrayIndex = 0; 83 bool inside = false; 84 85 for (int i = 0; i < htmlString.Length; i++) 86 { 87 char let = htmlString[i]; 88 if (let == '<') 89 { 90 inside = true; 91 continue; 92 } 93 if (let == '>') 94 { 95 inside = false; 96 continue; 97 } 98 if (!inside) 99 { 100 array[arrayIndex] = let; 101 arrayIndex++; 102 } 103 } 104 return new string(array, 0, arrayIndex); 105 } 106 107 //Make the correct count of columns 108 public static string ColumnMaker(int Col, string ScreenSize) 109 { 110 string Columns = ""; 111 112 switch (Col) 113 { 114 case 1: 115 Columns = "col-"+ScreenSize+"-12"; 116 break; 117 118 case 2: 119 Columns = "col-"+ScreenSize+"-6"; 120 break; 121 122 case 3: 123 Columns = "col-"+ScreenSize+"-4"; 124 break; 125 126 case 4: 127 Columns = "col-"+ScreenSize+"-3"; 128 break; 129 130 default: 131 Columns = "col-"+ScreenSize+"-3"; 132 break; 133 } 134 135 return Columns; 136 } 137 138 139 private string Custom(string firstoption, string secondoption) 140 { 141 if (firstoption == "custom") 142 { 143 return secondoption; 144 } 145 else 146 { 147 return firstoption; 148 } 149 } 150 } 151 } 152 153 154 155 156 157 158 @helper GetProductList(dynamic Loop, int ColMD = 3, int ColSM = 3, int ColXS = 1) 159 { 160 int Count = 0; 161 162 int index = 1; 163 164 var embeddedScript = GetString("Ecom:Group:Field.ProductEmbeddedScript"); 165 var embedIndex = GetInteger("Ecom:Group:Field.EmbedScriptStartingIndex"); 166 167 IFormatProvider jsNumberFormat = new NumberFormatInfo() { NumberDecimalSeparator = ".", NumberGroupSeparator = "" }; 168 169 170 var groupService = new GroupService(); 171 var productService = new ProductService(); 172 var pageService = new PageService(); 173 174 // SKI AND Region H 175 bool hasSkiDeal = false; 176 bool hasRegionHDeal = false; 177 bool activateLimitedProducts = false; 178 string limitedProductNumbers = ""; 179 var currentUser = User.GetCurrentExtranetUser(); 180 if (currentUser != null) 181 { 182 var skiGroup = currentUser.Groups.FirstOrDefault(e => e.Name == "SKI"); 183 if (skiGroup != null && !string.IsNullOrEmpty(skiGroup.Name)) 184 { 185 hasSkiDeal = true; 186 } 187 188 hasRegionHDeal = Dynamicweb.Core.Converter.ToBoolean(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_RegionH").Value); 189 activateLimitedProducts = Dynamicweb.Core.Converter.ToBoolean(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_ActivateLimitedProducts").Value); 190 limitedProductNumbers = Dynamicweb.Core.Converter.ToString(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_LimitedProductNumbers").Value); 191 } 192 193 List<string> limitedProductNumbersList = new List<string>(); 194 if (!String.IsNullOrWhiteSpace(limitedProductNumbers) && activateLimitedProducts) 195 { 196 limitedProductNumbersList = limitedProductNumbers.Split(',').ToList(); 197 } 198 199 200 foreach (LoopItem product in Loop) 201 { 202 203 string GroupLink = "/Default.aspx?ID=" + product.GetString("Ecom:Product.PrimaryOrCurrentPageID") + "&groupid=" + product.GetString("Ecom:Product.PrimaryOrFirstGroupID") + "&productid=" + product.GetString("Ecom:Product.ID"); 204 // string GroupLink = product.GetString("Ecom:Product.LinkGroup.Clean"); 205 int stock = product.GetInteger("Ecom:Product.Stock"); 206 string Name = product.GetString("Ecom:Product.Name"); 207 string Description = product.GetString("Ecom:Product.ShortDescription"); 208 string prodID = product.GetString("Ecom:Product.ID"); 209 string prodVariantID = product.GetString("Ecom:Product.VariantID"); 210 string prodLanguageID = product.GetString("Ecom:Product.LanguageID"); 211 string Image = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Products/" + prodID + ".jpg&format=webp&quality=85"; 212 213 //prices and units 214 string defaultUnitId = product.GetString("Ecom:Product.DefaultUnitID"); 215 var prices = product.GetLoop("CustomPriceLoop"); 216 List<string> pricesHtmlList = Hounisen.Website.Helpers.Helpers.PricesListMakeListString(prices); 217 List<Hounisen.Website.Models.Unit> unitsDropdown = Hounisen.Website.Helpers.Helpers.PopulateUnitsDropdown(prices); 218 var unitDefault = unitsDropdown.Where(x => x.Id.Equals(defaultUnitId)).FirstOrDefault(); 219 int minOrder = unitDefault != null ? unitDefault.MinOrder : 1; 220 221 if (!string.IsNullOrWhiteSpace(product.GetString("Ecom:Product.SelectedVariantComboID"))) 222 { 223 prodID = product.GetString("Ecom:Product.ID") + "&" + product.GetString("Ecom:Product.SelectedVariantComboID"); 224 } 225 226 227 GroupHelper gh = new GroupHelper(); 228 var masterPage = pageService.GetPage(product.GetInteger("Ecom:Product.PrimaryOrCurrentPageID")); 229 masterPage.GetDisplayName(); 230 231 var group = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 232 var groups = ""; 233 if (group != null) 234 { 235 var groupNames = gh.GetParentsRecursively(group, new List<string>()); 236 groupNames = gh.Format(groupNames, masterPage.GetDisplayName(), group); 237 groups = gh.ListToString(groupNames); 238 } 239 240 // should the button for adding to cart be disabled and show different text 241 bool showLimitedProduct = false; 242 if (activateLimitedProducts) 243 { 244 showLimitedProduct = true; 245 if (limitedProductNumbersList.Count > 0 && limitedProductNumbersList.Contains(product.GetString("Ecom:Product.Number"))) 246 { 247 showLimitedProduct = false; 248 } 249 } 250 251 <div class="product-list__item"> 252 <div class="col-xs-12"> 253 <div class="product-list__item-inner"> 254 <div class="col-xs-12 col-sm-4 col-flex"> 255 256 <div class="product-list-item__primary-image"> 257 @if (currentUser != null) 258 { 259 <a href="@GroupLink" title="@Name"> 260 <img class="product-list-item__image lazy" alt="@Name" data-src="@Image&width=300" class="img-responsive img-center"> 261 </a> 262 <a class="fancybox" data-fancybox href="@Image&width=1000" title="@Name"> 263 <svg class="product-list-item__image-icon"> 264 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#fullscreen"></use> 265 </svg> 266 </a> 267 } 268 else 269 { 270 <a href="@GroupLink" title="@Name"> 271 <img class="product-list-item__image lazy" alt="@Name" data-src="@Image&width=300" class="img-responsive img-center"> 272 </a> 273 <a class="fancybox" data-fancybox href="@Image&width=1000" title="@Name"> 274 <svg class="product-list-item__image-icon"> 275 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#fullscreen"></use> 276 </svg> 277 </a> 278 } 279 280 281 </div> 282 @{ 283 var productObj = productService.GetProductById(prodID, prodVariantID, prodLanguageID); 284 var overlayNameAndColors = new ProductImageOverlayHelper().GetTextAndColor(productObj); 285 286 <div class="product__primary-image__overlay-container"> 287 @foreach (var overlayNameAndColo in overlayNameAndColors) 288 { 289 <div style="background-color: @overlayNameAndColo.Value" class="product__primary-image__overlay-container__item"> 290 @overlayNameAndColo.Key 291 </div> 292 } 293 </div>} 294 </div> 295 <div class="col-xs-12 col-sm-8"> 296 <div class="product-list-item__info js-product-info"> 297 <div class="row"> 298 @*Title & Number*@ 299 <div class="col-xs-12"> 300 <h4 data-name="@Name" data-product_id="@prodID" data-categories="@groups" class="product-list-item__title"> 301 <a class="product-list-item__title-link" href="@GroupLink">@Name</a> 302 </h4> 303 </div> 304 </div> 305 <div class="row"> 306 <div class="col-xs-12 col-sm-5"> 307 308 <div class="product-list-item__attributes"> 309 <p> 310 <strong>Varenummer:</strong> @product.GetString("Ecom:Product.Number") 311 </p> 312 <p> 313 <strong>@product.GetString("Ecom:Product:Field.Attribut1A"):</strong><br />@product.GetString("Ecom:Product:Field.Attribut1B") 314 </p> 315 <p> 316 <strong>@product.GetString("Ecom:Product:Field.Attribut2A"):</strong> @product.GetString("Ecom:Product:Field.Attribut2B") 317 </p> 318 </div> 319 320 <div class="hidden-xs"> 321 <div class=""> 322 @* IKONER *@ 323 <div class="product-list-item__icons"> 324 @{ 325 foreach (var c in product.GetString("Ecom:Product:Field.Certificates").Split(',')) 326 { 327 if (!string.IsNullOrEmpty(c)) 328 { 329 string src = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Certificates/" + @c + ".jpg&format=webp&quality=85&width=100"; 330 <img class="product-list-item__icon lazy" alt="@Translate("Cert_" + c, c)" title="@Translate("Cert_" + c, c)" data-src=@src src=@src> 331 } 332 } 333 } 334 </div> 335 </div> 336 </div> 337 </div> 338 339 <div class="col-xs-12 col-sm-7"> 340 @if (Dynamicweb.Core.Converter.ToBoolean(GetGlobalValue("Global:Extranet.UserName"))) 341 { 342 <ul class="product-list-item__prices"> 343 @foreach (var priceHtml in pricesHtmlList) 344 { 345 <li> 346 @priceHtml 347 </li> 348 } 349 </ul> 350 } 351 </div> 352 </div> 353 354 <div class="row"> 355 <div class="col-xs-12 col-sm-4 col-lg-5"> 356 @* LAGERBEHOLDNING *@ 357 <div class="product-list-item__stock"> 358 @{ 359 if (stock > 0) 360 { 361 <p class="product-list-item__stock-text"> 362 <span class="product-list-item__stock-circle product-list-item__stock-circle--green"></span> På lager 363 </p> 364 } 365 else 366 { 367 <p class="product-list-item__stock-text"> 368 @if (!String.IsNullOrEmpty(product.GetString("Ecom:Manufacturer.Description"))) 369 { 370 @product.GetString("Ecom:Manufacturer.Description") 371 } 372 else 373 { 374 @Translate("StockStandardSoldoutText", "Kontakt os for leveringstid: 86210800 eller salg@hounisen.com") 375 } 376 </p> 377 } 378 } 379 </div> 380 381 <div class="product-list-item__see-product"> 382 <a class="product-list-item__link" href="@GroupLink" class="">@Translate("See product", "Se produkt")</a> 383 </div> 384 </div> 385 <div class="col-xs-12 col-sm-8 col-lg-7"> 386 @if (Dynamicweb.Core.Converter.ToBoolean(GetGlobalValue("Global:Extranet.UserName"))) 387 { 388 <div class="row"> 389 @if (showLimitedProduct) 390 { 391 <div class="col-xs-12"> 392 <button class="btn btn--ghost product__button disabled"> 393 @Translate("Kan bestilles på fritekstordre", "Kan bestilles på fritekstordre") 394 </button> 395 </div> 396 } 397 else 398 { 399 <div class="col-xs-7"> 400 <div class="product__addtocart-input js-addtocart-input"> 401 <input type="button" value="-" class="product-list-item__quantity-button product-list-item__quantity-button--minus qtyminus" field="quantity" /> 402 <input type="number" class="product__quantity-input product-list-item__quantity-input quantity" data-name="quantity" name="quantity" value="@minOrder" field="quantity" /> 403 <input type="button" value="+" class="product-list-item__quantity-button product-list-item__quantity-button--plus qtyplus" field="quantity" /> 404 </div> 405 <div class="product__unit-selector" style="display: inline-block;"> 406 @{ 407 int counter = 0; 408 } 409 @foreach (var unitDropdown in unitsDropdown) 410 { 411 var currentUnitPrice = prices[counter].Values["Ecom:Product.Prices.Amount"]; 412 <input type="radio" data-id="@index" data-price="@currentUnitPrice" class="unit-type" id="@(unitDropdown.Id + prodID)" name="UnitID@(product.GetString("Ecom:Product.Number"))" value="@unitDropdown.Id" required data-lot-size="@unitDropdown.LotSize" data-min-order="@unitDropdown.MinOrder" @(unitsDropdown.Count == 1 ? "checked='checked'" : "")> 413 <label for="@(unitDropdown.Id + prodID)">@unitDropdown.Name</label> 414 counter++; 415 } 416 </div> 417 <p class="product__unit-selector-error-message"> 418 @Translate("unit-error-message", "* Du mangler at vælge type") 419 </p> 420 </div> 421 <div class="col-xs-5"> 422 <div class="product__addtocart-button"> 423 <button data-id="@index" type="submit" name="submit" onclick="AddToCart(event, '@prodID', $(this).parent().parent().prev().find('input.quantity').val(), $(this).parent().parent().prev().find('input[name=\'UnitID@(product.GetString("Ecom:Product.Number"))\']:checked').val());" class="btn btn-primary product__button product__button-js"> 424 @Translate("Add to cart", "Add to cart") 425 <svg class="product__button-icon hidden-xs"> 426 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#basket"></use> 427 </svg> 428 </button> 429 430 <div class="product-list-item__favorite-list popup-wrap favorite"> 431 432 @if (hasRegionHDeal == false) 433 { 434 <button class="popup-show" data-popup="favorite-list" type="button" title="@Translate("Add to favorites", "Add to favorites")"> 435 <i class="fa fa-star-o"></i> <span>Tilføj til liste</span> 436 </button> 437 <div id="favorite-list" class="popup-form"> 438 <div class="close"> 439 <i class="fa fa-times"></i> 440 </div> 441 <h3>@Translate("Choose list", "Choose list")</h3> 442 <ul> 443 @{ 444 var lists = Dynamicweb.Ecommerce.CustomerCenter.CustomerProductList.GetListsByUserCustomerNumber(currentUser.CustomerNumber); 445 } 446 447 @foreach (var list in lists) 448 { 449 if (list.Products.Where(p => p.ProductId.ToString() == prodID).Count() > 0) 450 { 451 <li> 452 <a href="@(System.Web.HttpContext.Current.Request.Url)&CCRemoveFromMyLists=@prodID&CCAddToListVariantID=&CCAddToListLanguageID=LANG1&CCAddToListID=@list.ListId&CCListType="> 453 <i class="fa fa-star"></i> @list.Name 454 </a> 455 </li> 456 } 457 else 458 { 459 <li> 460 <a href="@(System.Web.HttpContext.Current.Request.Url)&CCAddToMyLists=@prodID&CCAddToListVariantID=&CCAddToListLanguageID=LANG1&CCAddToListID=@list.ListId&CCListType="> 461 <i class="fa fa-star-o"></i> @list.Name 462 </a> 463 </li> 464 } 465 } 466 <li> 467 <a href="/mine-favoritter/opret-favoritliste?ProdID=@prodID"> 468 <i class="fa fa-plus"></i>@Translate("Add new list", "Add new list") 469 </a> 470 </li> 471 </ul> 472 </div> 473 } 474 475 </div> 476 </div> 477 </div> 478 } 479 480 </div> 481 } 482 else 483 { 484 <div class="not-loggedin"> 485 <p class="not-loggedin-text"><a data-toggle="modal" data-target="#login" href="">@Translate("Login", "Login")</a> eller <a href="/kontakt/opret-brugerprofil">Bliv kunde</a> for at se priser og købe på Hounisen.com</p> 486 <a data-toggle="modal" data-target="#login" href="" class="btn btn-primary not-loggedin-button"> 487 <span>Log ind</span> 488 </a> 489 </div> 490 } 491 </div> 492 </div> 493 </div> 494 495 496 </div> 497 </div> 498 </div> 499 </div> 500 501 if (index == embedIndex) 502 { 503 <div class="product-list__item"> 504 <div class="col-xs-12"> 505 @embeddedScript 506 </div> 507 </div> 508 } 509 510 511 Count++; 512 index++; 513 514 if (Count == ColMD) 515 { 516 <div class="row"></div> 517 Count = 0; 518 } 519 } 520 } 521 522 <script> 523 524 document.addEventListener("DOMContentLoaded", function () { 525 526 var addToCartButtons = document.querySelectorAll('.product__button-js'); 527 var unitTypes = document.getElementsByClassName("unit-type"); 528 var product_names = document.getElementsByClassName("product-list-item__title"); 529 530 addToCartButtons.forEach(function (item) { 531 532 item.addEventListener('click', function () { 533 534 var product_name = product_names[this.dataset.id - 1].dataset.name; 535 var product_id = product_names[this.dataset.id - 1].dataset.product_id; 536 var initialCategories = product_names[this.dataset.id - 1].dataset.categories; 537 var categories = initialCategories.split("_"); 538 var quantity = document.getElementsByClassName("quantity")[this.dataset.id - 1].value; 539 var unitPrice; 540 var variantIsNull = true; 541 var unitTypeName = null; 542 543 //Get unittype price 544 for (const unitType of unitTypes) { 545 546 if (unitType.dataset.id == this.dataset.id) { 547 548 if (unitType.checked == true) { 549 550 var tempPrice = unitType.dataset.price.replace(".", ""); 551 tempPrice = tempPrice.replace(",", "."); 552 unitPrice = parseFloat(tempPrice); 553 variantIsNull = false; 554 unitTypeName = unitType.value; 555 556 unitTypeName = unitTypeName.toString().split("_")[1]; 557 558 } 559 } 560 } 561 562 if (!variantIsNull) { 563 dataLayer.push({ ecommerce: null }); 564 dataLayer.push({ 565 'event': 'add_to_cart', 566 "ecommerce": { 567 "currency": "DKK", 568 "value": unitPrice * parseInt(quantity).toFixed(2), 569 "items": [ 570 { 571 "item_name": product_name + " - " + unitTypeName, 572 'item_id': product_id, 573 'price': unitPrice, 574 "item_brand": "", 575 "item_category": (categories[0] != null ? categories[0] : ""), 576 "item_category2": (categories[1] != null ? categories[1] : ""), 577 "item_category3": (categories[2] != null ? categories[2] : ""), 578 "item_category4": (categories[3] != null ? categories[3] : ""), 579 "item_category5": (categories[4] != null ? categories[4] : ""), 580 "quantity": parseInt(quantity), 581 } 582 ] 583 }, 584 585 }); 586 } 587 }); 588 }) 589 }); 590 591 </script> 592 @using Dynamicweb.Security.UserManagement 593 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 594 595 @helper GetProductListRelated(dynamic Loop, int ColMD = 3, int ColSM = 3, int ColXS = 1) 596 { 597 var currentUser = User.GetCurrentExtranetUser(); 598 599 <ul class="shop-list__list"> 600 601 @foreach (LoopItem product in Loop) 602 { 603 604 string GroupLink = "/Default.aspx?ID=" + product.GetString("Ecom:Product.PrimaryOrCurrentPageID") + "&groupid=" + product.GetString("Ecom:Product.PrimaryOrFirstGroupID") + "&productid=" + product.GetString("Ecom:Product.ID"); 605 string Name = product.GetString("Ecom:Product.Name"); 606 string Description = product.GetString("Ecom:Product.ShortDescription"); 607 string prodID = product.GetString("Ecom:Product.ID"); 608 string Image = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Products/" + prodID + ".jpg&format=webp&quality=85"; 609 610 <li class="shop-list__item col-xs-12"> 611 <a class="shop-list__link" href="@GroupLink" title="@Name"> 612 <figure class="shop-list__figure"> 613 <img class="shop-list__image lazy" style="width: 213px;" alt="@Name" data-src="@Image&width=213"> 614 </figure> 615 <h4 class="shop-list__title">@Name</h4> 616 <div class="shop-list__text"> 617 @if (String.IsNullOrEmpty(product.GetString("Ecom:Product.LongDescription"))) 618 { 619 var gr = Dynamicweb.Ecommerce.Products.Group.GetGroupById(product.GetString("Ecom:Product.PrimaryOrFirstGroupID")); 620 621 @gr.Description.Replace("<a ", "<span ").Replace("</a>", "</span>").Replace("<h2>", "<p>").Replace("</h2>", "</p>") 622 } 623 else 624 { 625 @product.GetString("Ecom:Product.LongDescription").Replace("<a ", "<span ").Replace("</a>", "</span>").Replace("<h2>", "<p>").Replace("</h2>", "</p>") 626 } 627 628 </div> 629 <div class="shop-list__price"> 630 @if (currentUser != null) 631 { 632 string baseUnitPrice = String.Empty; 633 var price = product.GetLoop("CustomPriceLoop").OrderBy(x => x.GetDouble("Ecom:Product.Prices.BaseUnitPrice")).FirstOrDefault(); 634 if (price != null) 635 { 636 baseUnitPrice = price.GetString("Ecom:Product.Prices.BaseUnitPrice"); 637 } 638 <span class="shop-list__price-from">Priser fra</span> 639 <span class="shop-list__price-value">@baseUnitPrice</span> 640 <span class="shop-list__price-unit">kr./stk.</span> 641 } 642 </div> 643 </a> 644 </li> 645 646 } 647 </ul> 648 } 649 650 651 @using Dynamicweb.Security.UserManagement 652 @using System.Text.RegularExpressions; 653 @using Hounisen.Website.Helpers 654 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 655 656 @helper GetProductListFullRelated(dynamic Loop, int ColMD = 4, int ColSM = 4, int ColXS = 1) 657 { 658 int Count = 0; 659 660 string ColumnsMD = WrapMethods.ColumnMaker(ColMD, "md"); 661 string ColumnsSM = WrapMethods.ColumnMaker(ColSM, "sm"); 662 string ColumnsXS = WrapMethods.ColumnMaker(ColXS, "xs"); 663 var currentUser = User.GetCurrentExtranetUser(); 664 665 foreach (LoopItem product in Loop) 666 { 667 string GroupLink = "/Default.aspx?ID=" + product.GetString("Ecom:Product.PrimaryOrCurrentPageID") + "&groupid=" + product.GetString("Ecom:Product.PrimaryOrFirstGroupID") + "&productid=" + product.GetString("Ecom:Product.ID"); 668 string Name = product.GetString("Ecom:Product.Name"); 669 string Description = GetFormattedDescription(product.GetString("Ecom:Product.LongDescription")); 670 string prodID = product.GetString("Ecom:Product.ID"); 671 string Image = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Products/" + prodID + ".jpg&format=webp&quality=85"; 672 var productObject = Dynamicweb.Ecommerce.Products.Product.GetProductById(product.GetString("Ecom:Product.ID"), product.GetString("Ecom:Product.VariantID"), product.GetString("Ecom:Product.LanguageID")); 673 var textAndColors = new ProductImageOverlayHelper().GetTextAndColor(productObject); 674 <li class="shop-list__item col-xs-12"> 675 <a class="shop-list__link" href="@GroupLink" title="@Name"> 676 <figure class="shop-list__figure" style="position: relative"> 677 <img class="shop-list__image lazy" style="width: 213px;" alt="@Name" data-src="@Image&width=213"> 678 <div class="product__primary-image__overlay-container"> 679 @foreach (var overlayNameAndColor in textAndColors) 680 { 681 <div style="background-color: @overlayNameAndColor.Value" class="product__primary-image__overlay-container__item"> 682 @overlayNameAndColor.Key 683 </div> 684 } 685 </div> 686 </figure> 687 <h4 class="shop-list__title">@Name</h4> 688 <div class="shop-list__text">@Description</div> 689 <div class="shop-list__price"> 690 @if (currentUser != null) 691 { 692 string baseUnitPrice = String.Empty; 693 var price = product.GetLoop("CustomPriceLoop").OrderBy(x => x.GetDouble("Ecom:Product.Prices.BaseUnitPrice")).FirstOrDefault(); 694 if (price != null) 695 { 696 baseUnitPrice = price.GetString("Ecom:Product.Prices.BaseUnitPrice"); 697 } 698 699 <span class="shop-list__price-from">Priser fra</span> 700 <span class="shop-list__price-value">@baseUnitPrice</span> 701 <span class="shop-list__price-unit">kr./stk</span> 702 } 703 </div> 704 </a> 705 </li> 706 } 707 } 708 709 @functions { 710 private static string GetFormattedDescription(string description = "") 711 { 712 string formattedDescription = Regex.Replace(description, @"<.*?>|[\r\n\t]|&nbsp;", " "); 713 return formattedDescription.Length > 103 ? formattedDescription.Substring(0, 103).TrimEnd() + "..." : formattedDescription; 714 } 715 } 716 717 <script src="/Files/Templates/Designs/DwSimple/js/ProductVariantsAjax.js"></script> 718 <link rel="stylesheet" href="/Files/Templates/Designs/DwSimple/css/product.css" /> 719 720 @{ 721 IFormatProvider jsNumberFormat = new NumberFormatInfo() { NumberDecimalSeparator = ".", NumberGroupSeparator = "" }; 722 string initialPrice = string.Empty; 723 string metaDescription = string.Empty; 724 if (String.IsNullOrEmpty(GetString("Ecom:Product.LongDescription"))) 725 { 726 metaDescription = StripHTML(GetString("Ecom:Group.Description")); 727 } 728 else 729 { 730 metaDescription = StripHTML(GetString("Ecom:Product.LongDescription")); 731 } 732 733 bool isMobile = Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Mobile; 734 735 //prices and units 736 string defaultUnitId = GetString("Ecom:Product.DefaultUnitID"); 737 var prices = GetLoop("CustomPriceLoop"); 738 739 List<string> pricesHtmlList = Hounisen.Website.Helpers.Helpers.PricesListMakeListString(prices, false); 740 List<Hounisen.Website.Models.Unit> unitsDropdown = Hounisen.Website.Helpers.Helpers.PopulateUnitsDropdown(prices); 741 var unitDefault = unitsDropdown.Where(x => x.Id.Equals(defaultUnitId)).FirstOrDefault(); 742 int minOrder = unitDefault != null ? unitDefault.MinOrder : 1; 743 744 //other vars 745 var pid = GetString("Ecom:Product.ID"); 746 int stock = GetInteger("Ecom:Product.Stock"); 747 string image = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Products/" + pid + ".jpg"; 748 string currenthost = HttpContext.Current.Request.Url.AbsoluteUri; 749 Uri uri = new Uri(currenthost); 750 currenthost = string.Format("{0}://{1}", uri.Scheme, uri.Authority); 751 string ImagePathloop = ""; 752 string ImagePathClean = ""; 753 string options = "format=webp&quality=85&width=1000"; 754 string manufacturerName = GetString("Ecom:Manufacturer.Name"); 755 string manufacturerUrl = GetString("Ecom:Manufacturer.Web"); 756 757 var resizedImageLarge = VestjyskMarketing.Helpers.ImageHelper.ResizeImage(new ResizeImageSettings() 758 { 759 Image = $"/Files/Images/Ecom/Products/{pid}.jpg", 760 Width = 1000, 761 Quality = 99 762 }); 763 764 var resizedImage = VestjyskMarketing.Helpers.ImageHelper.ResizeImage(new ResizeImageSettings() 765 { 766 Image = $"/Files/Images/Ecom/Products/{pid}.jpg", 767 Width = 458, 768 Quality = 85 769 }); 770 771 772 string productname = GetString("Ecom:Product.Name"); 773 string productlink = "/Default.aspx?ID=" + GetString("Ecom:Product.PrimaryOrCurrentPageID") + "&groupid=" + GetString("Ecom:Product.PrimaryOrFirstGroupID") + "&productid=" + GetString("Ecom:Product.ID"); 774 775 776 // SKI AND Region H 777 bool hasSkiDeal = false; 778 bool hasRegionHDeal = false; 779 bool activateLimitedProducts = false; 780 string limitedProductNumbers = ""; 781 var currentUser = User.GetCurrentExtranetUser(); 782 if (currentUser != null) 783 { 784 var skiGroup = currentUser.Groups.FirstOrDefault(e => e.Name == "SKI"); 785 if (skiGroup != null && !string.IsNullOrEmpty(skiGroup.Name)) 786 { 787 hasSkiDeal = true; 788 } 789 790 hasRegionHDeal = Dynamicweb.Core.Converter.ToBoolean(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_RegionH").Value); 791 activateLimitedProducts = Dynamicweb.Core.Converter.ToBoolean(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_ActivateLimitedProducts").Value); 792 limitedProductNumbers = Dynamicweb.Core.Converter.ToString(currentUser.CustomFieldValues.Find(f => f.CustomField.SystemName == "AccessUser_LimitedProductNumbers").Value); 793 } 794 795 List<string> limitedProductNumbersList = new List<string>(); 796 if (!String.IsNullOrWhiteSpace(limitedProductNumbers) && activateLimitedProducts) 797 { 798 limitedProductNumbersList = limitedProductNumbers.Split(',').ToList(); 799 } 800 801 // should the button for adding to cart be disabled and show different text 802 bool showLimitedProduct = false; 803 if (activateLimitedProducts) 804 { 805 showLimitedProduct = true; 806 if (limitedProductNumbersList.Count > 0 && limitedProductNumbersList.Contains(GetString("Ecom:Product.Number"))) 807 { 808 showLimitedProduct = false; 809 } 810 } 811 812 //serialized data 813 var pagedata = new System.Collections.Generic.Dictionary<String, Object>(); 814 Pageview.Area.Item.SerializeTo(pagedata); 815 816 817 var pageService = new PageService(); 818 GroupHelper gh = new GroupHelper(); 819 var group = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Product.PrimaryOrFirstGroupID")); 820 var masterPage = pageService.GetPage(GetInteger("Ecom:Product.PrimaryOrCurrentPageID")); 821 masterPage.GetDisplayName(); 822 var groupNames = gh.GetParentsRecursively(group, new List<string>()); 823 groupNames = gh.Format(groupNames, masterPage.GetDisplayName(), group); 824 825 var groups = gh.ListToString(groupNames); 826 827 828 829 // ---- JSON-LD Product Schema ---- 830 831 string schemaName = GetString("Ecom:Product:Field.SchemaName"); 832 if (string.IsNullOrWhiteSpace(schemaName)) 833 { 834 schemaName = productname; 835 } 836 837 // FIX #3: normalize whitespace / strip &nbsp; (\u00A0) and \r\n from the product description, 838 // same treatment as groupDescription below. 839 string schemaDescription = System.Text.RegularExpressions.Regex.Replace( 840 metaDescription.Replace("&nbsp;", " ").Replace('\u00A0', ' '), 841 @"\s+", " " 842 ).Trim(); 843 844 string schemaAvailability = stock > 0 845 ? "https://schema.org/InStock" 846 : "https://schema.org/OutOfStock"; 847 string productEan = GetString("Ecom:Product.EAN"); 848 string firstEan = string.Empty; 849 if (!string.IsNullOrWhiteSpace(productEan)) 850 { 851 var separators = new[] { ';', ',' }; 852 firstEan = productEan.Split(separators, StringSplitOptions.RemoveEmptyEntries) 853 .FirstOrDefault()?.Trim() ?? string.Empty; 854 } 855 856 // isVariantOf / group linking 857 string groupName = GetString("Ecom:Group.Name"); 858 string productUnspsc = GetString("Ecom:Product:Field.Unspsc"); 859 string productRawPath = GetString("Ecom:Product.Link.Clean"); 860 string productCanonicalPath = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(productRawPath); 861 string productSlug = productCanonicalPath.TrimEnd('/').Split('/').LastOrDefault() ?? ""; 862 string groupRawPath = GetString("Ecom:Product.LinkGroup.Clean"); 863 string groupCanonicalPath = string.IsNullOrWhiteSpace(groupRawPath) 864 ? "" 865 : Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(groupRawPath); 866 if (!string.IsNullOrWhiteSpace(groupCanonicalPath)) 867 { 868 string groupTrimmed = groupCanonicalPath.TrimEnd('/'); 869 int groupLastSlash = groupTrimmed.LastIndexOf('/'); 870 string groupLastSegment = groupLastSlash >= 0 ? groupTrimmed.Substring(groupLastSlash + 1) : groupTrimmed; 871 872 if (groupLastSegment.Equals(productSlug, StringComparison.OrdinalIgnoreCase) && groupLastSlash >= 0) 873 { 874 groupCanonicalPath = groupTrimmed.Substring(0, groupLastSlash); 875 } 876 } 877 878 string groupAbsoluteUrl = string.IsNullOrWhiteSpace(groupCanonicalPath) 879 ? "" 880 : currenthost.TrimEnd('/') + "/" + groupCanonicalPath.TrimStart('/'); 881 string groupId_ld = string.IsNullOrWhiteSpace(groupAbsoluteUrl) ? "" : groupAbsoluteUrl + "#productgroup"; 882 883 // productUrl = groupAbsoluteUrl (full category chain) + "/" + productSlug 884 string productAbsoluteUrl = string.IsNullOrWhiteSpace(groupAbsoluteUrl) 885 ? currenthost.TrimEnd('/') + "/" + productCanonicalPath.TrimStart('/') 886 : groupAbsoluteUrl.TrimEnd('/') + "/" + productSlug; 887 string productId_ld = productAbsoluteUrl + "#product"; 888 string groupDescription = System.Text.RegularExpressions.Regex.Replace(StripHTML(GetString("Ecom:Group.Description")).Replace("&nbsp;", " ").Replace('\u00A0', ' '), @"\s+", " ").Trim(); 889 List<string> groupChainNames = groupNames 890 .Where(g => !string.IsNullOrWhiteSpace(g)) 891 .Where(g => !g.Equals("Forside", StringComparison.OrdinalIgnoreCase)) 892 .Where(g => !g.Equals(productname, StringComparison.OrdinalIgnoreCase)) 893 .ToList(); 894 895 // FIX #4: strip ® from each group name segment before joining into the category breadcrumb 896 string categoryBreadcrumb = string.Join(" > ", groupChainNames.Select(g => g.Replace("®", "").Trim())); 897 898 string productGroupIdSlug = groupAbsoluteUrl.Split('/').LastOrDefault().ToString(); 899 string rawVolume = ""; 900 for (int i = 1; i <= 7; i++) 901 { 902 string labelField = GetString("Ecom:Product:Field.Attribut" + i + "A"); 903 if (!string.IsNullOrWhiteSpace(labelField) && 904 labelField.Trim().Equals("Volumen", StringComparison.OrdinalIgnoreCase)) 905 { 906 rawVolume = GetString("Ecom:Product:Field.Attribut" + i + "B"); 907 break; 908 } 909 } 910 911 string volumeValue = rawVolume; 912 string volumeUnit = "ml"; 913 if (!string.IsNullOrWhiteSpace(rawVolume) && rawVolume.Contains(" ")) 914 { 915 var volumeParts = rawVolume.Split(' '); 916 volumeValue = volumeParts[0]; 917 volumeUnit = volumeParts.Length > 1 ? volumeParts[1] : "ml"; 918 } 919 string sizeDisplay = string.IsNullOrWhiteSpace(rawVolume) ? "" : (volumeValue + " " + volumeUnit); 920 921 // FIX #2: parse volumeValue ("1,2") into a numeric double (1.2) for the JSON-LD output. 922 // Falls back to the raw string if parsing fails, so no data is silently dropped. 923 double? volumeValueNumeric = null; 924 if (!string.IsNullOrWhiteSpace(volumeValue)) 925 { 926 string normalizedVolume = volumeValue.Replace(",", "."); 927 if (double.TryParse(normalizedVolume, NumberStyles.Any, CultureInfo.InvariantCulture, out double parsedVolume)) 928 { 929 volumeValueNumeric = parsedVolume; 930 } 931 } 932 933 string color = GetString("Ecom:Product:Field.Attribut3A").Trim().Split(' ')[0].ToLower() == "farvekode" 934 ? GetString("Ecom:Product:Field.Attribut3B").Trim() 935 : string.Empty; 936 var additionalProps = new List<object> 937 { 938 new Dictionary<string, object> 939 { 940 { "@type", "PropertyValue" }, 941 { "name", "UNSPSC" }, 942 { "value", productUnspsc } 943 }, 944 new Dictionary<string, object> 945 { 946 { "@type", "PropertyValue" }, 947 { "name", "Volumen" }, 948 { "value", volumeValueNumeric.HasValue ? (object)volumeValueNumeric.Value : volumeValue }, 949 { "unitText", volumeUnit } 950 }, 951 new Dictionary<string, object> 952 { 953 { "@type", "PropertyValue" }, 954 { "name", GetString("Ecom:Product:Field.Attribut2A") }, 955 { "value", GetString("Ecom:Product:Field.Attribut2B") } 956 }, 957 new Dictionary<string, object> 958 { 959 { "@type", "PropertyValue" }, 960 { "name", GetString("Ecom:Product:Field.Attribut3A") }, 961 { "value", GetString("Ecom:Product:Field.Attribut3B") } 962 }, 963 new Dictionary<string, object> 964 { 965 { "@type", "PropertyValue" }, 966 { "name", GetString("Ecom:Product:Field.Attribut4A") }, 967 { "value", GetString("Ecom:Product:Field.Attribut4B") } 968 }, 969 new Dictionary<string, object> 970 { 971 { "@type", "PropertyValue" }, 972 { "name", GetString("Ecom:Product:Field.Attribut5A") }, 973 { "value", GetString("Ecom:Product:Field.Attribut5B") } 974 }, 975 new Dictionary<string, object> 976 { 977 { "@type", "PropertyValue" }, 978 { "name", GetString("Ecom:Product:Field.Attribut6A") }, 979 { "value", GetString("Ecom:Product:Field.Attribut6B") } 980 } 981 }; 982 983 additionalProps = additionalProps 984 .Cast<Dictionary<string, object>>() 985 .Where(p => 986 { 987 bool nameOk = p.TryGetValue("name", out var nameVal) && 988 !string.IsNullOrWhiteSpace(nameVal as string); 989 990 bool valueOk = p.TryGetValue("value", out var valVal) && 991 valVal != null && 992 (!(valVal is string) || !string.IsNullOrWhiteSpace((string)valVal)); 993 994 return nameOk && valueOk; 995 }) 996 .Cast<object>() 997 .ToList(); 998 999 // ---- ProductGroup node ---- 1000 var productGroupNode = new Dictionary<string, object> 1001 { 1002 { "@id", groupId_ld }, 1003 { "@type", "ProductGroup" }, 1004 { "brand", new Dictionary<string, object> 1005 { 1006 { "@type", "Brand" }, 1007 { "name", manufacturerName } 1008 } 1009 }, 1010 { "category", categoryBreadcrumb }, 1011 { "description", groupDescription }, 1012 { "hasVariant", new List<Dictionary<string, object>> 1013 { 1014 new Dictionary<string, object> 1015 { 1016 { "@id", productId_ld } 1017 } 1018 } 1019 }, 1020 { "manufacturer", new Dictionary<string, object> 1021 { 1022 { "@type", "Organization" }, 1023 { "name", manufacturerName } 1024 } 1025 }, 1026 { "name", groupName }, 1027 { "productGroupID", productGroupIdSlug }, 1028 { "url", groupAbsoluteUrl }, 1029 { "variesBy", new List<string> { "https://schema.org/size", "https://schema.org/color" } }, 1030 }; 1031 1032 // ---- Product node ---- 1033 var productNode = new Dictionary<string, object> 1034 { 1035 { "@id", productId_ld }, 1036 { "@type", "Product" }, 1037 { "additionalProperty", additionalProps }, 1038 { "brand", new Dictionary<string, object> 1039 { 1040 { "@type", "Brand" }, 1041 { "name", manufacturerName } 1042 } 1043 }, 1044 { "category", categoryBreadcrumb }, 1045 { "color", color }, 1046 { "description", schemaDescription }, 1047 { "gtin13", firstEan }, 1048 // FIX #1: galleryImages didn't exist — use resizedImageLarge wrapped in a List<string> 1049 // so the JSON-LD "image" field serializes as an array, not a bare string. 1050 { "image", new List<string> { resizedImageLarge } }, 1051 { "isVariantOf", new Dictionary<string, object> 1052 { 1053 { "@id", groupId_ld } 1054 } 1055 }, 1056 { "manufacturer", new Dictionary<string, object> 1057 { 1058 { "@type", "Organization" }, 1059 { "name", manufacturerName } 1060 } 1061 }, 1062 { "mpn", GetString("Ecom:Product.Number") }, 1063 { "name", schemaName }, 1064 { "size", sizeDisplay }, 1065 { "sku", GetString("Ecom:Product.Number") }, 1066 { "url", productAbsoluteUrl }, 1067 }; 1068 1069 // ---- Combine into one @graph ---- 1070 var graph = new Dictionary<string, object> 1071 { 1072 { "@context", "https://schema.org" }, 1073 { "@graph", new List<object> { productGroupNode, productNode } } 1074 }; 1075 1076 string productSchemaJson = Newtonsoft.Json.JsonConvert.SerializeObject(graph, Newtonsoft.Json.Formatting.None); 1077 } 1078 1079 <div class="col-md-12 col-sm-12 col-xs-12" style="margin-bottom: 25px"> 1080 1081 <div class="row product" itemscope itemtype="http://schema.org/Product"> 1082 <section class="page-header" style="margin-top: 15px"> 1083 <div class="product__help-name-container"> 1084 <div class="col-xs-12 col-sm-7 p-0"> 1085 <meta itemprop="description" content="@metaDescription" /> 1086 <h1 data-categories="@groups" data-group="@GetString("Ecom:Group.Name")" class="page-header__title" itemprop="name">@GetString("Ecom:Product.Name")</h1> 1087 <div class="page-header__short-description"> 1088 <p class="product__number">@Translate("Productnumber", "Productnumber"): <span property="identifier" itemprop="mpn">@GetString("Ecom:Product.Number")</span></p> 1089 @if (hasSkiDeal) 1090 { 1091 <p class="product__number">@Translate("Skinumber", "SKI nr"): <span property="identifier">@GetString("Ecom:Product:Field.SkiId")</span></p> 1092 } 1093 @if (hasRegionHDeal && GetBoolean("Ecom:Product:Field.RegionH")) 1094 { 1095 <p class="product__number">@Translate("RegionHText")</p> 1096 } 1097 1098 </div> 1099 </div> 1100 1101 1102 <div class="d-none d-lg-block product__help-name-container__help p-0"> 1103 <div class="need-help product-view"> 1104 <img class="need-help__image lazy" width="81" height="81" data-src='@(Pageview.Area.Item["Support_Image"].ToString())?format=webp&width=81&quality=75' /> 1105 <div class="need-help__text product-view"> 1106 @Pageview.Area.Item["Support_Content"].ToString() 1107 </div> 1108 </div> 1109 </div> 1110 1111 1112 </div> 1113 </section> 1114 <div class="row"> 1115 1116 @* The image area *@ 1117 <div class="col-md-6 col-sm-4 col-xs-12 product__column"> 1118 <div class="product__gallery"> 1119 1120 @* Discount sticker *@ 1121 1122 @if (GetString("Ecom:Product.Discount.Price.PriceWithVATFormatted") != GetString("Ecom:Product.Price.PriceWithVATFormatted")) 1123 { 1124 if (pagedata["EcommerceStickerType"].ToString() == "ribbon") 1125 { 1126 <span class="ribbon base">@Translate("On sale!", "On sale!")</span> 1127 } 1128 1129 if (pagedata["EcommerceStickerType"].ToString() == "ball") 1130 { 1131 <span class="ball">@Translate("On sale!", "On sale!")</span> 1132 } 1133 } 1134 1135 1136 @* Product images *@ 1137 @{ 1138 1139 List<string> imagepath = new List<string>(); 1140 1141 1142 //i starts at 1 - avoids showing the first image twice 1143 for (int i = 1; i <= 5; i++) 1144 { 1145 ImagePathloop = "/Admin/Public/GetImage.ashx?Image=/Files/Images/Ecom/Products/" + pid + "-0" + i.ToString() + ".jpg"; 1146 //currenthost + 1147 ImagePathClean = "/Files/Images/Ecom/Products/" + pid + "-0" + i.ToString() + ".jpg"; 1148 1149 try 1150 { 1151 if (File.Exists(HttpContext.Current.Server.MapPath(ImagePathClean))) 1152 { 1153 imagepath.Add(ImagePathloop + "&" + options); 1154 } 1155 1156 HttpWebRequest request = (HttpWebRequest)System.Net.WebRequest.Create(ImagePathloop); 1157 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 1158 { 1159 if (response.StatusCode == HttpStatusCode.NotFound) 1160 { 1161 break; 1162 } 1163 else 1164 { 1165 imagepath.Add("/Files/Images/Ecom/Products/" + pid + "-0" + i.ToString() + ".jpg" + "" + options); 1166 } 1167 } 1168 } 1169 catch (Exception ex) 1170 { 1171 } 1172 } 1173 } 1174 1175 1176 <div class="product__primary-image"> 1177 <a href="@resizedImageLarge" data-fancybox="product-gallery" class="fancybox"> 1178 <img src="@resizedImage" data-src="@resizedImage" width="458" alt="@productname" class="product__image img-responsive lazy" itemprop="image"> 1179 @{ 1180 var productObj = new ProductService().GetProductById(GetString("Ecom:Product.ID"), GetString("Ecom:Product.VariantID"), GetString("Ecom:Product.LanguageID")); 1181 var overlayNameAndColors = new ProductImageOverlayHelper().GetTextAndColor(productObj); 1182 } 1183 1184 <div class="product__primary-image__overlay-container"> 1185 @foreach (var overlayNameAndColor in overlayNameAndColors) 1186 { 1187 <div style="background-color: @overlayNameAndColor.Value" class="product__primary-image__overlay-container__item"> 1188 @overlayNameAndColor.Key 1189 </div> 1190 } 1191 </div> 1192 <svg class="product__image-icon"> 1193 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#fullscreen"></use> 1194 </svg> 1195 </a> 1196 </div> 1197 <div class="row col-xs-12"> 1198 <div class="product__thumbnail-list"> 1199 @foreach (var imageThumb in imagepath) 1200 { 1201 <div class="col-sm-3 col-xs-4"> 1202 <div class="product__thumbnail-images"> 1203 <a href="@imageThumb&width=1000" data-fancybox="product-gallery" rel="product" class="fancybox"> 1204 <img data-src="@imageThumb&width=458" width="458" alt="@productname" class="img-responsive lazy"> 1205 </a> 1206 </div> 1207 </div> 1208 } 1209 @{ 1210 var youtubeVideos = productObj.Details.Where(x => "YoutubeVideos" == new DetailsGroupService().GetById(x.GroupId).SystemName).ToList(); 1211 var productVideos = productObj.Details.Where(x => "ProductVideos" == new DetailsGroupService().GetById(x.GroupId).SystemName).ToList(); 1212 1213 if (youtubeVideos.Count > 0) 1214 { 1215 foreach (var youtubeVideo in youtubeVideos) 1216 { 1217 string thumbnail = youtubeVideo.Value; 1218 string youtubeId = youtubeVideo.Keywords; 1219 1220 // For Fancybox, we typically include “?autoplay=1” in the iframe URL. 1221 var youtubeEmbedUrl = $"https://www.youtube.com/embed/{youtubeId}?autoplay=1&enablejsapi=1"; 1222 1223 <div class="col-sm-3 col-xs-4"> 1224 <div class="product__thumbnail-images"> 1225 <a style="" href="@youtubeEmbedUrl" 1226 data-fancybox="product-gallery" 1227 data-type="iframe" 1228 rel="product" 1229 class="fancybox"> 1230 1231 <div class="product__play-container"> 1232 <img alt="Video thumbnail" src="@thumbnail" /> 1233 <svg class="product__play-container__play-icon" style="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> 1234 <path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM188.3 147.1c7.6-4.2 16.8-4.1 24.3 .5l144 88c7.1 4.4 11.5 12.1 11.5 20.5s-4.4 16.1-11.5 20.5l-144 88c-7.4 4.5-16.7 4.7-24.3 .5s-12.3-12.2-12.3-20.9l0-176c0-8.7 4.7-16.7 12.3-20.9z" /> 1235 </svg> 1236 </div> 1237 </a> 1238 </div> 1239 </div> 1240 } 1241 } 1242 else 1243 { 1244 var youtubeId = GetString("Ecom:Product:Field.YoutubeId"); 1245 // For Fancybox, we typically include “?autoplay=1” in the iframe URL. 1246 var youtubeEmbedUrl = $"https://www.youtube.com/embed/{youtubeId}?autoplay=1&enablejsapi=1"; 1247 1248 if (!string.IsNullOrEmpty(youtubeId)) 1249 { 1250 <div class="col-sm-3 col-xs-4"> 1251 <div class="product__thumbnail-images"> 1252 <a style="height: 63px" href="@youtubeEmbedUrl" 1253 data-fancybox="product-gallery" 1254 data-type="iframe" 1255 rel="product" 1256 class="fancybox"> 1257 <svg class="product__play-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> 1258 <path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM188.3 147.1c7.6-4.2 16.8-4.1 24.3 .5l144 88c7.1 4.4 11.5 12.1 11.5 20.5s-4.4 16.1-11.5 20.5l-144 88c-7.4 4.5-16.7 4.7-24.3 .5s-12.3-12.2-12.3-20.9l0-176c0-8.7 4.7-16.7 12.3-20.9z" /> 1259 </svg> 1260 </a> 1261 </div> 1262 </div> 1263 } 1264 } 1265 1266 if (productVideos.Count > 0) 1267 { 1268 foreach (var productVideo in productVideos) 1269 { 1270 string thumbnail = productVideo.Value; 1271 string mp4Path = productVideo.Keywords; 1272 1273 1274 <div class="col-sm-3 col-xs-4"> 1275 <div class="product__thumbnail-images"> 1276 <!-- 1277 Link to a hidden <div> containing our <video>. 1278 We use data-fancybox="product-gallery" to group it with our images. 1279 data-src="#mp4VideoContent" + data-type="inline" to open inline HTML content. 1280 --> 1281 <a style="" data-fancybox="product-gallery" 1282 data-src="#mp4VideoContent" 1283 data-type="inline" 1284 href="javascript:;"> 1285 <div class="product__play-container"> 1286 <img alt="Video thumbnail" src="@thumbnail" /> 1287 <svg class="product__play-container__play-icon" style="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> 1288 <path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM188.3 147.1c7.6-4.2 16.8-4.1 24.3 .5l144 88c7.1 4.4 11.5 12.1 11.5 20.5s-4.4 16.1-11.5 20.5l-144 88c-7.4 4.5-16.7 4.7-24.3 .5s-12.3-12.2-12.3-20.9l0-176c0-8.7 4.7-16.7 12.3-20.9z" /> 1289 </svg> 1290 </div> 1291 </a> 1292 </div> 1293 </div> 1294 1295 <!-- Hidden HTML content that Fancybox will open --> 1296 <div style="display:none;" id="mp4VideoContent"> 1297 <video controls style="width:100%; height:auto;"> 1298 <source src="@mp4Path" type="video/mp4" /> 1299 <!-- You can add WebM or OGG sources if you like for broader browser support --> 1300 Your browser does not support the video tag. 1301 </video> 1302 </div> 1303 } 1304 } 1305 else 1306 { 1307 string mp4Path = GetString("Ecom:Product:Field.ProductVideo.FullPath"); 1308 1309 if (!string.IsNullOrEmpty(mp4Path)) 1310 { 1311 <div class="col-sm-3 col-xs-4"> 1312 <div class="product__thumbnail-images"> 1313 <!-- 1314 Link to a hidden <div> containing our <video>. 1315 We use data-fancybox="product-gallery" to group it with our images. 1316 data-src="#mp4VideoContent" + data-type="inline" to open inline HTML content. 1317 --> 1318 <a style="height: 63px" data-fancybox="product-gallery" 1319 data-src="#mp4VideoContent" 1320 data-type="inline" 1321 href="javascript:;"> 1322 <svg class="product__play-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> 1323 <path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zM188.3 147.1c7.6-4.2 16.8-4.1 24.3 .5l144 88c7.1 4.4 11.5 12.1 11.5 20.5s-4.4 16.1-11.5 20.5l-144 88c-7.4 4.5-16.7 4.7-24.3 .5s-12.3-12.2-12.3-20.9l0-176c0-8.7 4.7-16.7 12.3-20.9z" /> 1324 </svg> 1325 </a> 1326 </div> 1327 </div> 1328 1329 <!-- Hidden HTML content that Fancybox will open --> 1330 <div style="display:none;" id="mp4VideoContent"> 1331 <video controls style="width:100%; height:auto;"> 1332 <source src="@mp4Path" type="video/mp4" /> 1333 <!-- You can add WebM or OGG sources if you like for broader browser support --> 1334 Your browser does not support the video tag. 1335 </video> 1336 </div> 1337 } 1338 } 1339 } 1340 </div> 1341 </div> 1342 </div> 1343 <section class="d-block d-lg-none page-header m-0 p-0" style="border: none"> 1344 <div class="product__help-name-container" style="width: 100%"> 1345 <div class="product__help-name-container__help second"> 1346 <div class="need-help product-view"> 1347 <img class="need-help__image lazy" width="81" height="81" data-src='@(Pageview.Area.Item["Support_Image"].ToString())?format=webp&width=81&quality=75' /> 1348 <div class="need-help__text product-view second"> 1349 @Pageview.Area.Item["Support_Content"].ToString() 1350 </div> 1351 </div> 1352 </div> 1353 </div> 1354 </section> 1355 </div> 1356 1357 1358 @* The main product information area *@ 1359 1360 <div class="col-md-6 col-sm-8 col-xs-12 product__column"> 1361 <div class="product-info js-product-info" id="productinfo"> 1362 <div class="product__control"> 1363 <div class="row"> 1364 <div class="col-xs-12 col-sm-6"> 1365 @* LAGERBEHOLDNING *@ 1366 <div class="product__stock"> 1367 @{ 1368 if (stock > 0) 1369 { 1370 <p class="product__stock-text"> 1371 <span class="product__stock-circle product__stock-circle--green"></span> På lager 1372 </p> 1373 } 1374 else 1375 { 1376 <p class="product__stock-text"> 1377 @if (!String.IsNullOrEmpty(GetString("Ecom:Manufacturer.Description"))) 1378 { 1379 @GetString("Ecom:Manufacturer.Description") 1380 } 1381 else 1382 { 1383 @Translate("StockStandardSoldoutText", "Kontakt os for leveringstid: 86210800 eller salg@hounisen.com") 1384 } 1385 </p> 1386 } 1387 } 1388 </div> 1389 </div> 1390 <div class="col-xs-12 col-sm-6"> 1391 @* FAVORITTER *@ 1392 @if (Dynamicweb.Core.Converter.ToBoolean(GetGlobalValue("Global:Extranet.UserName"))) 1393 { 1394 if (hasRegionHDeal == false) 1395 { 1396 <div class="product__favorite-list popup-wrap favorite"> 1397 <button class="popup-show" data-popup="favorite-list" type="button" title="@Translate("Add to favorites", "Add to favorites")"> 1398 <i class="fa fa-star-o"></i> <span>@Translate("Add to favorites", "Add to favorites")</span> 1399 </button> 1400 <div id="favorite-list" class="popup-form"> 1401 <div class="close"> 1402 <i class="fa fa-times"></i> 1403 </div> 1404 <h3>@Translate("Choose list", "Choose list")</h3> 1405 <ul> 1406 @{ 1407 var lists = Dynamicweb.Ecommerce.CustomerCenter.CustomerProductList.GetListsByUserCustomerNumber(currentUser.CustomerNumber); 1408 } 1409 1410 @foreach (var list in lists) 1411 { 1412 if (list.Products.Where(p => p.ProductId.ToString() == GetString("Ecom:Product.ID")).Count() > 0) 1413 { 1414 <li> 1415 <a href="@(System.Web.HttpContext.Current.Request.Url)&CCRemoveFromMyLists=@GetString("Ecom:Product.ID")&CCAddToListVariantID=&CCAddToListLanguageID=LANG1&CCAddToListID=@list.ListId&CCListType="> 1416 <i class="fa fa-star"></i> @list.Name 1417 </a> 1418 </li> 1419 } 1420 else 1421 { 1422 <li> 1423 <a href="@(System.Web.HttpContext.Current.Request.Url)&CCAddToMyLists=@GetString("Ecom:Product.ID")&CCAddToListVariantID=&CCAddToListLanguageID=LANG1&CCAddToListID=@list.ListId&CCListType="> 1424 <i class="fa fa-star-o"></i> @list.Name 1425 </a> 1426 </li> 1427 } 1428 } 1429 <li> 1430 <a href="/mine-favoritter/opret-favoritliste?ProdID=@GetString("Ecom:Product.ID")"> 1431 <i class="fa fa-plus"></i>@Translate("Add new list", "Add new list") 1432 </a> 1433 </li> 1434 </ul> 1435 </div> 1436 </div> 1437 } 1438 } 1439 else 1440 { 1441 <div class="not-loggedin"> 1442 <p class="not-loggedin-text"><a data-toggle="modal" data-target="#login" href="">@Translate("Login", "Login")</a> eller <a href="/kontakt/opret-brugerprofil">Bliv kunde</a> for at se priser og købe på Hounisen.com</p> 1443 <a data-toggle="modal" data-target="#login" href="" class="btn btn-primary not-loggedin-button"> 1444 <span>Log ind</span> 1445 </a> 1446 </div> 1447 } 1448 </div> 1449 </div> 1450 1451 1452 @* Prices and actions *@ 1453 @if (Dynamicweb.Core.Converter.ToBoolean(GetGlobalValue("Global:Extranet.UserName"))) 1454 { 1455 var product = GetString("Ecom:Product.ID"); 1456 if (!string.IsNullOrWhiteSpace(GetString("Ecom:Product.SelectedVariantComboID"))) 1457 { 1458 product = GetString("Ecom:Product.ID") + "&" + GetString("Ecom:Product.SelectedVariantComboID"); 1459 } 1460 1461 <div class="row"> 1462 <div class="col-xs-12"> 1463 <ul class="product__prices"> 1464 @foreach (var priceHtml in pricesHtmlList) 1465 { 1466 <li> 1467 @priceHtml 1468 </li> 1469 } 1470 </ul> 1471 </div> 1472 </div> 1473 1474 <div class="row"> 1475 @if (showLimitedProduct) 1476 { 1477 <div class="col-xs-12"> 1478 <button class="btn btn--ghost product__button disabled"> 1479 @Translate("Kan bestilles på fritekstordre", "Kan bestilles på fritekstordre") 1480 </button> 1481 </div> 1482 } 1483 else 1484 { 1485 <div class="col-xs-7"> 1486 <div class="product__addtocart-input js-addtocart-input"> 1487 <input type="button" value="-" class="product__quantity-button product__quantity-button--minus qtyminus" field="quantity" /> 1488 <input type="number" class="product__quantity-input quantity" data-name="quantity" name="quantity" value="@minOrder" field="quantity" /> 1489 <input type="button" value="+" class="product__quantity-button product__quantity-button--plus qtyplus" field="quantity" /> 1490 </div> 1491 <div class="product__unit-selector" style="display: inline-block;"> 1492 1493 @{ 1494 var index = 0; 1495 } 1496 1497 @foreach (var unitDropdown in unitsDropdown) 1498 { 1499 index++; 1500 var currentUnitPrice = prices[index - 1].Values["Ecom:Product.Prices.Amount"]; 1501 <input data-price="@currentUnitPrice" class="unit-type" type="radio" id="@unitDropdown.Id" name="UnitID" value="@unitDropdown.Id" required data-lot-size="@unitDropdown.LotSize" data-min-order="@unitDropdown.MinOrder" @(unitsDropdown.Count == 1 ? "checked='checked'" : "")> 1502 <label for="@unitDropdown.Id">@unitDropdown.Name</label> 1503 } 1504 </div> 1505 <p class="product__unit-selector-error-message"> 1506 * Du mangler at vælge type 1507 </p> 1508 </div> 1509 <div class="col-xs-5"> 1510 <div class="product__addtocart-button"> 1511 <button type="submit" name="submit" onclick="AddToCart(event, '@product', $(this).parent().parent().prev().find('input.quantity').val(), $(this).parent().parent().prev().find('input[name=\'UnitID\']:checked').val());" class="btn btn-primary product__button"> 1512 @Translate("Add to cart", "Add to cart") 1513 <svg class="product__button-icon hidden-xs"> 1514 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#basket"></use> 1515 </svg> 1516 </button> 1517 </div> 1518 </div> 1519 } 1520 </div> 1521 } 1522 </div> 1523 1524 @* Product details *@ 1525 <div> 1526 @* IKONER *@ 1527 <div class="product__icons"> 1528 @{ 1529 foreach (var c in GetString("Ecom:Product:Field.Certificates").Split(',')) 1530 { 1531 if (!string.IsNullOrEmpty(c)) 1532 { 1533 <img class="product__icon lazy" data-src="/Files/Images/Ecom/certificates/@(c).jpg" title="@Translate("Cert_" + c, c)" /> 1534 } 1535 } 1536 } 1537 1538 1539 </div> 1540 1541 1542 </div> 1543 </div> 1544 </div> 1545 </div> 1546 </div> 1547 </div> 1548 1549 1550 @helper RenderDocuments() 1551 { 1552 var productObj = new ProductService().GetProductById(GetString("Ecom:Product.ID"), GetString("Ecom:Product.VariantID"), GetString("Ecom:Product.LanguageID")); 1553 var documents = productObj.Details.Where(x => "Documents" == new DetailsGroupService().GetById(x.GroupId).SystemName).ToList(); 1554 1555 bool hasAtLeastOneLink = 1556 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Link1Label")) || 1557 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Link2Label")) || 1558 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Link3Label")) || 1559 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Link4Label")) || 1560 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Link5Label")); 1561 bool atleastOneProductDetail = hasAtLeastOneLink || 1562 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.Guidance.Value.Clean")) || 1563 !string.IsNullOrEmpty(GetString("Ecom:Product:Field.FAQ.Value.Clean")) || documents.Count > 0; 1564 1565 1566 @* DOWNLOAD *@ 1567 if (hasAtLeastOneLink) 1568 { 1569 <div class="row"> 1570 <div class="col-xs-12"> 1571 <div onclick="updateDetailAccessibility(this)" class="product__section-container"> 1572 <h3 class="product__header">@Translate("Dokumenter")</h3> 1573 <div class="product__section-container__icon-wrapper" data-height="0px" data-content-id="documents"> 1574 <svg class="product__section-container__icon"> 1575 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#arrow-right"></use> 1576 </svg> 1577 </div> 1578 1579 </div> 1580 </div> 1581 <div class="col-xs-12"> 1582 <div style="display: block;"> 1583 <div class="product__details"> 1584 <div data-content-id="documents" class="product__details__content" style="max-height: 0px;"> 1585 1586 1587 @{ 1588 if (documents.Count > 0) 1589 { 1590 foreach (var document in documents) 1591 { 1592 <div> 1593 <a class="product__download-pdf" target="_blank" href="@document.Value" class="">@document.Keywords</a> 1594 </div> 1595 } 1596 } 1597 else 1598 { 1599 if (GetString("Ecom:Product:Field.Link1Label") != "") 1600 { 1601 <div> 1602 <a class="product__download-pdf" target="_blank" href="@GetString("Ecom:Product:Field.Link1File.Clean")" class="">@GetString("Ecom:Product:Field.Link1Label")</a> 1603 </div> 1604 } 1605 1606 if (GetString("Ecom:Product:Field.Link2Label") != "") 1607 { 1608 <div> 1609 <a class="product__download-pdf" target="_blank" href="@GetString("Ecom:Product:Field.Link2File.Clean")" class="">@GetString("Ecom:Product:Field.Link2Label")</a> 1610 </div> 1611 } 1612 1613 if (GetString("Ecom:Product:Field.Link3Label") != "") 1614 { 1615 <div> 1616 <a class="product__download-pdf" target="_blank" href="@GetString("Ecom:Product:Field.Link3File.Clean")" class="">@GetString("Ecom:Product:Field.Link3Label")</a> 1617 </div> 1618 } 1619 1620 if (GetString("Ecom:Product:Field.Link4Label") != "") 1621 { 1622 <div> 1623 <a class="product__download-pdf" target="_blank" href="@GetString("Ecom:Product:Field.Link4File.Clean")" class="">@GetString("Ecom:Product:Field.Link4Label")</a> 1624 </div> 1625 } 1626 1627 if (GetString("Ecom:Product:Field.Link5Label") != "") 1628 { 1629 <div> 1630 <a class="product__download-pdf" target="_blank" href="@GetString("Ecom:Product:Field.Link5File.Clean")" class="">@GetString("Ecom:Product:Field.Link5Label")</a> 1631 </div> 1632 } 1633 } 1634 } 1635 1636 </div> 1637 </div> 1638 </div> 1639 </div> 1640 </div> 1641 <hr class="product__hr" /> 1642 } 1643 } 1644 1645 @helper RenderGuidance() 1646 { 1647 if (!String.IsNullOrEmpty(GetString("Ecom:Product:Field.Guidance.Value.Clean"))) 1648 { 1649 <div class="row"> 1650 1651 <div class="col-xs-12"> 1652 1653 1654 <div onclick="updateDetailAccessibility(this)" class="product__section-container"> 1655 <h3 class="product__header">Vejledning</h3> 1656 1657 <div class="product__section-container__icon-wrapper" data-height="0px" data-content-id="Guidance"> 1658 <svg class="product__section-container__icon"> 1659 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#arrow-right"></use> 1660 </svg> 1661 </div> 1662 1663 </div> 1664 </div> 1665 <div class="col-xs-12 py-4"> 1666 <div style="display: block;"> 1667 <div class="product__details"> 1668 @{ 1669 var text = GetString("Ecom:Product:Field.Guidance.Value.Clean"); 1670 } 1671 1672 <div data-content-id="Guidance" class="product__details__content" style="max-height: 0px;"> 1673 @text 1674 </div> 1675 1676 </div> 1677 </div> 1678 </div> 1679 </div> 1680 1681 <hr class="product__hr" /> 1682 } 1683 } 1684 1685 @helper RenderFAQ() 1686 { 1687 if (!String.IsNullOrEmpty(GetString("Ecom:Product:Field.FAQ.Value.Clean"))) 1688 { 1689 <div class="row"> 1690 1691 <div class="col-xs-12"> 1692 1693 1694 <div onclick="updateDetailAccessibility(this)" class="product__section-container"> 1695 <h3 class="product__header">@Translate("FAQ")</h3> 1696 1697 <div class="product__section-container__icon-wrapper" data-height="0px" data-content-id="FAQ"> 1698 <svg class="product__section-container__icon"> 1699 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#arrow-right"></use> 1700 </svg> 1701 </div> 1702 1703 </div> 1704 </div> 1705 <div class="col-xs-12 py-4"> 1706 <div style="display: block;"> 1707 <div class="product__details"> 1708 @{ 1709 var text = GetString("Ecom:Product:Field.FAQ.Value.Clean"); 1710 } 1711 1712 <div data-content-id="FAQ" class="product__details__content" style="max-height: 0px;"> 1713 @text 1714 </div> 1715 1716 </div> 1717 </div> 1718 </div> 1719 </div> 1720 1721 <hr class="product__hr" /> 1722 } 1723 } 1724 1725 @helper RenderSpecifikationer() 1726 { 1727 string manufacturerName = GetString("Ecom:Manufacturer.Name"); 1728 string manufacturerUrl = GetString("Ecom:Manufacturer.Web"); 1729 <div class="row"> 1730 <div class="col-xs-12"> 1731 <hr class="product__hr" /> 1732 <div class="product__section-container product__section-container--no-hover"> 1733 <h3 class="product__header">Specifikationer</h3> 1734 </div> 1735 </div> 1736 <div class="col-xs-12 py-4"> 1737 <div style="display: block;"> 1738 <div class="product__details"> 1739 <div data-content-id="specifikationer" class="product__details__content" style="max-height: 9999px;"> 1740 @* ATTRIBUTTER *@ 1741 <div class="product__information"> 1742 1743 <dl class="product__information-table"> 1744 @{ 1745 int attributeLineCount = 0; 1746 for (int i = 1; i < 8; i++) 1747 { 1748 attributeLineCount++; 1749 string attributeName = "Ecom:Product:Field.Attribut" + attributeLineCount + "A"; 1750 string attributeValue = "Ecom:Product:Field.Attribut" + attributeLineCount + "B"; 1751 string attributeBrand = string.Empty; 1752 1753 if (GetString(attributeName) != "") 1754 { 1755 if (attributeLineCount == 7) 1756 { 1757 attributeBrand = "itemprop=\"brand\""; 1758 } 1759 1760 <dt>@GetString(attributeName) :</dt> 1761 <dd @attributeBrand>@GetString(attributeValue)</dd> 1762 } 1763 } 1764 1765 if (!manufacturerName.IsNullOrEmpty() && !manufacturerUrl.IsNullOrEmpty()) 1766 { 1767 <dt>Brand :</dt> 1768 <dd> 1769 <a target="_blank" class="product__information-table__manufacturer" href="@manufacturerUrl">@manufacturerName</a> 1770 </dd> 1771 } 1772 } 1773 1774 </dl> 1775 </div> 1776 1777 1778 </div> 1779 1780 </div> 1781 </div> 1782 </div> 1783 </div> 1784 } 1785 1786 @helper RenderDescription() 1787 { 1788 if (!String.IsNullOrEmpty(GetString("Ecom:Product.LongDescription"))) 1789 { 1790 var description = GetString("Ecom:Product.LongDescription"); 1791 1792 // Remove HTML tags 1793 string cleanedText = Regex.Replace(description, "<.*?>", " "); 1794 string[] words = cleanedText.Split(new[] { ' ', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries); 1795 bool readMoreButton = words.Length > 80; 1796 1797 <div class="row"> 1798 1799 <div class="col-xs-12"> 1800 <hr class="product__hr" /> 1801 <div onclick="updateDetailAccessibility(this)" class="product__section-container"> 1802 <h3 style="@(!readMoreButton ? "cursor: auto" : "") " id="productLongDescriptionID" class="product__header">@Translate("Description", "Description")</h3> 1803 1804 <div class="product__section-container__icon-wrapper" data-height="95px" data-content-id="description"> 1805 1806 @if (readMoreButton) 1807 { 1808 <svg class="product__section-container__icon"> 1809 <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/dist/icons/icons.svg#arrow-right"></use> 1810 </svg> 1811 } 1812 </div> 1813 </div> 1814 </div> 1815 <div class="col-xs-12 py-4"> 1816 <div style="display: block;"> 1817 <div class="product__details"> 1818 1819 @if (readMoreButton) 1820 { 1821 <div data-content-id="description" class="product__details__content" style="min-height: 95px;max-height: 95px;"> 1822 @description 1823 </div> 1824 } 1825 else 1826 { 1827 @description 1828 } 1829 </div> 1830 </div> 1831 </div> 1832 </div> 1833 } 1834 } 1835 1836 1837 <div class="row product__column__wrapper"> 1838 <div class="col-md-6 col-sm-12 col-xs-12 product__column"> 1839 @if (isMobile) 1840 { 1841 @RenderSpecifikationer() 1842 @RenderDescription() 1843 <hr class="product__hr" /> 1844 } 1845 else 1846 { 1847 @RenderDescription() 1848 <hr class="product__hr" /> 1849 @RenderGuidance() 1850 @RenderFAQ() 1851 } 1852 1853 </div> 1854 <div class="col-md-6 col-sm-12 col-xs-12 product__column"> 1855 @if (isMobile) 1856 { 1857 @RenderDocuments() 1858 @RenderGuidance() 1859 @RenderFAQ() 1860 } 1861 else 1862 { 1863 @RenderSpecifikationer() 1864 <hr class="product__hr" /> 1865 @RenderDocuments() 1866 } 1867 </div> 1868 </div> 1869 1870 1871 @* Related products *@ 1872 1873 @if (GetString("Ecom:Product.RelatedCount") != "0") 1874 { 1875 <div class="row"> 1876 <div class="col-xs-12 p-15 p-md-0 @(isMobile ? "related-products-slider" : "")"> 1877 <h2 class="product__header product__header--mobile-center">Relaterede varer</h2> 1878 <ul class="shop-list__list p-0-fc @(isMobile ? "owl-carousel" : "")"> 1879 @foreach (LoopItem relatedgroup in GetLoop("ProductRelatedGroups")) 1880 { 1881 var relatedproductloop = relatedgroup.GetLoop("RelatedProducts").OrderByDescending(g => g.GetString("Ecom:Product.LoopCounter")).Take(4).ToList(); 1882 @GetProductListFullRelated(relatedproductloop, 3, 3, 1) 1883 } 1884 </ul> 1885 </div> 1886 </div> 1887 } 1888 1889 1890 <!-- Udforsk mere --> 1891 1892 @functions 1893 { 1894 List<int> GetIds(string idsRaw) 1895 { 1896 List<int> ids = new List<int>(); 1897 if (idsRaw.IsNotNullOrEmpty()) 1898 { 1899 foreach (var articleId in idsRaw.Split(',')) 1900 { 1901 if (int.TryParse(articleId, out int id)) 1902 { 1903 ids.Add(id); 1904 } 1905 } 1906 } 1907 1908 return ids; 1909 } 1910 } 1911 1912 @{ 1913 var articlesPageId = GetPageIdByNavigationTag("ProductviewUniverseArticles"); 1914 bool hasActiveArticles = false; 1915 1916 var productId = Dynamicweb.Context.Current.Request.QueryString["productid"]; 1917 var defaultGroup = new ProductService().GetProductById(productId, "", true).DefaultGroup; 1918 1919 if (defaultGroup != null) 1920 { 1921 string articlesIdsRaw = defaultGroup.ProductGroupFieldValues.GetProductGroupFieldValue("Articles")?.Value?.ToString() ?? ""; 1922 string articleSubGroupIdsRaw = defaultGroup.ProductGroupFieldValues.GetProductGroupFieldValue("ArticleSubCategories")?.Value?.ToString() ?? ""; 1923 1924 List<int> articlesIds = new List<int>(); 1925 if (!string.IsNullOrEmpty(articlesIdsRaw)) 1926 { 1927 articlesIds = GetIds(articlesIdsRaw); 1928 1929 if (articlesIds.Count > 0) 1930 { 1931 hasActiveArticles = articlesIds.Any(x => new PageService().GetPage(x).Active); 1932 } 1933 } 1934 1935 if (!string.IsNullOrEmpty(articleSubGroupIdsRaw)) 1936 { 1937 var subCategoryIds = GetIds(articleSubGroupIdsRaw); 1938 1939 List<string> subCategoryNames = new List<string>(); 1940 1941 foreach (int subCategoryId in subCategoryIds) 1942 { 1943 var subCategory = new PageService().GetPage(subCategoryId); 1944 if (subCategory != null) 1945 { 1946 subCategoryNames.Add(subCategory.GetDisplayName()); 1947 } 1948 } 1949 1950 foreach (string subCategoryName in subCategoryNames) 1951 { 1952 try 1953 { 1954 var subCategoryArticles = new PageService().GetPagesForItems(new List<string> { "UniverseArticlePage" }).Where(x => x.Item["SubCategories"].ToString().Contains(subCategoryName)); 1955 if (subCategoryArticles.Any(x => x.Active)) 1956 { 1957 hasActiveArticles = true; 1958 } 1959 } 1960 catch 1961 { 1962 } 1963 } 1964 } 1965 } 1966 1967 if (hasActiveArticles) 1968 { 1969 string articlesHeader = ""; 1970 articlesHeader = defaultGroup.ProductGroupFieldValues.GetProductGroupFieldValue("ArticlesHeader").Value.ToString(); 1971 1972 if (string.IsNullOrEmpty(articlesHeader)) 1973 { 1974 articlesHeader = Translate("ProductViewArticleHeader"); 1975 } 1976 1977 var paragraph = new ParagraphService().GetParagraphsByPageId(articlesPageId).FirstOrDefault(); 1978 1979 if (paragraph != null) 1980 { 1981 <div class="row" style="margin-top: 15px"> 1982 <div class="col-xs-12 p-15 p-md-0"> 1983 <h2 class="product__header product__header--mobile-center articles">@articlesHeader</h2> 1984 </div> 1985 1986 </div> 1987 1988 <div class="row-25"> 1989 @RenderParagraphContent(paragraph.ID) 1990 </div> 1991 } 1992 } 1993 } 1994 1995 1996 <div class="row p-15 p-md-0" style="margin-top: 15px"> 1997 <!-- display block in clerk.js if at least one product --> 1998 <h2 class="product__header product__header--mobile-center" id="product-header-clerk" style="display: block;">Ofte købt sammen med</h2> 1999 <span class="clerk" 2000 data-template="@@product-page-others-also-bought" 2001 data-products='["@GetString("Ecom:Product.ID")"]'> 2002 </span> 2003 </div> 2004 2005 2006 <div class="row"> 2007 <div class="col-md-12 col-sm-12 col-xs-12">&nbsp;</div> 2008 </div> 2009 2010 @{ 2011 2012 2013 var pricesJs = new List<CustomUnitPriceViewModel>(); 2014 foreach (var priceJs in prices) 2015 { 2016 var productPrice = new CustomUnitPriceViewModel(); 2017 productPrice.BaseUnitPrice = priceJs.GetDouble("Ecom:Product.Prices.BaseUnitPrice"); 2018 productPrice.PriceQuantityPerUnit = priceJs.GetDouble("Ecom:Product.Prices.PriceQuantityPerUnit"); 2019 productPrice.Quantity = priceJs.GetDouble("Ecom:Product.Prices.Quantity"); 2020 productPrice.UnitId = priceJs.GetString("Ecom:Product.Prices.UnitID"); 2021 pricesJs.Add(productPrice); 2022 } 2023 } 2024 2025 @if (!String.IsNullOrWhiteSpace(GetString("Ecom:Product:Field.ScriptsBottom"))) 2026 { 2027 @SnippetStart("JavaScriptBottom") 2028 @GetString("Ecom:Product:Field.ScriptsBottom") 2029 @SnippetEnd("JavaScriptBottom") 2030 } 2031 2032 <script> 2033 var pricesJs = JSON.parse('@Newtonsoft.Json.JsonConvert.SerializeObject(pricesJs)'); 2034 2035 // Create our number formatter. 2036 var formatterUs = new Intl.NumberFormat('en-US', { 2037 minimumFractionDigits: 2, 2038 }); 2039 2040 2041 2042 2043 $('.product__addtocart-button').on('click', function () { 2044 2045 var quantityAdded = parseInt($(this).parent().prev().find('input.quantity').val()); 2046 var unitAdded = $(this).parent().prev().find('select.unit').val(); 2047 var priceFound = null; 2048 for (const key in pricesJs) { 2049 if (pricesJs[key].UnitId == unitAdded && quantityAdded >= parseInt(pricesJs[key].Quantity)){ 2050 priceFound = pricesJs[key]; 2051 } 2052 } 2053 2054 var priceAdded = 0; 2055 if (priceFound != null) { 2056 priceAdded = (priceFound.BaseUnitPrice * priceFound.PriceQuantityPerUnit) * quantityAdded; } 2057 2058 var quantity = document.getElementsByClassName("product__quantity-input")[0].value; 2059 var unitTypes = document.getElementsByClassName("unit-type"); 2060 var isVariantNull = true; 2061 2062 var index = 0; 2063 var unitPrice; 2064 var currentUnitName; 2065 for (const button of unitTypes){ 2066 2067 if (button.checked != false) 2068 { 2069 isVariantNull = false; 2070 var tempPrice = button.dataset.price.replace(".",""); 2071 tempPrice = tempPrice.replace(",","."); 2072 unitPrice = parseFloat(tempPrice); 2073 currentUnitName = button.value.split("_")[1]; 2074 index++; 2075 } 2076 } 2077 2078 if (!isVariantNull) 2079 { 2080 dataLayer.push({ecommerce:null}); 2081 dataLayer.push({ 2082 'event': 'add_to_cart', 2083 "ecommerce":{ 2084 "currency" : "@GetString("Ecom:Product.CurrencyCode")", 2085 "value" : unitPrice * (parseInt(quantity)).toFixed(2), 2086 "items":[ 2087 { 2088 "item_name": "@GetString("Ecom:Product.Name")" + " - " + currentUnitName, 2089 'item_id': '@GetString("Ecom:Product.ID")', 2090 'price': unitPrice, 2091 "item_brand" : "", 2092 "item_category": (categories[0] != null ? categories[0] : ""), 2093 "item_category2": (categories[1] != null ? categories[1] : ""), 2094 "item_category3": (categories[2] != null ? categories[2] : ""), 2095 "item_category4": (categories[3] != null ? categories[3] : ""), 2096 "item_category5": (categories[4] != null ? categories[4] : ""), 2097 "quantity": parseInt(quantity), 2098 } 2099 ] 2100 }, 2101 2102 }); 2103 } 2104 2105 }); 2106 2107 //Sets the unittype if only one unittype for the product exists 2108 var types = document.getElementsByClassName("unit-type"); 2109 var currentUnitName; 2110 2111 if (types.length === 1){ 2112 currentUnitName = " - " + (types[0].value.split("_")[1]); 2113 2114 } 2115 else{ 2116 currentUnitName = ""; 2117 } 2118 2119 2120 var initialCategories = document.getElementsByClassName("page-header__title")[0].dataset.categories; 2121 var categories = initialCategories.split("_"); 2122 2123 2124 if (types.length === 1){ 2125 2126 var currentUnitPrice = 0; 2127 var tempPrice = types[0].dataset.price.replace(".",""); 2128 tempPrice = tempPrice.replace(",","."); 2129 currentUnitPrice = parseFloat(tempPrice); 2130 2131 // View_item initial, if only one unit type exists 2132 dataLayer.push({ecommerce:null}); 2133 dataLayer.push({ 2134 'event': 'view_item', 2135 "ecommerce":{ 2136 "currency" : "@GetString("Ecom:Product.CurrencyCode")", 2137 "value" : currentUnitPrice, 2138 "items":[ 2139 { 2140 "item_name": "@GetString("Ecom:Product.Name")" + currentUnitName, 2141 'item_id': '@GetString("Ecom:Product.ID")', 2142 'price': currentUnitPrice, 2143 "item_brand" : "", 2144 "item_category": (categories[0] != null ? categories[0] : ""), 2145 "item_category2": (categories[1] != null ? categories[1] : ""), 2146 "item_category3": (categories[2] != null ? categories[2] : ""), 2147 "item_category4": (categories[3] != null ? categories[3] : ""), 2148 "item_category5": (categories[4] != null ? categories[4] : ""), 2149 "quantity": 1, 2150 2151 } 2152 ] 2153 }, 2154 2155 }); 2156 } 2157 2158 2159 var unitTypes = document.getElementsByClassName("unit-type"); 2160 2161 var price; 2162 var oldPrice; 2163 2164 for (const btn of unitTypes) 2165 { 2166 btn.addEventListener("click", function () { 2167 2168 2169 tempPrice = this.dataset.price.replace(".",""); 2170 tempPrice = tempPrice.replace(",","."); 2171 price = parseFloat(tempPrice); 2172 2173 currentUnitName = btn.value.split("_")[1]; 2174 2175 //checks to see if the new variant is the same as the old one 2176 if (price != oldPrice){ 2177 2178 dataLayer.push({ecommerce:null}); 2179 dataLayer.push({ 2180 'event': 'view_item', 2181 "ecommerce":{ 2182 "currency" : "@GetString("Ecom:Product.CurrencyCode")", 2183 "value" : price, 2184 "items":[ 2185 { 2186 "item_name": "@GetString("Ecom:Product.Name")" + " - " + currentUnitName, 2187 'item_id': '@GetString("Ecom:Product.ID")', 2188 'price': price, 2189 "item_brand" : "", 2190 "item_category": (categories[0] != null ? categories[0] : ""), 2191 "item_category2": (categories[1] != null ? categories[1] : ""), 2192 "item_category3": (categories[2] != null ? categories[2] : ""), 2193 "item_category4": (categories[3] != null ? categories[3] : ""), 2194 "item_category5": (categories[4] != null ? categories[4] : ""), 2195 "quantity": 1, 2196 2197 } 2198 ] 2199 }, 2200 2201 }); 2202 2203 } 2204 oldPrice = price; 2205 }) 2206 } 2207 2208 </script> 2209 2210 @SnippetStart("JavaScriptBottom") 2211 <script type="text/javascript" src="~/Files/Templates/Designs/Dwsimple/js/video-api.js"></script> 2212 2213 @if (currentUser != null) 2214 { 2215 <script type="text/javascript" src="~/Files/Templates/Designs/Dwsimple/js/clerk.js"></script> 2216 } 2217 2218 <script> 2219 function on() { 2220 document.getElementById("overlay").style.display = "block"; 2221 callPlayer('product-video', 'playVideo'); 2222 } 2223 function off() { 2224 document.getElementById("overlay").style.display = "none"; 2225 callPlayer('product-video', 'pauseVideo'); 2226 } 2227 function updateDetailAccessibility(button) { 2228 2229 //set wrapper equal to first child of button with class product__section-container__icon-wrapper 2230 wrapper = button.parentElement.parentElement.querySelector('.product__section-container__icon-wrapper'); 2231 2232 2233 var content = document.querySelector('.product__details__content[data-content-id="' + wrapper.getAttribute('data-content-id') + '"]'); 2234 var defaultHeight = wrapper.getAttribute("data-height"); 2235 2236 if (content.style.maxHeight == defaultHeight) { 2237 content.style.maxHeight = content.scrollHeight + "px"; 2238 } else { 2239 content.style.maxHeight = defaultHeight; 2240 } 2241 2242 var icons = wrapper.querySelectorAll('.product__section-container__icon'); 2243 icons.forEach(function (icon) { 2244 2245 2246 //toggle between product__section-container__icon--active and not 2247 if (icon.classList.contains('product__section-container__icon--active')) { 2248 icon.classList.remove('product__section-container__icon--active'); 2249 2250 } else { 2251 icon.classList.add('product__section-container__icon--active'); 2252 } 2253 2254 2255 }); 2256 2257 } 2258 2259 </script> 2260 2261 @SnippetEnd("JavaScriptBottom") 2262 2263 @functions { 2264 2265 public static string StripHTML(string input) 2266 { 2267 var doc = new HtmlDocument(); 2268 doc.LoadHtml(input); 2269 return doc.DocumentNode.InnerText; 2270 } 2271 2272 // Some DynamicWeb "Link" field tags render as a full <a href="...">text</a> 2273 // anchor instead of a plain URL string. This pulls just the href out if so; 2274 // if the input is already a plain string it's returned unchanged. 2275 public static string ExtractHref(string raw) 2276 { 2277 if (string.IsNullOrWhiteSpace(raw)) 2278 { 2279 return raw ?? ""; 2280 } 2281 if (raw.TrimStart().StartsWith("<a", StringComparison.OrdinalIgnoreCase)) 2282 { 2283 var doc = new HtmlDocument(); 2284 doc.LoadHtml(raw); 2285 var anchor = doc.DocumentNode.SelectSingleNode("//a"); 2286 return anchor?.GetAttributeValue("href", "") ?? ""; 2287 } 2288 return raw; 2289 } 2290 2291 // Slugifies a Danish string for use in IDs/slugs, transliterating æ/ø/å 2292 // instead of stripping them (matches the site's existing URL conventions, 2293 // e.g. "Venøs" -> "venoes", not "ven-s"). 2294 public static string DanishSlug(string input) 2295 { 2296 if (string.IsNullOrWhiteSpace(input)) 2297 { 2298 return ""; 2299 } 2300 string s = input.ToLowerInvariant(); 2301 s = s.Replace("æ", "ae").Replace("ø", "oe").Replace("å", "aa"); 2302 s = Regex.Replace(s, @"[^a-z0-9]+", "-").Trim('-'); 2303 return s; 2304 } 2305 2306 } 2307 <script type="application/ld+json"> 2308 @(new System.Web.HtmlString(productSchemaJson)) 2309 </script> 2310 2311 <style> 2312 2313 .product__details { 2314 position: relative; 2315 margin-bottom: 10px; 2316 2317 p { 2318 margin: 0; 2319 width: fit-content; 2320 } 2321 2322 a:hover { 2323 cursor: pointer; 2324 } 2325 2326 .product__details__content { 2327 overflow: hidden; 2328 transition: max-height 0.3s ease; 2329 } 2330 } 2331 </style> 2332

Dansk support (kl. 08-16)

Dag til dag-afsendelse af lagervarer

Hurtig genbestilling

Faguddannet personale