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