Skip to main content
Error executing template "Designs/Keflico/eCom/Productlist/products.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_1706cc757f984a73808e5789b0a9e4b7.FindTopGroup(Group group) in D:\dynamicweb.net\Solutions\keflico.live\Files\Templates\Designs\Keflico\eCom\Productlist\products.cshtml:line 1529
   at CompiledRazorTemplates.Dynamic.RazorEngine_1706cc757f984a73808e5789b0a9e4b7.Execute() in D:\dynamicweb.net\Solutions\keflico.live\Files\Templates\Designs\Keflico\eCom\Productlist\products.cshtml:line 209
   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 Dynamicweb.Ecommerce; 3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites 4 @using Dynamicweb.Ecommerce.Variants; 5 @using Keflico.Website.Custom 6 @{ 7 string AllProductsPage = Pageview.Area.Item["AllProductsPage"].ToString(); 8 string VariantsLookup = "/" + Pageview.Area.Item["Variantslookup"].ToString(); 9 string BundleLookup = "/" + Pageview.Area.Item["Bundlelookup"].ToString(); 10 bool isloggedin = false; 11 12 13 var allProductsList = new List<string>(); // Use a List to build your JSON string. 14 15 foreach (var product in GetLoop("Products")) 16 { 17 var primaryGroup = product.GetString("Ecom:Product.PrimaryGroupID").ToLower(); 18 var productId = product.GetString("Ecom:Product.ID"); 19 var totalStock = product.GetDouble("Ecom:Product.Stock"); 20 totalStock += product.GetDouble("Ecom:Product:Field.StockSea.Value"); 21 var totalStockWareHouse = product.GetInteger("Ecom:Product.Stock"); 22 var totalStockSea = product.GetInteger("Ecom:Product:Field.StockSea.Value"); 23 24 if (primaryGroup != "group32") { 25 string additionalStockString = product.GetString("Ecom:Product:Field.ProductPieceOnPurchase.Value"); 26 if (int.TryParse(additionalStockString, out int additionalStock)) 27 { 28 totalStock += additionalStock; 29 totalStockSea += additionalStock; 30 } 31 } 32 33 var productObject = $@"{{ 34 ""id"": ""{productId}"", 35 ""primaryGroup"": ""{primaryGroup}"", 36 ""totalStock"": {totalStock}, 37 ""totalStockWareHouse"": {totalStockWareHouse}, 38 ""totalStockSea"": {totalStockSea} 39 }}"; 40 allProductsList.Add(productObject); 41 } 42 43 // Join all the product objects with commas and wrap them in brackets to form a JSON array. 44 var allProductNumbersString = "[" + string.Join(",", allProductsList) + "]"; 45 46 47 48 string sortBy = GetString("Ecom:ProductList.SortBy"); 49 string sortOrder = GetString("Ecom:ProductList.SortOrder"); 50 51 bool isStandardSorting = string.IsNullOrWhiteSpace(HttpContext.Current.Request.QueryString["Sortby"]); 52 53 54 if (!string.IsNullOrWhiteSpace(GetGlobalValue("Global:Extranet.UserName").ToString())) 55 { 56 isloggedin = true; 57 } 58 59 60 FavoriteList favoriteList = null; 61 var service = new FavoriteProductService(); 62 if (isloggedin) 63 { 64 var user = Dynamicweb.Security.UserManagement.User.GetCurrentFrontendUser(); 65 favoriteList = user.GetFavoriteLists().FirstOrDefault(); 66 67 } 68 69 var shouldRefreshFavorite = (isloggedin && !Dynamicweb.Security.UserManagement.User.GetCurrentFrontendUser().UserHasFavoriteList()).ToString().ToLower(); 70 71 } 72 73 <header class="header header-overview module module-sand-light"> 74 <div class="breadcrumbs"> 75 @RenderNavigation(new 76 { 77 StartLevel = 1, 78 EndLevel = 4, 79 ExpandMode = "Pathonly", 80 Template = "breadcrumbs.xslt" 81 }) 82 </div> 83 @if (String.IsNullOrWhiteSpace(GetString("Ecom:ProductList:Page.GroupID"))) 84 { 85 <h1 class="header__title">@Translate("Translate_TopLevelGroup_Headline")</h1> 86 <p>@Translate("Translate_TopLevelGroup_Text")</p> 87 } 88 else 89 { 90 <h1 class="header__title">@GetString("Ecom:Group.Name")</h1> 91 92 if (!String.IsNullOrWhiteSpace(GetString("Ecom:Group.Description"))) 93 { 94 @GetString("Ecom:Group.Description") 95 } 96 } 97 98 </header> 99 100 @if (GetLoop("ProductGroups").Count() > 0 && String.IsNullOrWhiteSpace(GetString("Ecom:ProductList:Page.GroupID"))) 101 { 102 <section class="category-page module module-sand"> 103 <article class="card-list__wrapper"> 104 <a href="@AllProductsPage" class="all-link"> 105 @Translate("Translate_ProductList_SeeAllProducts") 106 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.006 11.031"> 107 <g class="arrow" transform="translate(441 901.014) rotate(180)"> 108 <path class="angle" d="M-17182.074-20447.988l4.809-4.809,4.809,4.809" transform="translate(20875.289 -16281.768) rotate(-90)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" /> 109 <line class="line" x2="17" transform="translate(423.5 895.5)" stroke-linecap="round" stroke-width="1" /> 110 </g> 111 </svg> 112 </a> 113 <ul class="card-list"> 114 @foreach (var group in GetLoop("ProductGroups")) 115 { 116 bool show = group.GetBoolean("Ecom:Group.ShowInMenu"); 117 if (show) 118 { 119 string groupLink = "/Default.aspx?Id=" + Pageview.Page.ID + "&GroupID=" + group.GetString("Ecom:Group.ID"); 120 string groupName = group.GetString("Ecom:Group.Name"); 121 string groupImage = group.GetString("Ecom:Group.LargeImage"); 122 string groupImageLg = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=665&Height=665&Crop=0&Compression=100"; 123 string groupImageMd = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=512&Height=512&Crop=0&Compression=100"; 124 string groupImageSm = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=415&Height=415&Crop=0&Compression=100"; 125 string groupImageDefault = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=620&Height=620&Crop=0&Compression=100"; 126 127 <li class="card-list__card"> 128 <a href="@groupLink" class="card__wrapper"> 129 <picture class="card__picture"> 130 <source srcset="@groupImageLg" media="(min-width: 1536px)"> 131 <source srcset="@groupImageMd" media="(min-width: 992px)"> 132 <source srcset="@groupImageSm" media="(min-width: 768px)"> 133 <img src="@groupImageDefault" alt="@groupName"> 134 </picture> 135 <ul class="card__info-list"> 136 <li class="card__info-list__item"> 137 <span class="info info--large">@groupName</span> 138 </li> 139 </ul> 140 </a> 141 </li> 142 } 143 144 } 145 </ul> 146 </article> 147 </section> 148 } 149 else if (GetLoop("Subgroups").Count() > 0) 150 { 151 var currentGroup = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Group.ID")); 152 var currentToplevel = FindTopGroup(currentGroup); 153 string allLink = AllProductsPage + "&Group=" + currentGroup.Name; 154 155 <section class="category-page module module-sand"> 156 <article class="card-list__wrapper"> 157 <a href="@allLink" class="all-link"> 158 @Translate("Translate_ProductList_SeeAllProducts") 159 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.006 11.031"> 160 <g class="arrow" transform="translate(441 901.014) rotate(180)"> 161 <path class="angle" d="M-17182.074-20447.988l4.809-4.809,4.809,4.809" transform="translate(20875.289 -16281.768) rotate(-90)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" /> 162 <line class="line" x2="17" transform="translate(423.5 895.5)" stroke-linecap="round" stroke-width="1" /> 163 </g> 164 </svg> 165 </a> 166 <ul class="card-list"> 167 @foreach (var group in GetLoop("Subgroups")) 168 { 169 bool show = group.GetBoolean("Ecom:Group.ShowInMenu"); 170 if (show) 171 { 172 string groupLink = "/Default.aspx?Id=" + Pageview.Page.ID + "&GroupID=" + group.GetString("Ecom:Group.ID"); 173 string groupName = group.GetString("Ecom:Group.Name"); 174 string groupImage = group.GetString("Ecom:Group.LargeImage"); 175 string groupImageLg = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=665&Height=665&Crop=0&Compression=100"; 176 string groupImageMd = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=512&Height=512&Crop=0&Compression=100"; 177 string groupImageSm = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=415&Height=415&Crop=0&Compression=100"; 178 string groupImageDefault = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(groupImage) + "&Width=620&Height=620&Crop=0&Compression=100"; 179 180 if (groupName.ToLower().Contains("alle") || groupName.ToLower().Contains("all")) 181 { 182 groupLink = allLink; 183 } 184 185 <li class="card-list__card"> 186 <a href="@groupLink" class="card__wrapper"> 187 <picture class="card__picture"> 188 <source srcset="@groupImageLg" media="(min-width: 1536px)"> 189 <source srcset="@groupImageMd" media="(min-width: 992px)"> 190 <source srcset="@groupImageSm" media="(min-width: 768px)"> 191 <img src="@groupImageDefault" alt="@groupName"> 192 </picture> 193 <ul class="card__info-list"> 194 <li class="card__info-list__item"> 195 <span class="info info--large">@groupName</span> 196 </li> 197 </ul> 198 </a> 199 </li> 200 } 201 } 202 </ul> 203 </article> 204 </section> 205 } 206 else 207 { 208 var currentGroup = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(GetString("Ecom:Group.ID")); 209 var currentToplevel = FindTopGroup(currentGroup); 210 211 <section class="module module-sand product-overview" id="productsFlowApp"> 212 <aside id="productFilters" class="filter-section" data-group="@currentGroup.Id" data-top-group="@currentToplevel.Name" data-all="false"> 213 <div class="filter-section__header"> 214 <h3>@Translate("Translate_ProductList_FilterHeadline")</h3> 215 216 <div class="filter-section__exit" id="filter-article-close"> 217 <svg xmlns="http://www.w3.org/2000/svg" width="10.819" height="10.819" viewBox="0 0 10.819 10.819"> 218 <g id="Group_1017" data-name="Group 1017" transform="translate(-1.591 2.559)"> 219 <rect id="Rectangle_702" data-name="Rectangle 702" width="14" height="1.3" rx="0.65" transform="translate(1.591 7.341) rotate(-45)" /> 220 <rect id="Rectangle_703" data-name="Rectangle 703" width="14" height="1.3" rx="0.65" transform="translate(2.51 -2.559) rotate(45)" /> 221 </g> 222 </svg> 223 </div> 224 </div> 225 <ul v-if="showFilters" class="filter-section__filter-list"> 226 <li v-for="facet in facets" v-if="((facet.QueryParameter != 'Group' && currentGroup && !allProducts) || allProducts) && sortedOptions(facet.Options).length > 1" :class="'filter-item filter-item--' + facet.RenderType.toLowerCase()"> 227 <template v-if="facet.RenderType.toLowerCase() != 'range'"> 228 <input class="filter-item__checkbox-toggle" type="checkbox" :id="facet.QueryParameter" aria-checked="true"> 229 <label class="filter-item__header" :for="facet.QueryParameter"> 230 <span class="filter-item__header__title">{{ translation('Translate_ProductList_Filter_' + facet.QueryParameter) }}</span> 231 </label> 232 </template> 233 234 <div class="filter-item__body"> 235 <ul class="filter-item__input-list"> 236 <template v-if="facet.RenderType.toLowerCase() == 'range'"> 237 <li class="input-list__filter"> 238 <label for="filter-length">{{ translation('Translate_ProductList_Filter_' + facet.QueryParameter.replace('Start', '')) }}</label> 239 <div class="duo-range-slider"> 240 <input class="duo-range-slider__range filter-option" :data-name="facet.QueryParameter" :name="facet.QueryParameter" :value="getLowestValue(facet.Options)" :min="getLowestValue(facet.Options)" :max="getHighestValue(facet.Options)" step="1" type="range"> 241 <input class="duo-range-slider__range filter-option" :data-name="facet.QueryParameter.replace('Start', 'End')" :name="facet.QueryParameter.replace('Start', 'End')" :value="getHighestValue(facet.Options)" :min="getLowestValue(facet.Options)" :max="getHighestValue(facet.Options)" step="1" type="range"> 242 <div class="duo-range-slider__values"> 243 <div class="duo-range-slider__values-min"><span>{{ facet.Options[0].Value }}</span> mm</div> 244 <div class="duo-range-slider__values-max"><span>{{ facet.Options.at(-1).Value }}</span> mm</div> 245 </div> 246 </div> 247 </li> 248 </template> 249 <template v-else-if="facet.RenderType.toLowerCase() == 'select'"> 250 <li class="input-list__filter"> 251 <div class="search-select"> 252 <div class="search-select__search"> 253 <input class="search-select__search-input" type="search" :placeholder="translation('Translate_General_SearchFor')"> 254 <button class="search-select__search-clear" type="button">@Translate("Translate_Close")</button> 255 </div> 256 <div class="search-select__dropdown"> 257 <label v-for="option in sortedOptions(facet.Options)" class="search-select__dropdown-item" :for="facet.QueryParameter + '_' + option.Value">{{ option.Value }}</label> 258 </div> 259 <div class="search-select__tags"> 260 <template v-for="option in sortedOptions(facet.Options)"> 261 <input type="checkbox" class="filter-option" :data-name="facet.QueryParameter" :data-parameter-type="facet.QueryParameterType" :name="facet.QueryParameter + addition(facet.QueryParameterType)" :value="option.Value" :id="facet.QueryParameter + '_' + option.Value"> 262 <label :for="facet.QueryParameter + '_' + option.Value">{{ option.Label }}</label> 263 </template> 264 </div> 265 </div> 266 </li> 267 </template> 268 <template v-else> 269 <li class="input-list__filter"> 270 <template v-for="option in sortedOptions(facet.Options)"> 271 <div class="form__fieldset" v-if="facet.QueryParameter != 'Type'"> 272 <div class="form__field-wrap"> 273 <input type="checkbox" class="form__field form__field--checkbox filter-option" :data-name="facet.QueryParameter" :name="facet.QueryParameter + addition(facet.QueryParameterType)" :value="option.Value" :id="facet.QueryParameter + '_' + option.Value"> 274 <label :for="facet.QueryParameter + '_' + option.Value">{{ option.Label }}</label> 275 </div> 276 </div> 277 <div class="form__fieldset" v-if="facet.QueryParameter == 'Type' && !(option.Label == 'kunde' || option.Label == 'relatordre')"> 278 <div class="form__field-wrap"> 279 <input type="checkbox" class="form__field form__field--checkbox filter-option" :data-name="facet.QueryParameter" :name="facet.QueryParameter + addition(facet.QueryParameterType)" :value="option.Value" :id="facet.QueryParameter + '_' + option.Value"> 280 <label :for="facet.QueryParameter + '_' + option.Value">{{ translation(`Translate_Filter_ProductType_${option.Label}`) }}</label> 281 </div> 282 </div> 283 </template> 284 </li> 285 </template> 286 </ul> 287 </div> 288 </li> 289 </ul> 290 <div class="filter-section__footer"> 291 <div class="footer-controls"> 292 <button id="clearFilterButton" class="btn btn-link">@Translate("Translate_ProductList_ResetFilters")</button> 293 <button id="applyFilterButton" class="btn btn-secondary btn--small">@Translate("Translate_ProductList_ApplyFilters")</button> 294 </div> 295 </div> 296 </aside> 297 <article class="card-list__wrapper"> 298 <div class="card-list__header"> 299 <div class="form__fieldset mobile-only"> 300 <button class="btn btn-secondary" id="toggle-filter-btn"> 301 @Translate("Translate_Filter") 302 </button> 303 </div> 304 <div class="form__fieldset"> 305 <label for="sorting" class="select-label">@Translate("Translate_ProductList_SortBy")</label> 306 <select class="form__field--narrow form__field-wrap--select" id="sorting"> 307 <option value="default">@Translate("Translate_ProductList_SortOption_Highlighted")</option> 308 <option value="asc" @(!isStandardSorting && sortOrder.ToLower() == "asc" ? "selected" : "")>@Translate("Translate_ProductList_SortOption_Alphabetic")</option> 309 <option value="desc" @(!isStandardSorting && sortOrder.ToLower() == "desc" ? "selected" : "")>@Translate("Translate_ProductList_SortOption_ReverseAlpabetic")</option> 310 </select> 311 </div> 312 </div> 313 <ul class="card-list"> 314 @foreach (var product in GetLoop("Products")) 315 { 316 string link = product.GetString("Ecom:Product.Link.Clean"); 317 string name = product.GetString("Ecom:Product.Name"); 318 string teaser = product.GetString("Ecom:Product:Field.Name2.Value.Clean"); 319 string productNumber = product.GetString("Ecom:Product.Number"); 320 string dbNumberLabel = product.GetString("Ecom:Product:Field.ProductDbNumber.Name"); 321 string dbNumber = product.GetString("Ecom:Product:Field.ProductDbNumber.Value"); 322 string primaryGroup = product.GetString("Ecom:Product.PrimaryGroupID").ToLower(); 323 var productLayout = !string.IsNullOrWhiteSpace(product.GetString("Ecom:Product:Field.ProductLayout")) ? product.GetString("Ecom:Product:Field.ProductLayout") : primaryGroup; 324 325 var combination = new VariantCombination(product.GetString("Ecom:Product.ID")); 326 IList<VariantCombination> productVariants = new List<VariantCombination>(); 327 var singleVariant = new VariantCombination(); 328 329 if (combination != null && combination.Product != null) 330 { 331 productVariants = combination.Product.VariantCombinations; 332 } 333 334 if (productVariants.Count == 1) 335 { 336 singleVariant = productVariants.FirstOrDefault(); 337 } 338 339 string labelCode = product.GetString("Ecom:Product:Field.ProductPurchaseCode"); 340 341 bool isBundleOnly = System.Convert.ToBoolean(product.GetString("Ecom:Product:Field.BundleOnly")); 342 bool usePriceExplanation = false; 343 344 string primaryPrice = product.GetString("Ecom:Product.Price.PriceWithoutVAT"); 345 string secondaryPrice = ""; 346 string tertiaryPrice = ""; 347 string primaryPriceDescription = ""; 348 string secondaryPriceDescription = ""; 349 string tertiaryPriceDescription = ""; 350 351 //product.GetRawTags(); 352 353 var totalStock = product.GetDouble("Ecom:Product.Stock"); 354 totalStock += product.GetDouble("Ecom:Product:Field.StockSea.Value"); 355 356 int totalStockWareHouse = product.GetInteger("Ecom:Product.Stock"); 357 int totalStockSea = product.GetInteger("Ecom:Product:Field.StockSea.Value"); 358 359 360 if (primaryGroup != "group32") 361 { 362 totalStock += product.GetDouble("Ecom:Product:Field.ProductPieceOnPurchase.Value"); 363 totalStockSea += product.GetInteger("Ecom:Product:Field.ProductPieceOnPurchase.Value"); 364 } 365 366 bool isSingleVariant = product.GetInteger("Ecom:Product.VariantCount") == 1 ? true : false; 367 368 double singleVariantStock = 0; 369 int singleVariantStockSea = 0; 370 371 if (isSingleVariant) 372 { 373 singleVariantStock = singleVariant.Product.UnitStock; 374 singleVariantStockSea = !String.IsNullOrWhiteSpace(singleVariant.Product.ProductFieldValues.GetProductFieldValue("ProductPieceOnPurchase").Value.ToString()) ? Convert.ToInt32(singleVariant.Product.ProductFieldValues.GetProductFieldValue("ProductPieceOnPurchase").Value.ToString().Replace(",", "")) : 0; 375 totalStockWareHouse = (int)singleVariantStock; 376 totalStock += singleVariantStock; 377 totalStock += singleVariantStockSea; 378 } 379 380 string priceCurrencySymbol = product.GetString("Ecom:Product.Currency.Symbol"); 381 string salesUnit = product.GetString("Ecom:Product.DefaultUnitName").ToLower(); 382 string salesUnitCode = product.GetString("Ecom:Product:Field.ProductLengthUnitCode"); 383 384 string productImage = product.GetString("Ecom:Product.ImageDefault.Clean"); 385 string productImageLg = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(productImage) + "&Width=416&Height=371&Crop=0&Compression=100"; 386 string productImageMd = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(productImage) + "&Width=310&Height=277&Crop=0&Compression=100"; 387 string productImageSm = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(productImage) + "&Width=230&Height=205&Crop=0&Compression=100"; 388 string productImageDefault = "/admin/public/getimage.ashx?Image=" + HttpUtility.UrlEncode(productImage) + "&Width=340&Height=304&Crop=0&Compression=100"; 389 bool isFavorite = favoriteList != null && service.GetFavoriteProducts(favoriteList.ListId).Any(x => x.ProductId == productNumber); 390 switch (productLayout) 391 { 392 case "group1": 393 if (isBundleOnly) 394 { 395 primaryPrice = ""; 396 } 397 398 secondaryPrice = product.GetString("Ecom:Product:Field.ProductPriceBundle.Value").Replace(",", "-").Replace(".", ",").Replace("-", "."); 399 400 primaryPriceDescription = Translate("Translate_ProductDecription_Hardwood_Anbrud"); 401 secondaryPriceDescription = Translate("Translate_ProductDecription_Hardwood_Bundt"); 402 403 if (secondaryPrice.Contains(",")) 404 { 405 string[] sp = secondaryPrice.Split(','); 406 407 if (sp[1].Length == 1) 408 { 409 secondaryPrice += "0"; 410 } 411 } 412 else 413 { 414 secondaryPrice += ",00"; 415 } 416 417 break; 418 419 case "group32": 420 secondaryPrice = product.GetString("Ecom:Product:Field.ProductPriceAbove100SQM"); 421 422 primaryPriceDescription = Translate("Translate_ProductDecription_Terrase_Anbrud"); 423 secondaryPriceDescription = Translate("Translate_ProductDecription_Terrase_Above"); 424 425 usePriceExplanation = true; 426 427 if (secondaryPrice.Contains(",")) 428 { 429 string[] sp = secondaryPrice.Split(','); 430 431 if (sp[1].Length == 1) 432 { 433 secondaryPrice += "0"; 434 } 435 } 436 else 437 { 438 secondaryPrice += ",00"; 439 } 440 441 break; 442 443 case "group73": 444 secondaryPrice = product.GetString("Ecom:Product:Field.ProductPriceAbove100SQM"); 445 tertiaryPrice = product.GetString("Ecom:Product:Field.ProductPriceAbove3000M.Value").Replace(".", ","); 446 447 primaryPriceDescription = Translate("Translate_ProductDecription_Facade_Under1000"); 448 secondaryPriceDescription = Translate("Translate_ProductDecription_Facade_Between1000and3000"); 449 tertiaryPriceDescription = Translate("Translate_ProductDecription_Facade_Above3000"); 450 451 usePriceExplanation = true; 452 453 if (secondaryPrice.Contains(",")) 454 { 455 string[] sp = secondaryPrice.Split(','); 456 457 if (sp[1].Length == 1) 458 { 459 secondaryPrice += "0"; 460 } 461 } 462 else 463 { 464 secondaryPrice += ",00"; 465 } 466 467 if (tertiaryPrice.Contains(",")) 468 { 469 string[] tp = tertiaryPrice.Split(','); 470 471 if (tp[1].Length == 1) 472 { 473 tertiaryPrice += "0"; 474 } 475 } 476 else 477 { 478 tertiaryPrice += ",00"; 479 } 480 481 break; 482 483 case "group52": 484 case "group66": 485 secondaryPrice = product.GetString("Ecom:Product:Field.ProductPriceHalfParcel").Replace(",", "-").Replace(".", ",").Replace("-", "."); 486 tertiaryPrice = product.GetString("Ecom:Product:Field.ProductPriceCompleteParcel").Replace(",", "-").Replace(".", ",").Replace("-", "."); 487 488 primaryPriceDescription = Translate("Translate_ProductDecription_Plader_Anbrud"); 489 secondaryPriceDescription = Translate("Translate_ProductDecription_Plader_Half"); 490 tertiaryPriceDescription = Translate("Translate_ProductDecription_Plader_Full"); 491 492 if (secondaryPrice.Contains(",")) 493 { 494 string[] sp = secondaryPrice.Split(','); 495 496 if (sp[1].Length == 1) 497 { 498 secondaryPrice += "0"; 499 } 500 } 501 else 502 { 503 secondaryPrice += ",00"; 504 } 505 506 if (tertiaryPrice.Contains(",")) 507 { 508 string[] tp = tertiaryPrice.Split(','); 509 510 if (tp[1].Length == 1) 511 { 512 tertiaryPrice += "0"; 513 } 514 } 515 else 516 { 517 tertiaryPrice += ",00"; 518 } 519 520 break; 521 522 case "group126": 523 primaryPriceDescription = Translate("Translate_ProductDecription_Accessories"); 524 break; 525 526 default: 527 primaryPriceDescription = Translate("Translate_ProductDecription_General_Description"); 528 break; 529 } 530 531 <li class="card-list__card product-card"> 532 533 <a href="@link" class="card__wrapper"> 534 535 @switch (labelCode.ToLower()) 536 { 537 case "prøver": 538 <div class="card-list__label-code label-code label-code--samples">@Translate("LabelCode_" + labelCode.ToLower().Replace("ø", "oe"))</div> 539 break; 540 case "skaffe": 541 case "relatordre": 542 <div class="card-list__label-code label-code">@Translate("LabelCode_" + labelCode.ToLower())</div> 543 break; 544 default: 545 break; 546 } 547 548 @if (!String.IsNullOrWhiteSpace(productImage)) 549 { 550 <picture class="card__picture"> 551 <source srcset="@productImageLg" media="(min-width: 1536px)"> 552 <source srcset="@productImageMd" media="(min-width: 992px)"> 553 <source srcset="@productImageSm" media="(min-width: 768px)"> 554 <img src="@productImageDefault" alt="@name.Replace("\"", "&quot;")"> 555 @if (isloggedin) 556 { 557 <button id="favoriteAdd-@productNumber" style="@(isFavorite ? "display:none" : "")" class="card-favorite" @@click.prevent="favoriteAdd('@productNumber')"> 558 <svg id="Lag_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"> 559 <!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 137) --> 560 <path d="M50,92.9c-.2,0-.5,0-.6-.3L10,53.2c-4.9-4.9-7.5-11.3-7.5-18.2s2.7-13.4,7.5-18.2c4.9-4.9,11.3-7.5,18.2-7.5s13.4,2.7,18.2,7.5l3.5,3.5,3.5-3.5c4.9-4.9,11.3-7.5,18.2-7.5s13.4,2.7,18.2,7.5c4.9,4.9,7.5,11.3,7.5,18.2s-2.7,13.4-7.5,18.2l-39.4,39.4c-.2.2-.4.3-.6.3ZM50.1,90.8l38.8-38.8c4.5-4.5,7-10.6,7-17s-2.5-12.5-7-17c-4.5-4.5-10.6-7-17-7s-12.5,2.5-17,7l-4.2,4.2c-.2.2-.4.3-.6.3s-.5,0-.6-.3l-4.2-4.2c-4.5-4.5-10.6-7-17-7s-12.5,2.5-17,7c-4.5,4.5-7,10.6-7,17s2.5,12.5,7,17l.4.4,38.4,38.4Z"/> 561 </svg> 562 563 </button> 564 <button id="favoriteRemove-@productNumber" style="@(isFavorite ? "" : "display:none")" class="card-favorite card-favorite--filled" @@click.prevent="favoriteRemove('@productNumber')"> 565 <svg id="Lag_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"> 566 <!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 137) --> 567 <path d="M50,92.9c-.2,0-.5,0-.6-.3L10,53.2c-4.9-4.9-7.5-11.3-7.5-18.2s2.7-13.4,7.5-18.2c4.9-4.9,11.3-7.5,18.2-7.5s13.4,2.7,18.2,7.5l3.5,3.5,3.5-3.5c4.9-4.9,11.3-7.5,18.2-7.5s13.4,2.7,18.2,7.5c4.9,4.9,7.5,11.3,7.5,18.2s-2.7,13.4-7.5,18.2l-39.4,39.4c-.2.2-.4.3-.6.3Z"/> 568 </svg> 569 </button> 570 } 571 572 </picture> 573 } 574 else 575 { 576 <div class="card__picture card__picture--dummie"> 577 @if (isloggedin) 578 { 579 <button id="favoriteAdd-@productNumber" style="@(isFavorite ? "display:none" : "")" class="card-favorite" @@click.prevent="favoriteAdd('@productNumber')"> 580 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 109.57" style="enable-background:new 0 0 122.88 109.57" xml:space="preserve"> 581 <g fill="gray"> 582 <path d="M65.46,19.57c-0.68,0.72-1.36,1.45-2.2,2.32l-2.31,2.41l-2.4-2.33c-0.71-0.69-1.43-1.4-2.13-2.09 c-7.42-7.3-13.01-12.8-24.52-12.95c-0.45-0.01-0.93,0-1.43,0.02c-6.44,0.23-12.38,2.6-16.72,6.65c-4.28,4-7.01,9.67-7.1,16.57 c-0.01,0.43,0,0.88,0.02,1.37c0.69,19.27,19.13,36.08,34.42,50.01c2.95,2.69,5.78,5.27,8.49,7.88l11.26,10.85l14.15-14.04 c2.28-2.26,4.86-4.73,7.62-7.37c4.69-4.5,9.91-9.49,14.77-14.52c3.49-3.61,6.8-7.24,9.61-10.73c2.76-3.42,5.02-6.67,6.47-9.57 c2.38-4.76,3.13-9.52,2.62-13.97c-0.5-4.39-2.23-8.49-4.82-11.99c-2.63-3.55-6.13-6.49-10.14-8.5C96.5,7.29,91.21,6.2,85.8,6.82 C76.47,7.9,71.5,13.17,65.46,19.57L65.46,19.57z M60.77,14.85C67.67,7.54,73.4,1.55,85.04,0.22c6.72-0.77,13.3,0.57,19.03,3.45 c4.95,2.48,9.27,6.1,12.51,10.47c3.27,4.42,5.46,9.61,6.1,15.19c0.65,5.66-0.29,11.69-3.3,17.69c-1.7,3.39-4.22,7.03-7.23,10.76 c-2.95,3.66-6.39,7.44-10,11.17C97.2,74.08,91.94,79.12,87.2,83.66c-2.77,2.65-5.36,5.13-7.54,7.29L63.2,107.28l-2.31,2.29 l-2.34-2.25l-13.6-13.1c-2.49-2.39-5.37-5.02-8.36-7.75C20.38,71.68,0.81,53.85,0.02,31.77C0,31.23,0,30.67,0,30.09 c0.12-8.86,3.66-16.18,9.21-21.36c5.5-5.13,12.97-8.13,21.01-8.42c0.55-0.02,1.13-0.03,1.74-0.02C46,0.48,52.42,6.63,60.77,14.85 L60.77,14.85z" /> 583 </g> 584 </svg> 585 </button> 586 <button id="favoriteRemove-@productNumber" style="@(isFavorite ? "" : "display:none")" class="card-favorite card-favorite--filled" @@click.prevent="favoriteRemove('@productNumber')"> 587 <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 107.39"> 588 <defs> 589 590 </defs> 591 <title>red-heart</title> 592 <path class="cls-1" d="M60.83,17.18c8-8.35,13.62-15.57,26-17C110-2.46,131.27,21.26,119.57,44.61c-3.33,6.65-10.11,14.56-17.61,22.32-8.23,8.52-17.34,16.87-23.72,23.2l-17.4,17.26L46.46,93.55C29.16,76.89,1,55.92,0,29.94-.63,11.74,13.73.08,30.25.29c14.76.2,21,7.54,30.58,16.89Z" /> 593 </svg> 594 </button> 595 } 596 597 598 </div> 599 } 600 601 <div class="card__info-list__item"> 602 <span class="info info--small info--header">@Translate("Translate_Product_Page_ProductNumber"): @productNumber</span> 603 @if (!string.IsNullOrWhiteSpace(dbNumber)) 604 { 605 <span class="info info--small info--header">@dbNumberLabel: @dbNumber</span> 606 } 607 </div> 608 <div class="card__info-list__item "> 609 <span class="info info--title">@name</span> 610 </div> 611 <div class="card__info-list__item"> 612 <span class="info info--small product-teaser">@teaser</span> 613 </div> 614 @if (isloggedin && ((!String.IsNullOrWhiteSpace(primaryPrice) && primaryPrice != "0,00" && primaryPrice != ",00") || (!String.IsNullOrWhiteSpace(secondaryPrice) && secondaryPrice != "0,00" && secondaryPrice != ",00") || (!String.IsNullOrWhiteSpace(tertiaryPrice) && tertiaryPrice != "0,00" && tertiaryPrice != ",00"))) 615 { 616 <div class="card__info-list__item column"> 617 @if (usePriceExplanation) 618 { 619 <div class="price-line price-line--explanation"> 620 @Translate("Translate_PriceExplanation_" + primaryGroup) 621 </div> 622 } 623 @if (!String.IsNullOrWhiteSpace(primaryPrice) && primaryPrice != "0,00" && primaryPrice != ",00") 624 { 625 <div class="price-line"> 626 <div class="definition">@primaryPriceDescription</div> 627 <div class="price">@primaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 628 </div> 629 } 630 631 @if (!String.IsNullOrWhiteSpace(secondaryPrice) && secondaryPrice != "0,00" && secondaryPrice != ",00") 632 { 633 <div class="price-line"> 634 <div class="definition">@secondaryPriceDescription</div> 635 <div class="price">@secondaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 636 </div> 637 } 638 639 @if (!String.IsNullOrWhiteSpace(tertiaryPrice) && tertiaryPrice != "0,00" && tertiaryPrice != ",00") 640 { 641 <div class="price-line"> 642 <div class="definition">@tertiaryPriceDescription</div> 643 <div class="price">@tertiaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 644 </div> 645 } 646 </div> 647 } 648 @*@if (isSingleVariant) 649 { 650 <div class="card__info-list__item"> 651 <span class="info info--small" style="font-size: 15px;margin-top:15px;"> 652 @if (totalStockWareHouse > 0) 653 { 654 <text> 655 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#12ca30"> 656 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 657 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 658 <g id="SVGRepo_iconCarrier"> <path d="M4 12.6111L8.92308 17.5L20 6.5" stroke="#24a84b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> </g> 659 </svg> 660 @Translate("Translate_ProductPage_StockOnWarehouse_OnStock") 661 </text> 662 } 663 else 664 { 665 <text> 666 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> 667 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 668 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 669 <g id="SVGRepo_iconCarrier"> <path d="M6.96967 16.4697C6.67678 16.7626 6.67678 17.2374 6.96967 17.5303C7.26256 17.8232 7.73744 17.8232 8.03033 17.5303L6.96967 16.4697ZM13.0303 12.5303C13.3232 12.2374 13.3232 11.7626 13.0303 11.4697C12.7374 11.1768 12.2626 11.1768 11.9697 11.4697L13.0303 12.5303ZM11.9697 11.4697C11.6768 11.7626 11.6768 12.2374 11.9697 12.5303C12.2626 12.8232 12.7374 12.8232 13.0303 12.5303L11.9697 11.4697ZM18.0303 7.53033C18.3232 7.23744 18.3232 6.76256 18.0303 6.46967C17.7374 6.17678 17.2626 6.17678 16.9697 6.46967L18.0303 7.53033ZM13.0303 11.4697C12.7374 11.1768 12.2626 11.1768 11.9697 11.4697C11.6768 11.7626 11.6768 12.2374 11.9697 12.5303L13.0303 11.4697ZM16.9697 17.5303C17.2626 17.8232 17.7374 17.8232 18.0303 17.5303C18.3232 17.2374 18.3232 16.7626 18.0303 16.4697L16.9697 17.5303ZM11.9697 12.5303C12.2626 12.8232 12.7374 12.8232 13.0303 12.5303C13.3232 12.2374 13.3232 11.7626 13.0303 11.4697L11.9697 12.5303ZM8.03033 6.46967C7.73744 6.17678 7.26256 6.17678 6.96967 6.46967C6.67678 6.76256 6.67678 7.23744 6.96967 7.53033L8.03033 6.46967ZM8.03033 17.5303L13.0303 12.5303L11.9697 11.4697L6.96967 16.4697L8.03033 17.5303ZM13.0303 12.5303L18.0303 7.53033L16.9697 6.46967L11.9697 11.4697L13.0303 12.5303ZM11.9697 12.5303L16.9697 17.5303L18.0303 16.4697L13.0303 11.4697L11.9697 12.5303ZM13.0303 11.4697L8.03033 6.46967L6.96967 7.53033L11.9697 12.5303L13.0303 11.4697Z" fill="#c81919" /> </g> 670 </svg> 671 @Translate("Translate_ProductPage_StockOnWarehouse_NotOnStock") 672 </text> 673 } 674 675 @if (totalStockSea > 0) 676 { 677 <text> 678 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#12ca30"> 679 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 680 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 681 <g id="SVGRepo_iconCarrier"> <path d="M4 12.6111L8.92308 17.5L20 6.5" stroke="#24a84b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> </g> 682 </svg> 683 @Translate("Translate_ProductPage_StockOnSea_InRoute") 684 </text> 685 } 686 else 687 { 688 <text> 689 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> 690 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 691 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 692 <g id="SVGRepo_iconCarrier"> <path d="M6.96967 16.4697C6.67678 16.7626 6.67678 17.2374 6.96967 17.5303C7.26256 17.8232 7.73744 17.8232 8.03033 17.5303L6.96967 16.4697ZM13.0303 12.5303C13.3232 12.2374 13.3232 11.7626 13.0303 11.4697C12.7374 11.1768 12.2626 11.1768 11.9697 11.4697L13.0303 12.5303ZM11.9697 11.4697C11.6768 11.7626 11.6768 12.2374 11.9697 12.5303C12.2626 12.8232 12.7374 12.8232 13.0303 12.5303L11.9697 11.4697ZM18.0303 7.53033C18.3232 7.23744 18.3232 6.76256 18.0303 6.46967C17.7374 6.17678 17.2626 6.17678 16.9697 6.46967L18.0303 7.53033ZM13.0303 11.4697C12.7374 11.1768 12.2626 11.1768 11.9697 11.4697C11.6768 11.7626 11.6768 12.2374 11.9697 12.5303L13.0303 11.4697ZM16.9697 17.5303C17.2626 17.8232 17.7374 17.8232 18.0303 17.5303C18.3232 17.2374 18.3232 16.7626 18.0303 16.4697L16.9697 17.5303ZM11.9697 12.5303C12.2626 12.8232 12.7374 12.8232 13.0303 12.5303C13.3232 12.2374 13.3232 11.7626 13.0303 11.4697L11.9697 12.5303ZM8.03033 6.46967C7.73744 6.17678 7.26256 6.17678 6.96967 6.46967C6.67678 6.76256 6.67678 7.23744 6.96967 7.53033L8.03033 6.46967ZM8.03033 17.5303L13.0303 12.5303L11.9697 11.4697L6.96967 16.4697L8.03033 17.5303ZM13.0303 12.5303L18.0303 7.53033L16.9697 6.46967L11.9697 11.4697L13.0303 12.5303ZM11.9697 12.5303L16.9697 17.5303L18.0303 16.4697L13.0303 11.4697L11.9697 12.5303ZM13.0303 11.4697L8.03033 6.46967L6.96967 7.53033L11.9697 12.5303L13.0303 11.4697Z" fill="#c81919" /> </g> 693 </svg> 694 @Translate("Translate_ProductPage_StockOnSea_NotInRoute") 695 </text> 696 } 697 698 </span> 699 </div> 700 } 701 else 702 {*@ 703 <div class="card__info-list__item"> 704 <span class="info info--small info--flex-wrap"> 705 <div style="display:none;" id="RequestProductText_@(product.GetString("Ecom:Product.ID"))"> 706 <div style="display: inline-flex; align-items: center;"> 707 <span style="width: 8px; height: 8px; background-color: orange; border-radius: 50%; margin-right: 8px;"></span> 708 @Translate("Translate_ProductPage_RequestProduct") 709 </div> 710 </div> 711 <div id="StockOnWarehouse_OnStock_@(product.GetString("Ecom:Product.ID"))" class="info--stock" style="display:none;" > 712 <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#24a84b" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg> 713 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnWarehouse_OnStock")</span> 714 </div> 715 <div id="StockOnWarehouse_NotOnStock_@(product.GetString("Ecom:Product.ID"))" class="info--stock" style="display:none;"> 716 <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#c81919" d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z"/></svg> 717 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnWarehouse_NotOnStock")</span> 718 </div> 719 <div id="StockOnSea_InRoute_@(product.GetString("Ecom:Product.ID"))" class="info--stock" style="display:none;"> 720 <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#24a84b" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg> 721 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnSea_InRoute")</span> 722 </div> 723 <div id="StockOnSea_NotInRoute_@(product.GetString("Ecom:Product.ID"))" class="info--stock" style="display:none;"> 724 <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#c81919" d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z"/></svg> 725 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnSea_NotInRoute")</span> 726 </div> 727 <div class="info--stock skeleton_@(product.GetString("Ecom:Product.ID"))" v-if="isLoading"> 728 <div class="info--stock--skeleton--icon"></div> 729 <div class="info--stock--skeleton--text"></div> 730 </div> 731 <div class="info--stock skeleton_@(product.GetString("Ecom:Product.ID"))" v-if="isLoading"> 732 <div class="info--stock--skeleton--icon"></div> 733 <div class="info--stock--skeleton--text"></div> 734 </div> 735 </span> 736 </div> 737 @*}*@ 738 739 </a> 740 </li> 741 } 742 </ul> 743 @if (GetInteger("Ecom:ProductList.TotalPages") > 1) 744 { 745 string prevClass = ""; 746 string nextClass = ""; 747 string extraPaginationClass = ""; 748 749 if (String.IsNullOrWhiteSpace(GetString("Ecom:ProductList.PrevPage.Clean"))) 750 { 751 prevClass = " disabled"; 752 } 753 754 if (String.IsNullOrWhiteSpace(GetString("Ecom:ProductList.NextPage.Clean"))) 755 { 756 nextClass = " disabled"; 757 } 758 759 if (GetInteger("Ecom:ProductList.TotalPages") > 5) 760 { 761 extraPaginationClass = "navigation__pagination--overload"; 762 } 763 764 <div class="card-list__footer"> 765 <div class="card-list__navigation"> 766 <a href="@GetString("Ecom:ProductList.PrevPage.Clean")" class="navigation__arrow back@(prevClass)"> 767 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 6.503"><path d="M10.94,6.5,14,3.249,10.94,0H9.693L12.4,2.8H0v.9H12.4L9.693,6.5Z" /></svg> 768 </a> 769 770 <div class="navigation__pagination @extraPaginationClass"> 771 <span class="navigation__pagination__title"> @Translate("Translate_ProductList_BreadCrumbPage") </span> 772 @for (int i = 0; i < GetInteger("Ecom:ProductList.TotalPages"); i++) 773 { 774 string currentClass = (i + 1) == GetInteger("Ecom:ProductList.CurrentPage") ? " active" : ""; 775 string url = GetString("Ecom:Group.Link.Clean") + "&PageNum=" + (i + 1); 776 777 foreach (var query in GetLoop("Query.Parameters")) 778 { 779 if (!String.IsNullOrWhiteSpace(query.GetString("Parameter.Value"))) 780 { 781 url += "&" + query.GetString("Parameter.Name") + "=" + query.GetString("Parameter.Value"); 782 } 783 } 784 785 if (!isStandardSorting) 786 { 787 url += "&Sortby=NameForSort" + "&SortOrder=" + sortOrder; 788 } 789 790 <a href="@url" class="navigation__pagination__link@(currentClass)">@(i + 1)</a> 791 } 792 </div> 793 <a href="@GetString("Ecom:ProductList.NextPage.Clean")" class="navigation__arrow forward@(nextClass)"> 794 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 6.503"><path d="M10.94,6.5,14,3.249,10.94,0H9.693L12.4,2.8H0v.9H12.4L9.693,6.5Z" /></svg> 795 </a> 796 </div> 797 </div> 798 } 799 </article> 800 801 </section> 802 <script async type="module"> 803 let activeFilters = {}; 804 let delay; 805 var allProductNumbers = @allProductNumbersString; 806 807 808 new Vue({ 809 el: '#productsFlowApp', 810 name: 'Products Flow', 811 components: { 812 813 }, 814 computed: { 815 816 }, 817 mounted() { 818 var allProductNumbers = @allProductNumbersString; 819 const fetchPromises = []; 820 821 for (const productNumberObjects of allProductNumbers) { 822 fetchPromises.push(this.getVariants(productNumberObjects.id)); 823 } 824 825 // Wait for all fetch operations to complete 826 Promise.all(fetchPromises) 827 .then(() => { 828 // This block will run after all variants have been successfully fetched 829 this.isLoading = false; 830 this.afterFetchingVariants(); 831 }); 832 833 @*@if(primaryGroup == "group1") { 834 <text> 835 this.lookUpBundle(@(productNumber)); 836 </text> 837 } 838 839 this.getAccessories(); 840 841 if(!this.enquireProduct && this.isLoggedIn) { 842 this.orderButtonSpinner = setTimeout(() => { 843 document.querySelector('#product-order-button .loader').style.display = "inline-block"; 844 }, 3000); 845 } else { 846 document.querySelector('#product-order-button .product-order-form-button').style.display = "block"; 847 }*@ 848 }, 849 data() { 850 return { 851 isLoggedIn: @isloggedin.ToString().ToLower(), 852 variants: [], 853 isLoading: true, 854 ongoingOperations: {}, 855 856 showFilters: false, 857 facets: null, 858 translations: null, 859 allProducts: false, 860 currentGroup: '', 861 } 862 }, 863 created() { 864 this.allProducts = (document.getElementById('productFilters').getAttribute('data-all') 865 .toLowerCase() === 'true'); 866 867 if (!this.allProducts) { 868 this.currentGroup = `&Group=${document.getElementById('productFilters').getAttribute('data-top-group')}&GroupID=${document.getElementById('productFilters').getAttribute('data-group')}`; 869 } else { 870 const urlSearchParams = new URLSearchParams(window.location.search); 871 const params = Object.fromEntries(urlSearchParams.entries()); 872 873 let group = ''; 874 875 if (params.Group) { 876 group = `&Group=${params.Group}`; 877 } 878 879 this.currentGroup = group; 880 } 881 882 fetch('/dwapi/translations/area/1') 883 .then(response => response.json()) 884 .then(response => { 885 this.translations = response; 886 }) 887 .catch(error => { 888 console.log(error); 889 }); 890 891 fetch(`/dwapi/ecommerce/products/search?RepositoryName=Products&QueryName=FilterQuery&ProductSettings.FilledProperties=Name&FilledProperties=Products,FacetGroups,TotalProductsCount${this.currentGroup}`) 892 .then(response => response.json()) 893 .then(response => { 894 this.facets = response.FacetGroups[0].Facets; 895 this.showFilters = true; 896 897 this.$nextTick(() => { 898 setUpFilters(); 899 setupSearchSelects(); 900 setupDuoRangeSliders(); 901 }); 902 }); 903 }, 904 methods: { 905 addition(paramType) { 906 let addition = ''; 907 908 if (paramType == 'System.String[]' || paramType == 'System.Double[]') { 909 addition = '[]'; 910 } 911 912 return addition; 913 }, 914 translation(key) { 915 return this.translations.find(x => x.Key == key) ? this.translations.find(x => x.Key == key).Value : key; 916 }, 917 sortedOptions(options) { 918 const newList = options.filter(x => x.Count && x.Count > 0 && parseInt(x.Value) != -1); 919 return newList; 920 }, 921 getLowestValue(options) { 922 const newList = options.filter(x => x.Count && x.Count > 0); 923 newList.sort((a, b) => a - b); 924 925 return newList[0].Value; 926 927 }, 928 getHighestValue(options) { 929 const newList = options.filter(x => x.Count && x.Count > 0); 930 newList.sort((a, b) => a - b); 931 932 return newList.at(-1).Value; 933 }, 934 favoriteRemove(productNumber) { 935 if (this.ongoingOperations[productNumber]) { 936 return; 937 } 938 this.ongoingOperations[productNumber] = true; 939 let url = location.protocol + '//' + location.host; //RemoveProductFromFavoriteList 940 url += "?FavoriteCmd=RemoveProductFromFavoriteList&ProductId=" + productNumber; 941 fetch(url).then(response => { 942 document.getElementById("favoriteRemove-" + productNumber).style = "display:none" 943 document.getElementById("favoriteAdd-" + productNumber).style = ""; 944 945 let favorite = document.getElementsByClassName("favorite-qty"); 946 if (favorite.length == 1) { 947 let qty = favorite[0]; 948 let currentCount = Number(qty.getAttribute("data-count")); 949 let count = currentCount === 0 ? currentCount : currentCount - 1; 950 qty.setAttribute("data-count", count) 951 qty.innerHTML = count; 952 953 } 954 }).finally(() => { 955 this.ongoingOperations[productNumber] = false; // Reset the flag when the operation is complete 956 }); 957 }, 958 favoriteAdd(productNumber) { 959 if (this.ongoingOperations[productNumber]) { 960 return; 961 } 962 this.ongoingOperations[productNumber] = true; 963 let url = location.protocol + '//' + location.host; 964 url += "?FavoriteCmd=addproducttofavoritelist&ProductId=" + productNumber; 965 let loc = location; 966 fetch(url).then(response => { 967 if ('@shouldRefreshFavorite' === 'true') { 968 loc.reload() 969 } else { 970 document.getElementById("favoriteAdd-" + productNumber).style = "display:none" 971 document.getElementById("favoriteRemove-" + productNumber).style = ""; 972 //simple just assume it went ok 973 let favorite = document.getElementsByClassName("favorite-qty"); 974 if (favorite.length == 1) { 975 let qty = favorite[0]; 976 let count = Number(qty.getAttribute("data-count")) + 1; 977 qty.setAttribute("data-count", count) 978 qty.innerHTML = count; 979 980 } 981 } 982 }).finally(() => { 983 this.ongoingOperations[productNumber] = false; // Reset the flag when the operation is complete 984 }); 985 }, 986 setElementDisplay(elementId, display = "") { 987 const element = document.getElementById(elementId); 988 if (element) { 989 element.style.display = display; 990 } 991 }, 992 doesCookieExist(cookieName) { 993 const cookies = document.cookie.split('; '); 994 const cookieExists = cookies.some(cookie => cookie.startsWith(cookieName + '=')); 995 return cookieExists; 996 }, 997 setCookie(name, value, days) { 998 let expires = ""; 999 if (days) { 1000 const date = new Date(); 1001 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 1002 expires = "; expires=" + date.toUTCString(); 1003 } 1004 document.cookie = name + "=" + (value || "") + expires + "; path=/"; 1005 }, 1006 deleteCookie(name) { 1007 document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 1008 }, 1009 getVariants: async function(productNo, url) { 1010 this.loadingVariants = true; 1011 return new Promise((resolve, reject) => { 1012 let requestUrl = `@(VariantsLookup)&ICC_itemId=${productNo}`; 1013 if (url && url != "") { 1014 requestUrl = url; 1015 } 1016 if (!this.isLoggedIn) { 1017 if (requestUrl.indexOf('username') < 0) { 1018 requestUrl += '&username=marketing@keflico.com&password=Keflico100%'; 1019 } 1020 if (!this.doesCookieExist('tempLogin')) { 1021 this.setCookie('tempLogin', true, 1); 1022 } 1023 } 1024 var credentials = this.isLoggedIn ? "same-origin" : "omit" 1025 fetch(requestUrl, { 1026 credentials: credentials 1027 }) 1028 .then(response => response.json()) 1029 .then(response => { 1030 if (response.variants && response.variants.length > 0) { // Check if there are any variants 1031 this.variants = this.variants.concat(response.variants); 1032 if (response.nextPage != "") { 1033 this.getVariants(productNo, response.nextPage).then(resolve); 1034 } else { 1035 resolve(); // Resolve the promise when the last page is fetched 1036 } 1037 } else { // There are no variants, so we check the stock directly 1038 var product = allProductNumbers.find(p => p.id === productNo); 1039 if (product) { 1040 // Product found, so we'll continue checking 1041 if (product.totalStockWareHouse == 0 && product.totalStockSea == 0) { 1042 // If both stocks are 0, we show the request product text 1043 this.setElementDisplay(`RequestProductText_${productNo}`); 1044 } else { 1045 // We have at least SOME stock, so we check which stock is available and show the appropriate texts 1046 if (product.totalStockWareHouse > 0) { 1047 this.setElementDisplay(`StockOnWarehouse_OnStock_${productNo}`); 1048 } else { 1049 this.setElementDisplay(`StockOnWarehouse_NotOnStock_${productNo}`); 1050 } 1051 if (product.totalStockSea > 0) { 1052 this.setElementDisplay(`StockOnSea_InRoute_${productNo}`); 1053 } else { 1054 this.setElementDisplay(`StockOnSea_NotInRoute_${productNo}`); 1055 } 1056 } 1057 } 1058 else { 1059 // Product not found, so we show the request product text 1060 this.setElementDisplay(`RequestProductText_${productNo}`); 1061 1062 } 1063 // Hide the loading skeletons for the element (we do it here, so it doesnt stay visible, before isLoading is set to false in afterFetchingVariants) 1064 Array.from(document.getElementsByClassName(`skeleton_${productNo}`) || []).forEach(el => el && (el.style.display = "none")); 1065 resolve(); 1066 } 1067 }) 1068 .catch(error => { 1069 console.error("Error fetching variants:", error); 1070 reject(error); // Reject the promise if there's an error 1071 }); 1072 }); 1073 }, 1074 afterFetchingVariants() { 1075 var allProductNumbers = @allProductNumbersString; 1076 // Code to execute after all variants have been fetched 1077 //console.debug("All variants have been fetched!"); 1078 1079 // Example: Group by id prefix or perform other operations 1080 const groupedStock = {}; 1081 this.variants.forEach(variant => { 1082 const idPrefix = variant.id.split('_')[0]; 1083 if (!groupedStock[idPrefix]) { 1084 groupedStock[idPrefix] = []; 1085 } 1086 groupedStock[idPrefix].push({ 1087 warehouse: variant.stock.warehouse, 1088 sea: variant.stock.sea, 1089 purchase: variant.stock.purchase 1090 }); 1091 }); 1092 1093 for (const idPrefix in groupedStock) { 1094 if (groupedStock.hasOwnProperty(idPrefix)) { 1095 //console.log("ID Prefix:", idPrefix); 1096 var totalWarehouse = 0; 1097 var totalSea = 0; 1098 1099 // Access the array of stock objects for this group 1100 const stocks = groupedStock[idPrefix]; 1101 1102 const productObject = allProductNumbers.find(product => product.id === idPrefix); 1103 const group = productObject.primaryGroup; 1104 1105 // Loop through the array of stock entries for this idPrefix 1106 stocks.forEach(stock => { 1107 //console.log("Warehouse:", stock.warehouse, "Sea:", stock.sea); 1108 totalSea += stock.sea 1109 totalWarehouse += stock.warehouse 1110 1111 if (group != "group32") { 1112 totalSea += Number(stock.purchase) 1113 totalWarehouse += Number(stock.purchase) 1114 } 1115 1116 }); 1117 1118 if (totalSea == 0 && totalWarehouse == 0) { 1119 document.getElementById(`RequestProductText_${idPrefix}`).style.display = ""; 1120 } 1121 else { 1122 1123 if (totalWarehouse > 0) { 1124 if (document.getElementById(`StockOnWarehouse_OnStock_${idPrefix}`) != null) 1125 document.getElementById(`StockOnWarehouse_OnStock_${idPrefix}`).style.display = ""; 1126 1127 } else { 1128 if (document.getElementById(`StockOnWarehouse_NotOnStock_${idPrefix}`) != null) 1129 document.getElementById(`StockOnWarehouse_NotOnStock_${idPrefix}`).style.display = ""; 1130 1131 1132 } 1133 1134 if (totalSea > 0) { 1135 if (document.getElementById(`StockOnSea_InRoute_${idPrefix}`) != null) 1136 document.getElementById(`StockOnSea_InRoute_${idPrefix}`).style.display = ""; 1137 1138 1139 } else { 1140 if (document.getElementById(`StockOnSea_NotInRoute_${idPrefix}`) != null) 1141 document.getElementById(`StockOnSea_NotInRoute_${idPrefix}`).style.display = ""; 1142 1143 } 1144 } 1145 1146 1147 } 1148 } 1149 } 1150 }, 1151 watch: { 1152 1153 } 1154 }); 1155 1156 1157 function setUpFilters() { 1158 const filterSection = document.querySelector('.filter-section'); 1159 const sorting = document.getElementById('sorting'); 1160 1161 if (filterSection) { 1162 const applyBtn = document.getElementById('applyFilterButton'); 1163 const clearBtn = document.getElementById('clearFilterButton'); 1164 1165 const filters = filterSection.querySelectorAll('.filter-option'); 1166 setupFromQueryString(); 1167 1168 Array.from(filters).forEach(filter => { 1169 const filterName = filter.getAttribute('data-name'); 1170 1171 filter.addEventListener('input', event => { 1172 clearTimeout(delay); 1173 1174 switch (filter.type) { 1175 case 'range': 1176 activeFilters[filterName] = filter.value; 1177 break; 1178 1179 case 'checkbox': 1180 default: 1181 if (filter.checked) { 1182 if (activeFilters[filterName]) { 1183 activeFilters[filterName].push(filter.value); 1184 } else { 1185 activeFilters[filterName] = [filter.value]; 1186 } 1187 } else { 1188 if (activeFilters[filterName]) { 1189 const valueIndex = activeFilters[filterName].indexOf(filter.value); 1190 activeFilters[filterName].splice(valueIndex, 1); 1191 1192 if (activeFilters[filterName].length == 0) { 1193 delete activeFilters[filterName]; 1194 } 1195 } 1196 } 1197 break; 1198 } 1199 }); 1200 }); 1201 1202 clearBtn.addEventListener('click', event => { 1203 if (activeFilters['Sortby']) { 1204 const tempBy = activeFilters['Sortby']; 1205 const tempOrder = activeFilters['SortOrder']; 1206 1207 activeFilters = {}; 1208 1209 activeFilters['Sortby'] = tempBy; 1210 activeFilters['SortOrder'] = tempOrder; 1211 } else { 1212 activeFilters = {}; 1213 } 1214 1215 buildQueryString(); 1216 }); 1217 1218 applyBtn.addEventListener('click', buildQueryString); 1219 } 1220 1221 if (sorting) { 1222 sorting.addEventListener('change', event => { 1223 const sortingValue = sorting.value; 1224 1225 if (sortingValue == 'default') { 1226 delete activeFilters['Sortby']; 1227 delete activeFilters['SortOrder']; 1228 } else { 1229 activeFilters['Sortby'] = 'NameForSort'; 1230 activeFilters['SortOrder'] = sortingValue.toUpperCase(); 1231 } 1232 1233 buildQueryString(); 1234 }); 1235 } 1236 } 1237 1238 function buildQueryString() { 1239 // Create a new object to hold the processed filters 1240 const processedFilters = {}; 1241 1242 // Process each filter 1243 Object.keys(activeFilters).forEach(key => { 1244 if (activeFilters[key] && activeFilters[key].length > 0) { 1245 // Get the first input with this key to check if it's a numeric filter 1246 const input = document.querySelector(`[data-name="${key}"]`); 1247 const isNumeric = input && input.getAttribute('data-parameter-type') === 'System.Double[]'; 1248 processedFilters[key] = activeFilters[key].join(','); 1249 } 1250 }); 1251 1252 // Build the query string 1253 const queryString = new URLSearchParams(processedFilters).toString(); 1254 const currentPath = window.location.pathname; 1255 1256 if (queryString.length > 0) { 1257 window.location.href = `${currentPath}?${queryString.replace(/PageNum=\d+&/g, '')}`; 1258 } else { 1259 window.location.href = currentPath; 1260 } 1261 } 1262 1263 function setupFromQueryString() { 1264 const urlSearchParams = new URLSearchParams(window.location.search); 1265 const params = Object.fromEntries(urlSearchParams.entries()); 1266 1267 Object.keys(params).forEach(key => { 1268 const value = params[key]; 1269 if (!activeFilters[key]) { 1270 activeFilters[key] = []; 1271 } 1272 1273 // Get all inputs with this key to check if it's a numeric filter 1274 const inputsWithKey = document.querySelectorAll(`[data-name="${key}"]`); 1275 const isNumeric = inputsWithKey.length > 0 && 1276 inputsWithKey[0].getAttribute('data-parameter-type') === 'System.Double[]'; 1277 1278 // Get all possible numeric values from the filter options 1279 const allNumericOptions = Array.from(inputsWithKey).map(input => input.value); 1280 1281 // For numeric filters, we need special handling 1282 if (isNumeric) { 1283 // First, split the value into parts 1284 const parts = value.split(','); 1285 const processedIndices = new Set(); // Track which parts we've processed 1286 1287 // Check for decimal values first (values that contain a comma in our options) 1288 for (let i = 0; i < parts.length - 1; i++) { 1289 if (processedIndices.has(i)) continue; // Skip if already processed 1290 1291 const potentialDecimal = parts[i] + ',' + parts[i + 1]; 1292 1293 // If this combination exists as an option, add it 1294 if (allNumericOptions.includes(potentialDecimal)) { 1295 activeFilters[key].push(potentialDecimal); 1296 processedIndices.add(i); 1297 processedIndices.add(i + 1); 1298 } 1299 } 1300 1301 // Now add any remaining individual values 1302 parts.forEach((part, index) => { 1303 if (!processedIndices.has(index) && allNumericOptions.includes(part)) { 1304 activeFilters[key].push(part); 1305 } 1306 }); 1307 } else { 1308 // For non-numeric values, keep the original behavior 1309 if (value.indexOf(',') > -1) { 1310 value.split(',').forEach(entry => { 1311 activeFilters[key].push(entry); 1312 }); 1313 } else { 1314 activeFilters[key].push(value); 1315 } 1316 } 1317 1318 // Update the checkboxes based on active filters 1319 if (key != 'GroupID' && key != 'PageNum' && key != 'Sortby' && key != 'SortOrder') { 1320 Array.from(inputsWithKey).forEach(input => { 1321 switch (input.type) { 1322 case 'range': 1323 input.value = value; 1324 break; 1325 case 'checkbox': 1326 default: 1327 // Check if the input value is in the active filters 1328 if (activeFilters[key].includes(input.value)) { 1329 input.checked = true; 1330 const toggle = input.closest('.filter-item')?.querySelector('.filter-item__checkbox-toggle'); 1331 if (toggle) toggle.checked = true; 1332 } 1333 break; 1334 } 1335 }); 1336 } 1337 }); 1338 } 1339 1340 function setupSearchSelects() { 1341 if (document.querySelector('.search-select')) { 1342 // Add CSS for hiding partial matches 1343 const style = document.createElement('style'); 1344 style.textContent = ` 1345 .search-select__tags label.partial-match { 1346 display: none !important; 1347 } 1348 `; 1349 document.head.appendChild(style); 1350 1351 Array.from(document.querySelectorAll('.search-select')).forEach(select => { 1352 const input = select.querySelector('.search-select__search-input'); 1353 const dropdown = select.querySelector('.search-select__dropdown'); 1354 const options = select.querySelectorAll('.search-select__tags input'); 1355 const optionsLabels = select.querySelectorAll('.search-select__dropdown-item'); 1356 const clearBtn = select.querySelector('.search-select__search-clear'); 1357 1358 // Determine if this is a numeric filter based only on parameter type 1359 const isNumeric = options.length > 0 && 1360 options[0].getAttribute('data-parameter-type') == 'System.Double[]'; 1361 1362 input.addEventListener('focus', open); 1363 clearBtn.addEventListener('click', clear); 1364 input.addEventListener('input', search); 1365 1366 // Fix the visual representation of selected tags 1367 function updateVisualTags() { 1368 // Only process numeric filters 1369 if (!isNumeric) return; 1370 1371 // Get the actual selected values from the URL 1372 const urlSearchParams = new URLSearchParams(window.location.search); 1373 const params = Object.fromEntries(urlSearchParams.entries()); 1374 1375 if (options.length > 0) { 1376 const filterName = options[0].getAttribute('data-name'); 1377 1378 // Only process if this filter is in the URL 1379 if (params[filterName]) { 1380 // For numeric filters, don't split the value 1381 const selectedValues = [params[filterName]]; 1382 1383 // Remove partial-match class from all labels 1384 select.querySelectorAll('.search-select__tags label').forEach(label => { 1385 label.classList.remove('partial-match'); 1386 }); 1387 1388 // For each selected value, mark exact matches 1389 selectedValues.forEach(selectedValue => { 1390 Array.from(options).forEach(option => { 1391 // For numeric filters, we want exact matches only 1392 if (option.value === selectedValue) { 1393 const label = select.querySelector(`label[for="${option.id}"]`); 1394 if (label) { 1395 label.classList.add('selected'); 1396 } 1397 } 1398 }); 1399 }); 1400 } 1401 } 1402 } 1403 1404 // Call this when page loads 1405 updateVisualTags(); 1406 1407 Array.from(options).forEach(option => { 1408 option.addEventListener('change', e => { 1409 if (isNumeric && option.checked) { 1410 // When a numeric option is checked, hide partial matches 1411 const selectedValue = option.value; 1412 Array.from(options).forEach(otherOption => { 1413 if (otherOption !== option) { 1414 const isPartialMatch = selectedValue.includes(otherOption.value) && 1415 selectedValue !== otherOption.value; 1416 1417 if (isPartialMatch) { 1418 const label = select.querySelector(`label[for="${otherOption.id}"]`); 1419 if (label) { 1420 label.classList.add('partial-match'); 1421 } 1422 } 1423 } 1424 }); 1425 } 1426 }); 1427 }); 1428 1429 function clear() { 1430 dropdown.classList.remove('search-select__dropdown--active'); 1431 clearBtn.classList.remove('search-select__search-clear--active'); 1432 input.value = ''; 1433 resetList(); 1434 } 1435 1436 function open() { 1437 dropdown.classList.add('search-select__dropdown--active'); 1438 clearBtn.classList.add('search-select__search-clear--active'); 1439 } 1440 1441 function search() { 1442 const searchTerm = input.value; 1443 1444 if (searchTerm.length > 0) { 1445 const searchWords = searchTerm.trim().split(' '); 1446 1447 Array.from(optionsLabels).forEach(option => { 1448 let hasMatch = false; 1449 1450 searchWords.forEach(word => { 1451 const searchRegEx = new RegExp(word, 'ig'); 1452 const matches = option.innerText.match(searchRegEx); 1453 1454 if (matches && matches.length > 0) { 1455 hasMatch = true; 1456 option.innerHTML = option.innerHTML.replaceAll(/\<span class\=\"js-highlight\"\>(.*?)\<\/span\>/gi, '$1').replaceAll(searchRegEx, `<span class="js-highlight">${matches[0]}</span>`); 1457 } 1458 }); 1459 1460 if (!hasMatch) { 1461 option.classList.add('js-no-match'); 1462 } else { 1463 option.classList.remove('js-no-match'); 1464 } 1465 }); 1466 } else { 1467 resetList(); 1468 } 1469 } 1470 1471 function resetList() { 1472 Array.from(optionsLabels).forEach(option => { 1473 option.classList.remove('js-no-match'); 1474 option.innerHTML = option.innerText; 1475 }); 1476 } 1477 }); 1478 } 1479 } 1480 1481 function setupDuoRangeSliders() { 1482 if (document.querySelector('.duo-range-slider')) { 1483 Array.from(document.querySelectorAll('.duo-range-slider')).forEach(rangeSlider => { 1484 Array.from(rangeSlider.querySelectorAll('.duo-range-slider__range')).forEach(slider => { 1485 slider.oninput = updateValues; 1486 slider.oninput(); 1487 }); 1488 1489 function updateValues() { 1490 const parent = this.parentNode; 1491 const slides = parent.getElementsByTagName('input'); 1492 let slide1 = parseFloat(slides[0].value); 1493 let slide2 = parseFloat(slides[1].value); 1494 const displayMin = parent.querySelector('.duo-range-slider__values-min span'); 1495 const displayMax = parent.querySelector('.duo-range-slider__values-max span'); 1496 1497 if (slide1 > slide2) { 1498 const temp = slide2; 1499 1500 slide2 = slide1; 1501 slide1 = temp; 1502 } 1503 1504 displayMin.innerText = slide1; 1505 displayMax.innerText = slide2; 1506 } 1507 }); 1508 } 1509 } 1510 </script> 1511 } 1512 1513 @{ 1514 string groupSeoText = GetString("Ecom:Group:Field.SEOText"); 1515 1516 if (!string.IsNullOrWhiteSpace(groupSeoText)) 1517 { 1518 <article class="module module-sand-light"> 1519 <div class="rich-text"> 1520 @groupSeoText 1521 </div> 1522 </article> 1523 } 1524 } 1525 1526 @functions { 1527 Dynamicweb.Ecommerce.Products.Group FindTopGroup(Dynamicweb.Ecommerce.Products.Group group) 1528 { 1529 if (group.IsTopGroup) 1530 { 1531 return group; 1532 } 1533 else if (group.ParentGroups != null && group.ParentGroups.Count > 0) 1534 { 1535 foreach (var parentGroup in group.ParentGroups) 1536 { 1537 Dynamicweb.Ecommerce.Products.Group topLevelGroup = FindTopGroup(parentGroup); 1538 if (topLevelGroup != null) 1539 { 1540 return topLevelGroup; 1541 } 1542 } 1543 } 1544 return null; 1545 } 1546 }
Template:BaseUrlSystem.String/Files/Templates/Designs/Keflico/QueryPublisher/
Template:DesignBaseUrlSystem.String/Files/Templates/Designs/Keflico/
Loops

Facet Groups

Parameters

QueryResult

Page of
TemplateTags() in code (Designs\Keflico\QueryPublisher/List.cshtml). Remove before going live...