Error executing template "Designs/Swift/Paragraph/Swift_ProductListListView.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
at CompiledRazorTemplates.Dynamic.RazorEngine_e3f157f1327e48678cb9cb75622bbeea.<>c__DisplayClass6_0.<RenderProductList>b__0(TextWriter __razor_helper_writer) in E:\dynamicweb.net\Solutions\www.licscadenta.no\Files\Templates\Designs\Swift\Paragraph\Swift_ProductListListView.cshtml:line 352
at CompiledRazorTemplates.Dynamic.RazorEngine_e3f157f1327e48678cb9cb75622bbeea.Execute() in E:\dynamicweb.net\Solutions\www.licscadenta.no\Files\Templates\Designs\Swift\Paragraph\Swift_ProductListListView.cshtml:line 54
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 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites
4 @using Dynamicweb.Core
5 @using System.Linq
6 @using Lic_Scadenta.CustomModules
7
8 @functions
9 {
10
11 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsLazyLoadingForProductInfoEnabled;
12 string liveInfoClass = "";
13 string productInfoFeed = "";
14 string showPricesWithVat = "";
15 bool neverShowVat = false;
16
17 ProductListViewModel productList = new ProductListViewModel();
18 }
19
20 @{
21 if (Dynamicweb.Context.Current.Items.Contains("ProductList"))
22 {
23 productList = (ProductListViewModel)Dynamicweb.Context.Current.Items["ProductList"];
24 }
25
26 showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
27 neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
28
29 if (isLazyLoadingForProductInfoEnabled)
30 {
31 if (Dynamicweb.Context.Current.Items.Contains("ProductInfoFeed"))
32 {
33 productInfoFeed = Dynamicweb.Context.Current.Items["ProductInfoFeed"]?.ToString();
34 if (!string.IsNullOrEmpty(productInfoFeed))
35 {
36 productInfoFeed = $"data-product-info-feed=\"{productInfoFeed}\"";
37 }
38 }
39 liveInfoClass = "js-live-info";
40 }
41
42 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
43 }
44
45 @if (!string.IsNullOrEmpty(theme))
46 {
47 <div class="h-100@(theme) item_@Model.Item.SystemName.ToLower()" @productInfoFeed>
48 @RenderProductList(productList)
49 </div>
50 }
51 else
52 {
53 <div class="item_@Model.Item.SystemName.ToLower()" @productInfoFeed>
54 @RenderProductList(productList)
55 </div>
56 }
57
58 @helper RenderProductList(ProductListViewModel productList)
59 {
60 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
61 bool anonymousUser = Pageview.User == null;
62 bool hideProductNumber = !string.IsNullOrEmpty(Model.Item.GetString("HideProductNumber")) ? Model.Item.GetBoolean("HideProductNumber") : false;
63 bool isErpConnectionDown = !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable();
64 bool hideStock = Model.Item.GetBoolean("HideStockState") || (Pageview.AreaSettings.GetBoolean("ErpDownHideStock") && isErpConnectionDown);
65 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && isErpConnectionDown;
66 bool hideAddToCart = !string.IsNullOrEmpty(Model.Item.GetString("HideAddToCart")) ? Model.Item.GetBoolean("HideAddToCart") : false;
67 hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && isErpConnectionDown ? true : hideAddToCart;
68 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false;
69
70 string detailsPageLink = Dynamicweb.Context.Current.Items["DetailsPageLink"] != null ? Dynamicweb.Context.Current.Items["DetailsPageLink"].ToString() : "";
71 string productTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ProductTheme")) ? " theme p-2 " + Model.Item.GetRawValueString("ProductTheme").Replace(" ", "").Trim().ToLower() : "";
72 string modalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ModalTheme")) ? " theme " + Model.Item.GetRawValueString("ModalTheme").Replace(" ", "").Trim().ToLower() : "theme light";
73 string textPadding = !string.IsNullOrEmpty(Model.Item.GetString("ContentPadding")) ? Model.Item.GetRawValueString("ContentPadding") : "none";
74 textPadding = textPadding == "none" ? "" : textPadding;
75 textPadding = textPadding == "small" ? "p-2 p-lg-3" : textPadding;
76 textPadding = textPadding == "large" ? "p-3 p-lg-4" : textPadding;
77
78 string groupId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("GroupID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("GroupID") : "";
79 string url = Dynamicweb.Context.Current.Request.RawUrl;
80
81 var badgeParms = new Dictionary<string, object>();
82 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
83 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
84 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
85 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
86 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
87
88 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
89 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
90
91 var favoriteParameters = new Dictionary<string, object>();
92 if (!anonymousUser && !hideFavoritesSelector)
93 {
94 int defaultFavoriteListId = 0;
95
96 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists();
97 if (favoreiteLists.Count() == 1)
98 {
99 foreach (FavoriteList list in favoreiteLists)
100 {
101 defaultFavoriteListId = list.ListId;
102 }
103 }
104
105 favoriteParameters.Add("ListId", defaultFavoriteListId);
106 }
107
108 if (productList.TotalProductsCount > 0)
109 {
110 int pageSizeSetting = 30;
111 int pageSize = productList.PageSize;
112 pageSize += pageSizeSetting;
113
114 int loadedProducts = productList.PageSize > productList.TotalProductsCount ? productList.TotalProductsCount : productList.PageSize;
115
116 foreach (ProductViewModel product in productList.Products)
117 {
118 var pro = Dynamicweb.Ecommerce.Services.Products.GetProductById(product.Id, product.VariantId, product.LanguageId);
119 var NavDiscount = Helpers.DiscountProvider(pro);
120 // SDSCADENT2-43 Punchout marked products green background
121 string markedItem = "";
122 if (Pageview.User != null)
123 {
124 markedItem = NavDiscount.SpecialPrice && Pageview.User.CustomerNumber == "10041" ? "marked-item" : "";
125 }
126
127 var defaultGroupId = product.PrimaryOrDefaultGroup.Id;
128 var selectedDetailPage = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(defaultGroupId)?.Meta.PrimaryPage ?? string.Empty;
129
130 string link = string.IsNullOrEmpty(selectedDetailPage) ? $"{detailsPageLink}&groupid={defaultGroupId}" : selectedDetailPage;
131 link += "&productid=" + product.Id;
132 link += !string.IsNullOrEmpty(product.VariantId) ? "&variantid=" + product.VariantId : "";
133 //string link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), false);
134
135 // Custom image path
136 string img = Helpers.ProductImage(product.Id);
137 string imagePath = !string.IsNullOrEmpty(img) ? img : "/Files/Images/missing_image.jpg";
138 imagePath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath);
139 imagePath = "/Admin/Public/GetImage.ashx?width=" + 80 + "&image=" + imagePath + "&v=2" + "&format=webp";
140
141 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", "");
142 ratio = ratio != "0" ? ratio : "";
143 string ratioCssClass = ratio != "" ? " ratio" : "";
144 string ratioVariable = ratio != "" ? "--bs-aspect-ratio: " + ratio : "";
145
146 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : "";
147 string imageId = "ProductImage_" + product.Id + product.VariantId;
148
149 @* Alternative image *@
150 var supportedImageFormats = new string[] { ".jpg", ".webp", ".png", ".gif" };
151 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : "";
152 var selectedAssetCategories = Model.Item.GetRawValueString("AlternativeImageAssets");
153 IEnumerable<MediaViewModel> alternativeImagesList = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets);
154
155 if (alternativeImagesList.FirstOrDefault() != null)
156 {
157 alternativeImagesList = alternativeImagesList.OrderByDescending(x => x.Value.Equals(defaultImage));
158
159 if (alternativeImagesList.First().Value == defaultImage)
160 {
161 alternativeImagesList = alternativeImagesList.Skip(1);
162 }
163 }
164
165 @* Badges *@
166 DateTime createdDate = product.Created.Value;
167 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
168 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
169 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
170
171 string alternativeImage = alternativeImagesList.FirstOrDefault() != null ? alternativeImagesList.FirstOrDefault().Value : "";
172 alternativeImage = !string.IsNullOrEmpty(alternativeImage) ? "/Admin/Public/GetImage.ashx?width=" + 80 + "&image=" + alternativeImage + "&format=webp" : "";
173 var isProductKonkurensvare = bool.Parse(product.ProductFields.FirstOrDefault(x => x.Key == "Konkurensvare").Value.Value.ToString());
174 @* Main features *@
175 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList();
176 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>();
177
178 foreach (var selection in selectedDisplayGroups)
179 {
180 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values)
181 {
182 if (selection == group.Id)
183 {
184 mainFeatures.Add(group);
185 }
186 }
187 }
188
189 <style>
190 .short-description > p:last-child {
191 margin: 0;
192 }
193 </style>
194
195 <article class="product-list-item grid gap-2 gap-lg-3 mb-3 @(productTheme) @textPadding product @liveInfoClass @markedItem" data-product-id="@product.Id">
196 <a href="@link" class="g-col-12 g-col-lg-7 d-flex text-decoration-none mb-2 mb-lg-0">
197
198 @if (!Model.Item.GetBoolean("HideImage"))
199 {
200
201
202 <div class="d-flex align-items-center position-relative me-2 me-lg-3" style="min-width: 110px">
203 @if (isProductKonkurensvare)
204 {
205 <div class="badge fast-lavpris roundedBadge">
206 <div>FAST LAVPRIS</div>
207 </div>
208 }
209
210 <div class="@ratioCssClass m-0" style="@(ratioVariable)">
211 <figure class="m-0 d-flex justify-content-center">
212 <div class="d-flex justify-content-center align-items-center h-100 w-100">
213 <img src="@imagePath" onmouseover="showBiggerImage('@imagePath');" onmouseout="hideBiggerImage()" alt="@product.Name" class="mw-100 mh-100" loading="lazy" style="object-fit: cover;" />
214 </div>
215 </figure>
216 </div>
217 </div>
218 }
219
220 @if (showBadges)
221 {
222 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
223 }
224
225 <div class="flex-fill d-flex flex-column justify-content-center">
226
227 <div class="d-flex align-items-center gap-2 w-lg-75 w-xxl-100">
228 <h3 class="h6 mb-1">
229 @product.Name @if (!string.IsNullOrEmpty(product.VariantName))
230 {<text>(@product.VariantName)</text>}
231 </h3>
232 </div>
233 <div class="fs-7 opacity-75">
234 @Translate("Manufacturer: ")@product.ProductFields.FirstOrDefault(x => x.Key == "ManufacturerField").Value.Value.ToString()
235 </div>
236
237 @if (!hideProductNumber)
238 {
239 <div class="fs-7 opacity-75">@Translate("Item no. ")@product.Number</div>
240 }
241 @if (!hideStock)
242 {
243 <div class="g-col-12 fs-7 d-block d-lg-none">
244 @RenderStockState(product)
245 </div>
246 }
247 @if (!Model.Item.GetBoolean("HideShortDescription") && selectedDisplayGroups.Count() > 0)
248 {
249 <div class="grid gap-2 gap-lg-4 w-100">
250 <div class="g-col-12 g-col-lg-8 fs-7 fs-7 d-none d-lg-block short-description">
251 @product.ShortDescription
252 </div>
253 <div class="g-col-12 g-col-lg-4">
254 @RenderMainFeatures(mainFeatures)
255 </div>
256 </div>
257 }
258 else
259 {
260 if (!Model.Item.GetBoolean("HideShortDescription"))
261 {
262 <div class="fs-7 d-none d-lg-block">
263 @product.ShortDescription
264 </div>
265 }
266 @RenderMainFeatures(mainFeatures)
267 }
268 </div>
269 </a>
270
271 <div class="g-col-12 g-col-lg-5 d-flex flex-row gap-2 gap-lg-3 align-items-center justify-content-end">
272 @if (!hidePrice)
273 {
274 <div class="text-end align-self-end h-100 w-100 d-flex flex-column align-items-start align-items-lg-end justify-content-center ms-2">
275 <b>@RenderPrice(product, NavDiscount)</b>
276 </div>
277 }
278 <div class="d-flex gap-2 justify-content-end">
279 @*@if (!hidePrice)
280 {
281 <div class="text-end d-flex h-100 flex-column align-items-center justify-content-center">
282 <b>@RenderPrice(product)</b>
283 @if (actualPrice.PriceWithoutVAT != product.Price.PriceWithoutVat)
284 {
285 <p class="text-decoration-line-through mb-0" style="color: gray; font-size: 14px;">@actualPrice.PriceWithoutVATFormatted</p>
286 }
287 </div>
288 }*@
289 <div class="d-flex flex-row">
290 <div class="flex-fill d-flex align-items-center add-to-cart-div">
291 @RenderAddToCart(product)
292 </div>
293 @if (!anonymousUser && !hideFavoritesSelector && product.VariantInfo.VariantInfo == null)
294 {
295 <div class="d-flex flex-column">
296 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
297 @if (!anonymousUser && !hideStock)
298 {
299 <div class="text-end align-self-end mt-1 d-none d-lg-block stock-state">
300 @RenderStockState(product)
301 </div>
302 }
303 </div>
304 }
305 else
306 {
307 <p class="product-list-log-in mb-0 me-2" data-bs-toggle="modal" data-bs-target="#loginModal">@Translate("Log in to shop")</p>
308 }
309 </div>
310 </div>
311 </div>
312 </article>
313 <div class="modal fade addToCartModal" id="addToCartModal-@product.Id" tabindex="-1" aria-labelledby="addToCartModalLabel" aria-hidden="true">
314 <div class="modal-dialog modal-dialog-centered d-flex justify-content-center">
315 <div class="modal-content">
316 <div class="d-flex justify-content-end">
317 <button type="button" class="btn-close p-2" data-bs-dismiss="modal" aria-label="Close"></button>
318 </div>
319 <div class="modal-body text-center py-2 d-flex flex-column align-items-center">
320 <img src="@imagePath" alt="@product.Name" class="mw-100 mh-100 pb-3" loading="lazy" style="object-fit: cover;" />
321 <h5>@product.Name</h5>
322 <h5>@Translate("Added to cart")!</h5>
323 </div>
324 <div class="d-flex flex-column align-items-center pb-4">
325 <button type="button" class="btn btn-primary cart-modal-button mb-2" onclick="location.href='/cart'">@Translate("View Cart")</button>
326 <button type="button" class="btn btn-secondary cart-modal-button" data-bs-dismiss="modal">@Translate("Continue Shopping")</button>
327 </div>
328 </div>
329 </div>
330 </div>
331 }
332
333 <div id="biggerImagePlace"></div>
334
335 <div class="my-3" id="LoadMoreButton">
336 <div class="text-center d-flex flex-column gap-3">
337 <div class="opacity-85">@loadedProducts @Translate("out of") @productList.TotalProductsCount @Translate("products")</div>
338 @if (productList.PageCount != 1)
339 {
340 string sortBySelection = Dynamicweb.Context.Current.Request?.Form["SortBy"] ?? "SortByProductId";
341 sortBySelection = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("SortBy")) ? Dynamicweb.Context.Current.Request.QueryString.Get("SortBy") : sortBySelection;
342
343 if (Pageview.User != null && Pageview.User.CustomerNumber == "10040")
344 {
345 sortBySelection = "-created";
346 }
347
348 string searchQuery = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("q")) ? Dynamicweb.Context.Current.Request.QueryString.Get("q") : "";
349 string searchLayout = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("SearchLayout")) ? Dynamicweb.Context.Current.Request.QueryString.Get("SearchLayout") : "";
350
351 <form method="get" action="@url" data-response-target-element="content" class="w-100">
352 @foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
353 {
354 foreach (FacetViewModel facetItem in facetGroup.Facets)
355 {
356 foreach (FacetOptionViewModel facetOption in facetItem.Options)
357 {
358 if (facetOption.Selected)
359 {
360 <input type="hidden" name="@facetItem.QueryParameter" value="[@facetOption.Value]" />
361 }
362 }
363 }
364 }
365
366 @if (productList?.Group?.Id != null)
367 {
368 <input type="hidden" name="GroupId" value="@productList.Group.Id" />
369 }
370
371 <input type="hidden" name="PageSize" value="@pageSize" />
372 <input type="hidden" name="SortBy" value="@sortBySelection" />
373 <input type="hidden" name="RequestType" value="UpdateList" />
374
375 @if (!string.IsNullOrEmpty(searchQuery))
376 {
377 <input type="hidden" name="q" value="@searchQuery" />
378 <input type="hidden" name="SearchLayout" value="@searchLayout" />
379 }
380
381 @{
382 string nextPageLink = "/Default.aspx?ID=" + Pageview.Page.ID + "&PageSize=" + pageSize + "&SortBy=" + sortBySelection;
383
384 foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
385 {
386 foreach (FacetViewModel facetItem in facetGroup.Facets)
387 {
388 foreach (FacetOptionViewModel facetOption in facetItem.Options)
389 {
390 if (facetOption.Selected)
391 {
392 nextPageLink += "&" + facetItem.QueryParameter + "=[" + facetOption.Value + "]";
393 }
394 }
395 }
396 }
397
398 nextPageLink += productList?.Group?.Id != null ? "&GroupID=" + productList.Group.Id : "";
399 nextPageLink += !string.IsNullOrEmpty(searchQuery) ? "&q=" + searchQuery : "";
400 }
401
402 <a href="@nextPageLink" class="btn btn-primary" type="button" onclick="swift.ProductList.Update(event)" id="LoadMoreButton_@Model.ID">@Translate("Load more products")</a>
403 </form>
404 }
405 </div>
406 </div>
407 <div class="position-sticky bottom-0 end-0 d-flex justify-content-end align-items-end" style="z-index: 2;">
408 <button id="backToTopButton" onclick="backToTop()" style="display: none;" class=""><img src="~/Files/Icons/chevron-up.svg" /></button>
409 </div>
410 }
411 else
412 {
413 if (!Pageview.IsVisualEditorMode)
414 {
415 <div class="alert alert-dark m-0">
416 @Translate("We did not find anything matching your search result")
417 </div>
418 }
419 else
420 {
421 <div class="alert alert-dark m-0" role="alert">
422 <span>@Translate("Product list: The list will be shown here, if any")</span>
423 </div>
424 }
425 }
426 }
427
428 @helper RenderMainFeatures(List<CategoryFieldViewModel> mainFeatures)
429 {
430 if (mainFeatures.Count > 0)
431 {
432 string featuresLayout = Model.Item.GetRawValueString("FeaturesLayout", "bullets");
433
434 if (featuresLayout == "bullets")
435 {
436 <ul class="m-0 p-0 lh-1 fs-7 opacity-75" style="list-style-position: inside">
437 @foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
438 {
439 foreach (var field in mainFeatureGroup.Fields)
440 {
441 @RenderField(field.Value)
442 }
443 }
444 </ul>
445 }
446 else
447 {
448 List<string> featuresList = new List<string>();
449 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
450 {
451 foreach (var field in mainFeatureGroup.Fields)
452 {
453 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
454 {
455 List<string> options = new List<string>();
456 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>)
457 {
458 if (!string.IsNullOrWhiteSpace(option.Value))
459 {
460 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
461 {
462 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>";
463 options.Add(colorSpan);
464 }
465 else
466 {
467 options.Add(option.Value);
468 }
469 }
470 }
471 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray()));
472 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
473 {
474 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray()));
475 }
476 featuresList.Add(field.Value.Name + ": " + optionsString);
477 }
478 else
479 {
480 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString()))
481 {
482 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
483 {
484 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>";
485 featuresList.Add(field.Value.Name + ": " + colorSpan);
486 }
487 else
488 {
489 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString());
490 }
491 }
492 }
493 }
494 }
495 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray()));
496
497 <div class="opacity-75 fs-7">@featuresString</div>
498 }
499 }
500 }
501
502 @helper RenderField(FieldValueViewModel field)
503 {
504 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
505
506 if (fieldValue != "")
507 {
508 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
509 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
510
511 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
512 {
513 fieldValue = "";
514 List<string> options = new List<string>();
515 foreach (FieldOptionValueViewModel option in field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>)
516 {
517 if (!string.IsNullOrWhiteSpace(option.Value))
518 {
519 if (option.Value.ToString().Contains("#") && (Translate(field.Name) == Translate("Color") || Translate(field.Name) == Translate("Colour")))
520 {
521 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>";
522 options.Add(colorSpan);
523 }
524 else
525 {
526 options.Add(option.Name);
527 }
528 }
529 }
530 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray()));
531 if ((Translate(field.Name) == Translate("Color") || Translate(field.Name) == Translate("Colour")))
532 {
533 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray()));
534 }
535
536 fieldValue = optionsString;
537 }
538
539 if (!string.IsNullOrEmpty(fieldValue))
540 {
541 <li>@(field.Name): @fieldValue</li>
542 }
543 }
544 }
545
546 @helper RenderStockState(ProductViewModel product)
547 {
548 bool isNeverOutOfStock = product.NeverOutOfstock;
549 // IsNeverOutOfStock for Kjeveortopedi
550 if (product.GroupPaths.FirstOrDefault() != null && product.GroupPaths.FirstOrDefault().FirstOrDefault() != null)
551 {
552 var productCategoryId = product.GroupPaths.FirstOrDefault().FirstOrDefault().Id;
553 if (productCategoryId == "2500")
554 {
555 isNeverOutOfStock = true;
556 }
557 }
558 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now;
559 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? "";
560
561 string stockLevel = product.StockLevel > 100 ? "100+" : product.StockLevel.ToString();
562
563 if (!isNeverOutOfStock)
564 {
565 if (isLazyLoadingForProductInfoEnabled)
566 {
567 <div class="js-stock-state">
568 @if (!Model.Item.GetBoolean("HideInventory"))
569 {
570 <p class="fs-7 text-success text-lg-center m-0 d-none" data-show-if="LiveProductInfo.product.StockLevel > 0"><span class="js-text-stock"></span> @Translate("In stock")</p>
571 }
572 else
573 {
574 <p class="fs-7 text-success text-lg-center m-0 d-none" data-show-if="LiveProductInfo.product.StockLevel > 0">@Translate("In stock")</p>
575 }
576 <p class="fs-7 text-danger text-lg-center m-0 d-none" data-show-if="LiveProductInfo.product.StockLevel <= 0">@Translate("Out of Stock")</p>
577
578 <p class="d-none" data-show-if="LiveProductInfo.product.ExpectedDelivery != null && new Date(LiveProductInfo.product.ExpectedDelivery) > new Date()">
579 <span>@Translate("Expected:")</span>
580 <span class="js-text-expected-delivery"></span>
581 </p>
582 </div>
583 }
584 else
585 {
586 if (product.StockLevel > 0)
587 {
588 if (!Model.Item.GetBoolean("HideInventory"))
589 {
590 <p class="fs-7 text-success text-lg-center m-0">@stockLevel @Translate("In stock")</p>
591 }
592 else
593 {
594 <p class="fs-7 text-success text-lg-center m-0">@Translate("In stock")</p>
595 }
596 }
597 else
598 {
599 <p class="fs-7 text-danger text-lg-center m-0">@Translate("Out of Stock")</p>
600 }
601
602 if (hasExpectedDelivery)
603 {
604 <p>
605 <span>@Translate("Expected:")</span>
606 <span>@expectedDeliveryDate</span>
607 </p>
608 }
609 }
610 }
611 }
612
613 @helper RenderAddToCart(ProductViewModel product)
614 {
615 string iconPath = "/Files/icons/";
616 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService"));
617 if (!url.Contains("LayoutTemplate"))
618 {
619 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
620 }
621
622 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
623 bool anonymousUser = Pageview.User == null;
624
625 bool hideAddToCart = !string.IsNullOrEmpty(Model.Item.GetString("HideAddToCart")) ? Model.Item.GetBoolean("HideAddToCart") : false;
626 hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable() ? true : hideAddToCart;
627 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("QuantitySelector")) ? Model.Item.GetBoolean("QuantitySelector") : false;
628 bool IsNeverOutOfStock = product.NeverOutOfstock;
629 List<ProductInfoViewModel> replacementProductList = new List<ProductInfoViewModel>();
630 replacementProductList.Add(product.ReplacementProduct);
631 var replacementProduct = replacementProductList.GetProducts().FirstOrDefault();
632 var productHasReplacement = replacementProduct != null;
633 string disableAddToCart = productHasReplacement == true ? "disabled" : "";
634 disableAddToCart = isLazyLoadingForProductInfoEnabled ? "disabled" : disableAddToCart;
635
636 if (!hideAddToCart)
637 {
638 if (product.VariantInfo.VariantInfo == null)
639 {
640 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\"";
641 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1";
642 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty;
643 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : "";
644 string filterProductName = product.Name.Replace(@"""", """);
645 <form method="post" action="@url" class="d-inline-block">
646 <input type="hidden" name="redirect" value="false" />
647 <input type="hidden" name="ProductId" value="@product.Id" />
648 <input type="hidden" name="cartcmd" value="add" />
649
650 @if (!string.IsNullOrEmpty(product.VariantId))
651 {
652 <input type="hidden" name="VariantId" value="@product.VariantId" />
653 }
654 @if (quantitySelector)
655 {
656 <div class="input-group input-primary-button-group d-flex flex-row w-100">
657 <label for="Quantity_@(product.Id)_@product.VariantId" class="visually-hidden">@Translate("Quantity")</label>
658 <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 100px" type="number">
659 <button type="button" onclick="AddToCartModal(event,'@filterProductName', '@product.NeverOutOfstock.ToString()', '@product.StockLevel','@product.Id')" class="btn btn-primary flex-fill js-add-to-cart-button" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)"><span class="icon-2">@ReadFile(iconPath + "shopping-cart.svg")</span></button>
660
661 @if (stepQty != "1")
662 {
663 <div class="invalid-feedback d-none">
664 @Translate("Please select a quantity that is dividable by") @stepQty
665 </div>
666 }
667 </div>
668 }
669 else
670 {
671 <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" type="hidden">
672 <button type="button" onclick="AddToCartModal(event, '@filterProductName', '@product.NeverOutOfstock.ToString()', '@product.StockLevel','@product.Id')" class="btn btn-primary js-add-to-cart-button" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)"><span class="icon-2">@ReadFile(iconPath + "shopping-cart.svg")</span></button>
673 }
674 <!-- Modal -->
675 <div class="modal fade" id="staticBackdrop_@product.Id" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
676 <div class="modal-dialog">
677 <div class="modal-content">
678 <div class="modal-header">
679 <h5 class="modal-title" id="staticBackdropLabel"></h5>
680 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
681 </div>
682 <div class="modal-body">
683
684 </div>
685 <div class="modal-footer">
686 <button type="button" id="cancelButton" class="btn btn-secondary" data-bs-dismiss="modal"></button>
687 <button type="button" id="submitModal" class="btn btn-primary" data-bs-dismiss="modal" data-bs-toggle="modal" data-bs-target="#addToCartModal-@product.Id"></button>
688 </div>
689 </div>
690 </div>
691 </div>
692 </form>
693 <button class="d-none" id="addToCartPopupButton-@product.Id" data-bs-toggle="modal" data-bs-target="#addToCartModal-@product.Id"></button>
694 }
695 else
696 {
697 string buttonWidth = quantitySelector ? "width: calc(80px + 3.5rem);" : "max-width: 3.5rem;";
698 string buttonText = quantitySelector ? Translate("Select") : "<span class=\"icon-2\">" + @ReadFile(iconPath + "shopping-cart.svg") + "</span>";
699
700 string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : "";
701 variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString();
702
703 string disableVariantSelector = isLazyLoadingForProductInfoEnabled ? "disabled" : "";
704
705 <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" class="d-inline-block">
706 <input type="hidden" name="ProductID" value="@product.Id" />
707 <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()" />
708 <input type="hidden" name="HideInventory" value="@Model.Item.GetBoolean("HideInventory").ToString()" />
709 <input type="hidden" name="HideStockState" value="@Model.Item.GetBoolean("HideStockState").ToString()" />
710 <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId" />
711 <input type="hidden" name="ViewType" value="ModalContent" />
712 <button type="button" onclick="swift.PageUpdater.Update(event)" class="btn btn-primary" style="@buttonWidth" @disableVariantSelector @disableVariantSelector title="@Translate("Select")" data-bs-toggle="modal" data-bs-target="#DynamicModal" id="OpenVariantSelectorModal@(product.Id)_@Pageview.CurrentParagraph.ID">@buttonText</button>
713 </form>
714 }
715
716 }
717 }
718 <script type="text/javascript">
719
720 function AddToCartModal(event, productName, isProcurement, productStock, productId) {
721 var addToCartPopupButton = document.getElementById('addToCartPopupButton-' + productId);
722 if (isProcurement == "False" && productStock != "0") {
723 swift.Cart.Update(event);
724 addToCartPopupButton.click();
725 }
726 else {
727 var modal = document.getElementById('staticBackdrop_' + productId);
728
729 //header
730 var headerText = modal.querySelector('#staticBackdropLabel');
731 headerText.innerText = "@Model.Item.GetString("ModalHeaderTitle")";
732
733 //body
734 var modalBody = modal.querySelector('.modal-body');
735 if (isProcurement == "True") {
736 var bodyMessage = "@Model.Item.GetString("ProcurementMessage")";
737 var rep = bodyMessage.replace("{0}", productName);
738 modalBody.innerHTML = rep;
739 }
740 if (isProcurement == "False" && productStock == "0") {
741 var bodyMessage = "@Model.Item.GetString("OutOfStockMessage")";
742 var rep = bodyMessage.replace("{0}", productName);
743 modalBody.innerHTML = rep;
744 }
745
746 //footer
747 var cancelButton = modal.querySelector('#cancelButton');
748 cancelButton.innerText = "@Model.Item.GetString("CancelButtonText")";
749 var submitButton = modal.querySelector('#submitModal');
750 submitButton.onclick = function (event) {
751 swift.Cart.Update(event);
752 addToCartPopupButton.click();
753 };
754 submitButton.innerText = "@Model.Item.GetString("AddToCartButtonText")";
755
756 //show modal
757 var myModal = new bootstrap.Modal(modal);
758 myModal.show();
759 }
760 }
761
762 //Back to top button
763
764 // When the user scrolls down 20px from the top of the document, show the button
765 window.onscroll = function () { scrollFunction() };
766 var backToTopButton = document.getElementById('backToTopButton');
767
768 function scrollFunction() {
769 if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
770 backToTopButton.style.display = "block";
771 } else {
772 backToTopButton.style.display = "none";
773 }
774 }
775 function backToTop() {
776 document.body.scrollTop = 0; // For Safari
777 document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
778 }
779
780 function showBiggerImage(imagePath) {
781 var biggerImagePlace = document.getElementById('biggerImagePlace');
782 var bigimagePath = imagePath.replace("width=80", "width=400");
783 var imageTag = "<img id='bigProductImage' src='" + bigimagePath + "'/>";
784 biggerImagePlace.innerHTML = imageTag;
785 }
786
787 function hideBiggerImage() {
788 var biggerImagePlace = document.getElementById('biggerImagePlace');
789 biggerImagePlace.innerHTML = "";
790 }
791
792 </script>
793
794 <script>
795 window.onload = function() {
796 var allowedCustomerNumbers = ["10040", "16526", "16896", "19349", "21790", "22031", "15768", "15732", "15729", "15728", "15727", "14431", "14445", "15723", "15724", "15725"];
797 var user = '@(Pageview.User != null ? Pageview.User.CustomerNumber : null)';
798 var hasParams = window.location.href.includes('?');
799 if (allowedCustomerNumbers.includes(user) && !hasParams) {
800 window.location.href = window.location.href + '?SortBy=-created';
801 }
802 };
803 </script>
804
805 @helper RenderPrice(ProductViewModel product, NavPriceModal navPrice)
806 {
807 var discountPrice = new Dynamicweb.Ecommerce.Prices.PriceInfo { PriceWithoutVAT = navPrice.Discount, Currency = Dynamicweb.Ecommerce.Common.Context.Currency };
808 var CustomerPrice = new Dynamicweb.Ecommerce.Prices.PriceInfo { PriceWithoutVAT = navPrice.DefaultCustomerPrice, Currency = Dynamicweb.Ecommerce.Common.Context.Currency };
809 string beforePrice = product.Price.PriceWithoutVatFormatted;
810 string afterPrice = discountPrice.PriceWithoutVATFormatted;
811
812 <div class="text-start text-lg-end lh-1 my-auto my-lg-0">
813 <div>
814 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span>
815 @if (product.Price.PriceWithoutVat != discountPrice.PriceWithoutVAT && discountPrice.PriceWithoutVAT > 0)
816 {
817 <div>
818 <span itemprop="price" content="@discountPrice.PriceWithoutVAT" class="d-none"></span>
819 <span class="text-price">@afterPrice</span>
820 <br />
821 <span class="text-decoration-line-through opacity-75 text-price price-line-through">@beforePrice</span>
822 </div>
823 }
824 else if (CustomerPrice.PriceWithoutVAT > 0)
825 {
826 <div>
827 <span itemprop="price" content="@CustomerPrice.PriceWithoutVAT" class="d-none"></span>
828 <span class="text-price">@CustomerPrice.PriceWithoutVATFormatted</span>
829 <br />
830 <span class="text-decoration-line-through opacity-75 text-price price-line-through">@beforePrice</span>
831 </div>
832 }
833 else
834 {
835 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span>
836 <span class="text-price">@product.Price.PriceWithoutVatFormatted</span>
837 }
838 </div>
839 </div>
840 }
841