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_1f04c9eb69c444a08e332bd0fbbfe930.FindTopGroup(Group group) in D:\dynamicweb.net\Solutions\keflico.live\Files\Templates\Designs\Keflico\eCom\Productlist\products.cshtml:line 1575
   at CompiledRazorTemplates.Dynamic.RazorEngine_1f04c9eb69c444a08e332bd0fbbfe930.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 case KeflicoProductExtensions.SampleProductLayout: 548 primaryPriceDescription = Translate("Translate_ProductSamplePriceDescription"); 549 break; 550 551 default: 552 primaryPriceDescription = Translate("Translate_ProductDecription_General_Description"); 553 break; 554 } 555 556 <li class="card-list__card product-card"> 557 558 <a href="@link" class="card__wrapper"> 559 560 @switch (labelCode.ToLower()) 561 { 562 case "skaffe": 563 case "relatordre": 564 <div class="card-list__label-code label-code">@Translate("LabelCode_" + labelCode.ToLower())</div> 565 break; 566 default: 567 break; 568 } 569 570 571 @if (isSample) 572 { 573 <div class="card-list__label-code label-code label-code--samples">@Translate("LabelCode_proeve")</div> 574 } 575 576 @if (!String.IsNullOrWhiteSpace(productImage)) 577 { 578 <picture class="card__picture"> 579 <source srcset="@productImageLg" media="(min-width: 1536px)"> 580 <source srcset="@productImageMd" media="(min-width: 992px)"> 581 <source srcset="@productImageSm" media="(min-width: 768px)"> 582 <img src="@productImageDefault" alt="@name.Replace("\"", "&quot;")"> 583 @if (isloggedin) 584 { 585 <button id="favoriteAdd-@productNumber" style="@(isFavorite ? "display:none" : "")" class="card-favorite" @@click.prevent="favoriteAdd('@productNumber')"> 586 <svg id="Lag_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"> 587 <!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 137) --> 588 <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" /> 589 </svg> 590 591 </button> 592 <button id="favoriteRemove-@productNumber" style="@(isFavorite ? "" : "display:none")" class="card-favorite card-favorite--filled" @@click.prevent="favoriteRemove('@productNumber')"> 593 <svg id="Lag_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100"> 594 <!-- Generator: Adobe Illustrator 29.5.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 137) --> 595 <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" /> 596 </svg> 597 </button> 598 } 599 600 </picture> 601 } 602 else 603 { 604 <div class="card__picture card__picture--dummie"> 605 @if (isloggedin) 606 { 607 <button id="favoriteAdd-@productNumber" style="@(isFavorite ? "display:none" : "")" class="card-favorite" @@click.prevent="favoriteAdd('@productNumber')"> 608 <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"> 609 <g fill="gray"> 610 <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" /> 611 </g> 612 </svg> 613 </button> 614 <button id="favoriteRemove-@productNumber" style="@(isFavorite ? "" : "display:none")" class="card-favorite card-favorite--filled" @@click.prevent="favoriteRemove('@productNumber')"> 615 <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 107.39"> 616 <defs> 617 618 </defs> 619 <title>red-heart</title> 620 <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" /> 621 </svg> 622 </button> 623 } 624 625 626 </div> 627 } 628 629 <div class="card__info-list__item"> 630 <span class="info info--small info--header">@Translate("Translate_Product_Page_ProductNumber"): @productNumber</span> 631 @if (!string.IsNullOrWhiteSpace(dbNumber)) 632 { 633 <span class="info info--small info--header">@dbNumberLabel: @dbNumber</span> 634 } 635 </div> 636 <div class="card__info-list__item "> 637 <span class="info info--title">@name</span> 638 </div> 639 <div class="card__info-list__item"> 640 <span class="info info--small product-teaser">@teaser</span> 641 </div> 642 @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"))) 643 { 644 <div class="card__info-list__item column"> 645 @if (usePriceExplanation) 646 { 647 <div class="price-line price-line--explanation"> 648 @Translate("Translate_PriceExplanation_" + primaryGroup) 649 </div> 650 } 651 @if (!String.IsNullOrWhiteSpace(primaryPrice) && primaryPrice != "0,00" && primaryPrice != ",00") 652 { 653 <div class="price-line"> 654 <div class="definition">@primaryPriceDescription</div> 655 <div class="price">@primaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 656 </div> 657 } 658 659 @if (!String.IsNullOrWhiteSpace(secondaryPrice) && secondaryPrice != "0,00" && secondaryPrice != ",00") 660 { 661 <div class="price-line"> 662 <div class="definition">@secondaryPriceDescription</div> 663 <div class="price">@secondaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 664 </div> 665 } 666 667 @if (!String.IsNullOrWhiteSpace(tertiaryPrice) && tertiaryPrice != "0,00" && tertiaryPrice != ",00") 668 { 669 <div class="price-line"> 670 <div class="definition">@tertiaryPriceDescription</div> 671 <div class="price">@tertiaryPrice <text> </text> @priceCurrencySymbol <text>pr.</text> @salesUnit</div> 672 </div> 673 } 674 </div> 675 } 676 @*@if (isSingleVariant) 677 { 678 <div class="card__info-list__item"> 679 <span class="info info--small" style="font-size: 15px;margin-top:15px;"> 680 @if (totalStockWareHouse > 0) 681 { 682 <text> 683 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#12ca30"> 684 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 685 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 686 <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> 687 </svg> 688 @Translate("Translate_ProductPage_StockOnWarehouse_OnStock") 689 </text> 690 } 691 else 692 { 693 <text> 694 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> 695 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 696 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 697 <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> 698 </svg> 699 @Translate("Translate_ProductPage_StockOnWarehouse_NotOnStock") 700 </text> 701 } 702 703 @if (totalStockSea > 0) 704 { 705 <text> 706 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#12ca30"> 707 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 708 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 709 <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> 710 </svg> 711 @Translate("Translate_ProductPage_StockOnSea_InRoute") 712 </text> 713 } 714 else 715 { 716 <text> 717 <svg width="30px" height="30px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"> 718 <g id="SVGRepo_bgCarrier" stroke-width="0" /> 719 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" /> 720 <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> 721 </svg> 722 @Translate("Translate_ProductPage_StockOnSea_NotInRoute") 723 </text> 724 } 725 726 </span> 727 </div> 728 } 729 else 730 {*@ 731 732 @if (!isSample) 733 { 734 <div class="card__info-list__item"> 735 <span class="info info--small info--flex-wrap"> 736 <div style="display:none;" id="RequestProductText_@(product.GetString("Ecom:Product.ID"))"> 737 <div style="display: inline-flex; align-items: center;"> 738 <span style="width: 8px; height: 8px; background-color: orange; border-radius: 50%; margin-right: 8px;"></span> 739 @Translate("Translate_ProductPage_RequestProduct") 740 </div> 741 </div> 742 <div id="StockOnWarehouse_OnStock_@(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="#24a84b" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z" /></svg> 744 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnWarehouse_OnStock")</span> 745 </div> 746 <div id="StockOnWarehouse_NotOnStock_@(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="#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> 748 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnWarehouse_NotOnStock")</span> 749 </div> 750 <div id="StockOnSea_InRoute_@(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="#24a84b" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z" /></svg> 752 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnSea_InRoute")</span> 753 </div> 754 <div id="StockOnSea_NotInRoute_@(product.GetString("Ecom:Product.ID"))" class="info--stock" style="display:none;"> 755 <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> 756 <span class="info--stock--text">@Translate("Translate_ProductPage_StockOnSea_NotInRoute")</span> 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 <div class="info--stock skeleton_@(product.GetString("Ecom:Product.ID"))" v-if="isLoading"> 763 <div class="info--stock--skeleton--icon"></div> 764 <div class="info--stock--skeleton--text"></div> 765 </div> 766 </span> 767 </div> 768 } 769 770 771 @*}*@ 772 773 </a> 774 </li> 775 } 776 </ul> 777 @if (GetInteger("Ecom:ProductList.TotalPages") > 1) 778 { 779 string prevClass = ""; 780 string nextClass = ""; 781 string extraPaginationClass = ""; 782 783 if (String.IsNullOrWhiteSpace(GetString("Ecom:ProductList.PrevPage.Clean"))) 784 { 785 prevClass = " disabled"; 786 } 787 788 if (String.IsNullOrWhiteSpace(GetString("Ecom:ProductList.NextPage.Clean"))) 789 { 790 nextClass = " disabled"; 791 } 792 793 if (GetInteger("Ecom:ProductList.TotalPages") > 5) 794 { 795 extraPaginationClass = "navigation__pagination--overload"; 796 } 797 798 <div class="card-list__footer"> 799 <div class="card-list__navigation"> 800 <a href="@GetString("Ecom:ProductList.PrevPage.Clean")" class="navigation__arrow back@(prevClass)"> 801 <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> 802 </a> 803 804 <div class="navigation__pagination @extraPaginationClass"> 805 <span class="navigation__pagination__title"> @Translate("Translate_ProductList_BreadCrumbPage") </span> 806 @for (int i = 0; i < GetInteger("Ecom:ProductList.TotalPages"); i++) 807 { 808 string currentClass = (i + 1) == GetInteger("Ecom:ProductList.CurrentPage") ? " active" : ""; 809 string url = GetString("Ecom:Group.Link.Clean") + "&PageNum=" + (i + 1); 810 811 foreach (var query in GetLoop("Query.Parameters")) 812 { 813 if (!String.IsNullOrWhiteSpace(query.GetString("Parameter.Value"))) 814 { 815 url += "&" + query.GetString("Parameter.Name") + "=" + query.GetString("Parameter.Value"); 816 } 817 } 818 819 if (!isStandardSorting) 820 { 821 url += "&Sortby=NameForSort" + "&SortOrder=" + sortOrder; 822 } 823 824 <a href="@url" class="navigation__pagination__link@(currentClass)">@(i + 1)</a> 825 } 826 </div> 827 <a href="@GetString("Ecom:ProductList.NextPage.Clean")" class="navigation__arrow forward@(nextClass)"> 828 <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> 829 </a> 830 </div> 831 </div> 832 } 833 </article> 834 835 </section> 836 <script async type="module"> 837 let activeFilters = {}; 838 let delay; 839 var allProductNumbers = @allProductNumbersString; 840 841 842 new Vue({ 843 el: '#productsFlowApp', 844 name: 'Products Flow', 845 components: { 846 847 }, 848 computed: { 849 850 }, 851 mounted() { 852 var allProductNumbers = @allProductNumbersString; 853 const fetchPromises = []; 854 855 for (const productNumberObjects of allProductNumbers) { 856 fetchPromises.push(this.getVariants(productNumberObjects.id)); 857 } 858 859 // Wait for all fetch operations to complete 860 Promise.all(fetchPromises) 861 .then(() => { 862 // This block will run after all variants have been successfully fetched 863 this.isLoading = false; 864 this.afterFetchingVariants(); 865 }); 866 867 @*@if(primaryGroup == "group1") { 868 <text> 869 this.lookUpBundle(@(productNumber)); 870 </text> 871 } 872 873 this.getAccessories(); 874 875 if(!this.enquireProduct && this.isLoggedIn) { 876 this.orderButtonSpinner = setTimeout(() => { 877 document.querySelector('#product-order-button .loader').style.display = "inline-block"; 878 }, 3000); 879 } else { 880 document.querySelector('#product-order-button .product-order-form-button').style.display = "block"; 881 }*@ 882 }, 883 data() { 884 return { 885 isLoggedIn: @isloggedin.ToString().ToLower(), 886 variants: [], 887 isLoading: true, 888 ongoingOperations: {}, 889 890 showFilters: false, 891 facets: null, 892 translations: null, 893 allProducts: false, 894 currentGroup: '', 895 } 896 }, 897 created() { 898 this.allProducts = (document.getElementById('productFilters').getAttribute('data-all') 899 .toLowerCase() === 'true'); 900 901 if (!this.allProducts) { 902 this.currentGroup = `&Group=${document.getElementById('productFilters').getAttribute('data-top-group')}&GroupID=${document.getElementById('productFilters').getAttribute('data-group')}`; 903 } else { 904 const urlSearchParams = new URLSearchParams(window.location.search); 905 const params = Object.fromEntries(urlSearchParams.entries()); 906 907 let group = ''; 908 909 if (params.Group) { 910 group = `&Group=${params.Group}`; 911 } 912 913 this.currentGroup = group; 914 } 915 916 fetch('/dwapi/translations/area/1') 917 .then(response => response.json()) 918 .then(response => { 919 this.translations = response; 920 }) 921 .catch(error => { 922 console.log(error); 923 }); 924 925 fetch(`/dwapi/ecommerce/products/search?RepositoryName=Products&QueryName=FilterQuery&ProductSettings.FilledProperties=Name&FilledProperties=Products,FacetGroups,TotalProductsCount${this.currentGroup}`) 926 .then(response => response.json()) 927 .then(response => { 928 this.facets = response.FacetGroups[0].Facets; 929 this.showFilters = true; 930 931 this.$nextTick(() => { 932 setUpFilters(); 933 setupSearchSelects(); 934 setupDuoRangeSliders(); 935 }); 936 }); 937 }, 938 methods: { 939 addition(paramType) { 940 let addition = ''; 941 942 if (paramType == 'System.String[]' || paramType == 'System.Double[]') { 943 addition = '[]'; 944 } 945 946 return addition; 947 }, 948 translation(key) { 949 return this.translations.find(x => x.Key == key) ? this.translations.find(x => x.Key == key).Value : key; 950 }, 951 sortedOptions(options) { 952 const newList = options.filter(x => x.Count && x.Count > 0 && parseInt(x.Value) != -1); 953 return newList; 954 }, 955 getLowestValue(options) { 956 const newList = options.filter(x => x.Count && x.Count > 0); 957 newList.sort((a, b) => a - b); 958 959 return newList[0].Value; 960 961 }, 962 getHighestValue(options) { 963 const newList = options.filter(x => x.Count && x.Count > 0); 964 newList.sort((a, b) => a - b); 965 966 return newList.at(-1).Value; 967 }, 968 favoriteRemove(productNumber) { 969 if (this.ongoingOperations[productNumber]) { 970 return; 971 } 972 this.ongoingOperations[productNumber] = true; 973 let url = location.protocol + '//' + location.host; //RemoveProductFromFavoriteList 974 url += "?FavoriteCmd=RemoveProductFromFavoriteList&ProductId=" + productNumber; 975 fetch(url).then(response => { 976 document.getElementById("favoriteRemove-" + productNumber).style = "display:none" 977 document.getElementById("favoriteAdd-" + productNumber).style = ""; 978 979 let favorite = document.getElementsByClassName("favorite-qty"); 980 if (favorite.length == 1) { 981 let qty = favorite[0]; 982 let currentCount = Number(qty.getAttribute("data-count")); 983 let count = currentCount === 0 ? currentCount : currentCount - 1; 984 qty.setAttribute("data-count", count) 985 qty.innerHTML = count; 986 987 } 988 }).finally(() => { 989 this.ongoingOperations[productNumber] = false; // Reset the flag when the operation is complete 990 }); 991 }, 992 favoriteAdd(productNumber) { 993 if (this.ongoingOperations[productNumber]) { 994 return; 995 } 996 this.ongoingOperations[productNumber] = true; 997 let url = location.protocol + '//' + location.host; 998 url += "?FavoriteCmd=addproducttofavoritelist&ProductId=" + productNumber; 999 let loc = location; 1000 fetch(url).then(response => { 1001 if ('@shouldRefreshFavorite' === 'true') { 1002 loc.reload() 1003 } else { 1004 document.getElementById("favoriteAdd-" + productNumber).style = "display:none" 1005 document.getElementById("favoriteRemove-" + productNumber).style = ""; 1006 //simple just assume it went ok 1007 let favorite = document.getElementsByClassName("favorite-qty"); 1008 if (favorite.length == 1) { 1009 let qty = favorite[0]; 1010 let count = Number(qty.getAttribute("data-count")) + 1; 1011 qty.setAttribute("data-count", count) 1012 qty.innerHTML = count; 1013 1014 } 1015 } 1016 }).finally(() => { 1017 this.ongoingOperations[productNumber] = false; // Reset the flag when the operation is complete 1018 }); 1019 }, 1020 setElementDisplay(elementId, display = "") { 1021 const element = document.getElementById(elementId); 1022 if (element) { 1023 element.style.display = display; 1024 } 1025 }, 1026 doesCookieExist(cookieName) { 1027 const cookies = document.cookie.split('; '); 1028 const cookieExists = cookies.some(cookie => cookie.startsWith(cookieName + '=')); 1029 return cookieExists; 1030 }, 1031 setCookie(name, value, days) { 1032 let expires = ""; 1033 if (days) { 1034 const date = new Date(); 1035 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 1036 expires = "; expires=" + date.toUTCString(); 1037 } 1038 document.cookie = name + "=" + (value || "") + expires + "; path=/"; 1039 }, 1040 deleteCookie(name) { 1041 document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 1042 }, 1043 getVariants: async function(productNo, url) { 1044 this.loadingVariants = true; 1045 return new Promise((resolve, reject) => { 1046 let requestUrl = `@(VariantsLookup)&ICC_itemId=${productNo}`; 1047 if (url && url != "") { 1048 requestUrl = url; 1049 } 1050 if (!this.isLoggedIn) { 1051 if (requestUrl.indexOf('username') < 0) { 1052 requestUrl += '&username=marketing@keflico.com&password=Keflico10000%'; 1053 } 1054 if (!this.doesCookieExist('tempLogin')) { 1055 this.setCookie('tempLogin', true, 1); 1056 } 1057 } 1058 var credentials = this.isLoggedIn ? "same-origin" : "omit" 1059 fetch(requestUrl, { 1060 credentials: credentials 1061 }) 1062 .then(response => response.json()) 1063 .then(response => { 1064 if (response.variants && response.variants.length > 0) { // Check if there are any variants 1065 this.variants = this.variants.concat(response.variants); 1066 if (response.nextPage != "") { 1067 this.getVariants(productNo, response.nextPage).then(resolve); 1068 } else { 1069 resolve(); // Resolve the promise when the last page is fetched 1070 } 1071 } else { // There are no variants, so we check the stock directly 1072 var product = allProductNumbers.find(p => p.id === productNo); 1073 if (product) { 1074 // Product found, so we'll continue checking 1075 if (product.totalStockWareHouse == 0 && product.totalStockSea == 0) { 1076 // If both stocks are 0, we show the request product text 1077 this.setElementDisplay(`RequestProductText_${productNo}`); 1078 } else { 1079 // We have at least SOME stock, so we check which stock is available and show the appropriate texts 1080 if (product.totalStockWareHouse > 0) { 1081 this.setElementDisplay(`StockOnWarehouse_OnStock_${productNo}`); 1082 } else { 1083 this.setElementDisplay(`StockOnWarehouse_NotOnStock_${productNo}`); 1084 } 1085 if (product.totalStockSea > 0) { 1086 this.setElementDisplay(`StockOnSea_InRoute_${productNo}`); 1087 } else { 1088 this.setElementDisplay(`StockOnSea_NotInRoute_${productNo}`); 1089 } 1090 } 1091 } 1092 else { 1093 // Product not found, so we show the request product text 1094 this.setElementDisplay(`RequestProductText_${productNo}`); 1095 1096 } 1097 // Hide the loading skeletons for the element (we do it here, so it doesnt stay visible, before isLoading is set to false in afterFetchingVariants) 1098 Array.from(document.getElementsByClassName(`skeleton_${productNo}`) || []).forEach(el => el && (el.style.display = "none")); 1099 resolve(); 1100 } 1101 }) 1102 .catch(error => { 1103 console.error("Error fetching variants:", error); 1104 reject(error); // Reject the promise if there's an error 1105 }); 1106 }); 1107 }, 1108 afterFetchingVariants() { 1109 var allProductNumbers = @allProductNumbersString; 1110 // Code to execute after all variants have been fetched 1111 //console.debug("All variants have been fetched!"); 1112 1113 // Example: Group by id prefix or perform other operations 1114 const groupedStock = {}; 1115 this.variants.forEach(variant => { 1116 const idPrefix = variant.id.split('_')[0]; 1117 if (!groupedStock[idPrefix]) { 1118 groupedStock[idPrefix] = []; 1119 } 1120 groupedStock[idPrefix].push({ 1121 warehouse: variant.stock.warehouse, 1122 sea: variant.stock.sea, 1123 purchase: variant.stock.purchase 1124 }); 1125 }); 1126 1127 for (const idPrefix in groupedStock) { 1128 if (groupedStock.hasOwnProperty(idPrefix)) { 1129 //console.log("ID Prefix:", idPrefix); 1130 var totalWarehouse = 0; 1131 var totalSea = 0; 1132 1133 // Access the array of stock objects for this group 1134 const stocks = groupedStock[idPrefix]; 1135 1136 const productObject = allProductNumbers.find(product => product.id === idPrefix); 1137 const group = productObject.primaryGroup; 1138 1139 // Loop through the array of stock entries for this idPrefix 1140 stocks.forEach(stock => { 1141 //console.log("Warehouse:", stock.warehouse, "Sea:", stock.sea); 1142 totalSea += stock.sea 1143 totalWarehouse += stock.warehouse 1144 1145 if (group != "group32") { 1146 totalSea += Number(stock.purchase) 1147 totalWarehouse += Number(stock.purchase) 1148 } 1149 1150 }); 1151 1152 if (totalSea == 0 && totalWarehouse == 0) { 1153 document.getElementById(`RequestProductText_${idPrefix}`).style.display = ""; 1154 } 1155 else { 1156 1157 if (totalWarehouse > 0) { 1158 if (document.getElementById(`StockOnWarehouse_OnStock_${idPrefix}`) != null) 1159 document.getElementById(`StockOnWarehouse_OnStock_${idPrefix}`).style.display = ""; 1160 1161 } else { 1162 if (document.getElementById(`StockOnWarehouse_NotOnStock_${idPrefix}`) != null) 1163 document.getElementById(`StockOnWarehouse_NotOnStock_${idPrefix}`).style.display = ""; 1164 1165 1166 } 1167 1168 if (totalSea > 0) { 1169 if (document.getElementById(`StockOnSea_InRoute_${idPrefix}`) != null) 1170 document.getElementById(`StockOnSea_InRoute_${idPrefix}`).style.display = ""; 1171 1172 1173 } else { 1174 if (document.getElementById(`StockOnSea_NotInRoute_${idPrefix}`) != null) 1175 document.getElementById(`StockOnSea_NotInRoute_${idPrefix}`).style.display = ""; 1176 1177 } 1178 } 1179 1180 1181 } 1182 } 1183 } 1184 }, 1185 watch: { 1186 1187 } 1188 }); 1189 1190 1191 function setUpFilters() { 1192 const filterSection = document.querySelector('.filter-section'); 1193 const sorting = document.getElementById('sorting'); 1194 1195 if (filterSection) { 1196 const applyBtn = document.getElementById('applyFilterButton'); 1197 const clearBtn = document.getElementById('clearFilterButton'); 1198 1199 const filters = filterSection.querySelectorAll('.filter-option'); 1200 setupFromQueryString(); 1201 1202 Array.from(filters).forEach(filter => { 1203 const filterName = filter.getAttribute('data-name'); 1204 1205 filter.addEventListener('input', event => { 1206 clearTimeout(delay); 1207 1208 switch (filter.type) { 1209 case 'range': 1210 activeFilters[filterName] = filter.value; 1211 break; 1212 1213 case 'checkbox': 1214 default: 1215 if (filter.checked) { 1216 if (activeFilters[filterName]) { 1217 activeFilters[filterName].push(filter.value); 1218 } else { 1219 activeFilters[filterName] = [filter.value]; 1220 } 1221 } else { 1222 if (activeFilters[filterName]) { 1223 const valueIndex = activeFilters[filterName].indexOf(filter.value); 1224 activeFilters[filterName].splice(valueIndex, 1); 1225 1226 if (activeFilters[filterName].length == 0) { 1227 delete activeFilters[filterName]; 1228 } 1229 } 1230 } 1231 break; 1232 } 1233 }); 1234 }); 1235 1236 clearBtn.addEventListener('click', event => { 1237 if (activeFilters['Sortby']) { 1238 const tempBy = activeFilters['Sortby']; 1239 const tempOrder = activeFilters['SortOrder']; 1240 1241 activeFilters = {}; 1242 1243 activeFilters['Sortby'] = tempBy; 1244 activeFilters['SortOrder'] = tempOrder; 1245 } else { 1246 activeFilters = {}; 1247 } 1248 1249 buildQueryString(); 1250 }); 1251 1252 applyBtn.addEventListener('click', buildQueryString); 1253 } 1254 1255 if (sorting) { 1256 sorting.addEventListener('change', event => { 1257 const sortingValue = sorting.value; 1258 1259 if (sortingValue == 'default') { 1260 delete activeFilters['Sortby']; 1261 delete activeFilters['SortOrder']; 1262 } else { 1263 activeFilters['Sortby'] = 'NameForSort'; 1264 activeFilters['SortOrder'] = sortingValue.toUpperCase(); 1265 } 1266 1267 buildQueryString(); 1268 }); 1269 } 1270 } 1271 1272 function buildQueryString() { 1273 // Create a new object to hold the processed filters 1274 const processedFilters = {}; 1275 1276 // Process each filter 1277 Object.keys(activeFilters).forEach(key => { 1278 if (activeFilters[key] && activeFilters[key].length > 0) { 1279 1280 // Get the first input with this key to check if it's a numeric filter 1281 const input = document.querySelector(`[data-name="${key}"]`); 1282 const isNumeric = input && input.getAttribute('data-parameter-type') === 'System.Double[]'; 1283 if (isNumeric) { 1284 // Map over each element in the array and replace commas with dots 1285 activeFilters[key] = activeFilters[key].map(item => { 1286 // Ensure each item is a string before replacing 1287 return typeof item === 'string' ? item.replace(',', '.') : item.toString().replace(',', '.'); 1288 }); 1289 } 1290 1291 1292 processedFilters[key] = activeFilters[key].join(','); 1293 } 1294 }); 1295 1296 // Build the query string 1297 const queryString = new URLSearchParams(processedFilters).toString(); 1298 const currentPath = window.location.pathname; 1299 1300 if (queryString.length > 0) { 1301 window.location.href = `${currentPath}?${queryString.replace(/PageNum=\d+&/g, '')}`; 1302 } else { 1303 window.location.href = currentPath; 1304 } 1305 } 1306 1307 function setupFromQueryString() { 1308 1309 const urlSearchParams = new URLSearchParams(window.location.search); 1310 const params = Object.fromEntries(urlSearchParams.entries()); 1311 1312 1313 Object.keys(params).forEach(key => { 1314 const value = params[key]; 1315 if (!activeFilters[key]) { 1316 activeFilters[key] = []; 1317 } 1318 1319 // Get all inputs with this key to check if it's a numeric filter 1320 const inputsWithKey = document.querySelectorAll(`[data-name="${key}"]`); 1321 const isNumeric = inputsWithKey.length > 0 && 1322 inputsWithKey[0].getAttribute('data-parameter-type') === 'System.Double[]'; 1323 1324 // Get all possible numeric values from the filter options 1325 const allNumericOptions = Array.from(inputsWithKey).map(input => input.value); 1326 1327 // For numeric filters, we need special handling 1328 if (isNumeric) { 1329 // First, split the value into parts 1330 const parts = value.split(','); 1331 const processedIndices = new Set(); // Track which parts we've processed 1332 1333 // Check for decimal values first (values that contain a comma in our options) 1334 for (let i = 0; i < parts.length; i++) { 1335 if (processedIndices.has(i)) continue; // Skip if already processed 1336 1337 const splitPart = parts[i].split('.') 1338 const potentialDecimal = splitPart[0] + ',' + splitPart[1]; 1339 1340 // If this combination exists as an option, add it 1341 if (allNumericOptions.includes(potentialDecimal)) { 1342 activeFilters[key].push(potentialDecimal); 1343 processedIndices.add(i); 1344 } 1345 } 1346 1347 // Now add any remaining individual values 1348 parts.forEach((part, index) => { 1349 if (!processedIndices.has(index) && allNumericOptions.includes(part)) { 1350 activeFilters[key].push(part); 1351 } 1352 }); 1353 } else { 1354 // For non-numeric values, keep the original behavior 1355 if (value.indexOf(',') > -1) { 1356 value.split(',').forEach(entry => { 1357 activeFilters[key].push(entry); 1358 }); 1359 } else { 1360 activeFilters[key].push(value); 1361 } 1362 } 1363 1364 // Update the checkboxes based on active filters 1365 if (key != 'GroupID' && key != 'PageNum' && key != 'Sortby' && key != 'SortOrder') { 1366 Array.from(inputsWithKey).forEach(input => { 1367 switch (input.type) { 1368 case 'range': 1369 input.value = value; 1370 break; 1371 case 'checkbox': 1372 default: 1373 // Check if the input value is in the active filters 1374 if (activeFilters[key].includes(input.value)) { 1375 input.checked = true; 1376 const toggle = input.closest('.filter-item')?.querySelector('.filter-item__checkbox-toggle'); 1377 if (toggle) toggle.checked = true; 1378 } 1379 break; 1380 } 1381 }); 1382 } 1383 }); 1384 } 1385 1386 function setupSearchSelects() { 1387 if (document.querySelector('.search-select')) { 1388 // Add CSS for hiding partial matches 1389 const style = document.createElement('style'); 1390 style.textContent = ` 1391 .search-select__tags label.partial-match { 1392 display: none !important; 1393 } 1394 `; 1395 document.head.appendChild(style); 1396 1397 Array.from(document.querySelectorAll('.search-select')).forEach(select => { 1398 const input = select.querySelector('.search-select__search-input'); 1399 const dropdown = select.querySelector('.search-select__dropdown'); 1400 const options = select.querySelectorAll('.search-select__tags input'); 1401 const optionsLabels = select.querySelectorAll('.search-select__dropdown-item'); 1402 const clearBtn = select.querySelector('.search-select__search-clear'); 1403 1404 // Determine if this is a numeric filter based only on parameter type 1405 const isNumeric = options.length > 0 && 1406 options[0].getAttribute('data-parameter-type') == 'System.Double[]'; 1407 1408 input.addEventListener('focus', open); 1409 clearBtn.addEventListener('click', clear); 1410 input.addEventListener('input', search); 1411 1412 // Fix the visual representation of selected tags 1413 function updateVisualTags() { 1414 // Only process numeric filters 1415 if (!isNumeric) return; 1416 1417 // Get the actual selected values from the URL 1418 const urlSearchParams = new URLSearchParams(window.location.search); 1419 const params = Object.fromEntries(urlSearchParams.entries()); 1420 1421 if (options.length > 0) { 1422 const filterName = options[0].getAttribute('data-name'); 1423 1424 // Only process if this filter is in the URL 1425 if (params[filterName]) { 1426 // For numeric filters, don't split the value 1427 const selectedValues = [params[filterName]]; 1428 1429 // Remove partial-match class from all labels 1430 select.querySelectorAll('.search-select__tags label').forEach(label => { 1431 label.classList.remove('partial-match'); 1432 }); 1433 1434 // For each selected value, mark exact matches 1435 selectedValues.forEach(selectedValue => { 1436 Array.from(options).forEach(option => { 1437 // For numeric filters, we want exact matches only 1438 if (option.value === selectedValue) { 1439 const label = select.querySelector(`label[for="${option.id}"]`); 1440 if (label) { 1441 label.classList.add('selected'); 1442 } 1443 } 1444 }); 1445 }); 1446 } 1447 } 1448 } 1449 1450 // Call this when page loads 1451 updateVisualTags(); 1452 1453 Array.from(options).forEach(option => { 1454 option.addEventListener('change', e => { 1455 if (isNumeric && option.checked) { 1456 // When a numeric option is checked, hide partial matches 1457 const selectedValue = option.value; 1458 Array.from(options).forEach(otherOption => { 1459 if (otherOption !== option) { 1460 const isPartialMatch = selectedValue.includes(otherOption.value) && 1461 selectedValue !== otherOption.value; 1462 1463 if (isPartialMatch) { 1464 const label = select.querySelector(`label[for="${otherOption.id}"]`); 1465 if (label) { 1466 label.classList.add('partial-match'); 1467 } 1468 } 1469 } 1470 }); 1471 } 1472 }); 1473 }); 1474 1475 function clear() { 1476 dropdown.classList.remove('search-select__dropdown--active'); 1477 clearBtn.classList.remove('search-select__search-clear--active'); 1478 input.value = ''; 1479 resetList(); 1480 } 1481 1482 function open() { 1483 dropdown.classList.add('search-select__dropdown--active'); 1484 clearBtn.classList.add('search-select__search-clear--active'); 1485 } 1486 1487 function search() { 1488 const searchTerm = input.value; 1489 1490 if (searchTerm.length > 0) { 1491 const searchWords = searchTerm.trim().split(' '); 1492 1493 Array.from(optionsLabels).forEach(option => { 1494 let hasMatch = false; 1495 1496 searchWords.forEach(word => { 1497 const searchRegEx = new RegExp(word, 'ig'); 1498 const matches = option.innerText.match(searchRegEx); 1499 1500 if (matches && matches.length > 0) { 1501 hasMatch = true; 1502 option.innerHTML = option.innerHTML.replaceAll(/\<span class\=\"js-highlight\"\>(.*?)\<\/span\>/gi, '$1').replaceAll(searchRegEx, `<span class="js-highlight">${matches[0]}</span>`); 1503 } 1504 }); 1505 1506 if (!hasMatch) { 1507 option.classList.add('js-no-match'); 1508 } else { 1509 option.classList.remove('js-no-match'); 1510 } 1511 }); 1512 } else { 1513 resetList(); 1514 } 1515 } 1516 1517 function resetList() { 1518 Array.from(optionsLabels).forEach(option => { 1519 option.classList.remove('js-no-match'); 1520 option.innerHTML = option.innerText; 1521 }); 1522 } 1523 }); 1524 } 1525 } 1526 1527 function setupDuoRangeSliders() { 1528 if (document.querySelector('.duo-range-slider')) { 1529 Array.from(document.querySelectorAll('.duo-range-slider')).forEach(rangeSlider => { 1530 Array.from(rangeSlider.querySelectorAll('.duo-range-slider__range')).forEach(slider => { 1531 slider.oninput = updateValues; 1532 slider.oninput(); 1533 }); 1534 1535 function updateValues() { 1536 const parent = this.parentNode; 1537 const slides = parent.getElementsByTagName('input'); 1538 let slide1 = parseFloat(slides[0].value); 1539 let slide2 = parseFloat(slides[1].value); 1540 const displayMin = parent.querySelector('.duo-range-slider__values-min span'); 1541 const displayMax = parent.querySelector('.duo-range-slider__values-max span'); 1542 1543 if (slide1 > slide2) { 1544 const temp = slide2; 1545 1546 slide2 = slide1; 1547 slide1 = temp; 1548 } 1549 1550 displayMin.innerText = slide1; 1551 displayMax.innerText = slide2; 1552 } 1553 }); 1554 } 1555 } 1556 </script> 1557 } 1558 1559 @{ 1560 string groupSeoText = GetString("Ecom:Group:Field.SEOText"); 1561 1562 if (!string.IsNullOrWhiteSpace(groupSeoText)) 1563 { 1564 <article class="module module-sand-light"> 1565 <div class="rich-text"> 1566 @groupSeoText 1567 </div> 1568 </article> 1569 } 1570 } 1571 1572 @functions { 1573 Dynamicweb.Ecommerce.Products.Group FindTopGroup(Dynamicweb.Ecommerce.Products.Group group) 1574 { 1575 if (group.IsTopGroup) 1576 { 1577 return group; 1578 } 1579 else if (group.ParentGroups != null && group.ParentGroups.Count > 0) 1580 { 1581 foreach (var parentGroup in group.ParentGroups) 1582 { 1583 Dynamicweb.Ecommerce.Products.Group topLevelGroup = FindTopGroup(parentGroup); 1584 if (topLevelGroup != null) 1585 { 1586 return topLevelGroup; 1587 } 1588 } 1589 } 1590 return null; 1591 } 1592 }
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...