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