Error executing template "Designs/Swift-v2/Paragraph/Swift-v2_ProductListItemRepeater.cshtml"
System.MissingMethodException: Method not found: 'System.Collections.Generic.List`1<Custom.Tracking.Ga4EcommerceItem> Custom.Tracking.Ga4EcommerceTracking.CreateProductItems(System.Collections.Generic.IEnumerable`1<Dynamicweb.Ecommerce.ProductCatalog.ProductViewModel>, System.String, System.String, System.String, System.String)'.
at CompiledRazorTemplates.Dynamic.RazorEngine_e188a1be6424490598e590186192e6c1.ExecuteAsync()
at CompiledRazorTemplates.Dynamic.RazorEngine_e188a1be6424490598e590186192e6c1.ExecuteAsync()
at CompiledRazorTemplates.Dynamic.RazorEngine_e188a1be6424490598e590186192e6c1.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
4 @using Dynamicweb.Core.Encoders
5 @using Dynamicweb.Frontend
6 @using Custom.Tracking
7
8 @{
9 int productsCount = 0;
10 int maxProductsCounter = 0;
11
12 ProductListViewModel? productList = null;
13 if (Dynamicweb.Context.Current is not null && Dynamicweb.Context.Current.Items.Contains("ProductList"))
14 {
15 productList = (ProductListViewModel?)Dynamicweb.Context.Current.Items["ProductList"];
16 }
17 else if (Pageview.Page?.Item?["DummyProductGroup"] != null && Pageview.IsVisualEditorMode)
18 {
19 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
20 ProductListViewModel? groupList = pageViewModel.Item?.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
21
22 if (groupList?.Products is object)
23 {
24 productList = groupList;
25 }
26 }
27 }
28
29 @*Custom functions*@
30 @functions {
31 private LinkViewModel GetLink(Dynamicweb.Ecommerce.Products.Group group)
32 {
33 var url = $"/Default.aspx?ID={GetPageIdByNavigationTag("Shop")}&GroupID={group.Id.Trim()}";
34
35 //Custom keep filtration when selecting new group and filters available
36 var keepFiltration = Model.Item.GetBoolean("CustomKeepFiltration");
37
38 if (keepFiltration)
39 {
40 var excludedParameters = !string.IsNullOrEmpty(Model.Item.GetList("CustomExcludedParameters")?.ToString()) ? Model.Item.GetList("CustomExcludedParameters")?.ToString() : "";
41
42 var requestQueryString = Dynamicweb.Context.Current?.Request.QueryString;
43
44 if (requestQueryString != null && requestQueryString.HasKeys())
45 {
46 foreach (var key in requestQueryString.Keys)
47 {
48 var keyStr = key.ToString();
49 if (Custom.AVN.Facets.Helpers.QueryStringHelper.AlwaysExcludedParameters.Contains(keyStr) || excludedParameters.Contains(keyStr, StringComparison.InvariantCultureIgnoreCase)) continue;
50
51 url += $"&{keyStr}={requestQueryString.Get(keyStr)}";
52 }
53 }
54 }
55 var link = new LinkViewModel()
56 {
57 Url = url,
58 IsExternal = false
59 };
60
61 return link;
62 }
63
64 private int GetGroupProductsCount(string groupId, ProductListViewModel productList)
65 {
66 int count = 0;
67
68 foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
69 {
70 foreach (FacetViewModel facet in facetGroup.Facets)
71 {
72 if (facet.Options.Count() > 0)
73 {
74
75 foreach (FacetOptionViewModel option in facet.Options.Where(p => p.Value == groupId))
76 {
77 count += Converter.ToInt32(option.Count);
78 }
79 }
80 }
81 }
82
83 return count;
84 }
85 }
86
87 @if (productList is not null)
88 {
89 <div class="h-100 product-list" data-dw-colorscheme="@Model.ColorScheme?.Id">
90 @{
91 bool isVisualEditor = !string.IsNullOrEmpty(Dynamicweb.Context.Current?.Request.QueryString.Get("VisualEdit")) ? Convert.ToBoolean(Dynamicweb.Context.Current.Request.QueryString.Get("VisualEdit")) : false;
92
93 string groupId = productList?.Group?.Id != null ? productList.Group.Id : "";
94
95 if (productList?.TotalProductsCount > 0)
96 {
97 int pageSizeSetting = Converter.ToInt32(Dynamicweb.Context.Current?.Request.QueryString.Get("OriginalPageSize")) > 0 ? Converter.ToInt32(Dynamicweb.Context.Current.Request.QueryString.Get("OriginalPageSize")) : productList.PageSize;
98 int pageNumber = Converter.ToInt32(Dynamicweb.Context.Current?.Request.QueryString.Get("PageNum")) > 0 ? Converter.ToInt32(Dynamicweb.Context.Current.Request.QueryString.Get("PageNum")) : productList.CurrentPage;
99 int pageSize = Converter.ToInt32(Dynamicweb.Context.Current?.Request.QueryString.Get("PageSize")) > 0 ? Converter.ToInt32(Dynamicweb.Context.Current.Request.QueryString.Get("PageSize")) : productList.PageSize;
100 pageNumber = pageSize != pageSizeSetting ? (pageSize / pageSizeSetting) : pageNumber;
101 int loadedProducts = productList.PageSize > productList.TotalProductsCount ? productList.TotalProductsCount : pageSizeSetting * pageNumber;
102 loadedProducts = loadedProducts > productList.TotalProductsCount ? productList.TotalProductsCount : loadedProducts;
103
104 pageNumber += 1;
105
106 string? searchQuery = Dynamicweb.Context.Current?.Request["q"];
107
108 string? searchLayout = Dynamicweb.Context.Current?.Request.QueryString.Get("SearchLayout");
109
110 @*Custom render group list if rendertype is GroupList*@
111 var pageRenderLayout = Custom.AVN.Facets.Helpers.ProductGroupHelper.PageContainerRenderLayout(groupId, Dynamicweb.Ecommerce.Common.Context.LanguageID, !string.IsNullOrEmpty(searchQuery));
112 @if (pageRenderLayout == Custom.AVN.Facets.Helpers.ProductGroupHelper.RenderLayouts.GroupList)
113 {
114 var subGroups = Dynamicweb.Ecommerce.Services.ProductGroups.GetSubgroups(Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(groupId));
115 @if (subGroups.Any())
116 {
117 <div class="group-list">
118 @foreach (var group in subGroups)
119 {
120 var count = GetGroupProductsCount(group.Id, productList);
121 if (count > 0)
122 {
123 var link = GetLink(group);
124
125 <div class="grid g-col-12 mb-3 gap-0">
126 <div class="g-col-2 group-list__list-item__left-wrapper">
127 <a href="@link" title="">
128 <img class="object-fit-contain w-100" src="/Admin/Public/GetImage.ashx?width=680&height=314&crop=5&DoNotUpscale=True&Compression=75&image=@group.LargeImage" alt="@HtmlEncoder.HtmlAttributeEncode(group.Name)" />
129 </a>
130 </div>
131 <div class="g-col-7 group-list__list-item__middle-wrapper dw-mod">
132 <a class="text-decoration-none" href="@link" title="@HtmlEncoder.HtmlAttributeEncode(group.Name)">
133 <h5>@group.Name</h5>
134 <p>@group.Description</p>
135 </a>
136 </div>
137 <div class="g-col-3 group-list__list-item__right-wrapper dw-mod">
138 <a href="@link" class="btn btn-primary" data-swift-btn="primary" title="">@string.Format(Translate("Custom:ViewList.ListViewItem.GroupList.ShowProducts.", "Vis {0} produkter"), count)</a>
139 </div>
140 </div>
141 }
142 }
143 </div>
144 }
145 }
146 else if (pageRenderLayout == Custom.AVN.Facets.Helpers.ProductGroupHelper.RenderLayouts.ProductList)
147 {
148 int itemSourcePageId = Convert.ToInt32(Model.Item?.GetRawValueString("ListComponentSource", "0"));
149 itemSourcePageId = Dynamicweb.Content.Services.Pages.GetPageOrLanguage(itemSourcePageId, Pageview.AreaID)?.ID ?? itemSourcePageId;
150
151 var page = Dynamicweb.Content.Services.Pages.GetPage(itemSourcePageId);
152
153 if (page != null)
154 {
155 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(page);
156 const string trackingItemListId = "product_list_item_repeater";
157 const string trackingItemListName = "Product list (Item Repeater)";
158 string trackingAffiliation = Pageview.Area?.Name;
159 string trackingCurrency = productList.Products.FirstOrDefault()?.Price?.CurrencyCode ?? string.Empty;
160 var trackingItems = Ga4EcommerceTracking.CreateProductItems(
161 productList.Products,
162 trackingAffiliation,
163 trackingItemListId,
164 trackingItemListName);
165 string viewItemListPayload = Ga4EcommerceTracking.EncodePayload(new
166 {
167 currency = trackingCurrency,
168 item_list_id = trackingItemListId,
169 item_list_name = trackingItemListName,
170 items = trackingItems
171 });
172
173 string? gridColumnSize = Model.Item?.GetRawValueString("GridLayoutDesktop", "3-columns");
174 gridColumnSize = gridColumnSize == "2-columns" ? "g-col-lg-6" : gridColumnSize;
175 gridColumnSize = gridColumnSize == "3-columns" ? "g-col-lg-4" : gridColumnSize;
176 gridColumnSize = gridColumnSize == "4-columns" ? "g-col-lg-3" : gridColumnSize;
177 gridColumnSize = gridColumnSize == "6-columns" ? "g-col-lg-2" : gridColumnSize;
178 gridColumnSize = gridColumnSize == "list" ? "g-col-lg-12" : gridColumnSize;
179
180 string? gridColumnMobileSize = Model.Item?.GetRawValueString("GridLayoutMobile", "2-columns");
181 gridColumnMobileSize = gridColumnMobileSize == "list" ? "g-col-12" : gridColumnMobileSize;
182 gridColumnMobileSize = gridColumnMobileSize == "2-columns" ? "g-col-6" : gridColumnMobileSize;
183
184 <div class="grid pb-3">
185 <script>
186 window.avnTracking.pushEncodedEcommerceEvent(
187 "view_item_list",
188 "@viewItemListPayload"
189 );
190 </script>
191
192 @foreach (ProductViewModel product in productList.Products)
193 {
194 if (maxProductsCounter == 0 || (productsCount < maxProductsCounter))
195 {
196 string link = string.Empty;
197 var currentGroupID = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("GroupID")) ? Dynamicweb.Context.Current.Request.QueryString.Get("GroupID") : string.Empty;
198
199 if (product.Groups.Any(g => g.Id.Equals(currentGroupID, StringComparison.InvariantCultureIgnoreCase)))
200 {
201 link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), Dynamicweb.Context.Current.Request.QueryString.Get("GroupID"), false);
202 }
203 else if (product.PrimaryOrDefaultGroup != null && product.Groups.Any(g => g.Id.Equals(product.PrimaryOrDefaultGroup.Id, StringComparison.InvariantCultureIgnoreCase))) {
204 link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), product.PrimaryOrDefaultGroup.Id, false);
205 }
206 else
207 {
208 link = product.GetProductLink(GetPageIdByNavigationTag("Shop"), false);
209 }
210
211 var keepFiltration = Model.Item.GetBoolean("CustomKeepFiltration");
212
213 if (keepFiltration)
214 {
215 var excludedParameters = !string.IsNullOrEmpty(Model.Item.GetList("CustomExcludedParameters")?.ToString()) ? Model.Item.GetList("CustomExcludedParameters")?.ToString() : "";
216
217 var requestQueryString = Dynamicweb.Context.Current?.Request.QueryString;
218
219 if (requestQueryString != null && requestQueryString.HasKeys())
220 {
221 foreach (var key in requestQueryString.Keys)
222 {
223 var keyStr = key.ToString();
224 if (Custom.AVN.Facets.Helpers.QueryStringHelper.AlwaysExcludedParameters.Contains(keyStr) || excludedParameters.Contains(keyStr, StringComparison.InvariantCultureIgnoreCase)) continue;
225
226 link += $"&{keyStr}={requestQueryString.Get(keyStr)}";
227 }
228 }
229 }
230
231 if (Dynamicweb.Context.Current?.Items.Contains("ProductDetails") != null)
232 {
233 Dynamicweb.Context.Current.Items["ProductDetails"] = product;
234 }
235 else
236 {
237 Dynamicweb.Context.Current?.Items.Add("ProductDetails", product);
238 }
239
240 if (Model.Item?.GetString("ListComponentSource") != null)
241 {
242 var selectItem = Ga4EcommerceTracking.CreateProductItem(
243 product,
244 trackingAffiliation,
245 trackingItemListId,
246 trackingItemListName,
247 productsCount);
248 string selectItemPayload = Ga4EcommerceTracking.EncodePayload(new
249 {
250 currency = product.Price.CurrencyCode,
251 item_list_id = trackingItemListId,
252 item_list_name = trackingItemListName,
253 items = new[] { selectItem }
254 });
255
256 <article class="@gridColumnMobileSize @gridColumnSize d-flex flex-column position-relative product js-product" data-product-id="@product.Id" data-variant-id="@product.VariantId" itemscope itemtype="https://schema.org/Product">
257 <a href="@link"
258 class="stretched-link"
259 data-ga4-ecommerce="@HtmlEncoder.HtmlAttributeEncode(selectItemPayload)"
260 onclick="window.avnTracking.pushEncodedEcommerceEvent('select_item', this.dataset.ga4Ecommerce)"
261 onmouseover="swift.Image.swapImage(event)"
262 onmouseout="swift.Image.swapImage(event)">
263 <span class="visually-hidden">@product.Name</span>
264 </a>
265
266 @RenderGrid(itemSourcePageId)
267 </article>
268 }
269
270 productsCount++;
271 }
272 }
273 </div>
274
275 <div class="my-3" id="ProductListLoadMore">
276 <div class="text-center">
277 <div class="opacity-85 mb-3">@loadedProducts @Translate("out of") @productList.TotalProductsCount @Translate("products")</div>
278 @if (productList.PageCount != 1 && maxProductsCounter == 0 && loadedProducts < productList.TotalProductsCount)
279 {
280 string? sortBySelection = Dynamicweb.Context.Current?.Request.Form["SortBy"] ?? "";
281 sortBySelection = Dynamicweb.Context.Current?.Request.QueryString.Get("SortBy") ?? sortBySelection;
282 string? mainProductId = Dynamicweb.Context.Current?.Request.QueryString.Get("MainProductID");
283
284 <form method="get" data-response-target-element=".product-list" data-swap="afterend" class="w-100">
285 @if (productList?.FacetGroups != null)
286 {
287 foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
288 {
289 foreach (FacetViewModel facetItem in facetGroup.Facets)
290 {
291 foreach (FacetOptionViewModel facetOption in facetItem.Options)
292 {
293 if (facetOption.Selected)
294 {
295 <input type="hidden" name="@facetItem.QueryParameter" value="[@facetOption.Value]">
296 }
297 }
298 }
299 }
300 }
301
302 @if (!string.IsNullOrEmpty(searchQuery))
303 {
304 <input type="hidden" name="q" value="@searchQuery">
305 <input type="hidden" name="SearchLayout" value="@searchLayout">
306 }
307
308 @if (!string.IsNullOrEmpty(mainProductId))
309 {
310 <input type="hidden" name="MainProductID" value="@mainProductId">
311 }
312
313 @if (productList?.Group?.Id != null)
314 {
315 <input type="hidden" name="GroupId" value="@groupId">
316 }
317
318 <input type="hidden" name="OriginalPageSize" value="@pageSizeSetting">
319 <input type="hidden" name="PageSize" value="@(loadedProducts + pageSizeSetting)">
320 <input type="hidden" name="PageNum" value="@pageNumber">
321 <input type="hidden" name="SortBy" value="@sortBySelection">
322 <input type="hidden" name="RequestType" value="UpdateList">
323 <input type="hidden" name="ParagraphID" value="@Model.ID">
324
325 @if (productList?.FacetGroups is object)
326 {
327 string nextPageLink = $"/Default.aspx?ID={Model.PageID}&PageNum={pageNumber}&SortBy={sortBySelection}";
328
329 foreach (FacetGroupViewModel facetGroup in productList.FacetGroups)
330 {
331 foreach (FacetViewModel facetItem in facetGroup.Facets)
332 {
333 foreach (FacetOptionViewModel facetOption in facetItem.Options)
334 {
335 if (facetOption.Selected)
336 {
337 nextPageLink += "&" + facetItem.QueryParameter + "=[" + facetOption.Value + "]";
338 }
339 }
340 }
341 }
342
343 nextPageLink += !string.IsNullOrEmpty(searchQuery) ? "&q=" + searchQuery : "";
344
345 <a href="@nextPageLink" class="btn btn-primary swift_load_more_button" data-dw-button="primary" onclick="swift.ProductList.Update(event)" id="LoadMoreButton_@Model.ID">@Translate("Load more products")</a>
346 }
347 </form>
348 }
349 </div>
350 </div>
351
352 <script>
353 function switchVariantProduct(id, price, imagesrc) {
354 var productImageElement = document.querySelector("#ProductImage_" + id);
355 var productPriceElement = document.querySelector("#ProductPrice_" + id + " .text-price");
356
357 if (productPriceElement) {
358 productPriceElement.innerText = price;
359 }
360
361 if (productImageElement) {
362 productImageElement.src = imagesrc;
363
364 var imageSrcset = productImageElement.srcset;
365 imageSrcset = imageSrcset.replace(/image=.*?&/g, 'image=' + imagesrc + "&");
366
367 productImageElement.srcset = imageSrcset;
368 }
369 }
370 </script>
371 }
372 else if (Pageview.IsVisualEditorMode)
373 {
374 <div class="alert alert-dark m-0" role="alert">
375 <span>@Translate("The selected component does not exist anymore")</span>
376 </div>
377 }
378
379 }
380 }
381 else
382 {
383 string? noProductsFoundMessage = Model.Item?.GetString("NoProductsFoundMessage") ?? Translate("We did not find anything matching your search result");
384 bool hasSubgroups = false;
385
386 if (productList?.SubGroups != null)
387 {
388 hasSubgroups = productList.SubGroups.Any();
389 }
390
391 if (Model.Item is not null && !Model.Item.GetBoolean("HideNoProductsFoundMessage"))
392 {
393 if (!isVisualEditor)
394 {
395 <div class="alert alert-dark m-0" role="alert">
396 @noProductsFoundMessage
397 </div>
398 }
399 else
400 {
401 <div class="alert alert-dark m-0" role="alert">
402 @Translate("Product list: The list will be shown here, if any")
403 </div>
404 }
405 }
406 else if (!hasSubgroups)
407 {
408 <div class="alert alert-dark m-0" role="alert">
409 @noProductsFoundMessage
410 </div>
411 }
412 }
413 }
414 </div>
415 }
416 else if (Pageview.IsVisualEditorMode)
417 {
418 <div class="alert alert-dark m-0" role="alert">
419 <span>@Translate("Product list item repeater: The repeater paragraph will be shown here, if any products are available")</span>
420 </div>
421 }
422