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