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