Product Info (right side)
Error executing template "Designs/Swift-v2/Paragraph/Swift-v2_ProductAddToCart.cshtml"
System.MissingMethodException: Method not found: 'Custom.Tracking.Ga4EcommerceItem Custom.Tracking.Ga4EcommerceTracking.CreateProductItem(Dynamicweb.Ecommerce.ProductCatalog.ProductViewModel, System.String, System.String, System.String, Int32, Double, System.String)'.
   at CompiledRazorTemplates.Dynamic.RazorEngine_df448cbfd93b488085cb8c6f3162367d.ExecuteAsync()
   at CompiledRazorTemplates.Dynamic.RazorEngine_df448cbfd93b488085cb8c6f3162367d.ExecuteAsync()
   at CompiledRazorTemplates.Dynamic.RazorEngine_df448cbfd93b488085cb8c6f3162367d.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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Core.Encoders 4 @using System.Globalization 5 @using Custom.Tracking 6 7 @functions { 8 string? DoubleToString(double? value) 9 { 10 if (value.HasValue) 11 { 12 return value.Value.ToString(CultureInfo.InvariantCulture); 13 } 14 return null; 15 } 16 } 17 18 @{ 19 ProductViewModel product = null; 20 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 21 { 22 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 23 } 24 else if (!string.IsNullOrEmpty(Pageview.Page.Item["DummyProduct"]?.ToString()) && Pageview.IsVisualEditorMode) 25 { 26 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 27 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 28 29 if (productList?.Products is object) 30 { 31 product = productList.Products[0]; 32 } 33 } else if (Pageview.IsVisualEditorMode) { 34 product = new ProductViewModel(); 35 product.Id = "1"; 36 product.VariantId = "394041"; 37 product.PurchaseMinimumQuantity = 1; 38 product.PurchaseQuantityStep = 1; 39 product.StockLevel = 10; 40 product.DefaultUnitId = "1"; 41 product.ProductType = Dynamicweb.Ecommerce.Products.ProductType.Stock; 42 product.NeverOutOfstock = false; 43 product.Discontinued = false; 44 product.Price = new PriceViewModel() { 45 Price = 99, 46 PriceFormatted = "99 " + Pageview.Area.EcomCurrencyId, 47 PriceWithoutVat = 99, 48 PriceWithoutVatFormatted = "99 " + Pageview.Area.EcomCurrencyId, 49 PriceWithVat = 99, 50 PriceWithVatFormatted = "99 " + Pageview.Area.EcomCurrencyId 51 }; 52 } 53 54 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 55 bool anonymousUser = Pageview.User == null; 56 57 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser; 58 hideAddToCart = Pageview.IsVisualEditorMode ? false : hideAddToCart; 59 } 60 61 @if (product is object) 62 { 63 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 64 horizontalAlign = horizontalAlign == "center" ? "justify-content-center" : horizontalAlign; 65 horizontalAlign = horizontalAlign == "end" ? "justify-content-end" : horizontalAlign; 66 horizontalAlign = horizontalAlign == "full" ? "" : horizontalAlign; 67 68 bool favoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowAddToFavorites")) ? Model.Item.GetBoolean("ShowAddToFavorites") : false; 69 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowQuantitySelector")) ? Model.Item.GetBoolean("ShowQuantitySelector") : false; 70 bool unitsSelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowUnitsSelector")) ? Model.Item.GetBoolean("ShowUnitsSelector") : false; 71 bool hideInventory = !string.IsNullOrEmpty(Model.Item.GetString("HideInventory")) ? Model.Item.GetBoolean("HideInventory") : false; 72 bool hideStockState = !string.IsNullOrEmpty(Model.Item.GetString("HideStockState")) ? Model.Item.GetBoolean("HideStockState") : false; 73 const string trackingItemListId = "component_product_add_to_cart"; 74 const string trackingItemListName = "Product detail"; 75 var addToCartTrackingItem = Ga4EcommerceTracking.CreateProductItem( 76 product, 77 Pageview.Area?.Name, 78 trackingItemListId, 79 trackingItemListName, 80 0); 81 string addToCartTrackingPayload = 82 Ga4EcommerceTracking.EncodePayload(addToCartTrackingItem); 83 84 string iconPath = "/Files/Images/Icons/"; 85 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); 86 if (!url.Contains("LayoutTemplate")) 87 { 88 url += url.Contains("?") ? "&LayoutTemplate=Swift-v2_MiniCart.cshtml" : "?LayoutTemplate=Swift-v2_MiniCart.cshtml"; 89 } 90 91 string whenVariantsExist = Model.Item.GetRawValueString("WhenVariantsExist", "hide"); 92 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : ""; 93 string fullWidth = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "w-100" : ""; 94 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "cart-shopping.svg"); 95 string addToCartLabel = !addToCartIcon.Contains("_none") ? $"<span class=\"icon-2\">{ReadFile(addToCartIcon)}</span>" : ""; 96 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : ""; 97 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? $"<span class=\"d-none d-md-inline\">{Translate("Add to cart")}</span><span class=\"d-inline d-md-none\">{Translate("Add")}</span>" : ""; 98 bool userHasPendingQuote = Dynamicweb.Ecommerce.Common.Context.Cart != null && Dynamicweb.Ecommerce.Common.Context.Cart.IsQuote; 99 100 if (product.VariantInfo?.VariantInfo == null || whenVariantsExist == "disable") 101 { 102 string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : product.DefaultUnitId; 103 if (string.IsNullOrEmpty(unitId) && product?.UnitOptions != null) 104 { 105 if (product.UnitOptions?.FirstOrDefault<UnitOptionViewModel>() != null) 106 { 107 unitId = product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Id; 108 } 109 } 110 111 double? stepQty = product.PurchaseQuantityStep > 0 ? product.PurchaseQuantityStep : 1; 112 double? minQty = product.PurchaseMinimumQuantity > 0 ? product.PurchaseMinimumQuantity : 1; 113 double? valueQty = minQty > stepQty ? minQty : stepQty; 114 string? disableAddToCart = null; 115 double? maxQty = null; 116 117 if (Dynamicweb.Context.Current.Items.Contains("ProductQuantity")) 118 { 119 valueQty = Convert.ToDouble(Dynamicweb.Context.Current.Items["ProductQuantity"].ToString()); 120 } 121 122 if (product.ProductType == Dynamicweb.Ecommerce.Products.ProductType.Stock && !product.NeverOutOfstock) 123 { 124 disableAddToCart = product.StockLevel <= 0 ? "disabled" : disableAddToCart; 125 maxQty = product.StockLevel; 126 } 127 128 disableAddToCart = whenVariantsExist == "disable" && product.VariantInfo?.VariantInfo != null && string.IsNullOrEmpty(product.VariantId) ? "disabled" : disableAddToCart; 129 disableAddToCart = product.Discontinued ? "disabled" : disableAddToCart; 130 131 if (unitsSelector && product.UnitOptions?.Count > 0 && !hideAddToCart) 132 { 133 <form method="post" action="/Default.aspx?ID=@(Pageview.Page.ID)&ProductId=@product.Id" id="UnitSelectorForm_@(product.Id)_@(product.VariantId?.Replace(".", "_"))_@Model.ID"> 134 <input type="hidden" name="redirect" value="false"> 135 <input type="hidden" name="VariantID" value="@product.VariantId"> 136 <input type="hidden" name="UnitID" class="js-unit-id" value="@unitId"> 137 </form> 138 } 139 140 //Custom check if is master product 141 bool isMasterProduct = string.IsNullOrEmpty(product.VariantId) && product.VariantGroups().Any(); 142 int variantProductsCount = isMasterProduct ? (product.VariantCombinations()?.Count ?? 0) : 0; 143 144 var priceDouble = !string.IsNullOrEmpty(unitId) && !Pageview.IsVisualEditorMode ? product.GetPrice(unitId).Price.PriceWithoutVat : product.Price.PriceWithoutVat; 145 <div class="d-flex @fullWidth js-input-group item_@Model.Item.SystemName.ToLower()"> 146 <div class="d-flex @fullWidth @horizontalAlign flex-wrap flex-lg-nowrap gap-2"> 147 @if (isMasterProduct) 148 { 149 if (string.IsNullOrEmpty(Dynamicweb.Context.Current.Request["ProductId"])) 150 { 151 <a href="@product.GetProductLink(GetPageIdByNavigationTag("Shop"), product.PrimaryOrDefaultGroup?.Id, false)" class="btn btn-primary" data-dw-button="primary"> 152 @(variantProductsCount > 0 153 ? string.Format(Translate("Custom:AddToCart.ShowMasterProductCount", "Vis {0} produkter"), variantProductsCount) 154 : Translate("Custom:AddToCart.ShowMasterProduct", "Vis produkt")) 155 </a> 156 } 157 else 158 { 159 <button type="button" 160 class="btn btn-primary" 161 data-dw-button="primary" 162 onclick="document.getElementById('custom-variant-selector').scrollIntoView({ behavior: 'smooth', block: 'center', container: 'nearest'});"> 163 @Translate("Custom.AddToCart.ShowVariants", "Vis Produkter") 164 </button> 165 } 166 } 167 else 168 { 169 @if (priceDouble > 0 && !hideAddToCart) 170 { 171 <form method="post" action="@url" class="@fullWidth" style="z-index: 1"> 172 <input type="hidden" name="redirect" value="false"> 173 <input type="hidden" name="ProductId" value="@product.Id"> 174 <input type="hidden" name="ProductName" value="@HtmlEncoder.HtmlEncode(product.Name)"> 175 <input type="hidden" name="ProductVariantName" value="@product.VariantName"> 176 <input type="hidden" name="ProductCurrency" value="@Dynamicweb.Ecommerce.Common.Context.Currency.Code"> 177 <input type="hidden" name="ProductPrice" value="@product.Price.ToStringInvariant()"> 178 <input type="hidden" name="ProductDiscount" value="@product.Discount.ToStringInvariant()"> 179 <input type="hidden" name="ProductReferer" value="component_ProductAddToCart"> 180 <input type="hidden" name="ProductGa4Item" value="@HtmlEncoder.HtmlAttributeEncode(addToCartTrackingPayload)"> 181 <input type="hidden" name="cartcmd" value="add"> 182 <input type="submit" class="d-none" onclick="event.preventDefault(); swift.Cart.Update(event)"> @* Fix for enterKey should not redirect to minicart page *@ 183 184 @if (!string.IsNullOrEmpty(product.VariantId)) 185 { 186 <input type="hidden" name="VariantId" value="@product.VariantId"> 187 } 188 189 <template class="js-step-quantity-warning"> 190 <div class="modal-header"> 191 <h1 class="modal-title fs-5">@Translate("The quantity is not valid")</h1> 192 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 193 </div> 194 <div class="modal-body"> 195 @Translate("Please select a quantity that is dividable by") @stepQty 196 </div> 197 </template> 198 199 <template class="js-min-quantity-warning"> 200 <div class="modal-header"> 201 <h1 class="modal-title fs-5">@Translate("The product could not be added to the cart")</h1> 202 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 203 </div> 204 <div class="modal-body"> 205 @Translate("The quantity is not valid. You must buy at least") @product.PurchaseMinimumQuantity 206 </div> 207 </template> 208 209 <template class="js-value-missing-warning"> 210 <div class="modal-header"> 211 <h1 class="modal-title fs-5">@Translate("No amount specified")</h1> 212 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 213 </div> 214 <div class="modal-body"> 215 @Translate("Specify an amount to add to the cart") 216 </div> 217 </template> 218 219 @if (userHasPendingQuote) 220 { 221 <input type="hidden" name="PendingQuote" value="true"> 222 223 <template class="js-pending-quote-notice"> 224 <div class="modal-header"> 225 <h1 class="modal-title fs-5">@Translate("Pending Quote")</h1> 226 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="@Translate("Close")"></button> 227 </div> 228 <div class="modal-body"> 229 @Translate("You need to complete your current quote or empty the cart before adding this product to cart.") 230 </div> 231 </template> 232 } 233 234 @if (quantitySelector || (!anonymousUser && product.VariantInfo?.VariantInfo != null) || (!anonymousUser && favoritesSelector)) 235 { 236 <input type="hidden" id="Unit_@(product.Id)_@product.VariantId?.Replace(".", "_")" name="UnitID" value="@unitId" /> 237 } 238 239 <div class="d-flex flex-wrap gap-2"> 240 @if (!quantitySelector) 241 { 242 <input id="Quantity_@(product.Id)_@product.VariantId?.Replace(".", "_")" class="swift_quantity_field" name="Quantity" value="@valueQty" type="hidden" @disableAddToCart> 243 } 244 245 @if (unitsSelector && product.UnitOptions?.Count > 0) 246 { 247 string selectedUnitName = !string.IsNullOrEmpty(unitId) && product?.UnitOptions != null ? unitId : product.UnitOptions.FirstOrDefault<UnitOptionViewModel>().Name; 248 249 foreach (var unitOption in product.UnitOptions) 250 { 251 if (unitOption.Id == unitId) 252 { 253 selectedUnitName = unitOption.Name; 254 } 255 } 256 257 <button class="btn btn-secondary @flexFill dropdown-toggle" data-dw-button="secondary" type="button" data-bs-toggle="dropdown" aria-expanded="false"> 258 @selectedUnitName 259 </button> 260 261 <ul class="dropdown-menu swift_unit-field"> 262 @foreach (var unitOption in product.UnitOptions) 263 { 264 var selectedUnit = unitOption.Id == unitId ? "selected" : ""; 265 266 <li> 267 <button type="button" class="btn dropdown-item" data-value="@unitOption.Id" onclick="document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId.Replace(".", "_"))_@Model.ID').querySelector('.js-unit-id').value = this.getAttribute('data-value'); 268 document.querySelector('#Unit_@(product.Id)_@product.VariantId?.Replace(".", "_")').value = this.getAttribute('data-value'); 269 swift.PageUpdater.Update(document.querySelector('#UnitSelectorForm_@(product.Id)_@(product.VariantId?.Replace(".", "_"))_@Model.ID'))"> 270 <span>@unitOption.Name</span> 271 <span> 272 @if (unitOption.StockLevel > 0 || unitOption.NeverOutOfStock) 273 { 274 if (!Model.Item.GetBoolean("HideInventory") && !unitOption.NeverOutOfStock) 275 { 276 <span class="small text-success">@unitOption.StockLevel @Translate("In stock")</span> 277 } 278 else 279 { 280 <span class="small text-success">@Translate("In stock")</span> 281 } 282 } 283 else 284 { 285 <span class="small text-danger">@Translate("Out of Stock")</span> 286 } 287 </span> 288 </button> 289 </li> 290 } 291 </ul> 292 } 293 @if (quantitySelector) 294 { 295 @RenderPartial("Components/QuantitySelector.cshtml", product) 296 } 297 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary @flexFill js-add-to-cart-button" data-dw-button="primary" style="white-space: nowrap" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID"> 298 @if (!Model.Item.GetBoolean("HideButtonText")) 299 { 300 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> 301 @addToCartLabel 302 </span> 303 } 304 else 305 { 306 @addToCartLabel 307 } 308 </button> 309 </div> 310 </form> 311 @if (!anonymousUser && favoritesSelector) 312 { 313 @RenderPartial("Components/ToggleFavorite.cshtml", product) 314 } 315 } 316 else 317 { 318 @*Custom inquiry*@ 319 var formPageId = GetPageIdByNavigationTag("ProductRequestForm"); 320 var productAutoId = Dynamicweb.Ecommerce.Services.Products.GetProductById(product.Id, product.VariantId, true)?.AutoId; 321 var modalId = $"productRequestModal_{productAutoId}"; 322 323 <button type="button" role="button" class="z-2 btn btn-primary" data-dw-button="primary" data-bs-toggle="modal" data-bs-target="#productRequestModal_@productAutoId" onclick="swift.Custom.LoadRequestForm(event,'@HtmlEncoder.HtmlAttributeEncode(modalId)', '@formPageId', '@HtmlEncoder.HtmlAttributeEncode(product.Id)', '@HtmlEncoder.HtmlAttributeEncode(product.Number)', '@HtmlEncoder.HtmlAttributeEncode(product.Name)')"> 324 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> 325 @Translate("Custom.ProductRequest.OpenModalButton.Text","Forespørg på produkt") 326 </span> 327 </button> 328 <div class="modal fade" id="@modalId" tabindex="-1" data-bs-backdrop="false" data-dw-colorscheme="@Model.ColorScheme?.Id"> 329 <div class="modal-dialog"> 330 <div class="modal-content"> 331 <div class="modal-header justify-content-between"> 332 <h5 class="modal-title m-0">@Translate("Custom.ProductRequest.Modal.HeaderText", "Produktforespørgsel")</h5> 333 <button type="button" data-bs-dismiss="modal" class="bg-transparent border-0 p-0 text-reset"> 334 <span class="icon-3" aria-hidden="true"> 335 @ReadFile(iconPath + "xmark.svg") 336 </span> 337 <span class="visually-hidden">@Translate("Close product request modal")</span> 338 </button> 339 </div> 340 <div class="modal-body"> 341 </div> 342 </div> 343 </div> 344 </div> 345 } 346 } 347 </div> 348 </div> 349 } 350 else if (whenVariantsExist == "modal") 351 { 352 string ButtonShape = Model.Item.GetRawValueString("VariantButtonShape", "square"); 353 string buttonAspectRatio = Model.Item.GetRawValueString("VariantImageAspectRatio", "56%"); 354 355 string buttonText = Translate("Select"); 356 string variantId = !string.IsNullOrWhiteSpace(product.VariantId) ? product.VariantId : product.DefaultVariantId; 357 358 string variantSelectorServicePageId = !string.IsNullOrEmpty(Model.Item.GetString("VariantSelectorServicePageId")) ? Model.Item.GetLink("VariantSelectorServicePageId").PageId.ToString() : ""; 359 variantSelectorServicePageId = variantSelectorServicePageId != "" ? variantSelectorServicePageId : GetPageIdByNavigationTag("VariantSelectorService").ToString(); 360 361 <div class="d-flex @horizontalAlign w-100 item_@Model.Item.SystemName.ToLower()"> 362 @if (!anonymousUser && favoritesSelector) 363 { 364 @RenderPartial("Components/ToggleFavorite.cshtml", product) 365 } 366 <form action="/Default.aspx?ID=@variantSelectorServicePageId" data-response-target-element="DynamicModalContent" data-preloader="inline" style="z-index: 1" class="@fullWidth"> 367 <input type="hidden" name="ProductID" value="@product.Id"> 368 <input type="hidden" name="VariantID" value="@variantId"> 369 <input type="hidden" name="QuantitySelector" value="@quantitySelector.ToString()"> 370 <input type="hidden" name="HideInventory" value="@hideInventory.ToString()"> 371 <input type="hidden" name="HideStockState" value="@hideStockState.ToString()"> 372 <input type="hidden" name="ButtonLayout" value="@ButtonShape"> 373 <input type="hidden" name="ButtonAspectRatio" value="@buttonAspectRatio"> 374 <input type="hidden" name="VariantSelectorServicePage" value="@variantSelectorServicePageId"> 375 <input type="hidden" name="ViewType" value="ModalContent"> 376 377 <button type="button" onclick="swift.PageUpdater.Update(event)" class="btn btn-primary @fullWidth" data-dw-button="primary" title="@Translate("Select")" data-bs-toggle="modal" data-bs-target="#DynamicModal" id="OpenVariantSelectorModal@(product.Id)_@Pageview.CurrentParagraph.ID">@buttonText</button> 378 </form> 379 </div> 380 } 381 } 382 else if (Pageview.IsVisualEditorMode) 383 { 384 <div class="alert alert-dark m-0">@Translate("The add to cart section will be shown here, if any")</div> 385 } 386
Field display groups will be shown here if any