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