Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsInfo.cshtml"
System.ArgumentOutOfRangeException: Index and length must refer to a location within the string.
Parameter name: length
at System.String.Substring(Int32 startIndex, Int32 length)
at CompiledRazorTemplates.Dynamic.RazorEngine_db64cd145ec046c0baa800401ef3e3ca.<>c__DisplayClass1_0.<RenderFieldValue>b__0(TextWriter __razor_helper_writer) in E:\Dynamicweb.NET\Solutions\swift.blomberg.espresso4.dk\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsInfo.cshtml:line 423
at RazorEngine.Templating.TemplateWriter.ToString()
at CompiledRazorTemplates.Dynamic.RazorEngine_db64cd145ec046c0baa800401ef3e3ca.<>c__DisplayClass0_0.<RenderField>b__0(TextWriter __razor_helper_writer) in E:\Dynamicweb.NET\Solutions\swift.blomberg.espresso4.dk\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsInfo.cshtml:line 373
at CompiledRazorTemplates.Dynamic.RazorEngine_db64cd145ec046c0baa800401ef3e3ca.Execute() in E:\Dynamicweb.NET\Solutions\swift.blomberg.espresso4.dk\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsInfo.cshtml:line 243
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites
4 @using Dynamicweb.Ecommerce.Products.FieldDisplayGroups
5 @using Dynamicweb.Frontend
6
7 @{
8 ProductViewModel product = new ProductViewModel();
9
10 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
11 {
12 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
13 }
14
15 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
16 bool anonymousUser = Pageview.User == null;
17 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser;
18 hideAddToCart = product.VariantInfo.VariantInfo != null && Model.Item.GetBoolean("HideVariantSelector") ? true : hideAddToCart;
19 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser;
20 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false;
21
22 bool IsNeverOutOfStock = product.NeverOutOfstock;
23 string[] variantId = product.VariantId.Split('.');
24 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : "";
25 if (IsNeverOutOfStock)
26 {
27 disableAddToCart = "";
28 }
29
30 // Does product has a expected delivery data
31 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now;
32 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? "";
33
34 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService"));
35 if (!url.Contains("LayoutTemplate"))
36 {
37 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
38 }
39
40 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList();
41 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>();
42
43 foreach (var selection in selectedDisplayGroups)
44 {
45 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values)
46 {
47 if (selection == group.Id)
48 {
49 mainFeatures.Add(group);
50 }
51 }
52 }
53
54 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
55
56 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6");
57
58 string contentPadding = Model.Item.GetRawValueString("ContentPadding", "");
59 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding;
60 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding;
61
62 string quantityPricesLayout = Model.Item.GetRawValueString("QuantityPricesLayout", "list");
63
64 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\"";
65 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1";
66 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty;
67 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : "";
68
69 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
70 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
71
72 string priceMin = "";
73 string priceMax = "";
74
75 var favoriteParameters = new Dictionary<string, object>();
76 if (!anonymousUser && !hideFavoritesSelector)
77 {
78 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists();
79 int defaultFavoriteListId = 0;
80
81 if (favoreiteLists.Count() == 1) {
82 foreach (FavoriteList list in favoreiteLists) {
83 defaultFavoriteListId = list.ListId;
84 }
85 }
86
87 favoriteParameters.Add("ListId", defaultFavoriteListId);
88 }
89
90 var badgeParms = new Dictionary<string, object>();
91 badgeParms.Add("size", "h7");
92 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
93 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
94 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
95 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
96 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
97
98 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
99 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
100 DateTime createdDate = product.Created.Value;
101 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
102 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
103 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
104 }
105
106 <div class="h-100 @(contentPadding) @(theme)">
107 <div class="d-flex flex-column gap-4 js-product">
108 @if (showBadges) {
109 <div>
110 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
111 </div>
112 }
113
114 <div>
115 <h1 class="@titleFontSize" itemprop="name">@product.Name</h1>
116 @if (!Model.Item.GetBoolean("HideProductNumber"))
117 {
118 <div class="opacity-85">@product.Number</div>
119 }
120 </div>
121
122 @if (!hidePrice && false)
123 {
124 <div>
125 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
126 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span>
127
128 @if (showPricesWithVat == "false" && !neverShowVat)
129 {
130 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted;
131
132 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span>
133 if (product.Price.Price != product.PriceBeforeDiscount.Price)
134 {
135 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
136 }
137 }
138 else
139 {
140 string beforePrice = product.PriceBeforeDiscount.PriceFormatted;
141
142 <span itemprop="price" content="@product.Price.Price" class="d-none"></span>
143 if (product.Price.Price != product.PriceBeforeDiscount.Price)
144 {
145 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
146 }
147 }
148
149 @if (showPricesWithVat == "false" && !neverShowVat)
150 {
151 string price = product.Price.PriceWithoutVatFormatted;
152 if (product?.VariantInfo?.VariantInfo != null)
153 {
154 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : "";
155 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : "";
156 }
157 if (priceMin != priceMax)
158 {
159 price = priceMin + " - " + priceMax;
160 }
161 <span class="text-price">@price</span>
162 }
163 else
164 {
165 string price = product.Price.PriceFormatted;
166 if (product?.VariantInfo?.VariantInfo != null)
167 {
168 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : "";
169 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : "";
170 }
171 if (priceMin != priceMax)
172 {
173 price = priceMin + " - " + priceMax;
174 }
175 <span class="text-price">@price</span>
176 }
177 </div>
178 @if (showPricesWithVat == "false" && !neverShowVat)
179 {
180 string price = product.Price.PriceWithVatFormatted;
181 if (product?.VariantInfo?.VariantInfo != null)
182 {
183 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : "";
184 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : "";
185 }
186 if (priceMin != priceMax)
187 {
188 price = priceMin + " - " + priceMax;
189 }
190 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small>
191 }
192
193 @if (product.Prices.Count > 0) {
194 if (quantityPricesLayout == "list") {
195 <div class="mt-3">
196 @foreach (PriceListViewModel quantityPrice in product.Prices)
197 {
198 string quantityLabel = Translate("PCS");
199 string quantityPriceSuffix = quantityPrice.Quantity > 1 ? Translate("pr. PCS") : "";
200
201 <small class="d-block opacity-75"><span>@quantityPrice.Quantity @quantityLabel</span> - <span class="fw-bold">@quantityPrice.Price.PriceFormatted @quantityPriceSuffix</span></small>
202 }
203 </div>
204 } else if (quantityPricesLayout == "table") {
205 <div class="grid">
206 <table class="table table-sm mt-3 g-col-12 g-col-lg-6">
207 <thead>
208 <tr>
209 <td>@Translate("QTY")</td>
210 <td>@Translate("pr. PCS")</td>
211 </tr>
212 </thead>
213 <tbody>
214 @foreach (PriceListViewModel quantityPrice in product.Prices)
215 {
216 <tr>
217 <td>@quantityPrice.Quantity</td>
218 <td>@quantityPrice.Price.PriceFormatted</td>
219 </tr>
220 }
221 </tbody>
222 </table>
223 </div>
224 }
225 }
226 </div>
227 }
228
229 @if (!string.IsNullOrEmpty(product.ShortDescription))
230 {
231 <div class="mb-0-last-child" itemprop="disambiguatingDescription">
232 @product.ShortDescription
233 </div>
234 }
235
236 @if (mainFeatures.Count > 0)
237 {
238 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
239 {
240 <dl class="grid gap-0">
241 @foreach (var field in mainFeatureGroup.Fields)
242 {
243 @RenderField(field.Value)
244 }
245 </dl>
246 }
247 }
248
249 @if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector"))
250 {
251 int groupNumber = 1;
252
253 <form class="mb-3 js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())">
254 <input type="hidden" name="variantid" />
255
256 @foreach (var variantGroup in product.VariantGroups())
257 {
258 VariantGroupViewModel group = variantGroup;
259
260 <h3 class="h6">@group.Name</h3>
261 <div class="mb-3 js-variant-group" data-group-id="@groupNumber">
262 @foreach (var option in group.Options)
263 {
264 string active = variantId.Contains(option.Id) ? "active" : "";
265
266 if (!string.IsNullOrEmpty(option.Color))
267 {
268 <button type="button" class="btn colorbox rounded-circle me-1 mb-2 d-inline-block variant-option js-variant-option @active" style="background-color: @option.Color" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"></button>
269 }
270 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value))
271 {
272 <button type="button" class="btn p-0 d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id">
273 <img src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" />
274 </button>
275 }
276 else
277 {
278 <button type="button" class="btn btn-secondary d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id">
279 @option.Name
280 </button>
281 }
282 }
283 </div>
284
285 groupNumber++;
286 }
287 </form>
288 }
289
290 <div class="d-flex flex-row flex-nowrap gap-2">
291 @if (!hideAddToCart && false)
292 {
293 <form method="post" action="@url" class="flex-fill">
294 <input type="hidden" name="redirect" value="false" />
295 <input type="hidden" name="ProductId" value="@product.Id" />
296 <input type="hidden" name="cartcmd" value="add" />
297
298 @if (!string.IsNullOrEmpty(product.VariantId))
299 {
300 <input type="hidden" name="VariantId" value="@product.VariantId" />
301 }
302 @if (!Model.Item.GetBoolean("QuantitySelector"))
303 {
304 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" type="hidden">
305 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary w-100 js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">@Translate("Add to cart")</button>
306 } else {
307 <div class="input-group input-primary-button-group js-input-group d-flex flex-row flex-nowrap">
308 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label>
309 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 96px; min-width:64px;" type="number">
310 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">@Translate("Add to cart")</button>
311 </div>
312
313 if (stepQty != "1")
314 {
315 <div class="invalid-feedback d-none">
316 @Translate("Please select a quantity that is dividable by") @stepQty
317 </div>
318 }
319 }
320 </form>
321 if (!anonymousUser && !hideFavoritesSelector)
322 {
323 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
324 }
325 }
326 else if (!anonymousUser && !hideFavoritesSelector)
327 {
328 <div class="flex-fill">
329 @Translate("Add to favorites") @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
330 </div>
331 }
332 </div>
333 </div>
334 @if (!Model.Item.GetBoolean("HideStockState"))
335 {
336 if (!IsNeverOutOfStock)
337 {
338 <div class="mt-3 js-stock-state">
339
340 @if (product.StockLevel > 0)
341 {
342 if (!Model.Item.GetBoolean("HideInventory"))
343 {
344 <p class="small text-success m-0">@product.StockLevel @Translate("Products available in stock")</p>
345 }
346 else
347 {
348 <p class="small text-success m-0">@Translate("Available in stock")</p>
349 }
350 }
351
352 else
353 {
354 <p class="small text-danger m-0">@Translate("Out of Stock")</p>
355 }
356
357 @if (hasExpectedDelivery)
358 {
359 <p>
360 <span>@Translate("Expected back in stock:")</span>
361 <span>@expectedDeliveryDate</span>
362 </p>
363 }
364
365 </div>
366 }
367 }
368 </div>
369
370 @helper RenderField(FieldValueViewModel field)
371 {
372 string fieldValue = field?.Value != null ? field.Value.ToString() : "",
373 energyLabel = RenderFieldValue(field).ToString().ToLower(),
374 iconSrc = "/admin/public/getimage.ashx?Image=/Files/Images/energylabels/",
375 iconOption = "&Resolution=72&Compression=90&Width=100",
376 energyClass = iconSrc + energyLabel.Replace(" ", "_") + ".png" + iconOption,
377 energyClassDisc = iconSrc + "grade_" + energyLabel.Replace(" ", "_") + ".png" + iconOption;
378
379 bool noValues = false;
380
381 if (!string.IsNullOrEmpty(fieldValue))
382 {
383 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
384 {
385 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
386 noValues = values.Count > 0 ? false : true;
387 }
388 }
389
390 if (!string.IsNullOrEmpty(fieldValue) && noValues == false)
391 {
392 <dt class="g-col-12 g-col-sm-4 g-col-lg-12 fw-bold m-0">@field.Name</dt>
393 <dd class="g-col-12 g-col-sm-8 g-col-lg-12 mb-3" data-type="@field.Type">
394 @if ( energyClass.Contains( "_class" ) && field.Type != "EditorText") {
395 <img src="@energyClass" alt="@energyLabel" title="@energyLabel" />
396 } else if ( energyClassDisc.Contains( "_disc" ) && field.Type != "EditorText") {
397 <img src="@energyClassDisc" alt="@energyLabel" title="@energyLabel" />
398 }
399 else {
400 @RenderFieldValue(field)
401 }
402 </dd>
403 }
404 }
405
406 @helper RenderFieldValue(FieldValueViewModel field)
407 {
408 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
409
410 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
411 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
412
413 bool isColor = false;
414
415 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>))
416 {
417 int valueCount = 0;
418 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
419 int totalValues = values.Count;
420
421 foreach (FieldOptionValueViewModel option in values)
422 {
423 if (option.Value.Substring(0, 1) == "#")
424 {
425 isColor = true;
426 }
427
428 if (!isColor)
429 {
430 @option.Name
431 }
432 else
433 {
434 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Value"></span>
435 }
436
437 if (valueCount != totalValues && valueCount < (totalValues - 1))
438 {
439 if (isColor)
440 {
441 <text> </text>
442 }
443 else
444 {
445 <text>, </text>
446 }
447 }
448 valueCount++;
449 }
450 }
451 else
452 {
453 if (fieldValue.Substring(0, 1) == "#")
454 {
455 isColor = true;
456 }
457
458 if (!isColor)
459 {
460 @fieldValue
461 }
462 else
463 {
464 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span>
465 }
466 }
467 }
468
469 @if (product.VariantInfo.VariantInfo != null)
470 {
471 <script type="module">
472 swift.VariantSelector.init();
473 </script>
474 }
475
476
477