Error executing template "Designs/Swift/Paragraph/Swift_ProductSpecification.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
at Dynamicweb.Ecommerce.Products.GroupRelation.GetGroupRelationsByChildId(String childId)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.GetOrderedInheritableParentIds(Group group, String defaultLanguageId)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.RecursivelySearchForFieldValues(Group group, String defaultLanguageId, List`1 categoryFields)
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.GetGroupFieldValuesByLanguage(Group group, List`1 categoryFields, String languageId, Boolean isInheritedValue, Boolean searchRecursively)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.SearchForGroupFieldValue(Group group, List`1 categoryFields, Boolean allowFallback)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.GetGroupCategoryFieldValues(IEnumerable`1 groups, List`1 fields, Boolean allowFallback)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.GetDefaultCategoryValuesFromGroups(IEnumerable`1 groupInfos, List`1 catFields)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldGroupValueService.GetDefaultCategoryValueFromGroups(IEnumerable`1 groupInfos, Field catField)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetCategoryValue(Product product, String defaultLanguageId, IEnumerable`1 orderedGroups, Field catField, Boolean includeInheritance)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetCategoryValue(Product product, String categoryId, String fieldId, Boolean includeInheritance)
at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetProductCategoryFieldValue(Product product, String categoryId, Field field)
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.CreateView(ProductViewModelSettings settings, Product product, Field field)
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetProductCategoryFieldValues(ProductViewModelSettings settings, Product product, Lazy`1 categoryFieldSortings, Lazy`1 bulkCategoryFieldValues)
at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.<>c__DisplayClass3_1.<BulkCreateView>b__59()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at CompiledRazorTemplates.Dynamic.RazorEngine_0a1ddbd2651640a08ccd43811a41c8bd.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductSpecification.cshtml:line 117
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using System.Globalization;
4 @functions
5 {
6 public static class NumberHelper
7 {
8 /// <summary>
9 /// Converts a scientific notation string with a comma (e.g. "5,8E-05") to a decimal.
10 /// Returns 0 if the string is invalid or empty.
11 /// </summary>
12 public static decimal ToDecimal(string input)
13 {
14 if (string.IsNullOrWhiteSpace(input))
15 {
16 return 0m;
17 }
18
19 // Tell the parser to look for a comma
20 var format = new NumberFormatInfo { NumberDecimalSeparator = "," };
21
22 // TryParse safely attempts to convert without throwing exceptions on bad data
23 if (decimal.TryParse(input, NumberStyles.Float, format, out decimal result))
24 {
25 return result;
26 }
27
28 return 0m; // Return a default value if parsing fails
29 }
30 }
31 }
32 @{
33 ProductViewModel product = null;
34 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
35 {
36 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
37 }
38 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode)
39 {
40 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page);
41 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel();
42
43 if (productList?.Products is object)
44 {
45 product = productList.Products[0];
46 }
47 }
48 }
49
50 @if (product is object && Model?.Item != null)
51 {
52 var displayGroupsRaw = Model.Item.GetRawValueString("DisplayGroups") ?? "";
53 IEnumerable<string> selectedDisplayGroupIds =
54 displayGroupsRaw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
55 List<CategoryFieldViewModel> displayGroups = new List<CategoryFieldViewModel>();
56
57 foreach (var selection in selectedDisplayGroupIds)
58 {
59 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups?.Values ?? Enumerable.Empty<CategoryFieldViewModel>())
60 {
61 if (selection == group.Id)
62 {
63 int fieldsWithNoValueOrZero = 0;
64
65 foreach (var field in group.Fields)
66 {
67 var value = field.Value?.Value?.ToString();
68
69 if (string.IsNullOrWhiteSpace(value))
70 {
71 fieldsWithNoValueOrZero++;
72 }
73 }
74
75 if (fieldsWithNoValueOrZero != group.Fields.Count)
76 {
77 displayGroups.Add(group);
78 }
79 }
80 }
81 }
82
83 bool showProductFields = Model.Item.GetBoolean("ProductFields");
84
85 bool hideTitle = Model.Item.GetBoolean("HideTitle");
86
87 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
88
89 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-4");
90
91 string contentPadding = Model.Item.GetRawValueString("ContentPadding", "");
92 contentPadding = contentPadding == "none" ? string.Empty : contentPadding;
93 contentPadding = contentPadding == "small" ? " p-2 p-md-3" : contentPadding;
94 contentPadding = contentPadding == "large" ? " p-4 p-md-5" : contentPadding;
95
96 string layout = Model.Item.GetRawValueString("Layout", "list");
97 string size = Model.Item.GetRawValueString("Size", "full");
98 string gaps = size == "full" ? " gap-4" : " gap-2";
99
100
101 if (Pageview.IsVisualEditorMode && displayGroups.Count() == 0)
102 {
103 product.ProductFields.Clear();
104 product.ProductFields.Add(Translate("Width"), new FieldValueViewModel { Name = Translate("Width"), Value = "99cm" });
105 product.ProductFields.Add(Translate("Height"), new FieldValueViewModel { Name = Translate("Height"), Value = "195cm" });
106 showProductFields = true;
107 }
108
109 if (layout == "commas")
110 {
111 gaps = size == "full" ? " gap-4" : " gap-2";
112
113 }
114
115 <div class="h-100@(gaps)@(theme)@(contentPadding) item_@Model.Item.SystemName.ToLower()">
116 <div class="grid">
117 @if ((product.ProductFields != null && Model.Item.GetBoolean("ProductFields")) || (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) || (displayGroups.Count != 0))
118 {
119 if (!hideTitle)
120 {
121 <h2 class="g-col-12 @titleFontSize">@Model.Item.GetString("Title")</h2>
122 }
123 }
124
125 @if (displayGroups.Count != 0)
126 {
127 if (layout != "accordion")
128 {
129 foreach (var group in displayGroups)
130 {
131 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders");
132
133 if (!hideHeader)
134 {
135 <h4 class="g-col-12 h4 mb-0">@group.Name</h4>
136 }
137
138 { @RenderFieldsFromList(group.Fields, layout) }
139
140 }
141 }
142 else
143 {
144 <div class="g-col-12">
145 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID">
146 @foreach (var group in displayGroups)
147 {
148 <div class="accordion-item">
149 <h2 class="accordion-header" id="SpecificationHeading_@group.Id">
150 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Id">
151 @group.Name
152 </button>
153 </h2>
154 <div id="SpecificationItem_@group.Id" class="accordion-collapse collapse" aria-labelledby="SpecificationHeading_@group.Id" data-bs-parent="#Specifications_@Model.ID">
155 <div class="accordion-body">
156 @{ @RenderFieldsFromList(group.Fields, "list") }
157 </div>
158 </div>
159 </div>
160 }
161 </div>
162 </div>
163 }
164 }
165
166 @if (product.ProductFields != null && showProductFields)
167 {
168 if (product.ProductFields.Count > 0)
169 {
170 if (layout != "accordion")
171 {
172 {@RenderFieldsFromList(product.ProductFields, layout) }
173 }
174 else
175 {
176 <div class="g-col-12">
177 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID">
178 <div class="accordion-item">
179 <h2 class="accordion-header" id="SpecificationHeading_@Model.ID">
180 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@Model.ID" aria-expanded="false" aria-controls="SpecificationItem_@Model.ID">
181 @Translate("Specifications")
182 </button>
183 </h2>
184 <div id="SpecificationItem_@Model.ID" class="accordion-collapse" aria-labelledby="SpecificationHeading_@Model.ID" data-bs-parent="#Specifications_@Model.ID">
185 <div class="accordion-body">
186 @{ @RenderFieldsFromList(product.ProductFields, "List") }
187 </div>
188 </div>
189 </div>
190 </div>
191 </div>
192 }
193 }
194 }
195
196 @if (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields"))
197 {
198 if (product.ProductCategories.Count > 0)
199 {
200 if (layout != "accordion")
201 {
202 foreach (var group in product.ProductCategories)
203 {
204 CategoryFieldViewModel category = group.Value;
205 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders");
206
207 if (!hideHeader)
208 {
209 <h4 class="g-col-12 h4 mb-0">@group.Value.Name</h4>
210 }
211
212 { @RenderFieldsFromList(category.Fields, layout) }
213 }
214 }
215 else
216 {
217 <div class="g-col-12">
218 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID">
219 @foreach (var group in product.ProductCategories)
220 {
221 CategoryFieldViewModel category = group.Value;
222
223 <div class="accordion-item">
224 <h2 class="accordion-header" id="SpecificationHeading_@group.Value.Id">
225 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Value.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Value.Id">
226 @group.Value.Name
227 </button>
228 </h2>
229 <div id="SpecificationItem_@group.Value.Id" class="accordion-collapse" aria-labelledby="SpecificationHeading_@group.Value.Id" data-bs-parent="#Specifications_@Model.ID">
230 <div class="accordion-body">
231 @{ @RenderFieldsFromList(category.Fields, "list") }
232 </div>
233 </div>
234 </div>
235 }
236 </div>
237 </div>
238 }
239 }
240 }
241 </div>
242 </div>
243 }
244 else if (Pageview.IsVisualEditorMode)
245 {
246 <div class="alert alert-warning m-0">@Translate("No products available")</div>
247 }
248
249 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields, string layout)
250 {
251 string size = Model.Item.GetRawValueString("Size", "full");
252 string gaps = size != "full" ? " gap-1" : string.Empty;
253 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels");
254 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue");
255
256 if (layout == "columns")
257 {
258 <div class="g-col-12">
259 <div class="grid@(gaps)">
260 @foreach (var field in fields)
261 {
262 {@RenderField(field.Value, layout)}
263 }
264 </div>
265 </div>
266 }
267 if (layout == "list")
268 {
269 <div class="g-col-12">
270 <dl class="grid@(gaps)">
271 @foreach (var field in fields)
272 {
273 {@RenderField(field.Value, layout)}
274 }
275 </dl>
276 </div>
277 }
278 if (layout == "table")
279 {
280 string tableSize = size == "full" ? "" : " table-sm";
281 <div class="g-col-12">
282 <table class="table table-striped@(tableSize)">
283 @foreach (var field in fields)
284 {
285 {@RenderField(field.Value, layout)}
286 }
287 </table>
288 </div>
289 }
290 if (layout == "bullets")
291 {
292 string listSize = size == "full" ? "" : "m-0 p-0 lh-1 fs-7 opacity-75";
293 string listStyle = size == "full" ? "" : "style=\"list-style-position: inside\"";
294 <div class="g-col-12">
295 <ul class="@listSize" @listStyle>
296 @foreach (var field in fields)
297 {
298 {@RenderField(field.Value, layout)}
299 }
300 </ul>
301 </div>
302 }
303 if (layout == "commas")
304 {
305 List<string> featuresList = new List<string>();
306
307 foreach (var field in fields)
308 {
309 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value
310
311 if (field.Value?.Value != null)
312 {
313 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
314 {
315 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
316
317 //Hack to support field type providers with a single value
318 if (values.FirstOrDefault() != null)
319 {
320 firstListItemValue = values.FirstOrDefault().Value;
321 }
322 }
323 }
324
325 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.Value.ToString() != "0" && field.Value.Value.ToString() != "0.0"))
326 {
327 if (field.Value.Value is object && !string.IsNullOrEmpty(field.Value.Value.ToString()))
328 {
329 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
330 {
331 List<string> options = new List<string>();
332 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>)
333 {
334 if (!string.IsNullOrWhiteSpace(option.Value))
335 {
336 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
337 {
338 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>";
339 options.Add(colorSpan);
340 }
341 else if (!string.IsNullOrEmpty(option.Value))
342 {
343 options.Add(option.Name);
344 }
345 }
346 }
347 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray()));
348 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
349 {
350 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray()));
351 }
352
353 if (!string.IsNullOrEmpty(optionsString))
354 {
355 if (!hideFieldLabels)
356 {
357 featuresList.Add(field.Value.Name + ": " + optionsString);
358 }
359 else
360 {
361 featuresList.Add(optionsString);
362 }
363 }
364 }
365 else
366 {
367 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString()))
368 {
369 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour")))
370 {
371 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>";
372
373 if (!hideFieldLabels)
374 {
375 featuresList.Add(field.Value.Name + ": " + colorSpan);
376 }
377 else
378 {
379 featuresList.Add(colorSpan);
380 }
381 }
382 else
383 {
384 if (!hideFieldLabels)
385 {
386 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString());
387 }
388 else
389 {
390 featuresList.Add(field.Value.Value.ToString());
391 }
392 }
393 }
394 }
395 }
396 }
397 }
398
399 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray()));
400
401 <div class="g-col-12 opacity-75 fs-7">@featuresString</div>
402 }
403 }
404
405 @helper RenderField(FieldValueViewModel field, string layout)
406 {
407 string size = Model.Item.GetRawValueString("Size", "full");
408 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
409 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels");
410 bool noValues = false;
411 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value
412 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue");
413
414 if (!string.IsNullOrEmpty(fieldValue))
415 {
416 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
417 {
418 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
419 noValues = values.Count > 0 ? false : true;
420
421 //Hack to support field type providers with a single value
422 if (values.FirstOrDefault() != null)
423 {
424 firstListItemValue = values.FirstOrDefault().Value;
425 }
426 }
427 }
428
429 if (!string.IsNullOrEmpty(fieldValue) && noValues == false)
430 {
431 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.ToString() != "0" && field.Value.ToString() != "0.0"))
432 {
433 if (layout == "columns")
434 {
435
436 <div class="grid g-col-6 g-col-lg-4 gap-1">
437 @if (!hideFieldLabels)
438 {
439 <dt class="g-col-12 g-col-lg-4">@field.Name</dt>
440 }
441 <dd class="g-col-12 g-col-lg-8 mb-0 text-break">
442
443 @{ @RenderFieldValue(field)}
444 </dd>
445 </div>
446 }
447 if (layout == "list")
448 {
449 if (!hideFieldLabels)
450 {
451 <dt class="g-col-4">@field.Name</dt>
452 }
453 <dd class="g-col-8 mb-0 text-break">
454 @{ @RenderFieldValue(field)}
455 </dd>
456 }
457 if (layout == "table")
458 {
459 <tr>
460 @if (!hideFieldLabels)
461 {
462 <th class="w-25 w-lg-50" scope="row">@field.Name</th>
463 }
464 <td class="text-break">
465 @{ @RenderFieldValue(field) }
466 </td>
467 </tr>
468 }
469 if (layout == "bullets")
470 {
471 <li>
472 @if (!hideFieldLabels)
473 {
474 <strong>@field.Name</strong>
475 }
476 <span>
477 @{ @RenderFieldValue(field) }
478 </span>
479 </li>
480 }
481 }
482 }
483 }
484
485 @helper RenderFieldValue(FieldValueViewModel field)
486 {
487 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
488 if (field.SystemName == "ProductWeight")
489 {
490
491 decimal calculatedValue = NumberHelper.ToDecimal(fieldValue);
492 decimal baseValue = NumberHelper.ToDecimal("0,00001");
493
494
495 fieldValue = calculatedValue.ToString("0.######", System.Globalization.CultureInfo.InvariantCulture).Replace(".", ",");
496
497 }
498 bool isLink = field?.Type == "Link";
499 bool isColor = false;
500 bool isBrandName = field?.SystemName == "Brand_name";
501
502 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
503 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
504
505
506 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>))
507 {
508 int valueCount = 0;
509 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
510 int totalValues = values.Count;
511
512 foreach (FieldOptionValueViewModel option in values)
513 {
514 if (!string.IsNullOrEmpty(option.Value))
515 {
516 if (option.Value.Substring(0, 1) == "#")
517 {
518 isColor = true;
519 }
520 }
521
522 if (!isColor)
523 {
524 @option.Name
525 }
526 else
527 {
528 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Name"></span>
529 }
530
531 if (valueCount != totalValues && valueCount < (totalValues - 1))
532 {
533 if (isColor)
534 {
535 <text> </text>
536 }
537 else
538 {
539 <text>, </text>
540 }
541 }
542 valueCount++;
543 }
544 }
545 else
546 {
547 if (fieldValue.Substring(0, 1) == "#")
548 {
549 isColor = true;
550 }
551
552 if (!isColor)
553 {
554 if (isLink)
555 {
556 string linktTitle = !fieldValue.Contains("aspx") ? fieldValue : Translate("Go to link");
557 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "target=\"_blank\"" : string.Empty;
558 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "rel=\"noopener\"" : string.Empty;
559
560 <a href="@field.Value" title="@field.Name" @target @rel>@linktTitle</a>
561 }
562 else if (isBrandName)
563 {
564 <span itemprop="brand" itemtype="https://schema.org/Brand" itemscope>
565 <span itemprop="name">@fieldValue</span>
566 </span>
567 }
568 else
569 {
570 @fieldValue
571 }
572
573 }
574 else
575 {
576 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span>
577 }
578 }
579 }
580