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_df2e6c7423ea413e84dcf274e6c08886.FindTopGroup(Group group) in D:\dynamicweb.net\Solutions\keflico.live\Files\Templates\Designs\Keflico\eCom\Productlist\products.cshtml:line 1532
   at CompiledRazorTemplates.Dynamic.RazorEngine_df2e6c7423ea413e84dcf274e6c08886.Execute() in D:\dynamicweb.net\Solutions\keflico.live\Files\Templates\Designs\Keflico\eCom\Productlist\products.cshtml:line 209
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

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