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