SPIKER HUGG 21GR 2,8X60 M-FUS -1800

21 GRADER HUGGEN M-FUSION C4
kr 932,50
kr 746,00 eks. MVA
Error executing template "Designs/Swift/Paragraph/Swift_ProductAddToCartWithPopup.cshtml"
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at CompiledRazorTemplates.Dynamic.RazorEngine_9a1e635db71b4be7bc0c72c457d9c146.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   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> @using Dynamicweb.Ecommerce.ProductCatalog @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites @using Dynamicweb.Ecommerce.Variants @using Proff1.Service @using System.Text.Json @using System.Linq @using System.Collections.Generic @functions { // Fetch the "Cross_sell" related products for the added product and materialize // them into full ProductViewModels (needed for name/image/price/link). List<ProductViewModel> GetCrossSellProducts(ProductViewModel sourceProduct, string groupName, int max) { var result = new List<ProductViewModel>(); if (sourceProduct?.RelatedGroups == null) { return result; } var settings = new ProductViewModelSettings { LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, ShopId = Pageview.Area.EcomShopId }; var group = sourceProduct.RelatedGroups.FirstOrDefault(g => g.Name == groupName); if (group?.Products == null) { return result; } // De-dup by product + variant, so different variants of the same product // (e.g. 1219577:VO507 and 1219577:VO509) are both kept. var seen = new HashSet<string>(); foreach (var rel in group.Products) { if (result.Count >= max) break; var relId = rel?.ProductId; var relVid = rel?.VariantId ?? ""; if (string.IsNullOrEmpty(relId) || !seen.Add(relId + "|" + relVid)) continue; try { var relProduct = ViewModelFactory.CreateView(settings, relId, relVid); if (relProduct != null) result.Add(relProduct); } catch { } } return result; } // Resolve a usable image path for a cross-sell product. The relation-created // view model does not always populate DefaultImage, so fall back to the ProductImageService string ResolveCrossSellImage(ProductViewModel rel) { string img = rel?.DefaultImage?.Value; if (string.IsNullOrEmpty(img) && rel != null) { try { var imageService = new Dynamicweb.Ecommerce.Products.ProductImageService(); var prod = Dynamicweb.Ecommerce.Services.Products.GetProductById(rel.Id, rel.VariantId ?? "", Pageview.Area.EcomLanguageId); if (prod != null) img = imageService.GetImagePath(prod); } catch { } } return string.IsNullOrEmpty(img) ? "" : img; } // Enumerate the purchasable variants of a master product (all option-id combinations across its variant groups), materialized as ProductViewModels. List<ProductViewModel> GetVariantList(ProductViewModel master, int max) { var result = new List<ProductViewModel>(); if (master == null) return result; try { var groups = master.VariantGroups(); if (groups == null || !groups.Any()) return result; var combos = new List<string> { "" }; foreach (var g in groups) { var next = new List<string>(); foreach (var combo in combos) { foreach (var opt in g.Options) { next.Add(string.IsNullOrEmpty(combo) ? opt.Id : combo + "." + opt.Id); } } combos = next; } var settings = new ProductViewModelSettings { LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, ShopId = Pageview.Area.EcomShopId }; var seen = new HashSet<string>(); foreach (var variantId in combos) { if (result.Count >= max) break; if (string.IsNullOrEmpty(variantId) || !seen.Add(variantId)) continue; try { var v = ViewModelFactory.CreateView(settings, master.Id, variantId); // Keep only ids that resolve to a real variant product. if (v != null && !string.IsNullOrEmpty(v.VariantId)) result.Add(v); } catch { } } } catch { } return result; } // Short description as plain text (tags stripped) for the hover/tap tooltip. string GetShortDescriptionPlain(ProductViewModel p) { if (p == null) return ""; var raw = p.ShortDescription; if (string.IsNullOrWhiteSpace(raw)) { try { var prod = Dynamicweb.Ecommerce.Services.Products.GetProductById(p.Id, p.VariantId ?? "", Pageview.Area.EcomLanguageId); raw = prod?.ShortDescription; } catch { } } if (string.IsNullOrWhiteSpace(raw)) return ""; var text = System.Text.RegularExpressions.Regex.Replace(raw, "<.*?>", " "); text = System.Net.WebUtility.HtmlDecode(text); text = System.Text.RegularExpressions.Regex.Replace(text, "\\s+", " ").Trim(); return text; } } @{ ProductViewModel product = null; List<Dynamicweb.Ecommerce.ProductCatalog.GroupInfoViewModel> productGroup = null; string lastGroup = null; string productIdTag = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("ProductID") : ""; var productService = new Dynamicweb.Ecommerce.Products.ProductService(); var regularProduct = productService.GetProductById(productIdTag, "", true); var productBrand = regularProduct?.ProductFieldValues.GetProductFieldValue("TK_Brand").Value.ToString(); if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) { product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; productGroup = product.GroupPaths[0]; lastGroup = productGroup.Last().Name; } else if (Pageview.Page.Item["DummyProduct"] != null) { var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); if (productList?.Products is object) { product = productList.Products[0]; } } string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); bool anonymousUser = Pageview.User == null; bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"]); bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && isErpConnectionDown; hideAddToCart = Pageview.IsVisualEditorMode ? false : hideAddToCart; string addedMessageId = $"addedMsg{product.Id}_{Pageview.CurrentParagraph.ID}"; string addToCartButtonId = $"AddToCartButton{product.Id}_{Pageview.CurrentParagraph.ID}"; int packagingQuantity = 0; if (product.ProductFields != null && product.ProductFields.ContainsKey("P1_Product_Packaging_Quantity")) { packagingQuantity = Dynamicweb.Core.Converter.ToInt32(product.ProductFields["P1_Product_Packaging_Quantity"].Value); } bool showBoxButton = packagingQuantity > 9; // --- Cross-sell popup data --- const string CrossSellGroupName = "Cross_sell"; List<ProductViewModel> crossSellProducts = product is object ? GetCrossSellProducts(product, CrossSellGroupName, 12) : new List<ProductViewModel>(); bool hasCrossSell = crossSellProducts.Count > 0; string popupId = $"atcPopup_{product.Id}_{Pageview.CurrentParagraph.ID}"; int crossSellShopPageId = GetPageIdByNavigationTag("Shop"); } @if (product is object && !hideAddToCart) { string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); horizontalAlign = horizontalAlign == "center" ? "justify-content-center" : horizontalAlign; horizontalAlign = horizontalAlign == "end" ? "justify-content-end" : horizontalAlign; horizontalAlign = horizontalAlign == "full" ? "" : horizontalAlign; bool favoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowAddToFavorites")) ? Model.Item.GetBoolean("ShowAddToFavorites") : false; bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowQuantitySelector")) ? Model.Item.GetBoolean("ShowQuantitySelector") : false; bool unitsSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowUnitsSelector")) ? Model.Item.GetBoolean("ShowUnitsSelector") : false; bool hideInventory = !string.IsNullOrEmpty(Model.Item.GetString("HideInventory")) ? Model.Item.GetBoolean("HideInventory") : false; bool hideStockState = !string.IsNullOrEmpty(Model.Item.GetString("HideStockState")) ? Model.Item.GetBoolean("HideStockState") : false; string buttonSize = Model.Item.GetRawValueString("ButtonSize", "regular"); string inputSize = string.Empty; switch (buttonSize) { case "small": inputSize = " input-group-sm"; buttonSize = " btn-sm"; break; case "regular": buttonSize = string.Empty; break; case "large": inputSize = " input-group-lg"; buttonSize = " btn-lg"; break; } string iconPath = "/Files/icons/"; string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); if (!url.Contains("LayoutTemplate")) { url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; } bool isNeverOutOfStock = product.NeverOutOfstock; var stockInfo = Proff1Services.StockService.GetStockInfo(product.Number); var totalItemsInStock = stockInfo.Sum(s => s.ItemsInStock); string disableAddToCart = (totalItemsInStock <= 0) ? "disabled" : ""; string addToCartButtonText = Translate("Add to cart"); if (product.VariantInfo.VariantInfo != null && string.IsNullOrEmpty(product.VariantId)) { addToCartButtonText = "Velg variant"; } else if (!product.NeverOutOfstock && totalItemsInStock <= 0) { addToCartButtonText = Translate("Ikke på lager"); } else if (totalItemsInStock <= 0) { addToCartButtonText = Translate("Bestill"); } disableAddToCart = isNeverOutOfStock ? "" : disableAddToCart; string whenVariantsExist = Model.Item.GetRawValueString("WhenVariantsExist", "hide"); string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : ""; string fullWidth = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "w-100" : ""; string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "shopping-cart.svg"); string checkIcon = Model.Item.GetRawValueString("Icon", iconPath + "check.svg"); string addToCartLabel = !addToCartIcon.Contains("_none") ? "<span class=\"icon-2\">" + ReadFile(addToCartIcon) + "</span>" : ""; addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : ""; addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? addToCartButtonText : ""; string productId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("ProductID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("ProductID") : ""; bool isProductDetailsPage = !string.IsNullOrEmpty(productId); bool isMasterWithVariant = product.VariantInfo.VariantInfo != null && string.IsNullOrEmpty(product.VariantId); var deliveryDays = product.ProductFields["P1_Product_Delivery_Days"].Value; if (product.VariantInfo.VariantInfo == null || isProductDetailsPage) { string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : product.DefaultUnitId; if (string.IsNullOrEmpty(unitId) && product?.UnitOptions != null) { if (product.UnitOptions.FirstOrDefault<UnitOptionViewModel>() != null) { unitId = product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Id; } } string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\""; string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1"; string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty; disableAddToCart = product.VariantInfo.VariantInfo != null && string.IsNullOrEmpty(product.VariantId) ? "disabled" : disableAddToCart; var reserveMode = Dynamicweb.Ecommerce.Frontend.Cart.ProductReserve.Mode; if (unitsSelector && product.UnitOptions.Count > 0) { <form method="post" action="/Default.aspx?ID=@(Pageview.Page.ID)&ProductId=@product.Id" id="UnitSelectorForm_@(product.Id)_@(product.VariantId)_@Model.ID"> <input type="hidden" name="redirect" value="false"> <input type="hidden" name="VariantID" value="@product.VariantId"> <input type="hidden" name="UnitID" class="js-unit-id" value="@unitId"> </form> } <style> div.item_@(Model.Item.SystemName.ToLower()) input[type=number]::-webkit-outer-spin-button, div.item_@(Model.Item.SystemName.ToLower()) input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } div.item_@(Model.Item.SystemName.ToLower()) input.swift_quantity-field[type=number] { -moz-appearance: textfield; appearance: textfield; } </style> <div class="d-grid @horizontalAlign @fullWidth js-input-group item_@Model.Item.SystemName.ToLower()"> <div class="d-flex flex-wrap gap-2 align-items-start"> <form method="post" action="@url" class="@fullWidth" style="z-index: 1"> <input type="hidden" name="redirect" value="false"> <input type="hidden" name="ProductId" value="@product.Id"> <input type="hidden" name="ProductName" value="@product.Name"> <input type="hidden" name="ProductVariantName" value="@product.VariantName"> <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> <input type="hidden" name="ProductPrice" value="@PriceViewModelExtensions.ToStringInvariant(product.Price)"> <input type="hidden" name="ProductReferer" value="component_ProductAddToCart"> <input type="hidden" name="cartcmd" value="add"> @if (reserveMode == Dynamicweb.Ecommerce.Frontend.Cart.ProductReserveMode.AddToCart) { <input type="hidden" name="GetReservedAmount" value="true"> } @if (!string.IsNullOrEmpty(product.VariantId)) { <input type="hidden" name="VariantId" value="@product.VariantId"> } @if (!product.NeverOutOfstock) { <input type="hidden" name="Stock" value="@product.StockLevel"> <template class="js-out-of-stock-notice"> <div class="modal-header"> <h1 class="modal-title fs-5">@Translate("Stock limit")</h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> @Translate("There are not enough products in stock. The product might be sold out or discontinued. Please adjust the quantity.") </div> </template> } @if (stepQty != "1") { <template class="js-step-quantity-warning"> <div class="modal-header"> <h1 class="modal-title fs-5">@Translate("The quantity is not valid")</h1> </div> <div class="modal-body"> @Translate("Please select a quantity that is dividable by") @stepQty </div> </template> } @if (product.PurchaseMinimumQuantity != 1) { <template class="js-min-quantity-warning"> <div class="modal-header"> <h1 class="modal-title fs-5">@Translate("The product could not be added to the cart")</h1> </div> <div class="modal-body"> @Translate("The quantity is not valid. You must buy at least") @product.PurchaseMinimumQuantity </div> </template> } @if (quantitySelector || (!anonymousUser && product.VariantInfo.VariantInfo != null) || (!anonymousUser && favoritesSelector)) { <input type="hidden" id="Unit_@(product.Id)_@product.VariantId" name="UnitID" value="@unitId" /> } <div class="d-flex flex-row w-100"> @if (!anonymousUser && favoritesSelector) { @RenderPartial("Components/ToggleFavorite.cshtml", product) } @if (!quantitySelector) { <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart> } <div class="input-group input-primary-button-group flex-nowrap@(inputSize)"> @if (quantitySelector) { <div class="d-flex me-2" style="border: 1px solid #5555"> <button type="button" class="btn btn-stepper fs-6 fw-normal rounded-0" onclick="decreaseQuantity('Quantity_@(product.Id)_@product.VariantId')" style="border-right: 1px solid #5555">-</button> <input id="Quantity_@(product.Id)_@product.VariantId" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control swift_quantity-field text-center border-white" style="max-width: 40px; z-index: 1" type="number" onchange="swift.Cart.UpdateOnEnterKey(event)" onkeyup="swift.Cart.UpdateOnEnterKey(event)" @disableAddToCart> <button type="button" class="btn btn-stepper fs-6 fw-normal m-r-5 rounded-0" onclick="increaseQuantity('Quantity_@(product.Id)_@product.VariantId')" style="border-left: 1px solid #5555">+</button> </div> } @if (unitsSelector && product.UnitOptions.Count > 0) { string selectedUnitName = !string.IsNullOrEmpty(unitId) && product?.UnitOptions != null ? unitId : product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Name; foreach (var unitOption in product.UnitOptions) { if (unitOption.Id == unitId) { selectedUnitName = unitOption.Name; } } <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> @selectedUnitName </button> <ul class="dropdown-menu swift_unit-field"> @foreach (var unitOption in product.UnitOptions) { var selectedUnit = unitOption.Id == unitId ? "selected" : ""; <li> <button type="button" class="btn dropdown-item" data-value="@unitOption.Id" onclick="document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId)_@Model.ID').querySelector('.js-unit-id').value = this.getAttribute('data-value'); document.querySelector('#Unit_@(product.Id)_@product.VariantId').value = this.getAttribute('data-value'); swift.PageUpdater.Update(document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId)_@Model.ID'))"> <span>@unitOption.Name</span> <span> @if (unitOption.StockLevel > 0) { if (!Model.Item.GetBoolean("HideInventory")) { <span class="small text-success">@unitOption.StockLevel @Translate("In stock")</span> } else { <span class="small text-success">@Translate("In stock")</span> } } else { <span class="small text-danger">@Translate("Out of Stock")</span> } </span> </button> </li> } </ul> } <div> <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary @(buttonSize) @flexFill js-add-to-cart-button rounded " style="white-space: nowrap; font-size: 0.9rem;" @disableAddToCart title="@Translate("Add to cart")" id="@addToCartButtonId"> @if (!Model.Item.GetBoolean("HideButtonText")) { <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> @addToCartLabel </span> } else { @addToCartLabel } </button> @if (isProductDetailsPage) { <p class="position-absolute top-100 mt-2 pb-2 d-none" id="addedMsg@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Lagt i handlekurv")</p> } else { <p class="position-absolute top-100 end-0 mt-2 pb-2 d-none" id="addedMsg@(product.Id)_@Pageview.CurrentParagraph.ID">@Translate("Lagt i handlekurv")</p> } </div> </div> </div> </form> @if (showBoxButton && !isMasterWithVariant) { <form method="post" action="@url" class="@fullWidth " style="z-index: 1"> <input type="hidden" name="redirect" value="false"> <input type="hidden" name="ProductId" value="@product.Id"> <input type="hidden" name="ProductName" value="@product.Name"> <input type="hidden" name="ProductVariantName" value="@product.VariantName"> <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> <input type="hidden" name="ProductPrice" value="@PriceViewModelExtensions.ToStringInvariant(product.Price)"> <input type="hidden" name="ProductReferer" value="component_ProductAddToCart_Box"> <input type="hidden" name="cartcmd" value="add"> @if (reserveMode == Dynamicweb.Ecommerce.Frontend.Cart.ProductReserveMode.AddToCart) { <input type="hidden" name="GetReservedAmount" value="true"> } @if (!string.IsNullOrEmpty(product.VariantId)) { <input type="hidden" name="VariantId" value="@product.VariantId"> } <input type="hidden" name="UnitID" value="@unitId"> <input type="hidden" name="Quantity" value="@packagingQuantity"> <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary ms-3 @(buttonSize) @flexFill js-add-to-cart-button rounded" style="white-space: nowrap; font-size: 0.9rem;" @disableAddToCart title="Legg eske (@packagingQuantity) i handlekurv"> Legg eske (@packagingQuantity) i handlekurv </button> </form> } </div> @if (isProductDetailsPage && !isMasterWithVariant) { <div class="stock-status py-2"> <span class="stock-label text-decoration-underline">Lagerstatus</span> <div class="stock-info"> @foreach (var location in stockInfo) { <span>@location.LocationName: @location.ItemsInStock</span><br /> } </div> </div> <div class="stock-status pt-3"> @if (totalItemsInStock <= 0) { if (Dynamicweb.Core.Converter.ToInt32(deliveryDays) > 0) { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-danger me-2 stock__dot"></span>@Translate("Order item - Expected delivery time") @deliveryDays @Translate("days") </p> } else { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-danger me-2 stock__dot"></span>@Translate("Order item - Expected delivery time unknown, please contact us") </p> } } else if (totalItemsInStock <= 4) { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-warning me-2 stock__dot"></span>@Translate("5 In Stock") </p> } else if (totalItemsInStock <= 9) { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-success me-2 stock__dot"></span>@Translate("5-10 In Stock") </p> } else if (totalItemsInStock <= 99) { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-success me-2 stock__dot"></span>@Translate("10 In Stock") </p> } else if (totalItemsInStock >= 100) { <p class="m-0 small d-flex align-items-center lh-1"> <span class="bg-success me-2 stock__dot"></span>@Translate("100+ In Stock") </p> } </div> } else if (isProductDetailsPage && isMasterWithVariant) { <div class="stock-status pt-3"> <p class="m-0 small d-flex align-items-center lh-1"> @Translate("Choose a product/variant to see stock status") </p> </div> } </div> } else { string buttonText = Translate("Select"); string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : ""; variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString(); string link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), false); <div class="d-flex @horizontalAlign w-100 item_@Model.Item.SystemName.ToLower()"> @if (!anonymousUser && favoritesSelector) { @RenderPartial("Components/ToggleFavorite.cshtml", product) } <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" style="z-index: 1" class="@fullWidth"> <input type="hidden" name="ProductID" value="@product.Id"> <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()"> <input type="hidden" name="HideInventory" value="@hideInventory.ToString()"> <input type="hidden" name="HideStockState" value="@hideStockState.ToString()"> <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId"> <input type="hidden" name="ViewType" value="ModalContent"> <a href="@link" class="btn btn-primary" title="@Translate("Select")">@buttonText</a> </form> </div> } } else if (Pageview.IsVisualEditorMode) { <div class="alert alert-dark m-0">@Translate("No products available")</div> } @* ============================================================================ Cross-sell "added to cart" popup Renders only when the added product has products in the "Cross_sell" related group. Shown after a successful add-to-cart via the global "updated.swift.cart" event dispatched by swift.Cart. ============================================================================ *@ @if (product is object && !hideAddToCart && hasCrossSell) { string cartPageUrl = "/handlevogn"; // Cart service endpoint for the cross-sell "Add to cart" buttons string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); if (!url.Contains("LayoutTemplate")) { url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; } <div id="@popupId" class="p1atc-overlay" role="dialog" aria-modal="true" aria-labelledby="@(popupId)_title" hidden> <div class="p1atc-dialog" role="document"> <button type="button" class="p1atc-close" aria-label="@Translate("Lukk")" data-atc-close>&times;</button> <h2 class="p1atc-title" id="@(popupId)_title">@Translate("Spar penger ved å samtidig kjøpe dette:")</h2> <div class="p1atc-carousel@(crossSellProducts.Count > 1 ? " p1atc-carousel--nav" : "")"> @if (crossSellProducts.Count > 1) { <button type="button" class="p1atc-arrow p1atc-arrow--prev" aria-label="@Translate("Forrige")" data-atc-prev> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"></path></svg> </button> } <div class="p1atc-track" data-atc-track> @foreach (var rel in crossSellProducts) { string relLink; try { relLink = rel.GetProductLink(crossSellShopPageId, false); } catch { relLink = "/Default.aspx?ID=" + crossSellShopPageId + "&ProductID=" + rel.Id; } string relImageVal = ResolveCrossSellImage(rel); string relImg = !string.IsNullOrEmpty(relImageVal) ? (relImageVal.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? relImageVal : "/Admin/Public/GetImage.ashx?width=300&height=300&Format=WebP&Quality=90&image=" + Dynamicweb.Context.Current.Server.UrlEncode(relImageVal)) : ""; string relPrice = ""; try { relPrice = rel.Price?.PriceWithVatFormatted; } catch { } string relDesc = GetShortDescriptionPlain(rel); // A cross-sell relation pointing to a master (no specific variant) that // actually has variants -> let the user pick one instead of adding directly. List<ProductViewModel> relVariants = string.IsNullOrEmpty(rel.VariantId) ? GetVariantList(rel, 24) : new List<ProductViewModel>(); bool relIsVariantMaster = relVariants.Count > 0; // For a variant master, show the lowest variant price string masterPriceLabel = ""; if (relIsVariantMaster) { double? minPv = null, maxPv = null; string minFmt = ""; foreach (var v in relVariants) { try { var pi = v.Price; if (pi == null) continue; double pv = pi.PriceWithVat; if (minPv == null || pv < minPv.Value) { minPv = pv; minFmt = pi.PriceWithVatFormatted; } if (maxPv == null || pv > maxPv.Value) { maxPv = pv; } } catch { } } if (minPv != null) { bool differ = maxPv != null && Math.Abs(maxPv.Value - minPv.Value) > 0.01; masterPriceLabel = differ ? (Translate("fra") + " " + minFmt) : minFmt; } } <div class="p1atc-card" data-cs-id="@rel.Id" data-cs-vid="@rel.VariantId"> <div class="p1atc-card__imgwrap"> <a href="@relLink" class="p1atc-card__img" title="@rel.Name"> @if (!string.IsNullOrEmpty(relImg)) { <img src="@relImg" alt="@rel.Name" loading="lazy"> } </a> @if (!string.IsNullOrEmpty(relDesc)) { <button type="button" class="p1atc-card__info" data-atc-info aria-label="@Translate("Vis beskrivelse")">?</button> <div class="p1atc-card__desc" data-atc-desc>@relDesc</div> } </div> <a href="@relLink" class="p1atc-card__name" title="@rel.Name">@rel.Name</a> @* DISCOUNT PREVIEW START - price container is updated by JS when a bundle discount applies *@ <div class="p1atc-card__pricebox" data-cs-pricebox> @if (relIsVariantMaster && !string.IsNullOrEmpty(masterPriceLabel)) { <span class="p1atc-card__price">@masterPriceLabel</span> } else if (!string.IsNullOrEmpty(relPrice) && !relIsVariantMaster) { <span class="p1atc-card__price">@relPrice</span> } </div> @* DISCOUNT PREVIEW END *@ @if (relIsVariantMaster) { <div class="p1atc-card__form"> <button type="button" class="p1atc-card__add p1atc-card__selectvariant" data-atc-togglevariants data-atc-label-closed="@Translate("Velg variant")" data-atc-label-open="@Translate("Lukk varianter")"> <span class="p1atc-card__add-text">@Translate("Velg variant")</span> </button> </div> <div class="p1atc-card__variants" data-atc-variants hidden> @foreach (var v in relVariants) { string vName = !string.IsNullOrEmpty(v.VariantName) ? v.VariantName : v.Name; string vPrice = ""; try { vPrice = v.Price?.PriceWithVatFormatted; } catch { } <form method="post" action="@url" class="p1atc-var" data-cs-id="@v.Id" data-cs-vid="@v.VariantId" onsubmit="return false;"> <input type="hidden" name="redirect" value="false"> <input type="hidden" name="cartcmd" value="add"> <input type="hidden" name="ProductId" value="@v.Id"> <input type="hidden" name="ProductName" value="@v.Name"> <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> <input type="hidden" name="ProductReferer" value="component_ProductAddToCart_CrossSell"> <input type="hidden" name="Quantity" value="1"> <input type="hidden" name="VariantId" value="@v.VariantId"> <span class="p1atc-var__name" title="@vName">@vName</span> <span class="p1atc-var__price" data-cs-varprice>@vPrice</span> <button type="button" class="p1atc-card__add p1atc-var__add" data-atc-added-label="@Translate("Lagt i handlekurv")" aria-label="@Translate("Add to cart")" title="@Translate("Add to cart")"> <span class="p1atc-var__plus" aria-hidden="true">+</span> <span class="p1atc-var__check" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg> </span> </button> </form> } </div> } else { <form method="post" action="@url" class="p1atc-card__form" onsubmit="return false;"> <input type="hidden" name="redirect" value="false"> <input type="hidden" name="cartcmd" value="add"> <input type="hidden" name="ProductId" value="@rel.Id"> <input type="hidden" name="ProductName" value="@rel.Name"> <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> <input type="hidden" name="ProductReferer" value="component_ProductAddToCart_CrossSell"> <input type="hidden" name="Quantity" value="1"> @if (!string.IsNullOrEmpty(rel.VariantId)) { <input type="hidden" name="VariantId" value="@rel.VariantId"> } <button type="button" class="p1atc-card__add" data-atc-added-label="@Translate("Lagt i handlekurv")"> <span class="p1atc-card__add-text">@Translate("Add to cart")</span> </button> </form> } </div> } </div> @if (crossSellProducts.Count > 1) { <button type="button" class="p1atc-arrow p1atc-arrow--next" aria-label="@Translate("Neste")" data-atc-next> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"></path></svg> </button> } </div> <div class="p1atc-footer"> <a href="@cartPageUrl" class="p1atc-footer__cart">@Translate("Gå til handlekurv")</a> <button type="button" class="btn btn-outline-secondary p1atc-footer__continue" data-atc-close>@Translate("Fortsett å handle")</button> </div> </div> <button type="button" class="p1atc-scrolldown" data-atc-scrolldown aria-label="@Translate("Vis mer")" hidden> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg> </button> </div> <style> #@(popupId).p1atc-overlay { position: fixed; inset: 0; z-index: 1080; display: none; align-items: flex-start; justify-content: center; overflow-y: auto; padding: 16px; background: rgba(20, 20, 20, .55); } #@(popupId).p1atc-overlay.p1atc-open { display: flex; } #@popupId .p1atc-dialog { position: relative; width: 100%; max-width: 760px; margin: auto; background: #fff; border-radius: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, .28); padding: 26px 28px 24px; } #@popupId .p1atc-close { position: absolute; top: 12px; right: 14px; width: 34px; height: 34px; border: none; background: transparent; font-size: 26px; line-height: 1; color: #888; cursor: pointer; border-radius: 8px; transition: background .15s ease, color .15s ease; } #@popupId .p1atc-close:hover { background: #f2f2f2; color: #222; } #@popupId .p1atc-title { font-size: 19px; font-weight: 700; color: #1c1c1c; margin: 0 0 18px; padding-right: 30px; } #@popupId .p1atc-carousel { position: relative; } #@popupId .p1atc-track { display: flex; gap: 12px; overflow-x: auto; scroll-behavior: smooth; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; padding-bottom: 4px; } #@popupId .p1atc-track::-webkit-scrollbar { height: 6px; } #@popupId .p1atc-track::-webkit-scrollbar-thumb { background: #d8d8d8; border-radius: 3px; } #@popupId .p1atc-carousel--nav .p1atc-track { padding-left: 4px; padding-right: 4px; } #@popupId .p1atc-card { scroll-snap-align: start; flex: 0 0 auto; width: calc((100% - 24px) / 3); min-width: 168px; display: flex; flex-direction: column; background: #fff; border: 1px solid #e7e7e7; border-radius: 11px; padding: 12px; } #@popupId .p1atc-card__imgwrap { position: relative; margin-bottom: 10px; } #@popupId .p1atc-card__img { position: relative; display: block; width: 100%; height: 0; padding-top: 100%; background: #fff; border: 1px solid #f0f0f0; border-radius: 8px; overflow: hidden; text-decoration: none; } #@popupId .p1atc-card__info { position: absolute; top: 6px; right: 6px; z-index: 3; width: 22px; height: 22px; padding: 0; border: none; border-radius: 50%; background: rgba(0, 0, 0, .5); color: #fff; font-size: 13px; font-weight: 700; line-height: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; } #@popupId .p1atc-card__desc { position: absolute; inset: 0; z-index: 2; display: none; padding: 10px; overflow-y: auto; background: rgba(255, 255, 255, .97); border: 1px solid #f0f0f0; border-radius: 8px; font-size: 11px; line-height: 1.35; color: #333; } #@popupId .p1atc-card--desc-open .p1atc-card__desc { display: block; } @@media (hover: hover) { #@popupId .p1atc-card__imgwrap:hover .p1atc-card__desc { display: block; } #@popupId .p1atc-card__info { display: none; } #@popupId .p1atc-card__desc { inset: auto; top: 0; left: 0; right: 0; height: auto; max-height: 260px; border-radius: 10px; box-shadow: 0 12px 30px rgba(0, 0, 0, .28); } } #@popupId .p1atc-card__img img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: calc(100% - 20px); max-height: calc(100% - 20px); object-fit: contain; } #@popupId .p1atc-card__name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 13px; font-weight: 600; color: #14365a; line-height: 1.3; min-height: 34px; text-decoration: none; } #@popupId .p1atc-card__price { display: block; font-size: 16px; font-weight: 700; color: #1c1c1c; margin: 6px 0 12px; } #@popupId .p1atc-card__pricebox { margin: 6px 0 12px; } #@popupId .p1atc-card__pricebox .p1atc-card__price { margin: 0; } #@popupId .p1atc-card__price--old { display: inline-block; font-size: 13px; font-weight: 600; color: #8a8a8a; text-decoration: line-through; margin: 0 0 2px; } #@popupId .p1atc-card__price--new { display: block; font-size: 16px; font-weight: 700; color: #D0232E; margin: 0; } #@popupId .p1atc-card__save { display: inline-block; margin-top: 4px; padding: 2px 7px; border-radius: 5px; background: #fdeaec; color: #D0232E; font-size: 11px; font-weight: 700; letter-spacing: .02em; } #@popupId .p1atc-card__form { margin: auto 0 0; } #@popupId .p1atc-card__add { display: flex; align-items: center; justify-content: center; gap: 7px; width: 100%; min-height: 42px; padding: 6px 12px; border: 1px solid #D0232E; border-radius: 9px; background: #D0232E; color: #fff; white-space: normal; text-align: center; line-height: 1.15; font-family: inherit; font-size: 14px; font-weight: 700; cursor: pointer; transition: filter .15s ease; } #@popupId .p1atc-card__add:hover { filter: brightness(0.92); } #@popupId .p1atc-card__add--added { background: #1c7a3d; border-color: #1c7a3d; cursor: default; } #@popupId .p1atc-card__variants { margin-top: 8px; max-height: 190px; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 6px; } #@popupId .p1atc-card__variants[hidden] { display: none; } #@popupId .p1atc-var { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border: 1px solid #ececec; border-radius: 8px; background: #fafafa; margin: 0; } #@popupId .p1atc-var__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 600; color: #14365a; } #@popupId .p1atc-var__price { flex: none; display: flex; flex-direction: column; align-items: flex-end; line-height: 1.15; font-size: 12px; font-weight: 700; color: #1c1c1c; } #@popupId .p1atc-var__price--old { font-size: 10px; font-weight: 600; color: #8a8a8a; text-decoration: line-through; } #@popupId .p1atc-var__price--new { font-size: 12px; font-weight: 700; color: #D0232E; } #@popupId .p1atc-var__pct { font-size: 9px; font-weight: 700; color: #D0232E; white-space: nowrap; } #@popupId .p1atc-var__add { flex: none; width: 34px; height: 34px; min-height: 0; padding: 0; gap: 0; border-radius: 8px; } #@popupId .p1atc-var__plus { font-size: 18px; line-height: 1; } #@popupId .p1atc-var__check { display: none; align-items: center; justify-content: center; } #@popupId .p1atc-var__add.p1atc-card__add--added .p1atc-var__plus { display: none; } #@popupId .p1atc-var__add.p1atc-card__add--added .p1atc-var__check { display: inline-flex; } #@popupId .p1atc-arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: 38px; height: 38px; border-radius: 50%; border: 1px solid #e0e0e0; background: #fff; color: #1c1c1c; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 3px 10px rgba(0, 0, 0, .12); transition: background .15s ease, border-color .15s ease; } #@popupId .p1atc-arrow:hover { background: #f6f6f6; border-color: #ccc; } #@popupId .p1atc-arrow--prev { left: -6px; } #@popupId .p1atc-arrow--next { right: -6px; } #@popupId .p1atc-footer { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; padding-top: 18px; border-top: 1px solid #eee; } #@popupId .p1atc-footer__cart, #@popupId .p1atc-footer__continue { flex: 1 1 auto; min-width: 180px; font-weight: 700; white-space: nowrap; } #@popupId .p1atc-footer__cart { display: inline-flex; align-items: center; justify-content: center; height: 46px; padding: 0 16px; border: 1px solid #1c7a3d; border-radius: 9px; background: #1c7a3d; color: #fff; font-size: 15px; text-decoration: none; transition: filter .15s ease; } #@popupId .p1atc-footer__cart:hover { filter: brightness(0.92); color: #fff; } #@popupId .p1atc-scrolldown { position: fixed; bottom: 14px; left: 50%; transform: translateX(-50%); z-index: 3; width: 42px; height: 42px; border-radius: 50%; border: none; background: #1c1c1c; color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: .9; box-shadow: 0 4px 14px rgba(0, 0, 0, .3); animation: p1atcBob 1.4s ease-in-out infinite; } #@popupId .p1atc-scrolldown[hidden] { display: none; } @@keyframes p1atcBob { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(3px); } } @@media (max-width: 575.98px) { #@popupId .p1atc-card { width: calc((100% - 12px) / 2); min-width: 148px; } #@popupId .p1atc-arrow { display: none; } #@popupId .p1atc-card__add { padding: 6px 6px; font-size: 13px; } #@popupId .p1atc-var { flex-wrap: wrap; row-gap: 6px; padding: 8px; gap: 6px; } #@popupId .p1atc-var__name { flex-basis: 100%; white-space: normal; } #@popupId .p1atc-var__price { flex: 1 1 auto; align-items: flex-start; } #@popupId .p1atc-var__add { flex-basis: 100%; width: 100%; height: 38px; } } </style> <script> (function () { var overlay = document.getElementById("@popupId"); if (!overlay || overlay.dataset.atcBound === "1") return; overlay.dataset.atcBound = "1"; var mainProductId = "@product.Id"; var track = overlay.querySelector("[data-atc-track]"); var scrollDown = overlay.querySelector("[data-atc-scrolldown]"); function updateScrollIndicator() { if (!scrollDown) return; var remaining = overlay.scrollHeight - overlay.scrollTop - overlay.clientHeight; if (remaining > 24) scrollDown.removeAttribute("hidden"); else scrollDown.setAttribute("hidden", ""); } function openPopup() { overlay.hidden = false; overlay.classList.add("p1atc-open"); document.body.style.overflow = "hidden"; loadDiscountPreview(); requestAnimationFrame(updateScrollIndicator); } overlay.addEventListener("scroll", updateScrollIndicator); window.addEventListener("resize", updateScrollIndicator); if (scrollDown) { scrollDown.addEventListener("click", function () { overlay.scrollTo({ top: overlay.scrollHeight, behavior: "smooth" }); }); } var previewLoaded = false; var previewUrl = "/Default.aspx?ID=@(GetPageIdByNavigationTag("CartService"))&LayoutTemplate=Swift_CrossSellDiscountPreview.cshtml"; var mainVariantId = "@product.VariantId"; function formatMoney(value, currency) { try { return new Intl.NumberFormat("nb-NO", { style: "currency", currency: currency || "NOK" }).format(value); } catch (e) { return (currency ? currency + " " : "") + Number(value).toFixed(2).replace(".", ","); } } function loadDiscountPreview() { if (previewLoaded) return; previewLoaded = true; var simpleCards = Array.prototype.slice.call(overlay.querySelectorAll(".p1atc-card[data-cs-id]")) .filter(function (c) { return !c.querySelector("[data-atc-variants]"); }); var varRows = Array.prototype.slice.call(overlay.querySelectorAll(".p1atc-var[data-cs-id]")); var reqEls = simpleCards.concat(varRows); if (!reqEls.length) return; var ids = reqEls.map(function (el) { return el.getAttribute("data-cs-id") || ""; }); var vids = reqEls.map(function (el) { return el.getAttribute("data-cs-vid") || ""; }); var url = previewUrl + "&mainId=" + encodeURIComponent(mainProductId) + "&mainVariant=" + encodeURIComponent(mainVariantId) + "&ids=" + encodeURIComponent(ids.join(",")) + "&vids=" + encodeURIComponent(vids.join(",")); fetch(url, { headers: { "Accept": "application/json" }, credentials: "same-origin" }) .then(function (r) { return r.text(); }) .then(function (text) { var data; try { var start = text.indexOf("{"); var end = text.lastIndexOf("}"); var jsonStr = (start >= 0 && end > start) ? text.slice(start, end + 1) : text.trim(); data = JSON.parse(jsonStr); } catch (e) { return; } if (!data || !data.products) return; var currency = data.currency; var byKey = {}; data.products.forEach(function (p) { byKey[(p.id || "") + "|" + (p.vid || "")] = p; }); function keyOf(el) { return (el.getAttribute("data-cs-id") || "") + "|" + (el.getAttribute("data-cs-vid") || ""); } simpleCards.forEach(function (card) { var p = byKey[keyOf(card)]; if (!p || !p.hasDiscount) return; var box = card.querySelector("[data-cs-pricebox]"); if (!box) return; box.innerHTML = '<span class="p1atc-card__price--old">' + formatMoney(p.before, currency) + '</span>' + '<span class="p1atc-card__price--new">' + formatMoney(p.after, currency) + '</span>' + '<span class="p1atc-card__save">@Translate("Spar")' + " " + formatMoney(p.save, currency) + (p.percent ? " (-" + p.percent + "%)" : "") + '</span>'; }); varRows.forEach(function (row) { var p = byKey[keyOf(row)]; if (!p || !p.hasDiscount) return; var priceEl = row.querySelector("[data-cs-varprice]"); if (!priceEl) return; priceEl.innerHTML = '<span class="p1atc-var__price--old">' + formatMoney(p.before, currency) + '</span>' + '<span class="p1atc-var__price--new">' + formatMoney(p.after, currency) + (p.percent ? ' <span class="p1atc-var__pct">-' + p.percent + '%</span>' : '') + '</span>'; }); overlay.querySelectorAll(".p1atc-card[data-cs-id]").forEach(function (card) { var list = card.querySelector("[data-atc-variants]"); if (!list) return; var rows = Array.prototype.slice.call(list.querySelectorAll(".p1atc-var[data-cs-id]")); var minBefore = null, minAfter = null, minSave = null, bestPct = 0; rows.forEach(function (row) { var p = byKey[keyOf(row)]; if (!p) return; var before = parseFloat(p.before); var after = p.hasDiscount ? parseFloat(p.after) : before; if (!isNaN(before) && (minBefore === null || before < minBefore)) minBefore = before; if (!isNaN(after) && (minAfter === null || after < minAfter)) minAfter = after; if (p.hasDiscount) { var save = parseFloat(p.save); if (!isNaN(save) && (minSave === null || save < minSave)) minSave = save; if (p.percent && p.percent > bestPct) bestPct = p.percent; } }); if (minBefore === null || minAfter === null) return; if (minAfter >= minBefore - 0.01) return; // no discount on any variant var box = card.querySelector("[data-cs-pricebox]"); if (!box) return; box.innerHTML = '<span class="p1atc-card__price--old">@Translate("fra") ' + formatMoney(minBefore, currency) + '</span>' + '<span class="p1atc-card__price--new">@Translate("fra") ' + formatMoney(minAfter, currency) + '</span>' + (minSave !== null ? '<span class="p1atc-card__save">@Translate("Spar") @Translate("fra") ' + formatMoney(minSave, currency) + (bestPct ? " (-" + bestPct + "%)" : "") + '</span>' : ''); }); }) .catch(function () { }); } function closePopup() { overlay.classList.remove("p1atc-open"); overlay.hidden = true; document.body.style.overflow = ""; } overlay.querySelectorAll("[data-atc-close]").forEach(function (el) { el.addEventListener("click", closePopup); }); overlay.addEventListener("click", function (e) { if (e.target === overlay) closePopup(); }); document.addEventListener("keydown", function (e) { if (e.key === "Escape" && overlay.classList.contains("p1atc-open")) closePopup(); }); function scrollByCards(dir) { if (!track) return; var card = track.querySelector(".p1atc-card"); var step = card ? (card.offsetWidth + 12) : Math.round(track.clientWidth * 0.8); track.scrollBy({ left: dir * step, behavior: "smooth" }); } var prev = overlay.querySelector("[data-atc-prev]"); var next = overlay.querySelector("[data-atc-next]"); if (prev) prev.addEventListener("click", function () { scrollByCards(-1); }); if (next) next.addEventListener("click", function () { scrollByCards(1); }); document.addEventListener("updated.swift.cart", function (e) { try { var fd = e.detail && e.detail.formData; var addedId = fd ? fd.get("ProductId") : null; var referer = fd ? fd.get("ProductReferer") : null; // Ignore adds coming from the cross-sell buttons inside the popup. if (referer === "component_ProductAddToCart_CrossSell") return; if (addedId && addedId === mainProductId) { openPopup(); } } catch (err) { } }); overlay.querySelectorAll(".p1atc-card__add").forEach(function (btn) { btn.addEventListener("click", function () { if (btn.dataset.csAdded === "1") return; var form = btn.closest("form"); if (!form) return; var textEl = btn.querySelector(".p1atc-card__add-text"); var addedLabel = btn.getAttribute("data-atc-added-label"); fetch(form.action, { method: "POST", body: new FormData(form), credentials: "same-origin" }) .then(function (r) { return r.ok ? r.text() : null; }) .then(function (text) { if (text === null) return; document.querySelectorAll(".js-cart-qty").forEach(function (el) { el.innerHTML = text.trim(); }); if (textEl && addedLabel) textEl.textContent = addedLabel; btn.dataset.csAdded = "1"; btn.classList.add("p1atc-card__add--added"); }) .catch(function () { }); }); }); overlay.querySelectorAll("[data-atc-togglevariants]").forEach(function (btn) { var textEl = btn.querySelector(".p1atc-card__add-text"); var labelClosed = btn.getAttribute("data-atc-label-closed"); var labelOpen = btn.getAttribute("data-atc-label-open"); btn.addEventListener("click", function () { var card = btn.closest(".p1atc-card"); var list = card ? card.querySelector("[data-atc-variants]") : null; if (!list) return; var priceBox = card.querySelector("[data-cs-pricebox]"); var willOpen = list.hasAttribute("hidden"); if (willOpen) { list.removeAttribute("hidden"); btn.classList.add("p1atc-card__selectvariant--open"); if (textEl && labelOpen) textEl.textContent = labelOpen; // Hide the master price while variants are open, to free vertical space. if (priceBox) priceBox.style.display = "none"; } else { list.setAttribute("hidden", ""); btn.classList.remove("p1atc-card__selectvariant--open"); if (textEl && labelClosed) textEl.textContent = labelClosed; if (priceBox) priceBox.style.display = ""; } requestAnimationFrame(updateScrollIndicator); }); }); overlay.querySelectorAll("[data-atc-info]").forEach(function (btn) { btn.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); var card = btn.closest(".p1atc-card"); if (card) card.classList.toggle("p1atc-card--desc-open"); }); }); overlay.querySelectorAll("[data-atc-desc]").forEach(function (desc) { desc.addEventListener("click", function () { var card = desc.closest(".p1atc-card"); if (card) card.classList.remove("p1atc-card--desc-open"); }); }); })(); </script> } @{ var text = "available online and in store."; var translated = @Translate(text); string metaDescription = $"Vi har {(string.IsNullOrEmpty(productBrand) ? "" : productBrand + " ")}{product.Name} {translated} Proff1 hjelper deg med {lastGroup}."; string metaTitle = $"{(string.IsNullOrEmpty(productBrand) ? "" : productBrand + " ")}{product.Name}"; } <script> function decreaseQuantity(inputId) { const input = document.getElementById(inputId); const currentValue = parseInt(input.value); if (currentValue > 1) { input.value = currentValue - 1; } } function increaseQuantity(inputId) { const input = document.getElementById(inputId); const currentValue = parseInt(input.value); input.value = currentValue + 1; } function UpdateCart(productId) { } var addToCartButton = document.getElementById("@addToCartButtonId"); if (addToCartButton) { addToCartButton.addEventListener('click', UpdateCart); } </script> <script> (function () { const BTN_ID = "@addToCartButtonId"; const MSG_ID = "@addedMessageId"; const firedOnce = new Set(); function trackAddToCart(payload) { const { productId, qty, unitPriceInclVat, lineValueInclVat, currency } = payload; if (!productId) return; if (firedOnce.has(productId)) return; if (typeof fbq === "function") { fbq('track', 'AddToCart', { content_ids: [productId], content_type: 'product', value: lineValueInclVat, currency, contents: [{ id: productId, quantity: qty, item_price: unitPriceInclVat }] }); } firedOnce.add(productId); setTimeout(() => firedOnce.delete(productId), 1000); } function onAddToCartClick(e) { const btn = e.currentTarget; const form = btn.closest('form'); if (!form) return; const productId = form.querySelector('input[name="ProductId"]')?.value || ""; const variantId = form.querySelector('input[name="VariantId"]')?.value || ""; const quantityInput = form.querySelector(`#Quantity_${productId}_${variantId}`) || form.querySelector('input[name="Quantity"]'); const qty = parseInt(quantityInput?.value || "1", 10); const unitPriceInclVat = parseFloat("@product.Price.PriceWithVat.ToString(System.Globalization.CultureInfo.InvariantCulture)"); const currency = "@Dynamicweb.Ecommerce.Common.Context.Currency.Code"; const lineValueInclVat = qty * unitPriceInclVat; const msg = document.getElementById(MSG_ID); if (msg) { msg.classList.remove('d-none'); setTimeout(() => msg.classList.add('d-none'), 1000); } trackAddToCart({ productId, qty, unitPriceInclVat, lineValueInclVat, currency }); } const btn = document.getElementById(BTN_ID); if (btn) { btn.addEventListener('click', onAddToCartClick, { passive: true }); } })(); </script> <script> (function () { const productId = "@product.Id"; const productPriceInclVat = parseFloat("@product.Price.PriceWithVat.ToString(System.Globalization.CultureInfo.InvariantCulture)"); const currency = "@Dynamicweb.Ecommerce.Common.Context.Currency.Code"; if (window.__dwViewContentTracked) return; window.__dwViewContentTracked = true; function fireViewContent() { if (typeof fbq === "function") { fbq('track', 'ViewContent', { content_ids: [productId], content_type: 'product', value: productPriceInclVat, currency }); } } if (typeof withMarketingConsent === "function") { withMarketingConsent(fireViewContent); } else { fireViewContent(); } })(); </script> @if (!string.IsNullOrEmpty(productIdTag)) { <meta name="description" content="@metaDescription" /> <title>@metaTitle</title> <meta property="og:title" content="@metaTitle"> }
Betal med Vipps/kort eller del opp med Klarna Betalingsalternativer

Beskrivelse

ESSVE Maskinspiker, Huggen, M-fusion. Kompatibel med de fleste standardverktøy. M-Fusion er en overflatebehandling med bedre rustmotstand (C4), mot klassisk varmforsinket (C3). Ved bruk av varmforsinket spiker C3, eller M-Fusion C4, skal kledning alltid beises/males umiddelbart etter montering. Dette gjelder uansett om panel/trevirke er forbehandlet fra fabrikk. Kledning som skal forbli ubehandlet, behandles med jernvitrol eller monteres i kystnære miljøer skal benytte syrefast A4 spiker.

Produsentens produktnummer
772463
EAN
7317761228134
Ved å klikke på 'Godkjenn alle' gir du samtykke til at vi samler informasjon om deg til forskjellige formål, inkludert: Statistikk og Markedsføring