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