Error executing template "Designs/Swift/eCom/ProductCatalog/ProductViewDetail.cshtml" System.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - An existing connection was forcibly closed by the remote host.) ---> System.ComponentModel.Win32Exception (0x80004005): An existing connection was forcibly closed by the remote host at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry) at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry) at System.Data.SqlClient.SqlConnection.Open() at Dynamicweb.Data.DatabaseConnectionProvider.CreateConnection(Boolean open) at Dynamicweb.Data.Database.CreateConnection() at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout) at Dynamicweb.Ecommerce.Products.DetailRepository.GetInheritedDetailsBulk(List`1 productIds, String detailType, Boolean onlyDefault) at Dynamicweb.Ecommerce.Products.DetailService.GetPrimaryDetailsBulk(IEnumerable`1 productKeys, String detailType) at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetDefaultImage(MediaViewModelSettings settings, String productId, String languageId, String variantId, Lazy`1 productImages, Lazy`1 details) at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetDefaultImage(MediaViewModelSettings settings, Product product, Lazy`1 productImages, Lazy`1 details) at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.<>c__DisplayClass3_2.<BulkCreateView>b__62() at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at CompiledRazorTemplates.Dynamic.RazorEngine_6b3db378ecbe457c9fb58108c69ebd64.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\eCom\ProductCatalog\ProductViewDetail.cshtml:line 13 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() ClientConnectionId:6d103052-d171-4ee3-b61e-32a762056566 Error Number:10054,State:0,Class:20
1 @inherits ViewModelTemplate<ProductViewModel> 2 @using Dynamicweb.Rendering 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 @using Dynamicweb.Core 5 @using Dynamicweb.Environment 6 @using Dynamicweb.Configuration @*//CUSTOM*@ 7 8 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 9 10 @{ 11 string metaDescription = string.IsNullOrEmpty(Model.MetaDescription) ? Model.Name : Model.MetaDescription; 12 13 Pageview.Meta.AddTag($"<meta property=\"og:image\" content=\"{Dynamicweb.Context.Current.Request.Url.Scheme}://{Dynamicweb.Context.Current.Request.Url.Host}{Model.DefaultImage.Value}\">"); 14 Pageview.Meta.AddTag($"<meta property=\"og:image:alt\" content=\"{Model.Name}\">"); 15 Pageview.Meta.AddTag($"<meta property=\"og:description\" content=\"{metaDescription}\">"); 16 17 Pageview.Meta.AddTag("twitter:image", Model.DefaultImage.Value); 18 Pageview.Meta.AddTag("twitter:image:alt", Model.Name); 19 Pageview.Meta.AddTag("twitter:description", metaDescription); 20 21 //CUSTOM - NOTE: This use the group 'show in sitemap' 22 if (Dynamicweb.Context.Current.Request.HasRequest("GroupID")) 23 { 24 var group = Dynamicweb.Ecommerce.Services.ProductGroups.GetGroup(Dynamicweb.Context.Current.Request.GetString("GroupID")); 25 if (group is object && group.NavigationShowInSiteMap == false) 26 { 27 Pageview.Meta.AddTag("robots", "noindex, nofollow"); 28 } 29 } 30 //--CUSTOM 31 } 32 33 @{ 34 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 35 { 36 Dynamicweb.Context.Current.Items["ProductDetails"] = Model; 37 } 38 else 39 { 40 Dynamicweb.Context.Current.Items.Add("ProductDetails", Model); 41 } 42 43 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 44 if (isLazyLoadingForProductInfoEnabled) 45 { 46 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 47 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 48 bool hasVariantId = !string.IsNullOrEmpty(Model.VariantId); 49 string variantIdParam = hasVariantId ? $"/{Model.VariantId}" : ""; 50 string priceFilledProperties = $"Price,PriceFormatted{(showPricesWithVat == "false" && !neverShowVat ? ",PriceWithVat,PriceWithVatFormatted" : "")}"; 51 string productInfoFeed = $@"/dwapi/ecommerce/products/{Model.Id}{variantIdParam} 52 ?UserId={Converter.ToString(Pageview.User?.ID)} 53 &LanguageId={Pageview.Area.EcomLanguageId}&CurrencyCode={Pageview.Area.EcomCurrencyId}&CountryCode={Pageview.Area.EcomCountryCode}&ShopId={Pageview.Area.EcomShopId} 54 &FilledProperties=Id,Price,PriceBeforeDiscount,StockLevel,VariantInfo,NeverOutOfstock,Prices 55 &PriceSettings.ShowPricesWithVat={Pageview.Area.EcomPricesWithVat} 56 &PriceSettings.FilledProperties={priceFilledProperties} 57 &getproductinfo=true"; 58 Dynamicweb.Context.Current.Items["ProductInfoFeed"] = productInfoFeed; 59 60 <script type="module"> 61 swift.LiveProductInfo.init(); 62 </script> 63 } 64 65 string googleTagManagerID = Pageview.AreaSettings.GetString("GoogleTagManagerID"); 66 string googleAnalyticsMeasurementID = Pageview.AreaSettings.GetString("GoogleAnalyticsMeasurementID"); 67 68 bool allowTracking = true; 69 if (CookieManager.IsCookieManagementActive) 70 { 71 var cookieOptInLevel = CookieManager.GetCookieOptInLevel(); 72 allowTracking = cookieOptInLevel == CookieOptInLevel.All || (cookieOptInLevel == CookieOptInLevel.Functional && CookieManager.GetCookieOptInCategories().Contains("Statistical")); 73 } 74 75 if ((!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) || !string.IsNullOrWhiteSpace(googleTagManagerID)) && allowTracking) 76 { 77 @Custom.EvaSolo.Tracking.Renders.DataLayer.RenderPartial("Components/DataLayers/ViewItem_Custom.cshtml", new Custom.EvaSolo.Tracking.Model.DataLayerViewModel { Product = Model }); @*//CUSTOM*@ 78 } 79 } 80 81 <script> 82 window.addEventListener('load', function (event) { 83 swift.Video.init(); 84 }); 85 </script> 86
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage_Custom.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: second at System.Linq.Enumerable.Union[TSource](IEnumerable`1 first, IEnumerable`1 second) at CompiledRazorTemplates.Dynamic.RazorEngine_62113a89fcb84cfc94dcf8a73c8613e8.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage_Custom.cshtml:line 163 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.Core.Encoders @*//CUSTOM*@ 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 @using Dynamicweb.Frontend 5 @using System.IO 6 7 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 8 @* TODO: Migrate to swiffyslider *@ 9 10 @functions { 11 public ProductViewModel product { get; set; } = new ProductViewModel(); 12 public string galleryLayout { get; set; } 13 public string[] supportedImageFormats { get; set; } 14 public string[] supportedVideoFormats { get; set; } 15 public string[] supportedDocumentFormats { get; set; } 16 public string[] allSupportedFormats { get; set; } 17 18 public class RatioSettings 19 { 20 public string Ratio { get; set; } 21 public string CssClass { get; set; } 22 public string CssVariable { get; set; } 23 public string Fill { get; set; } 24 } 25 26 public RatioSettings GetRatioSettings(string size = "desktop") 27 { 28 var ratioSettings = new RatioSettings(); 29 30 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 31 ratio = ratio != "0" ? ratio : ""; 32 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 33 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 34 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 35 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 36 37 ratioSettings.Ratio = ratio; 38 ratioSettings.CssClass = cssClass; 39 ratioSettings.CssVariable = cssVariable; 40 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 41 42 return ratioSettings; 43 } 44 45 public string GetArrowsColor() 46 { 47 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 48 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 49 return arrowsColor; 50 } 51 52 public string GetThumbnailPlacement() 53 { 54 return Model.Item.GetRawValueString("ThumbnailPlacement", "bottom"); 55 } 56 57 public string GetThumbnailRowSettingCss() 58 { 59 switch (GetThumbnailPlacement()) 60 { 61 case "bottom": 62 return "custom-productdetailsimage__position-bottom"; //CUSTOM 63 case "left": 64 return "order-first"; //CUSTOM 65 case "right": 66 return "order-last"; //CUSTOM 67 default: 68 return ""; //CUSTOM 69 } 70 } 71 72 //CUSTOM 73 public string GetThumbnailRowSettingCssWrapper() 74 { 75 switch (GetThumbnailPlacement()) 76 { 77 case "bottom": 78 return "d-flex"; 79 case "left": 80 return "d-flex flex-column"; 81 case "right": 82 return "d-flex flex-column"; 83 default: 84 return "d-flex flex-wrap"; 85 } 86 } 87 //--CUSTOM 88 89 //CUSTOM 90 public IEnumerable<MediaViewModel> SortAssets(IEnumerable<MediaViewModel> assetsList, string[] sortedFormats) 91 { 92 return assetsList 93 .Select((asset, i) => new { asset, originalIndex = i }) 94 .OrderBy(x => 95 { 96 var index = Array.FindIndex(sortedFormats, f => x.asset.DisplayName.StartsWith(f)); 97 return index == -1 ? int.MaxValue : index; 98 }) 99 .ThenBy(x => x.originalIndex) 100 .Select(x => x.asset) 101 .ToList(); 102 } 103 //--CUSTOM 104 105 //CUSTOM 106 public static string GetAspectRatio(string value) 107 { 108 switch (value) 109 { 110 case "0": return "auto"; 111 case "fill": return "cover"; 112 case "100%": return "1 / 1"; 113 case "56%": return "16 / 9"; 114 case "177%": return "9 / 16"; 115 case "75%": return "4 / 3"; 116 case "133%": return "3 / 4"; 117 case "28%": return "32 / 9"; 118 default: return "auto"; 119 } 120 } 121 //--CUSTOM 122 } 123 124 @{ 125 ProductViewModel product = null; 126 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 127 { 128 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 129 } 130 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 131 { 132 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 133 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 134 135 if (productList?.Products is object) 136 { 137 product = productList.Products[0]; 138 } 139 } 140 } 141 142 @if (product is object) 143 { 144 @* Supported formats *@ 145 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 146 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 147 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 148 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 149 150 @* Collect the assets *@ 151 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>() ?? Enumerable.Empty<string>(); 152 var selectedAssetCategoriesSort = Pageview.AreaSettings.GetItem("CustomSettings").GetRawValueString("AssetCategoriesSort").Split(','); //CUSTOM 153 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 154 155 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 156 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 157 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories?.Where(x => 158 !string.IsNullOrEmpty(x?.SystemName) && 159 selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets) ?? Enumerable.Empty<MediaViewModel>(); 160 assetsImages = assetsImages.OrderByDescending(x => x.Value == defaultImage); 161 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 162 assetsList = assetsList.Union(assetsImages); 163 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 164 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 165 assetsList = selectedAssetCategoriesSort.Any() ? SortAssets(assetsList, selectedAssetCategoriesSort) : assetsList; //CUSTOM 166 167 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 168 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 169 170 int totalAssets = 0; 171 if (showOnlyPrimaryImage == false) 172 { 173 foreach (MediaViewModel asset in assetsList) 174 { 175 var assetValue = asset.Value; 176 foreach (string format in allSupportedFormats) 177 { 178 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 179 { 180 totalAssets++; 181 } 182 } 183 } 184 } 185 186 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 187 { 188 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 189 totalAssets = 1; 190 } 191 192 @* Theme settings *@ 193 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 194 195 var badgeParms = new Dictionary<string, object>(); 196 badgeParms.Add("size", "h5"); 197 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 198 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 199 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 200 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 201 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 202 203 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 204 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 205 DateTime createdDate = product.Created.Value; 206 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 207 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 208 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 209 210 //CUSTOM 211 var videoObjectFit = Model.Item.GetRawValueString("Custom_VideoObjectFit", "plyr__wrap-aspect"); 212 var videoControlsPosition = Model.Item.GetRawValueString("Custom_VideoControlsPosition", " ").Trim(); 213 videoControlsPosition = videoObjectFit == "plyr__wrap-aspect" && (videoControlsPosition == "plyr__wrap-controls") ? "" : videoControlsPosition; 214 videoControlsPosition = videoObjectFit == "plyr__wrap-cover" ? "plyr__wrap-nocontrols" : videoControlsPosition; 215 var videoAspectRatio = GetAspectRatio(Model.Item.GetRawValueString("ImageAspectRatio", "")); 216 videoAspectRatio = videoObjectFit == "plyr__wrap-aspect" ? "auto" : videoAspectRatio; 217 //--CUSTOM 218 219 @* Get assets from selected categories or get all assets *@ 220 if (totalAssets != 0) 221 { 222 int assetNumber = 0; 223 int thumbnailNumber = 0; 224 int modalAssetNumber = 0; 225 string thumbnailAxisCss = (GetThumbnailPlacement() == "none" || GetThumbnailPlacement() == "bottom") ? "flex-column" : string.Empty; 226 227 <div class="d-flex h-100 @(thumbnailAxisCss) @(theme) item_@Model.Item.SystemName.ToLower() custom"> @*//CUSTOM*@ 228 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor()) slide col position-relative" data-bs-ride="carousel" style="--transition-duration: 0.1s;"> @*//CUSTOM*@ 229 230 @*//CUSTOM*@ 231 @if (totalAssets > 1 && (GetThumbnailPlacement() == "none" || GetThumbnailPlacement() == "bottom")) 232 { 233 int indicatorIndex = 0; 234 235 <div class="carousel-counter">1 / @totalAssets</div> 236 <div class="carousel-indicators-wrapper @(GetThumbnailPlacement() == "bottom" ? "d-flex d-lg-none" : "")"> 237 <button type="button" class="indicator-prev" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide="prev" aria-label="@Translate("Previous")"></button> 238 <ol class="carousel-indicators"> 239 @foreach (MediaViewModel asset in assetsList) 240 { 241 <li data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@(indicatorIndex)" class="@(indicatorIndex == 0 ? "active" : null)"></li> 242 indicatorIndex++; 243 } 244 </ol> 245 <button type="button" class="indicator-next" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide="next" aria-label="@Translate("Next")"></button> 246 </div> 247 } 248 @*//--CUSTOM*@ 249 250 <div class="carousel-inner h-100"> 251 @foreach (MediaViewModel asset in assetsList) 252 { 253 var assetValue = asset.Value; 254 foreach (string format in allSupportedFormats) 255 { 256 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 257 { 258 string activeSlide = assetNumber == 0 ? "active" : ""; 259 260 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 261 @{ 262 string size = "mobile"; 263 264 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 265 266 267 <div class="h-100 @(imageTheme)"> 268 @foreach (string imageFormat in supportedImageFormats) 269 { //Images 270 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 271 { 272 if (product is object) 273 { 274 string productName = product.Name; 275 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 276 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 277 278 RatioSettings ratioSettings = GetRatioSettings(size); 279 280 var parms = new Dictionary<string, object>(); 281 parms.Add("alt", productName + asset.Keywords); 282 parms.Add("itemprop", "image"); 283 parms.Add("columns", Model.GridRowColumnCount); 284 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 285 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 286 if (!string.IsNullOrEmpty(asset.DisplayName)) 287 { 288 parms.Add("title", asset.DisplayName); 289 } 290 291 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 292 { 293 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 294 } 295 else 296 { 297 parms.Add("cssClass", "mw-100 mh-100"); 298 } 299 300 //CUSTOM 301 if (Model.Item.GetRawValueString("Custom_OpenImageInModal", "true") == "true") 302 { 303 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 304 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 305 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 306 </div> 307 </a> 308 } 309 else 310 { 311 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 312 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 313 </div> 314 } 315 //--CUSTOM 316 } 317 } 318 } 319 @foreach (string videoFormat in supportedVideoFormats) 320 { //Videos 321 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 322 { 323 if (Model.Item.GetString("OpenVideoInModal") == "true") 324 { 325 if (product is object) 326 { 327 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 328 329 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 330 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 331 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 332 333 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 334 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 335 336 string productName = product.Name; 337 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 338 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + HtmlEncoder.HtmlAttributeEncode(asset.DisplayName) + "\"" : ""; //CUSTOM 339 340 RatioSettings ratioSettings = GetRatioSettings(size); 341 342 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 343 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@assetNumber"> 344 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 345 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 346 { 347 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@HtmlEncoder.HtmlAttributeEncode(productName)" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> @*//CUSTOM*@ 348 } 349 else 350 { 351 string videoType = Path.GetExtension(asset.Value).ToLower(); 352 string videoPathEncoded = System.Uri.EscapeDataString(asset.Value); 353 354 355 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 356 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 357 </video> 358 } 359 </div> 360 </div> 361 362 <script> 363 function CheckIfVideoThumbnailExist(image) { 364 if (image.width == 120) { 365 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 366 image.src = lowQualityImage; 367 } 368 } 369 </script> 370 } 371 } 372 else 373 { 374 if (product is object) 375 { 376 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 377 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 378 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 379 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 380 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 381 382 string openInModal = Model.Item.GetString("OpenVideoInModal"); 383 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 384 385 // CUSTOM 386 387 <div class="d-block h-100" itemscope itemtype="https://schema.org/VideoObject"> 388 <span class="visually-hidden" itemprop="name">@assetName</span> 389 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 390 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 391 392 @if (type != "selfhosted") 393 { 394 var playerId = $"player_{Pageview.CurrentParagraph.ID}_{videoId}_{size}"; 395 396 <div class="h-100 w-100 plyr__wrap @(videoObjectFit) @(videoControlsPosition)" style="aspect-ratio: @(videoAspectRatio);"> 397 <div id="@(playerId)" 398 class="plyr__video-embed custom" 399 data-plyr-provider="@(type)" 400 data-plyr-embed-id="@(videoId)" 401 style="--plyr-color-main: var(--swift-foreground-color);"> 402 </div> 403 </div> 404 405 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 406 <script type="module"> 407 var player = new Plyr('#@(playerId)', { 408 type: 'video', 409 fullscreen: { 410 enabled: true, 411 iosNative: true 412 }, 413 youtube: { 414 noCookie: true, 415 showinfo: 0 416 }, 417 vimeo: { 418 playsinline: true, 419 }, 420 clickToPlay: false, 421 autopause: false 422 }); 423 424 @if (autoPlay && openInModal == "false") 425 { 426 <text> 427 player.config.autoplay = false; 428 player.config.muted = true; 429 player.config.volume = 0; 430 431 let useIntersecting = true; 432 433 player.on('ready', function (e) { 434 const observer = new IntersectionObserver(entries => { 435 entries.forEach(entry => { 436 if (useIntersecting) { 437 if (entry.isIntersecting) { 438 e.detail.plyr.play(); 439 useIntersecting = true; 440 } 441 else { 442 e.detail.plyr.pause(); 443 useIntersecting = true; 444 } 445 } 446 }); 447 }); 448 observer.observe(e.target.parentElement); 449 }); 450 451 player.on('play', () => { 452 useIntersecting = true; 453 }); 454 455 player.on('pause', () => { 456 useIntersecting = false; 457 }); 458 459 player.on('ended', () => { 460 player.stop(); 461 player.restart(); 462 useIntersecting = false; 463 }); 464 465 </text> 466 } 467 468 @if (openInModal == "true") 469 { 470 <text> 471 var productDetailsGalleryModal = document.querySelector('#modal_@(Model.ID)') 472 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 473 player.media.pause(); 474 }) 475 </text> 476 } 477 478 initPlyrFit(player); 479 </script> 480 } 481 else 482 { 483 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 484 string videoType = Path.GetExtension(assetValue).ToLower(); 485 string videoPathEncoded = System.Uri.EscapeDataString(assetValue); 486 487 <video preload="auto" @(autoPlayAttributes) class="h-100 w-100" style="object-fit: cover;" controls> 488 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 489 </video> 490 } 491 </div> 492 //--CUSTOM 493 } 494 } 495 } 496 } 497 @foreach (string documentFormat in supportedDocumentFormats) 498 { //Documents 499 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 500 { 501 if (product is object) 502 { 503 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 504 505 string productName = product.Name; 506 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 507 string imageLinkPath = imagePath; 508 509 RatioSettings ratioSettings = GetRatioSettings(size); 510 511 var parms = new Dictionary<string, object>(); 512 parms.Add("alt", productName + asset.Keywords); 513 parms.Add("itemprop", "image"); 514 parms.Add("fullwidth", true); 515 parms.Add("columns", Model.GridRowColumnCount); 516 if (!string.IsNullOrEmpty(asset.DisplayName)) 517 { 518 parms.Add("title", asset.DisplayName); 519 } 520 521 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 522 { 523 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 524 } 525 else 526 { 527 parms.Add("cssClass", "mw-100 mh-100"); 528 } 529 530 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@HtmlEncoder.HtmlAttributeEncode(Translate("Download"))"> @*//CUSTOM*@ 531 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 532 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 533 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 534 { 535 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 536 } 537 </div> 538 </a> 539 } 540 541 } 542 } 543 </div> 544 } 545 546 547 </div> 548 assetNumber++; 549 } 550 } 551 } 552 </div> 553 @if (showBadges) 554 { 555 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 556 @{@RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)} 557 </div> 558 } 559 560 </div> 561 562 @if (totalAssets > 1 && GetThumbnailPlacement() != "none") 563 { 564 <div id="SmallScreenImagesThumbnails_@Model.ID" class="@(GetThumbnailRowSettingCss())" @(GetThumbnailPlacement() == "bottom" ? $"data-swiffyslider-class=\"{GetThumbnailRowSettingCss()} swiffy-slider slider-nav-square-small slider-item-reveal slider-nav-outside\" style=\"--swiffy-slider-nav-light: var(--swift-foreground-color); --swiffy-slider-nav-dark: var(--swift-background-color);\"" : null)> @*//CUSTOM*@ 565 <div class="@(GetThumbnailRowSettingCssWrapper()) gap-3" @(GetThumbnailPlacement() == "bottom" ? "data-swiffyslider-class=\"slider-container\"" : null)> @*//CUSTOM*@ 566 567 @foreach (MediaViewModel asset in assetsList) 568 { 569 var assetValue = asset.Value; 570 foreach (string format in allSupportedFormats) 571 { 572 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 573 { 574 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 575 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/mqdefault.jpg" : imagePath; 576 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath : assetValue; 577 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 578 579 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 580 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 581 582 bool isDocument = false; 583 foreach (string documentFormat in supportedDocumentFormats) 584 { 585 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 586 { 587 isDocument = true; 588 } 589 } 590 591 string assetName = asset.Name; 592 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 593 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + HtmlEncoder.HtmlAttributeEncode(asset.DisplayName) + "\"" : ""; //CUSTOM 594 if (!isDocument) 595 { 596 RatioSettings ratioSettings = GetRatioSettings("desktop"); 597 598 <div class="border outline-none ratio ratio-4x3" style="cursor: pointer; min-width: 4rem; max-width: 5rem;" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> @*//CUSTOM*@ 599 <div class="d-flex align-items-center justify-content-center overflow-hidden position-absolute h-100"> 600 @foreach (string videoFormat in supportedVideoFormats) 601 { //Videos 602 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 603 { 604 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 605 } 606 } 607 </div> 608 609 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 610 { 611 <img src="@imagePathThumb" alt="@HtmlEncoder.HtmlAttributeEncode(assetName)" @assetTitle class="p-1 @vimeoJsClass w-100 h-100" style="object-fit: cover;" data-video-id="@videoId"> @*//CUSTOM*@ 612 } 613 else 614 { 615 string videoType = Path.GetExtension(asset.Value).ToLower(); 616 string videoPathEncoded = System.Uri.EscapeDataString(assetValue); 617 618 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 619 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 620 </video> 621 } 622 </div> 623 624 } 625 else 626 { 627 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer; min-width: 4rem; max-width: 5rem;" download title="@HtmlEncoder.HtmlAttributeEncode(asset.Value)"> @*//CUSTOM*@ 628 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 629 { 630 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 631 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 632 </div> 633 <img src="@imagePathThumb" alt="@HtmlEncoder.HtmlAttributeEncode(assetName)" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> @*//CUSTOM*@ 634 } 635 else 636 { 637 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 638 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 639 </div> 640 } 641 </a> 642 } 643 644 thumbnailNumber++; 645 } 646 } 647 } 648 </div> @*//CUSTOM*@ 649 650 @*//CUSTOM*@ 651 @if (GetThumbnailPlacement() == "bottom") 652 { 653 <button type="button" title="@HtmlEncoder.HtmlAttributeEncode(Translate("Previous slide"))" class="slider-nav" style="z-index:2;"> 654 <span class="visually-hidden">@Translate("Previous slide")</span> 655 </button> 656 <button type="button" title="@HtmlEncoder.HtmlAttributeEncode(Translate("Next slide"))" class="slider-nav slider-nav-next" style="z-index:2;"> 657 <span class="visually-hidden">@Translate("Next slide")</span> 658 </button> 659 } 660 @*//--CUSTOM*@ 661 </div> 662 } 663 </div> 664 665 //CUSTOM 666 if (totalAssets > 1) 667 { 668 if (GetThumbnailPlacement() == "bottom") 669 { 670 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 671 <script type="module"> 672 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 673 document.addEventListener('load.swift.assetloader', (e) => { 674 if (e == null || e.detail == null || e.detail.parentEvent == null || e.detail.parentEvent.target == null || e.detail.parentEvent.target.href == null || !e.detail.parentEvent.target.href.includes('swiffy-slider.min.css')) return; 675 676 const sliderEl = document.querySelector('#SmallScreenImagesThumbnails_@Model.ID'); 677 const sliderWrapperEl = sliderEl.querySelector('div'); 678 679 swiffyslider.initSlider(sliderEl); 680 681 function updateSwiffySlider() { 682 if (sliderEl.hasAttribute("data-class")) { 683 sliderEl.setAttribute('class', sliderEl.getAttribute('data-class')); 684 } 685 if (sliderWrapperEl.hasAttribute("data-class")) { 686 sliderWrapperEl.setAttribute('class', sliderWrapperEl.getAttribute('data-class')); 687 } 688 if (sliderEl.scrollWidth > sliderEl.offsetWidth && window.innerWidth > 991) { 689 sliderEl.setAttribute('data-class', sliderEl.getAttribute("class")); 690 sliderEl.setAttribute('class', sliderEl.getAttribute('data-swiffyslider-class')); 691 sliderWrapperEl.setAttribute('data-class', sliderWrapperEl.getAttribute("class")); 692 sliderWrapperEl.setAttribute('class', sliderWrapperEl.getAttribute('data-swiffyslider-class')); 693 694 sliderEl.style.setProperty('--swiffy-slider-item-count', Math.min(10, Math.max(1, Math.ceil(sliderEl.offsetWidth / 120) - 1))); 695 swiffyslider.slideTo(sliderEl, 0); 696 } 697 } 698 699 updateSwiffySlider(); 700 window.addEventListener('resize', updateSwiffySlider); 701 }); 702 </script> 703 } 704 705 if (GetThumbnailPlacement() == "none" || GetThumbnailPlacement() == "bottom") 706 { 707 <script type="module"> 708 document.addEventListener("DOMContentLoaded", () => { 709 const carouselEl = document.getElementById("SmallScreenImages_@Model.ID"); 710 const carouselCounterEl = carouselEl.querySelector(".carousel-counter"); 711 712 carouselEl.addEventListener("slid.bs.carousel", function (e) { 713 carouselCounterEl.textContent = `${(e.to + 1)} / @(totalAssets)`; 714 }); 715 }); 716 </script> 717 } 718 } 719 //--CUSTOM 720 721 @* Modal with slides *@ 722 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 723 <div class="modal-dialog modal-dialog-centered modal-xl"> 724 <div class="modal-content"> 725 <div class="modal-header visually-hidden"> 726 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 727 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 728 </div> 729 <div class="modal-body p-2 p-lg-3 h-100"> 730 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 731 <div class="carousel-inner h-100 @theme"> 732 @foreach (MediaViewModel asset in assetsList) 733 { 734 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 735 foreach (string supportedFormat in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) 736 { 737 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 738 { 739 string imagePath = assetValue; 740 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 741 742 var parms = new Dictionary<string, object>(); 743 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 744 parms.Add("fullwidth", true); 745 parms.Add("columns", Model.GridRowColumnCount); 746 747 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 748 @*//CUSTOM*@ 749 @if (Model.Item.GetRawValueString("Custom_OpenImageInModal", "true") == "true") 750 { 751 foreach (string imageFormat in supportedImageFormats) 752 { //Images 753 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 754 { 755 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 756 } 757 } 758 } 759 @*//--CUSTOM*@ 760 @foreach (string videoFormat in supportedVideoFormats) 761 { //Videos 762 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 763 { 764 if (product is object) 765 { 766 string videoPlayerSize = "modal"; 767 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 768 assetValue = asset.Value; 769 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 770 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 771 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 772 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 773 774 string openInModal = Model.Item.GetString("OpenVideoInModal"); 775 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 776 777 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 778 <span class="visually-hidden" itemprop="name">@assetName</span> 779 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 780 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 781 782 @if (type != "selfhosted") 783 { 784 var playerId = $"player_{Pageview.CurrentParagraph.ID}_{videoId}_{videoPlayerSize}"; 785 786 <div class="h-100 plyr__wrap plyr__wrap-aspect"> 787 <div id="@(playerId)" 788 class="plyr__video-embed" 789 data-plyr-provider="@(type)" 790 data-plyr-embed-id="@videoId" 791 style="--plyr-color-main: var(--swift-foreground-color);"> 792 </div> 793 </div> 794 795 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 796 <script type="module"> 797 var player = new Plyr('#@(playerId)', { 798 type: 'video', 799 fullscreen: { 800 enabled: true, 801 iosNative: true 802 }, 803 youtube: { 804 noCookie: true, 805 showinfo: 0 806 }, 807 vimeo: { 808 playsinline: true, 809 }, 810 clickToPlay: false, 811 autopause: false 812 }); 813 814 @if (autoPlay && openInModal == "false") 815 { 816 <text> 817 player.config.autoplay = true; 818 player.config.muted = true; 819 player.config.volume = 0; 820 player.media.loop = true; 821 822 player.on('ready', function() { 823 if (player.config.autoplay === true) { 824 player.media.play(); 825 } 826 }); 827 </text> 828 } 829 830 @if (openInModal == "true") 831 { 832 <text> 833 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 834 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 835 player.pause(); 836 }) 837 </text> 838 } 839 840 initPlyrFit(player); 841 </script> 842 } 843 else 844 { 845 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 846 string videoType = Path.GetExtension(assetValue).ToLower(); 847 string videoPathEncoded = System.Uri.EscapeDataString(assetValue); 848 849 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 850 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 851 </video> 852 } 853 </div> 854 } 855 } 856 } 857 </div> 858 modalAssetNumber++; 859 } 860 } 861 } 862 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 863 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 864 <span class="visually-hidden">@Translate("Previous")</span> 865 </button> 866 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 867 <span class="carousel-control-next-icon" aria-hidden="true"></span> 868 <span class="visually-hidden">@Translate("Next")</span> 869 </button> 870 </div> 871 </div> 872 </div> 873 </div> 874 </div> 875 </div> 876 } 877 else if (Pageview.IsVisualEditorMode) 878 { 879 RatioSettings ratioSettings = GetRatioSettings("desktop"); 880 881 <div class="h-100 @theme"> 882 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 883 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 884 </div> 885 </div> 886 } 887 } 888 else if (Pageview.IsVisualEditorMode) 889 { 890 <div class="alert alert-dark m-0">@Translate("No products available")</div> 891 } 892
Error executing template "/Designs/Swift/Paragraph/Swift_ProductHeader_Custom_v2.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_661fa60f98d24fa1b0c1ffe564e425a1.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductHeader_Custom_v2.cshtml:line 41 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.Core @* CUSTOM *@ 4 5 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 6 7 @{ 8 ProductViewModel product = null; 9 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 10 { 11 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 12 } 13 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 14 { 15 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 16 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 17 18 if (productList?.Products is object) 19 { 20 product = productList.Products[0]; 21 } 22 } 23 } 24 25 @if (product is object) 26 { 27 string titleFontSize = Model.Item.GetRawValueString("FontSize", "fs-6"); 28 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 29 horizontalAlign = horizontalAlign == "center" ? "text-center" : horizontalAlign; 30 horizontalAlign = horizontalAlign == "end" ? "text-end" : horizontalAlign; 31 32 string headingLevel = Model.Item.GetString("HeadingLevel", "h2"); 33 string headingLevelStart = $"<{headingLevel} class=\"{titleFontSize} {horizontalAlign} m-0 item_{Model.Item.SystemName.ToLower()}\" itemprop=\"name\">"; 34 string headingLevelStop = $"</{headingLevel}>"; 35 36 // CUSTOM 37 string brandLogo = Model.Item.GetString("Custom_BrandLogo"); 38 string brandLogoKey = Model.Item.GetString("Custom_BrandLogoKey"); 39 string brandLogoAltText = Model.Item.GetString("Custom_BrandAltText"); 40 int brandLogoWidth = Model.Item.GetInt32("Custom_BrandLogoWidth") > 0 ? Model.Item.GetInt32("Custom_BrandLogoWidth") : 70; 41 string productBrand = product.ProductFields.ContainsKey("Custom_Brand") ? Converter.ToString(product.ProductFields["Custom_Brand"].Value) : ""; 42 bool enableBrandLogo = !string.IsNullOrEmpty(brandLogo) && !string.IsNullOrEmpty(brandLogoKey) && brandLogoKey == productBrand; 43 44 if (enableBrandLogo) 45 { 46 <img src="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(brandLogo)" 47 alt="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(brandLogoAltText)" 48 width="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(brandLogoWidth.ToString())" /> 49 } 50 // CUSTOM END 51 52 @headingLevelStart 53 @product.Name 54 @headingLevelStop 55 } 56 else if (Pageview.IsVisualEditorMode) 57 { 58 <div class="alert alert-dark m-0">@Translate("No products available")</div> 59 } 60
Error executing template "Designs/Swift/Paragraph/Swift_ProductBadges_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_0bb715a1dd2547f3bf697188cea3ed26.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductBadges_Custom.cshtml:line 27 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb.Frontend 6 @using Dynamicweb.Content 7 @using Dynamicweb.Core 8 @using Dynamicweb.Ecommerce.ProductCatalog 9 10 @{ 11 ProductViewModel product = null; 12 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 13 { 14 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 15 } 16 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 17 { 18 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 19 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 20 21 if (productList?.Products is object) 22 { 23 product = productList.Products[0]; 24 } 25 } 26 27 DateTime launchDate = product.ProductFields.ContainsKey("Custom_LaunchDate") ? Converter.ToDateTime(product.ProductFields["Custom_LaunchDate"].Value) : DateTime.MinValue; 28 DateTime currentDate = DateTime.Today; 29 bool showComingSoonBadge = currentDate < launchDate && string.Equals( Model.Item.GetRawValueString("NewBadgeDesign"), "comingsoon", StringComparison.InvariantCultureIgnoreCase ); 30 } 31 32 @if (product is object) 33 { 34 var badgeParms = new Dictionary<string, object>(); 35 badgeParms.Add("size", "h7"); 36 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 37 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 38 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 39 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 40 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 41 42 string badgeSize = Model.Item.GetRawValueString("BadgeSize", "fs-2"); 43 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 44 horizontalAlign = horizontalAlign == "center" ? "text-center" : horizontalAlign; 45 horizontalAlign = horizontalAlign == "end" ? "text-end" : horizontalAlign; 46 47 Dictionary<string, ParagraphInfoViewModel> badgeConfigurations; 48 List<string> campaignBadgesValues = Model.Item.GetRawValueString("CampaignBadges") != null ? Model.Item.GetRawValueString("CampaignBadges").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>(); 49 50 if (Dynamicweb.Context.Current.Items.Contains("badgeConfigurations")) 51 { 52 badgeConfigurations = (Dictionary<string, ParagraphInfoViewModel>)Dynamicweb.Context.Current.Items["badgeConfigurations"]; 53 } 54 else 55 { 56 var badgesPage = Pageview.AreaSettings.GetLink("EcommerceBadgesPage") != null ? Pageview.AreaSettings.GetLink("EcommerceBadgesPage").PageId : 0; 57 var allBadges = badgesPage != 0 ? Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(badgesPage) : null; 58 59 badgeConfigurations = new Dictionary<string, ParagraphInfoViewModel>(); 60 61 if ( allBadges != null ) 62 { 63 foreach ( Paragraph badge in allBadges ) 64 { 65 var paragraphviewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreateParagraphInfoViewModel( badge ); 66 string cssClassName = paragraphviewModel.Item.GetString( "CssClassName" )?.Trim().ToLower() ?? string.Empty;; 67 if ( !badgeConfigurations.ContainsKey( cssClassName ) ) 68 { 69 badgeConfigurations.Add( cssClassName, paragraphviewModel ); 70 } 71 } 72 } 73 74 Dynamicweb.Context.Current.Items.Add("badgeConfigurations", badgeConfigurations); 75 } 76 77 int badgesCount = 0; 78 if (badgeConfigurations.Any()) 79 { 80 foreach (string campaign in campaignBadgesValues) 81 { 82 if (!string.IsNullOrEmpty(campaign)) 83 { 84 FieldValueViewModel availableCampaignsObject; 85 product.ProductFields.TryGetValue("Campaign", out availableCampaignsObject); 86 87 if (availableCampaignsObject != null) 88 { 89 string campaignType = string.Empty; 90 91 if (badgeConfigurations.ContainsKey(campaign)) 92 { 93 ParagraphInfoViewModel paragraphviewModel; 94 if (badgeConfigurations.TryGetValue(campaign, out paragraphviewModel)) 95 { 96 campaignType = paragraphviewModel.Item.GetRawValueString("CampaignType"); 97 } 98 } 99 100 List<FieldOptionValueViewModel> availableCampaigns = (List<FieldOptionValueViewModel>)availableCampaignsObject.Value; 101 102 foreach (FieldOptionValueViewModel availableOption in availableCampaigns) 103 { 104 if (campaignType == availableOption.Value) 105 { 106 badgesCount++; 107 break; 108 } 109 } 110 } 111 } 112 } 113 } 114 115 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 116 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 117 DateTime createdDate = product.Created.Value; 118 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 119 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 120 showBadges = (!string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) && badgesCount != 0) || showComingSoonBadge || showBadges; 121 122 if (showBadges) 123 { 124 <div class="@badgeSize @horizontalAlign item_@Model.Item.SystemName.ToLower()"> 125 @RenderPartial("Components/EcommerceBadge_Custom.cshtml", product, badgeParms) @*//CUSTOM*@ 126 </div> 127 } 128 else if (Pageview.IsVisualEditorMode) 129 { 130 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 131 } 132 } 133 else if (Pageview.IsVisualEditorMode) 134 { 135 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 136 } 137 138
Error executing template "Designs/Swift/Paragraph/Swift_ProductVariantSelector_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_65333fabe0b74032858bcfe2403afc99.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductVariantSelector_Custom.cshtml:line 88 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 System 3 @using System.Collections.Generic 4 @using Dynamicweb.Ecommerce.ProductCatalog 5 @using Dynamicweb.Core 6 @using System.Web 7 @using System.Globalization 8 @using System.Linq 9 @using System.Text.RegularExpressions 10 11 @* TODO: REFACTURE/UPGRADE TO SWIFT (v1.25.0) *@ 12 13 @functions { 14 15 //Find contrast color (white, black) 16 public static string GetContrastColor(string hexString) 17 { 18 System.Drawing.Color bg = System.Drawing.ColorTranslator.FromHtml(hexString); 19 20 int nThreshold = 105; 21 int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + (bg.B * 0.114)); 22 23 string foreColor = (255 - bgDelta < nThreshold) ? "#333" : "#fff"; 24 return foreColor; 25 } 26 27 public bool TryExtractDouble(string input, out double result) 28 { 29 result = 0; 30 if (string.IsNullOrWhiteSpace(input)) 31 return false; 32 33 Match match = Regex.Match(input, @"(\d+[\,\. ])+", RegexOptions.IgnoreCase, 34 TimeSpan.FromMilliseconds(5000)); 35 if (match.Success) 36 { 37 string numberStr = match.Value.Trim(); 38 return double.TryParse(numberStr, NumberStyles.Any, new CultureInfo("en-US").NumberFormat, out result); 39 } 40 41 return false; 42 } 43 44 } 45 46 @{ 47 bool isVisualEditor = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("VisualEdit")) ? Convert.ToBoolean(Dynamicweb.Context.Current.Request.QueryString.Get("VisualEdit")) : false; 48 49 ProductViewModel product = new ProductViewModel(); 50 51 ProductViewModelSettings productSetting = new ProductViewModelSettings 52 { 53 LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, 54 CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, 55 CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, 56 ShopId = Pageview.Area.EcomShopId 57 }; 58 59 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 60 { 61 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 62 } 63 else if (Pageview.Item["DummyProduct"] != null) 64 { 65 string dummyProductId = ""; 66 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 67 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 68 if (productList.Products != null) 69 { 70 foreach (var p in productList.Products) 71 { 72 dummyProductId = p.Id; 73 } 74 75 ProductViewModel dummyProduct = dummyProductId != "" ? ViewModelFactory.CreateView(productSetting, dummyProductId) : new ProductViewModel(); 76 product = dummyProduct; 77 } 78 else 79 { 80 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 81 } 82 } 83 else if (Pageview.Item["DummyProduct"] == null) 84 { 85 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 86 } 87 88 bool isColorVariant = product.ProductFields.ContainsKey("Custom_ColorVariantOnWeb") ? Converter.ToBoolean(product.ProductFields["Custom_ColorVariantOnWeb"].Value) : false; 89 90 IEnumerable<ProductViewModel> relatedProducts = product.GetRelatedProducts(); 91 relatedProducts = Smartpage.EvaSolo.Assortment.Helpers.RelatedProducts.FilterOutNonAvailableRelatedProducts(relatedProducts); 92 string[] variantId = { }; 93 94 if (product?.VariantId != null) 95 { 96 variantId = product.VariantId.Split('.'); 97 } 98 99 if (relatedProducts.Any()) 100 { 101 relatedProducts = relatedProducts.Prepend(product); 102 103 bool hideVariantInfoOnProduct = Converter.ToBoolean(Pageview.AreaSettings.GetItem("CustomSettings").GetRawValueString("HideVariantInfoOnProduct_Custom")); 104 105 if (!hideVariantInfoOnProduct) 106 { 107 <div> 108 @if (isColorVariant) 109 { 110 string productFieldCustomColor = product.ProductFields.ContainsKey("Custom_Color") ? Converter.ToString(product.ProductFields["Custom_Color"].Value) : null; 111 <span>@Translate("Custom:Variant.Color.Info", "Farve"): <b>@productFieldCustomColor</b></span> 112 } 113 else 114 { 115 string productFieldCustomSize = product.ProductFields.ContainsKey("Custom_Size") ? Converter.ToString(product.ProductFields["Custom_Size"].Value) : null; 116 <span>@Translate("Custom:Variant.Size.Info", "Størrelse"): <b>@productFieldCustomSize</b></span> 117 } 118 </div> 119 } 120 121 <div class="p-0 mt-2"> @* container overflow-hidden*@ 122 @if (isColorVariant && Model.Item.GetRawValueString("Custom_ColorVariantLayout", "Slider") == "slider") 123 { 124 @RenderRelatedProductSliders(relatedProducts, product) 125 } 126 else 127 { 128 @RenderRelatedProducts(relatedProducts, product, isColorVariant) 129 } 130 </div> 131 } 132 133 if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector")) 134 { 135 int groupNumber = 1; 136 137 <form class="js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())"> 138 <input type="hidden" name="variantid"/> 139 140 @foreach (var variantGroup in product.VariantGroups()) 141 { 142 VariantGroupViewModel group = variantGroup; 143 144 <h3 class="h6">@group.Name</h3> 145 <div class="d-flex gap-2 flex-wrap js-variant-group" data-group-id="@groupNumber"> 146 @foreach (var option in group.Options) 147 { 148 string active = variantId.Contains(option.Id) ? "active" : ""; 149 150 if (!string.IsNullOrEmpty(option.Color)) 151 { 152 string contrastColor = GetContrastColor(option.Color); 153 <button type="button" 154 class="btn colorbox rounded-circle d-inline-block variant-option border js-variant-option @active" 155 style="background-color: @option.Color; --variantoption-check-color: @contrastColor" 156 onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" 157 id="@(product.Id)_@(option.Id)_@Pageview.CurrentParagraph.ID"></button> 158 } 159 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value)) 160 { 161 <button type="button" 162 class="btn p-0 d-inline-block variant-option border js-variant-option @active" 163 onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"> 164 <img 165 src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" /> 166 </button> 167 } 168 else 169 { 170 <button type="button" 171 class="btn btn-secondary d-inline-block variant-option js-variant-option @active" 172 onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"> 173 @option.Name 174 </button> 175 } 176 } 177 </div> 178 179 groupNumber++; 180 } 181 </form> 182 } 183 } 184 185 186 @helper RenderRelatedProducts(IEnumerable<ProductViewModel> relatedProducts, ProductViewModel product, bool isColorVariant) 187 { 188 int variantsToShow = 100; 189 int counter = 1; 190 bool showMoreButton = true; 191 192 if (isColorVariant == false) 193 { 194 relatedProducts = relatedProducts.OrderBy(rp => 195 { 196 double size = double.MaxValue; // default if parse fails 197 if (rp.ProductFields.TryGetValue("Custom_Size", out var sizeFieldValue) && 198 TryExtractDouble(sizeFieldValue?.Value?.ToString(), out var parsed)) 199 { 200 size = parsed; 201 } 202 203 return size; 204 }); 205 } 206 207 <div class="js-related-products-wrapper"> 208 <div class="related-products-container js-related-products-container"> 209 @if (showMoreButton) 210 { 211 <button class="p-2 js-show-all btn-show-all text-center"> 212 <span>+</span><span class="js-hidden-variants-count"></span> 213 </button> 214 } 215 @foreach (ProductViewModel relatedProduct in relatedProducts) 216 { 217 bool isActive = product.Id == relatedProduct.Id; 218 @RenderRelatedProduct(relatedProduct, isActive, (counter > variantsToShow), isColorVariant) 219 counter++; 220 } 221 </div> 222 </div> 223 } 224 225 @helper RenderRelatedProduct(ProductViewModel relatedProduct, bool isActive, bool isHidden, bool isColorVariant = true) 226 { 227 var productListPageId = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ProductListPage")) ? Model.Item.GetInt32("ProductListPage") : GetPageIdByNavigationTag("Shop"); 228 var relatedProductLink = relatedProduct.GetProductLink(productListPageId); 229 isHidden = isColorVariant ? isHidden : false; 230 string aCssClassesByType = isColorVariant ? string.Empty : "text-nowrap w-auto"; 231 232 <div class="related-product @(isActive ? "active" : "") @(aCssClassesByType) js-related-product"> 233 @if (isColorVariant) 234 { 235 var relatedProductFieldCustomColor = relatedProduct.ProductFields.ContainsKey("Custom_Color") ? Converter.ToString(relatedProduct.ProductFields["Custom_Color"].Value) : null; 236 var relatedProductImage = relatedProduct.DefaultImage.Value.StartsWith("/Files/") ? $"/Admin/Public/GetImage.ashx?image={relatedProduct.DefaultImage.Value}&width=180&format=webp" : relatedProduct.DefaultImage.Value; 237 238 <a href="@(relatedProductLink)" title="@HttpUtility.HtmlAttributeEncode(relatedProductFieldCustomColor ?? relatedProduct.Name)"> 239 <img src="@(relatedProductImage)" class="p-0 p-lg-1 w-100 h-100" style="object-fit:contain;" loading="lazy" decoding="async" alt="@HttpUtility.HtmlAttributeEncode(relatedProductFieldCustomColor ?? relatedProduct.Name)" /> 240 </a> 241 } 242 else 243 { 244 string relatedProductFieldCustomSize = relatedProduct.ProductFields.ContainsKey("Custom_Size") ? Converter.ToString(relatedProduct.ProductFields["Custom_Size"].Value) : null; 245 246 <a href="@(relatedProductLink)" class="d-block py-2 px-3 small text-center text-decoration-none" title="@HttpUtility.HtmlAttributeEncode(relatedProductFieldCustomSize ?? relatedProduct.Name)">@(relatedProductFieldCustomSize ?? relatedProduct.Name)</a> 247 } 248 </div> 249 } 250 251 @helper RenderRelatedProductSliders(IEnumerable<ProductViewModel> relatedProducts, ProductViewModel product) //NOTE: Only support color variants 252 { 253 <div class="swiffy-slider slider-item-show4 slider-item-show5-sm slider-item-show7-md slider-item-show4-lg slider-item-show5-xl slider-item-show7-xxl slider-item-snapstart slider-item-reveal slider-nav-square-small slider-indicators-hide js-swiffyslider" data-slider-nav-autoplay-interval="2500"> 254 <div class="slider-container"> 255 @foreach (ProductViewModel relatedProduct in relatedProducts) 256 { 257 @RenderRelatedProductSlider(relatedProduct, product) 258 } 259 </div> 260 </div> 261 } 262 263 @helper RenderRelatedProductSlider(ProductViewModel relatedProduct, ProductViewModel product) //NOTE: Only support color variants 264 { 265 var productListPageId = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ProductListPage")) ? Model.Item.GetInt32("ProductListPage") : GetPageIdByNavigationTag("Shop"); 266 var relatedProductLink = relatedProduct.GetProductLink(productListPageId); 267 268 var relatedProductFieldCustomColor = relatedProduct.ProductFields.ContainsKey("Custom_Color") ? Converter.ToString(relatedProduct.ProductFields["Custom_Color"].Value) : null; 269 var relatedProductImage = relatedProduct.DefaultImage.Value.StartsWith("/Files/") ? $"/Admin/Public/GetImage.ashx?image={relatedProduct.DefaultImage.Value}&width=180&format=webp" : relatedProduct.DefaultImage.Value; 270 271 <div class="related-product @(relatedProduct.Id == product.Id ? "active" : "")"> 272 <a href="@(relatedProductLink)" title="@HttpUtility.HtmlAttributeEncode(relatedProductFieldCustomColor ?? relatedProduct.Name)"> 273 <img src="@(relatedProductImage)" class="p-0 p-lg-1 w-100 h-100" style="object-fit:contain;" loading="lazy" decoding="async" alt="@HttpUtility.HtmlAttributeEncode(relatedProductFieldCustomColor ?? relatedProduct.Name)" /> 274 </a> 275 </div> 276 } 277 278 <script> 279 function setVariantEnvironment() { 280 const container = document.querySelector('.js-related-products-wrapper'); 281 const innerContainer = document.querySelector('.js-related-products-container'); 282 if (container !== null) { 283 const items = container.querySelectorAll(".js-related-product"); 284 const btnShowAll = document.querySelector('.js-show-all'); 285 let hiddenCount = 0; 286 let firstRowTopPos = null; 287 innerContainer.classList.remove('flex-nowrap'); 288 289 btnShowAll.classList.remove('order-1'); 290 items.forEach(item => { 291 item.classList.remove('order-0', 'order-2', 'hidden'); 292 }); 293 294 items.forEach(item => { 295 const itemTop = item.getBoundingClientRect().top; 296 if (firstRowTopPos === null) { 297 firstRowTopPos = itemTop; 298 } 299 if (itemTop !== firstRowTopPos) { 300 item.classList.add('hidden'); 301 hiddenCount++; 302 } else { 303 item.classList.add('order-0'); 304 } 305 }); 306 307 if (hiddenCount === 1) { 308 innerContainer.classList.add('flex-nowrap'); 309 btnShowAll.classList.add('d-none'); 310 items.forEach(item => { 311 item.classList.remove('hidden'); 312 item.classList.add('order-0'); 313 }); 314 } else if (hiddenCount > 1) { 315 btnShowAll.classList.remove('d-none', 'order-0'); 316 btnShowAll.classList.add('order-1'); 317 btnShowAll.querySelector('.js-hidden-variants-count').textContent = hiddenCount; 318 } else { 319 btnShowAll.classList.add('d-none'); 320 } 321 } 322 } 323 324 window.addEventListener('DOMContentLoaded', () => { 325 setVariantEnvironment(); 326 327 }); 328 window.addEventListener("resize", () => { 329 setVariantEnvironment(); 330 }); 331 332 </script> 333
Error executing template "Designs/Swift/Paragraph/Swift_ProductBadges_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_0bb715a1dd2547f3bf697188cea3ed26.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductBadges_Custom.cshtml:line 27 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb.Frontend 6 @using Dynamicweb.Content 7 @using Dynamicweb.Core 8 @using Dynamicweb.Ecommerce.ProductCatalog 9 10 @{ 11 ProductViewModel product = null; 12 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 13 { 14 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 15 } 16 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 17 { 18 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 19 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 20 21 if (productList?.Products is object) 22 { 23 product = productList.Products[0]; 24 } 25 } 26 27 DateTime launchDate = product.ProductFields.ContainsKey("Custom_LaunchDate") ? Converter.ToDateTime(product.ProductFields["Custom_LaunchDate"].Value) : DateTime.MinValue; 28 DateTime currentDate = DateTime.Today; 29 bool showComingSoonBadge = currentDate < launchDate && string.Equals( Model.Item.GetRawValueString("NewBadgeDesign"), "comingsoon", StringComparison.InvariantCultureIgnoreCase ); 30 } 31 32 @if (product is object) 33 { 34 var badgeParms = new Dictionary<string, object>(); 35 badgeParms.Add("size", "h7"); 36 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 37 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 38 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 39 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 40 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 41 42 string badgeSize = Model.Item.GetRawValueString("BadgeSize", "fs-2"); 43 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 44 horizontalAlign = horizontalAlign == "center" ? "text-center" : horizontalAlign; 45 horizontalAlign = horizontalAlign == "end" ? "text-end" : horizontalAlign; 46 47 Dictionary<string, ParagraphInfoViewModel> badgeConfigurations; 48 List<string> campaignBadgesValues = Model.Item.GetRawValueString("CampaignBadges") != null ? Model.Item.GetRawValueString("CampaignBadges").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>(); 49 50 if (Dynamicweb.Context.Current.Items.Contains("badgeConfigurations")) 51 { 52 badgeConfigurations = (Dictionary<string, ParagraphInfoViewModel>)Dynamicweb.Context.Current.Items["badgeConfigurations"]; 53 } 54 else 55 { 56 var badgesPage = Pageview.AreaSettings.GetLink("EcommerceBadgesPage") != null ? Pageview.AreaSettings.GetLink("EcommerceBadgesPage").PageId : 0; 57 var allBadges = badgesPage != 0 ? Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(badgesPage) : null; 58 59 badgeConfigurations = new Dictionary<string, ParagraphInfoViewModel>(); 60 61 if ( allBadges != null ) 62 { 63 foreach ( Paragraph badge in allBadges ) 64 { 65 var paragraphviewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreateParagraphInfoViewModel( badge ); 66 string cssClassName = paragraphviewModel.Item.GetString( "CssClassName" )?.Trim().ToLower() ?? string.Empty;; 67 if ( !badgeConfigurations.ContainsKey( cssClassName ) ) 68 { 69 badgeConfigurations.Add( cssClassName, paragraphviewModel ); 70 } 71 } 72 } 73 74 Dynamicweb.Context.Current.Items.Add("badgeConfigurations", badgeConfigurations); 75 } 76 77 int badgesCount = 0; 78 if (badgeConfigurations.Any()) 79 { 80 foreach (string campaign in campaignBadgesValues) 81 { 82 if (!string.IsNullOrEmpty(campaign)) 83 { 84 FieldValueViewModel availableCampaignsObject; 85 product.ProductFields.TryGetValue("Campaign", out availableCampaignsObject); 86 87 if (availableCampaignsObject != null) 88 { 89 string campaignType = string.Empty; 90 91 if (badgeConfigurations.ContainsKey(campaign)) 92 { 93 ParagraphInfoViewModel paragraphviewModel; 94 if (badgeConfigurations.TryGetValue(campaign, out paragraphviewModel)) 95 { 96 campaignType = paragraphviewModel.Item.GetRawValueString("CampaignType"); 97 } 98 } 99 100 List<FieldOptionValueViewModel> availableCampaigns = (List<FieldOptionValueViewModel>)availableCampaignsObject.Value; 101 102 foreach (FieldOptionValueViewModel availableOption in availableCampaigns) 103 { 104 if (campaignType == availableOption.Value) 105 { 106 badgesCount++; 107 break; 108 } 109 } 110 } 111 } 112 } 113 } 114 115 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 116 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 117 DateTime createdDate = product.Created.Value; 118 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 119 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 120 showBadges = (!string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) && badgesCount != 0) || showComingSoonBadge || showBadges; 121 122 if (showBadges) 123 { 124 <div class="@badgeSize @horizontalAlign item_@Model.Item.SystemName.ToLower()"> 125 @RenderPartial("Components/EcommerceBadge_Custom.cshtml", product, badgeParms) @*//CUSTOM*@ 126 </div> 127 } 128 else if (Pageview.IsVisualEditorMode) 129 { 130 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 131 } 132 } 133 else if (Pageview.IsVisualEditorMode) 134 { 135 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 136 } 137 138
Error executing template "/Designs/Swift/Paragraph/Swift_ProductPrice_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_1da0255e26244a9ba50a2bd9a4f16cc5.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductPrice_Custom.cshtml:line 70 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.Configuration @*//CUSTOM*@ 3 @using Dynamicweb.Core.Encoders @*//CUSTOM*@ 4 @using Dynamicweb.Ecommerce.ProductCatalog 5 @using System.Globalization 6 @using System.IO @*//CUSTOM*@ 7 8 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 9 10 @{ 11 ProductViewModel product = null; 12 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 13 { 14 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 15 } 16 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 17 { 18 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 19 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 20 21 if (productList?.Products is object) 22 { 23 product = productList.Products[0]; 24 } 25 } 26 27 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 28 bool anonymousUser = Pageview.User == null; 29 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"]); 30 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && isErpConnectionDown; 31 32 bool productIsDiscontinued = product is object && product.Discontinued; 33 bool doNotShowPriceIfProductIsDiscontinued = Model.Item.GetBoolean("DoNotShowPriceIfProductIsDiscontinued"); 34 var isDiscontinued = productIsDiscontinued && doNotShowPriceIfProductIsDiscontinued; 35 } 36 37 @if (product is object && !hidePrice && !isDiscontinued) 38 { 39 bool showInformativePrice = Model.Item.GetBoolean("ShowInformativePrice"); 40 string unitId = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.Form.Get("UnitId")) ? Dynamicweb.Context.Current.Request.Form.Get("UnitId") : string.Empty; 41 42 string priceFontSize = Model.Item.GetRawValueString("PriceSize", "h6 justify-content-lg-start"); //CUSTOM 43 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 44 string layout = Model.Item.GetRawValueString("Layout", "horizontal"); 45 string textAlign = horizontalAlign == "center" ? "text-center" : string.Empty; 46 textAlign = horizontalAlign == "end" ? "text-end" : textAlign; 47 48 horizontalAlign = horizontalAlign == "center" && layout == "horizontal" ? "justify-content-center" : horizontalAlign; 49 horizontalAlign = horizontalAlign == "end" && layout == "horizontal" ? "justify-content-end" : horizontalAlign; 50 horizontalAlign = horizontalAlign == "center" && layout == "vertical" ? "align-items-center" : horizontalAlign; 51 horizontalAlign = horizontalAlign == "end" && layout == "vertical" ? "align-items-end" : horizontalAlign; 52 53 string flexDirection = layout == "horizontal" ? string.Empty : "flex-column"; 54 string flexGap = layout == "horizontal" ? "gap-3" : string.Empty; 55 string order = layout == "horizontal" ? string.Empty : "order-2"; 56 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? "theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 57 theme = GetViewParameter("theme") != null ? GetViewParameterString("theme") : theme; 58 59 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 60 contentPadding = contentPadding == "none" ? "p-0" : contentPadding; 61 contentPadding = contentPadding == "small" ? "p-1 px-md-2 py-md-1" : contentPadding; 62 contentPadding = contentPadding == "large" ? "p-2 px-md-3 py-md-2" : contentPadding; 63 64 //CUSTOM 65 Dictionary<string, FieldValueViewModel> productFields = product.ProductFields; 66 var discountText = ""; 67 bool discountDiscontinued = false; 68 bool discountOnSale = false; 69 70 if (productFields.TryGetValue("Custom_Discontinued", out FieldValueViewModel discontinued)) 71 { 72 var firstValue = (discontinued.Value as List<FieldOptionValueViewModel>).FirstOrDefault()?.Value; 73 74 if (!string.IsNullOrWhiteSpace(firstValue)) 75 { 76 bool.TryParse(firstValue, out discountDiscontinued); 77 } 78 } 79 80 if (productFields.TryGetValue("Custom_OnSale", out FieldValueViewModel onsale)) 81 { 82 var firstValue = (onsale.Value as List<FieldOptionValueViewModel>).FirstOrDefault()?.Value; 83 84 if (!string.IsNullOrWhiteSpace(firstValue)) 85 { 86 bool.TryParse(firstValue, out discountOnSale); 87 } 88 } 89 90 if (discountDiscontinued && discountOnSale) 91 { 92 discountText = Translate("Outlet - tilbuddet gælder så længe lager haves"); 93 } 94 else 95 { 96 var discountInfo = Smartpage.EvaSolo.CampaignPrices.Helpers.DiscountHelper.GetDiscountInfo(product); 97 if (discountInfo != null) 98 { 99 discountText = string.Format(Translate(Model.Item.GetRawValueString("Custom_DiscountText", "Tilbuddet gælder {0} - {1}")), $"‌{discountInfo.ValidFromFormatted}‌", $"‌{discountInfo.ValidToFormatted}‌"); 100 } 101 } 102 string discountType = Model.Item.GetRawValueString("Custom_DiscountType", "none"); 103 string discountTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Custom_DiscountTheme")) ? "theme " + Model.Item.GetRawValueString("Custom_DiscountTheme").Replace(" ", "").Trim().ToLower() : string.Empty; 104 string discountIconColor = Model.Item.GetString("Custom_DiscountIconColor", "text-inherit"); 105 string discountIconSize = Model.Item.GetRawValueString("Custom_DiscountIconSize", "2"); 106 string discountImageSrc = !string.IsNullOrEmpty(Model.Item.GetString("Custom_DiscountImage")) ? Model.Item.GetFile("Custom_DiscountImage").Path : string.Empty; 107 string discountImagePath = discountImageSrc != string.Empty ? discountImageSrc : Model.Item.GetRawValueString("Custom_DiscountIcon", string.Empty); 108 discountImagePath = Path.GetExtension(discountImagePath).ToLower() != ".svg" ? "/Admin/Public/GetImage.ashx?image=" + discountImagePath + "&height=" + discountIconSize + "&width=" + discountIconSize + "&Crop=0&format=webp" : discountImagePath; 109 string discountImgAltText = Model.Item.GetRawValueString("FeatureAltText", string.Empty); 110 //--CUSTOM 111 112 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 113 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 114 115 string priceMin = ""; 116 string priceMax = ""; 117 118 string liveInfoClass = ""; 119 string productInfoFeed = ""; 120 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 121 if (isLazyLoadingForProductInfoEnabled) 122 { 123 if (Dynamicweb.Context.Current.Items.Contains("ProductInfoFeed")) 124 { 125 productInfoFeed = Dynamicweb.Context.Current.Items["ProductInfoFeed"]?.ToString(); 126 if (!string.IsNullOrEmpty(productInfoFeed)) 127 { 128 productInfoFeed = $"data-product-info-feed=\"{productInfoFeed}\""; 129 } 130 } 131 liveInfoClass = "js-live-info"; 132 } 133 134 <div class="@textAlign @liveInfoClass item_@Model.Item.SystemName.ToLower()" data-product-id="@product.Id" data-variant-id="@product.VariantId" @productInfoFeed> 135 @if (showInformativePrice && product.PriceInformative.Price != 0) 136 { 137 <div class="opacity-50"> 138 <span>@Translate("RRP") </span> 139 <span class="text-decoration-line-through text-price">@product.PriceInformative.PriceFormatted</span> 140 </div> 141 } 142 <div class="@priceFontSize m-0 d-flex flex-wrap @flexDirection @flexGap @horizontalAlign" style="row-gap: 0 !important" itemprop="offers" itemscope itemtype="https://schema.org/Offer"> 143 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span> 144 145 @*//CUSTOM*@ 146 @if (showPricesWithVat == "false" && !neverShowVat) 147 { 148 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 149 { 150 <span class="text-price js-text-price"> 151 <span class="spinner-border" role="status"></span> 152 </span> 153 } 154 else 155 { 156 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceWithoutVatFormatted : product.Price.PriceWithoutVatFormatted; 157 158 if (product?.VariantInfo?.VariantInfo != null) 159 { 160 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : ""; 161 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : ""; 162 } 163 if (priceMin != priceMax) 164 { 165 price = priceMin + " - " + priceMax; 166 } 167 168 <span class="@theme"> 169 <span class="text-price">@price</span> 170 </span> 171 } 172 } 173 else 174 { 175 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 176 { 177 <span class="text-price js-text-price"> 178 <span class="spinner-border" role="status"></span> 179 </span> 180 } 181 else 182 { 183 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceFormatted : product.Price.PriceFormatted; 184 185 if (product?.VariantInfo?.VariantInfo != null) 186 { 187 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : ""; 188 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : ""; 189 } 190 if (priceMin != priceMax) 191 { 192 price = priceMin + " - " + priceMax; 193 } 194 195 <span class="@theme"> 196 <span class="text-price">@price</span> 197 </span> 198 } 199 } 200 201 @if (showPricesWithVat == "false" && !neverShowVat) 202 { 203 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 204 { 205 <span itemprop="price" content="" class="d-none"></span> 206 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span> 207 } 208 else 209 { 210 string beforePrice = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).PriceBeforeDiscount.PriceWithoutVatFormatted : product.PriceBeforeDiscount.PriceWithoutVatFormatted; 211 212 <span itemprop="price" content="@product.Price.PriceWithoutVat.ToString(CultureInfo.InvariantCulture)" class="d-none"></span> 213 214 if (product.Price.Price != product.PriceBeforeDiscount.Price) 215 { 216 <span class="opacity-75 @(order)"> 217 <span class="fs-7 text-price text-decoration-line-through">@beforePrice</span> 218 </span> 219 220 if (Model.Item.GetBoolean("Custom_ShowDiscountSavings")) 221 { 222 var currency = Dynamicweb.Ecommerce.Common.Context.Currency; 223 var priceInfo = new Dynamicweb.Ecommerce.Prices.PriceInfo(currency) 224 { 225 PriceWithoutVAT = (product.PriceBeforeDiscount.PriceWithoutVat - product.Price.PriceWithoutVat) 226 }; 227 228 <span class="d-none d-sm-inline opacity-75 @(order)"> 229 <span class="fs-7 text-price">@string.Format(Translate("Spar {0}"), priceInfo.PriceWithoutVATFormatted)</span> 230 </span> 231 } 232 } 233 } 234 } 235 else 236 { 237 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 238 { 239 <span itemprop="price" content="" class="d-none"></span> 240 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price < LiveProductInfo.product.PriceBeforeDiscount.Price"></span> 241 } 242 else 243 { 244 string beforePrice = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).PriceBeforeDiscount.PriceFormatted : product.PriceBeforeDiscount.PriceFormatted; 245 246 <span itemprop="price" content="@product.Price.Price.ToString(CultureInfo.InvariantCulture)" class="d-none"></span> 247 248 if (product.Price.Price < product.PriceBeforeDiscount.Price) 249 { 250 <span class="opacity-75 @(order)"> 251 <span class="fs-7 text-price text-decoration-line-through">@beforePrice</span> 252 </span> 253 254 if (Model.Item.GetBoolean("Custom_ShowDiscountSavings")) 255 { 256 var currency = Dynamicweb.Ecommerce.Common.Context.Currency; 257 var priceInfo = new Dynamicweb.Ecommerce.Prices.PriceInfo(currency) 258 { 259 PriceWithVAT = (product.PriceBeforeDiscount.Price - product.Price.Price) 260 }; 261 262 <span class="d-none d-sm-inline opacity-75 @(order)"> 263 <span class="fs-7 text-price">@string.Format(Translate("Spar {0}"), priceInfo.PriceFormatted)</span> 264 </span> 265 } 266 } 267 } 268 } 269 @*//--CUSTOM*@ 270 271 @*//CUSTOM*@ 272 @if (discountType == "icon-tooltip" && product.Price.Price != product.PriceBeforeDiscount.Price && discountText != null) 273 { 274 if ((!string.IsNullOrEmpty(Model.Item.GetString("Custom_DiscountImage")) || !discountImagePath.ToLower().Contains("none")) && discountImagePath != string.Empty) 275 { 276 <span data-bs-toggle="tooltip" data-bs-custom-class="@(discountTheme)" data-bs-html="true" data-bs-title="@HtmlEncoder.HtmlAttributeEncode(discountText)" data-bs-custom-prevent-event="click" data-bs-custom-click-close-delay="2000"> 277 @if (Path.GetExtension(discountImagePath).ToLower() == ".svg") 278 { 279 <span class="icon-auto @(discountIconColor)" style="vertical-align:text-top;height:@(discountIconSize)px;width:@(discountIconSize)px;">@ReadFile(discountImagePath)</span> 280 } 281 else 282 { 283 <img loading="lazy" src="@(discountImagePath)" alt="@HtmlEncoder.HtmlAttributeEncode(discountImgAltText)" title="@HtmlEncoder.HtmlAttributeEncode(discountImgAltText)" style="height:@(discountIconSize)px;width:@(discountIconSize)px;"> 284 } 285 </span> 286 } 287 } 288 @*//--CUSTOM*@ 289 290 @* Stock state for Schema.org, start *@ 291 @{ 292 Uri url = Dynamicweb.Context.Current.Request.Url; 293 } 294 295 <link itemprop="url" href="@url"> 296 297 @{ 298 bool IsNeverOutOfStock = product.NeverOutOfstock; 299 } 300 301 @if (IsNeverOutOfStock) 302 { 303 <span itemprop="availability" class="d-none">@Translate("Available in stock")</span> 304 } 305 else 306 { 307 if (product.StockLevel > 0) 308 { 309 <span itemprop="availability" class="d-none">InStock</span> 310 } 311 else 312 { 313 <span itemprop="availability" class="d-none">OutOfStock</span> 314 } 315 } 316 @* Stock state for Schema.org, stop *@ 317 318 </div> 319 320 @if (showPricesWithVat == "false" && !neverShowVat) 321 { 322 if (isLazyLoadingForProductInfoEnabled && !Pageview.IsVisualEditorMode) 323 { 324 <small class="opacity-85 fst-normal js-text-price-with-vat d-none" data-suffix="@Translate("Incl. VAT")"></small> 325 } 326 else 327 { 328 string price = !string.IsNullOrEmpty(unitId) ? product.GetPrice(unitId).Price.PriceWithVatFormatted : product.Price.PriceWithVatFormatted; 329 330 if (product?.VariantInfo?.VariantInfo != null) 331 { 332 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : ""; 333 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : ""; 334 } 335 if (priceMin != priceMax) 336 { 337 price = priceMin + " - " + priceMax; 338 } 339 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small> 340 } 341 } 342 343 @*//CUSTOM*@ 344 @if (discountType == "inline" && product.Price.Price != product.PriceBeforeDiscount.Price && !string.IsNullOrEmpty(discountText)) 345 { 346 var discountFontSize = priceFontSize.Contains("h6") ? priceFontSize.Replace("h6", "small") : "small"; 347 348 <div class="@(discountFontSize) mt-1 d-flex flex-wrap @(flexDirection) @(horizontalAlign)"> 349 @discountText 350 </div> 351 } 352 @*//--CUSTOM*@ 353 354 </div> 355 } 356 else if (Pageview.IsVisualEditorMode) 357 { 358 <div class="alert alert-dark m-0" role="alert"> 359 <span>@Translate("No products available")</span> 360 </div> 361 } 362
Error executing template "Designs/Swift/Paragraph/Swift_ProductBadges_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_0bb715a1dd2547f3bf697188cea3ed26.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductBadges_Custom.cshtml:line 27 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb.Frontend 6 @using Dynamicweb.Content 7 @using Dynamicweb.Core 8 @using Dynamicweb.Ecommerce.ProductCatalog 9 10 @{ 11 ProductViewModel product = null; 12 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 13 { 14 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 15 } 16 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 17 { 18 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 19 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 20 21 if (productList?.Products is object) 22 { 23 product = productList.Products[0]; 24 } 25 } 26 27 DateTime launchDate = product.ProductFields.ContainsKey("Custom_LaunchDate") ? Converter.ToDateTime(product.ProductFields["Custom_LaunchDate"].Value) : DateTime.MinValue; 28 DateTime currentDate = DateTime.Today; 29 bool showComingSoonBadge = currentDate < launchDate && string.Equals( Model.Item.GetRawValueString("NewBadgeDesign"), "comingsoon", StringComparison.InvariantCultureIgnoreCase ); 30 } 31 32 @if (product is object) 33 { 34 var badgeParms = new Dictionary<string, object>(); 35 badgeParms.Add("size", "h7"); 36 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 37 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 38 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 39 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 40 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 41 42 string badgeSize = Model.Item.GetRawValueString("BadgeSize", "fs-2"); 43 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 44 horizontalAlign = horizontalAlign == "center" ? "text-center" : horizontalAlign; 45 horizontalAlign = horizontalAlign == "end" ? "text-end" : horizontalAlign; 46 47 Dictionary<string, ParagraphInfoViewModel> badgeConfigurations; 48 List<string> campaignBadgesValues = Model.Item.GetRawValueString("CampaignBadges") != null ? Model.Item.GetRawValueString("CampaignBadges").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>(); 49 50 if (Dynamicweb.Context.Current.Items.Contains("badgeConfigurations")) 51 { 52 badgeConfigurations = (Dictionary<string, ParagraphInfoViewModel>)Dynamicweb.Context.Current.Items["badgeConfigurations"]; 53 } 54 else 55 { 56 var badgesPage = Pageview.AreaSettings.GetLink("EcommerceBadgesPage") != null ? Pageview.AreaSettings.GetLink("EcommerceBadgesPage").PageId : 0; 57 var allBadges = badgesPage != 0 ? Dynamicweb.Content.Services.Paragraphs.GetParagraphsByPageId(badgesPage) : null; 58 59 badgeConfigurations = new Dictionary<string, ParagraphInfoViewModel>(); 60 61 if ( allBadges != null ) 62 { 63 foreach ( Paragraph badge in allBadges ) 64 { 65 var paragraphviewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreateParagraphInfoViewModel( badge ); 66 string cssClassName = paragraphviewModel.Item.GetString( "CssClassName" )?.Trim().ToLower() ?? string.Empty;; 67 if ( !badgeConfigurations.ContainsKey( cssClassName ) ) 68 { 69 badgeConfigurations.Add( cssClassName, paragraphviewModel ); 70 } 71 } 72 } 73 74 Dynamicweb.Context.Current.Items.Add("badgeConfigurations", badgeConfigurations); 75 } 76 77 int badgesCount = 0; 78 if (badgeConfigurations.Any()) 79 { 80 foreach (string campaign in campaignBadgesValues) 81 { 82 if (!string.IsNullOrEmpty(campaign)) 83 { 84 FieldValueViewModel availableCampaignsObject; 85 product.ProductFields.TryGetValue("Campaign", out availableCampaignsObject); 86 87 if (availableCampaignsObject != null) 88 { 89 string campaignType = string.Empty; 90 91 if (badgeConfigurations.ContainsKey(campaign)) 92 { 93 ParagraphInfoViewModel paragraphviewModel; 94 if (badgeConfigurations.TryGetValue(campaign, out paragraphviewModel)) 95 { 96 campaignType = paragraphviewModel.Item.GetRawValueString("CampaignType"); 97 } 98 } 99 100 List<FieldOptionValueViewModel> availableCampaigns = (List<FieldOptionValueViewModel>)availableCampaignsObject.Value; 101 102 foreach (FieldOptionValueViewModel availableOption in availableCampaigns) 103 { 104 if (campaignType == availableOption.Value) 105 { 106 badgesCount++; 107 break; 108 } 109 } 110 } 111 } 112 } 113 } 114 115 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 116 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 117 DateTime createdDate = product.Created.Value; 118 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 119 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 120 showBadges = (!string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) && badgesCount != 0) || showComingSoonBadge || showBadges; 121 122 if (showBadges) 123 { 124 <div class="@badgeSize @horizontalAlign item_@Model.Item.SystemName.ToLower()"> 125 @RenderPartial("Components/EcommerceBadge_Custom.cshtml", product, badgeParms) @*//CUSTOM*@ 126 </div> 127 } 128 else if (Pageview.IsVisualEditorMode) 129 { 130 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 131 } 132 } 133 else if (Pageview.IsVisualEditorMode) 134 { 135 <span class="badge bg-success text-light rounded-0">@Translate("Badge example")</span> 136 } 137 138
Error executing template "Designs/Swift/Paragraph/Swift_ProductAddToCart_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_d4f717c9a291433fb50fdd116af23ee4.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductAddToCart_Custom.cshtml:line 55 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 System 3 @using Dynamicweb.Ecommerce.ProductCatalog 4 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites 5 @using Dynamicweb.Ecommerce.Products.FieldDisplayGroups 6 @using Dynamicweb.Frontend 7 @using Dynamicweb.Core 8 @using Dynamicweb.Core.Encoders 9 @using System.Drawing 10 @using System.Web 11 @using Dynamicweb.Environment 12 @using System.Globalization 13 @using Custom.EvaSolo.Tracking.Extensions @*//CUSTOM*@ 14 15 @* TODO: REFACTURE/UPGRADE TO SWIFT (v1.25.0) *@ 16 17 @{ 18 ProductViewModel product = null; 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 22 } 23 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 24 { 25 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 26 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 27 28 if (productList?.Products is object) 29 { 30 product = productList.Products[0]; 31 } 32 } 33 34 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 35 bool anonymousUser = Pageview.User == null; 36 bool isErpConnectionDown = !Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsWebServiceConnectionAvailable"]); 37 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && isErpConnectionDown; 38 hideAddToCart = Pageview.IsVisualEditorMode ? false : hideAddToCart; 39 } 40 41 @if (product is object && !hideAddToCart) 42 { 43 bool quantitySelector = !string.IsNullOrEmpty(Model.Item.GetString("ShowQuantitySelector")) ? Model.Item.GetBoolean("ShowQuantitySelector") : false; 44 45 string iconPath = "/Files/icons/"; 46 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); 47 if (!url.Contains("LayoutTemplate")) 48 { 49 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; 50 } 51 52 bool stockLevelZeroOrless = product.StockLevel <= 0; 53 bool isDiscontinued = product.Discontinued; 54 bool IsNeverOutOfStock = product.NeverOutOfstock; 55 bool plytixIsNotForSale = product.ProductFields.ContainsKey("Custom_NotForSale") ? Converter.ToBoolean(product.ProductFields["Custom_NotForSale"].Value) : false; 56 57 bool isCartDisabled = stockLevelZeroOrless || isDiscontinued; 58 isCartDisabled = IsNeverOutOfStock ? false : isCartDisabled; 59 string disableAddToCart = isCartDisabled ? "disabled" : ""; 60 61 double stepAmount = product.PurchaseQuantityStep ?? 1; 62 stepAmount = stepAmount > 1 ? stepAmount : 1; 63 string stepQty = stepAmount.ToString(); 64 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\""; 65 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty; 66 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : ""; 67 string onChangeEvent = !IsNeverOutOfStock ? $"onChange=\"UpdateQuantity(this, '{product.StockLevel}', '{stepAmount}')\"" : ""; 68 69 DateTime launchDate = product.ProductFields.ContainsKey("Custom_LaunchDate") ? Converter.ToDateTime(product.ProductFields["Custom_LaunchDate"].Value) : DateTime.MinValue; 70 DateTime currentDate = DateTime.Today; 71 72 string addToCartIcon = Model.Item.GetRawValueString("Icon", iconPath + "shopping-cart.svg"); 73 string addToCartLabel = !addToCartIcon.Contains("_none") && !isCartDisabled ? $"<span class=\"icon-2\">{ReadFile(addToCartIcon)}</span>" : ""; 74 addToCartLabel += !addToCartIcon.Contains("_none") && !Model.Item.GetBoolean("HideButtonText") ? " " : ""; 75 addToCartLabel += !Model.Item.GetBoolean("HideButtonText") ? (isCartDisabled ? Translate("Sold out") : $"<span class=\"d-none d-md-inline\">{Translate("Add to cart")}</span><span class=\"d-inline d-md-none\">{Translate("Add")}</span>") : ""; 76 77 if (currentDate < launchDate) 78 { 79 <p class="mb-1 mt-2">@Translate("Available from") @launchDate.ToLocalTime().ToLongDateString()</p> 80 } 81 else 82 { 83 <div class="d-flex flex-row flex-nowrap gap-2 order-2 order-lg-4 item_@Model.Item.SystemName.ToLower()"> 84 <form method="post" action="@url" class="flex-fill"> 85 <input type="hidden" name="redirect" value="false"> 86 <input type="hidden" name="ProductId" value="@product.Id"> 87 @*//CUSTOM*@ 88 <input type="hidden" name="ProductName" value="@HtmlEncoder.HtmlAttributeEncode(product.GetTrackingName() ?? product.Name)"> 89 <input type="hidden" name="ProductCurrency" value="@product.Price.CurrencyCode"> 90 <input type="hidden" name="ProductPrice" value="@product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)"> 91 <input type="hidden" name="ProductDiscount" value="@product.Discount.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)"> 92 @*//--CUSTOM*@ 93 @* CUSTOM - used for frontend failsafe quantity validation in _cart.ts *@ 94 <input type="hidden" name="ProductIsNeverOutOfStock" value="@IsNeverOutOfStock.ToString()"> 95 <input type="hidden" name="ProductStock" value="@product.StockLevel"> 96 @* CUSTOM END *@ 97 <input type="hidden" name="ProductReferer" value="product_details_info"> 98 <input type="hidden" name="cartcmd" value="add"> 99 100 @if (!string.IsNullOrEmpty(product.VariantId)) 101 { 102 <input type="hidden" name="VariantId" value="@product.VariantId"> 103 } 104 105 @if (!plytixIsNotForSale) 106 { 107 if (!quantitySelector || isCartDisabled) 108 { 109 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" type="hidden"> 110 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary w-100 js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@HtmlEncoder.HtmlAttributeEncode(isCartDisabled ? Translate("Sold out") : Translate("Add to cart"))" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@addToCartLabel</button> 111 } 112 else 113 { 114 <div class="input-group input-primary-button-group js-input-group d-flex flex-row"> 115 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label> 116 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" step="@stepQty" @minQty @onChangeEvent class="form-control" style="max-width: 96px; min-width:64px;" type="number" onkeydown="swift.Cart.UpdateOnEnterKey(event)"> 117 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@HtmlEncoder.HtmlAttributeEncode(Translate("Add to cart"))" id="AddToCartButton@(product.Id)_@Pageview.CurrentParagraph.ID">@addToCartLabel</button> 118 @* CUSTOM - Validate stock level in quantity-input, mimicking behavior on OrderLineItem in Cart. 119 NOTE: Below is copied from standard Cart.cshtml template *@ 120 @if (!IsNeverOutOfStock) 121 { 122 <script> 123 function debounce(func, wait, immediate) { 124 var timeout; 125 return function () { 126 var context = this, args = arguments; 127 var later = function () { 128 timeout = null; 129 if (!immediate) func.apply(context, args); 130 }; 131 var callNow = immediate && !timeout; 132 clearTimeout(timeout); 133 timeout = setTimeout(later, wait); 134 if (callNow) func.apply(context, args); 135 }; 136 }; 137 138 // NOTE: 'step' param is custom 139 var UpdateQuantity = debounce(function (input, stock, step) { 140 var inputValue = parseFloat(input.value); 141 var stock = parseFloat(stock); 142 var step = parseFloat(step); 143 144 if (inputValue <= stock) { 145 input.classList.remove("is-invalid"); 146 input.closest(".js-input-group").querySelector(".js-not-enough-stock").classList.add("d-none"); 147 } else { 148 var validQuantity = Math.floor(stock / step) * step; 149 if (validQuantity === 0) { 150 validQuantity = step; 151 } 152 input.value = validQuantity; 153 input.classList.add("is-invalid"); 154 input.closest(".js-input-group").querySelector(".js-not-enough-stock").classList.remove("d-none"); 155 } 156 }, 300); 157 </script> 158 <div class="invalid-feedback js-not-enough-stock d-none w-100"> 159 @Translate("We do not have enough products in stock") 160 </div> 161 } 162 </div> 163 } 164 165 if (stepQty != "1") 166 { 167 <div class="invalid-feedback d-none"> 168 @Translate("Please select a quantity that is dividable by") @stepQty 169 </div> 170 } 171 } 172 </form> 173 </div> 174 } 175 } 176 else if (Pageview.IsVisualEditorMode) 177 { 178 <div class="alert alert-dark m-0">@Translate("No products available")</div> 179 } 180
Error executing template "Designs/Swift/Paragraph/Swift_BackInStockNotification_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_7ce78922dbed4b55aec75f29f83581e8.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_BackInStockNotification_Custom.cshtml:line 45 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 System 3 @using System.Linq 4 @using Dynamicweb.Ecommerce 5 @using Dynamicweb.Ecommerce.ProductCatalog 6 @using Dynamicweb.Ecommerce.Products 7 @using Dynamicweb.Ecommerce.Stocks 8 @using Dynamicweb.Core 9 10 @functions 11 { 12 private static bool BackInStockRegisteredForUser(ProductViewModel product, string unitId = "", long stocklocationId = 0) 13 { 14 if (!Dynamicweb.Security.UserManagement.User.IsExtranetUserLoggedIn()) return false; 15 16 Product productObject = Services.Products.GetProductById(product.Id, product.VariantId, product.LanguageId); 17 StockLocation stockLocation = Services.StockService.GetStockLocation(stocklocationId); 18 double unitStock = productObject.GetUnitStock(stockLocation, unitId); 19 return unitStock <= 0d && ProductBackInStockNotification.BackInStockNotificationExists(productObject, unitId); 20 } 21 } 22 23 @{ 24 ProductViewModel product = null; 25 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 26 { 27 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 28 } 29 else if (Pageview.Page.Item["DummyProduct"] != null) 30 { 31 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 32 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 33 34 if (productList?.Products is object) 35 { 36 product = productList.Products[0]; 37 } 38 } 39 } 40 41 @if ((product is object)) 42 { 43 bool productInStock = product.StockLevel > 0 || product.NeverOutOfstock; 44 45 DateTime launchDate = product.ProductFields.ContainsKey("Custom_LaunchDate") ? Converter.ToDateTime(product.ProductFields["Custom_LaunchDate"].Value) : DateTime.MinValue; 46 DateTime currentDate = DateTime.Today; 47 48 bool beforeLaunchDate = currentDate < launchDate; 49 50 if (!productInStock || beforeLaunchDate) 51 { 52 string notifiedMessage = Model.Item.GetString("NotifiedMessage"); 53 string notifiedMessageId = $"NotifiedMessage_item_{Model.Item.SystemName.ToLower()}_{product.Id}_{product.VariantId.Replace(".", "_")}"; 54 55 string horizontalAlign = Model.Item.GetRawValueString("HorizontalAlignment", ""); 56 horizontalAlign = horizontalAlign == "center" ? "justify-content-center" : horizontalAlign; 57 horizontalAlign = horizontalAlign == "end" ? "justify-content-end" : horizontalAlign; 58 horizontalAlign = horizontalAlign == "full" ? "" : horizontalAlign; 59 60 string flexFill = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "flex-fill" : ""; 61 string fullWidth = Model.Item.GetRawValueString("HorizontalAlignment", "") == "full" ? "w-100" : ""; 62 63 bool notificationRegisteredForUser = BackInStockRegisteredForUser(product); 64 65 <div class="d-flex flex-row w-100 @horizontalAlign @fullWidth item_@Model.Item.SystemName.ToLower()"> 66 <div class="@horizontalAlign @fullWidth @(notificationRegisteredForUser ? "" : "d-none")" id="@notifiedMessageId"> 67 @notifiedMessage 68 </div> 69 70 @if (!notificationRegisteredForUser) 71 { 72 73 string notifyIcon = Model.Item.GetString("Icon"); 74 string notifyLabel = !notifyIcon.Contains("_none") ? "<span class=\"icon-2\">" + ReadFile(notifyIcon) + "</span>" : ""; 75 notifyLabel += " " + Translate("Notify me when available"); 76 77 string buttonSize = Model.Item.GetRawValueString("ButtonSize", "regular"); 78 string inputSize = string.Empty; 79 80 switch (buttonSize) 81 { 82 case "small": 83 inputSize = " input-group-sm"; 84 buttonSize = " btn-sm"; 85 break; 86 case "regular": 87 buttonSize = string.Empty; 88 break; 89 case "large": 90 inputSize = " input-group-lg"; 91 buttonSize = " btn-lg"; 92 break; 93 } 94 95 string modalId = $"modal_item_{Model.Item.SystemName.ToLower()}_{product.Id}_{product.VariantId.Replace(".", "_")}"; 96 string formId = $"NotificationForm_item_{Model.Item.SystemName.ToLower()}_{product.Id}_{product.VariantId.Replace(".", "_")}"; 97 string notificationButtonId = $"NotificationButton_item_{Model.Item.SystemName.ToLower()}_{product.Id}_{product.VariantId.Replace(".", "_")}"; 98 string notificationOnClick = !Pageview.IsVisualEditorMode ? $"swift.BackInStockNotification.SubmitNotification('{formId}', '{modalId}', '{notifiedMessageId}', '{notificationButtonId}')" : ""; 99 100 string productPage = $"Default.aspx?ID={GetPageIdByNavigationTag("ProductDetailPage")}"; 101 102 <div class="d-flex @horizontalAlign @fullWidth js-input-group"> 103 <div class="input-group input-primary-button-group flex-nowrap@(inputSize)"> 104 <button type="button" class="btn btn-primary @(buttonSize) @flexFill" id="@notificationButtonId" data-bs-toggle="modal" data-bs-target="#@modalId"> 105 <span class="text-nowrap d-flex align-items-center justify-content-center gap-2"> 106 @notifyLabel 107 </span> 108 </button> 109 </div> 110 </div> 111 112 <div class="modal fade" id="@modalId" tabindex="-1" aria-labelledby="label_@modalId" aria-hidden="true"> 113 <div class="modal-dialog modal-dialog-centered"> 114 <div class="modal-content"> 115 <div class="modal-header"> 116 <h5 class="modal-title" id="label_@modalId">@Translate("Back in Stock")</h5> 117 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(Translate("Close"))"></button> 118 </div> 119 <div class="modal-body"> 120 <form name="@product.Id" id="@formId" method="post" action="@productPage"> 121 <input type="hidden" name="ProductID" value="@product.Id" /> 122 <input type="hidden" name="VariantID" value="@product.VariantId" /> 123 <input type="hidden" name="LanguageID" value="@product.LanguageId" /> 124 <input type="hidden" name="CartCmd" value="createnotificationforthisproduct" /> 125 <div class="m-2"> 126 @Translate("Send me an email when the product is in stock.") 127 </div> 128 <div class="form-floating m-2"> 129 <input class="form-control" type="text" id="NotificationEmail" value="@(Pageview.User != null ? Pageview.User.Email : "")" name="NotificationEmail" placeholder="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(Translate("Email"))" required="required" aria-required="true" /> 130 <label for="NotificationEmail" class="form-label">@Translate("Email")</label> 131 </div> 132 </form> 133 </div> 134 <div class="modal-footer"> 135 <button type="button" class="btn btn-primary" onclick="@notificationOnClick">@Translate("Submit")</button> 136 </div> 137 </div> 138 </div> 139 </div> 140 } 141 </div> 142 } 143 } 144 else if (Pageview.IsVisualEditorMode) 145 { 146 <div class="alert alert-dark m-0">@Translate("Back in stock notification will be shown here")</div> 147 } 148
![]()
![]()
![]()
Error executing template "/Designs/Swift/Paragraph/Swift_Custom_ProductDeliveryInfo.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_18f41bd497f4425e97418ec68387a47c.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_Custom_ProductDeliveryInfo.cshtml:line 55 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 @using Dynamicweb.Core 7 @using System.Drawing 8 @using System.Web 9 @using Dynamicweb.Environment 10 @using System.Globalization 11 12 @* CUSTOM TEMPLATE SUPPORTED BY SWIFT (v1.25.0) *@ 13 14 @{ 15 bool isVisualEditor = !string.IsNullOrEmpty(Dynamicweb.Context.Current.Request.QueryString.Get("VisualEdit")) ? Convert.ToBoolean(Dynamicweb.Context.Current.Request.QueryString.Get("VisualEdit")) : false; 16 17 ProductViewModel product = new ProductViewModel(); 18 19 ProductViewModelSettings productSetting = new ProductViewModelSettings 20 { 21 LanguageId = Dynamicweb.Ecommerce.Common.Context.LanguageID, 22 CurrencyCode = Dynamicweb.Ecommerce.Common.Context.Currency.Code, 23 CountryCode = Dynamicweb.Ecommerce.Common.Context.Country.Code2, 24 ShopId = Pageview.Area.EcomShopId 25 }; 26 27 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 28 { 29 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 30 } 31 else if (Pageview.Item["DummyProduct"] != null) 32 { 33 34 string dummyProductId = ""; 35 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 36 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 37 if (productList.Products != null) 38 { 39 foreach (var p in productList.Products) { dummyProductId = p.Id; } 40 ProductViewModel dummyProduct = dummyProductId != "" ? ViewModelFactory.CreateView(productSetting, dummyProductId) : new ProductViewModel(); 41 product = dummyProduct; 42 } 43 else 44 { 45 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 46 } 47 } 48 else if (Pageview.Item["DummyProduct"] == null) 49 { 50 product = ViewModelFactory.CreateView(productSetting, Dynamicweb.Ecommerce.Services.Products.GetLastActiveProducts(1, Dynamicweb.Ecommerce.Common.Context.LanguageID, false).FirstOrDefault().Id); 51 } 52 53 if (product is object) 54 { 55 bool requiresCustomCarrier = product.ProductFields.ContainsKey("Custom_Carrier") ? Converter.ToBoolean(product.ProductFields["Custom_Carrier"].Value) : false; 56 var isNotForSaleText = Pageview.AreaSettings.GetItem("CustomSettings").GetString("Custom_NotForSaleRichTxt"); 57 bool plytixIsNotForSale = product.ProductFields.ContainsKey("Custom_NotForSale") ? Converter.ToBoolean(product.ProductFields["Custom_NotForSale"].Value) : false; 58 59 if (!plytixIsNotForSale) 60 { 61 <div class="order-3 small mb-3 mb-lg-0"> 62 <ul class="list-unstyled"> 63 <li><span class="icon-2">@ReadFile("/Files/Templates/Designs/Swift/Assets/icons/check.svg")</span> <span class="ms-2">@Translate("Free delivery")</span></li> 64 <li><span class="icon-2">@ReadFile("/Files/Templates/Designs/Swift/Assets/icons/check.svg")</span> <span class="ms-2">@Translate("Delivery time: 1-3 business days")</span></li> 65 @if (requiresCustomCarrier) 66 { 67 <li class="mt-3"><span><strong>@Translate("This product requires a carrier")</strong></span></li> 68 } 69 </ul> 70 </div> 71 } 72 else 73 { 74 <div class="order-3 small mb-3 mb-lg-0"> 75 <div>@isNotForSaleText</div> 76 </div> 77 } 78 } 79 } 80
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsGallery_Custom.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_894a50e1dcf24089b50b306640391486.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsGallery_Custom.cshtml:line 125 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.Frontend 4 @using System.IO 5 @using System.Web; 6 7 @* CUSTOMIZED STANDARD SWIFT (v1.?) TEMPLATE *@ 8 @* TODO: REFACTURE/UPGRADE TO SWIFT (v1.25.0) *@ 9 @* NOTE: This template use the standard customized item logic; so template need to be selected on item (layout), or one the website (Item paragraph layouts) *@ 10 11 @functions { 12 public ProductViewModel product { get; set; } = new ProductViewModel(); 13 public string galleryLayout { get; set; } 14 public string[] supportedImageFormats { get; set; } 15 public string[] supportedVideoFormats { get; set; } 16 public string[] supportedDocumentFormats { get; set; } 17 public string[] allSupportedFormats { get; set; } 18 19 public class RatioSettings 20 { 21 public string Ratio { get; set; } 22 public string CssClass { get; set; } 23 public string CssVariable { get; set; } 24 public string Fill { get; set; } 25 } 26 27 public RatioSettings GetRatioSettings(string size = "desktop") 28 { 29 var ratioSettings = new RatioSettings(); 30 31 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 32 ratio = ratio != "0" ? ratio : ""; 33 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 34 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 35 cssClass = ratio != "" && ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 36 cssVariable = ratio != "" && ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 37 38 ratioSettings.Ratio = ratio; 39 ratioSettings.CssClass = cssClass; 40 ratioSettings.CssVariable = cssVariable; 41 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 42 43 return ratioSettings; 44 } 45 46 // CUSTOM parameter 47 public string GetColumnClass(int total, int assetNumber, bool customIsIconGallery) 48 { 49 string colClass = total > 1 ? "g-col-lg-6" : "g-col-12"; 50 colClass = galleryLayout == "full-first" && assetNumber == 0 ? "g-col-12" : colClass; 51 colClass = galleryLayout == "full-last" && assetNumber == (total - 1) ? "g-col-12" : colClass; 52 colClass = galleryLayout == "advanced-grid" && assetNumber > 1 ? "g-col-4" : colClass; 53 54 colClass = galleryLayout == "advanced-grid" && total == 1 ? "g-col-12" : colClass; 55 //colClass = galleryLayout == "advanced-grid" && total == 3 && assetNumber == 2 ? "g-col-12" : colClass; //CUSTOM 56 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 2 ? "g-col-6" : colClass; 57 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 3 ? "g-col-6" : colClass; 58 //colClass = galleryLayout == "advanced-grid" && total == 6 && assetNumber == 5 ? "g-col-12" : colClass; //CUSTOM 59 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 5 ? "g-col-6" : colClass; 60 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 6 ? "g-col-6" : colClass; 61 //colClass = galleryLayout == "advanced-grid" && total == 9 && assetNumber == 8 ? "g-col-12" : colClass; //CUSTOM 62 colClass = customIsIconGallery ? "g-col-1" : colClass; // CUSTOM 63 64 return colClass; 65 } 66 67 public string GetArrowsColor() 68 { 69 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 70 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 71 return arrowsColor; 72 } 73 74 //CUSTOM 75 public static string GetAspectRatio(string value) 76 { 77 switch (value) 78 { 79 case "0": return "auto"; 80 case "fill": return "cover"; 81 case "100%": return "1 / 1"; 82 case "56%": return "16 / 9"; 83 case "177%": return "9 / 16"; 84 case "75%": return "4 / 3"; 85 case "133%": return "3 / 4"; 86 case "28%": return "32 / 9"; 87 default: return "auto"; 88 } 89 } 90 //--CUSTOM 91 } 92 93 @{ 94 @* Get the product data *@ 95 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 96 { 97 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 98 } 99 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 100 { 101 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 102 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 103 104 if (productList?.Products is object) 105 { 106 product = productList.Products[0]; 107 } 108 } 109 } 110 111 @if (product is object) 112 { 113 @* Supported formats *@ 114 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 115 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 116 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 117 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 118 119 @* Collect the assets *@ 120 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 121 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 122 123 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 124 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 125 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 126 assetsImages = selectedAssetCategories.Contains("Videos") ? assetsImages.OrderByDescending(x => x.Value.Contains("vimeo")) : assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); //CUSTOM 127 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 128 assetsList = assetsList.Union(assetsImages); 129 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 130 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 131 132 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 133 bool customIsIconGallery = selectedAssetCategories.Any(category => category.Contains("Icons")) || selectedAssetCategories.Any(category => category.Contains("Awards")); //CUSTOM 134 135 int totalAssets = 0; 136 foreach (MediaViewModel asset in assetsList) 137 { 138 var assetValue = asset.Value; 139 foreach (string format in allSupportedFormats) 140 { 141 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 142 { 143 totalAssets++; 144 } 145 } 146 } 147 148 if (totalAssets == 0) 149 { 150 if (defaultImageFallback) 151 { 152 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 153 totalAssets = 1; 154 } 155 else 156 { 157 assetsList = new List<MediaViewModel>() { }; 158 totalAssets = 0; 159 } 160 } 161 162 @* Layout settings *@ 163 string spacing = Model.Item.GetRawValueString("Spacing", "4"); 164 spacing = spacing == "none" ? "gap-0" : spacing; 165 spacing = spacing == "small" ? "gap-3" : spacing; 166 spacing = spacing == "large" ? "gap-4" : spacing; 167 168 galleryLayout = Model.Item.GetRawValueString("Layout", "grid"); 169 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 170 171 var badgeParms = new Dictionary<string, object>(); 172 badgeParms.Add("size", "h5"); 173 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 174 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 175 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 176 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 177 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 178 179 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 180 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 181 DateTime createdDate = product.Created.Value; 182 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 183 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 184 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 185 186 @* Get assets from selected categories or get all assets *@ 187 if (totalAssets != 0 && assetsList.Count() != 0) 188 { 189 int desktopAssetNumber = 0; 190 int mobileAssetNumber = 0; 191 int mobileAssetThumbnailNumber = 0; 192 int modalAssetNumber = 0; 193 string customBsColumns = customIsIconGallery ? "custom-col-8" : ""; 194 195 @* Desktop: Show the gallery on large screens *@ 196 <div class="d-none d-lg-block h-100 position-relative @theme item_@Model.Item.SystemName.ToLower() desktop"> 197 <div class="grid @spacing @customBsColumns"> 198 @foreach (MediaViewModel asset in assetsList) 199 { 200 var assetName = asset.Value.ToLower(); 201 foreach (string format in allSupportedFormats) 202 { 203 if (assetName.Contains(format)) 204 { 205 <div class="@GetColumnClass(totalAssets, desktopAssetNumber, customIsIconGallery)"> 206 @{@RenderAsset(asset, desktopAssetNumber)} 207 </div> 208 desktopAssetNumber++; 209 } 210 } 211 } 212 </div> 213 214 @if (showBadges) 215 { 216 <div class="position-absolute top-0 left-0 p-2 p-lg-3 w-100"> 217 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 218 </div> 219 } 220 </div> 221 222 // CUSTOM 223 if (!customIsIconGallery) 224 { 225 @* Mobile: Show the thumbs on small screens *@ 226 <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile"> 227 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 228 <div class="carousel-inner h-100"> 229 @foreach (MediaViewModel asset in assetsList) 230 { 231 var assetValue = asset.Value.ToLower(); 232 foreach (string format in allSupportedFormats) 233 { 234 if (assetValue.Contains(format)) 235 { 236 string activeSlide = mobileAssetNumber == 0 ? "active" : ""; 237 238 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 239 @{@RenderAsset(asset, mobileAssetNumber, "mobile")} 240 </div> 241 mobileAssetNumber++; 242 } 243 } 244 } 245 </div> 246 </div> 247 248 @if (totalAssets > 1) 249 { 250 <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3"> 251 @foreach (MediaViewModel asset in assetsList) 252 { 253 var assetValue = asset.Value; 254 foreach (string format in allSupportedFormats) 255 { 256 if (assetValue.Contains(format)) 257 { 258 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 259 imagePath = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/default.jpg" : imagePath; 260 string imagePathThumb = !imagePath.Contains("youtube") ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=160&format=webp" : imagePath; 261 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 262 263 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 264 string vimeoJsClass = assetValue.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 265 imagePathThumb = imagePathThumb.Contains("vimeo") ? "" : imagePathThumb; 266 267 string productName = product.Name; 268 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 269 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 270 271 <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber"> 272 @foreach (string videoFormat in supportedVideoFormats) 273 { 274 if (assetValue.Contains(videoFormat)) 275 { 276 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 277 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 278 </div> 279 } 280 } 281 <img src="@imagePathThumb" class="p-1 @vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" alt="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(productName)" @assetTitle> 282 </div> 283 284 mobileAssetThumbnailNumber++; 285 } 286 } 287 } 288 </div> 289 } 290 291 @if (showBadges) 292 { 293 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 294 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 295 </div> 296 } 297 </div> 298 } 299 else 300 { 301 <div class="d-block d-lg-none h-100 position-relative mobile"> 302 <div class="grid"> 303 @foreach (MediaViewModel asset in assetsList) 304 { 305 var assetName = asset.Value.ToLower(); 306 foreach (string format in allSupportedFormats) 307 { 308 if (assetName.Contains(format)) 309 { 310 <div class="g-col-3"> 311 @RenderAsset(asset, mobileAssetNumber) 312 </div> 313 mobileAssetNumber++; 314 } 315 } 316 } 317 </div> 318 </div> 319 } 320 321 @* Modal with slides *@ 322 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 323 <div class="modal-dialog modal-dialog-centered modal-xl"> 324 <div class="modal-content"> 325 <div class="modal-header visually-hidden"> 326 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 327 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 328 </div> 329 <div class="modal-body p-2 p-lg-3 h-100"> 330 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 331 <div class="carousel-inner h-100 @theme"> 332 @foreach (MediaViewModel asset in assetsList) 333 { 334 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 335 foreach (string format in allSupportedFormats) 336 { 337 if (assetValue.Contains(format)) 338 { 339 string imagePath = assetValue; 340 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 341 342 var parms = new Dictionary<string, object>(); 343 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 344 parms.Add("fullwidth", true); 345 parms.Add("columns", Model.GridRowColumnCount); 346 347 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 348 @foreach (string imageFormat in supportedImageFormats) 349 { 350 if (assetValue.Contains(imageFormat)) 351 { 352 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 353 } 354 } 355 356 @foreach (string videoFormat in supportedVideoFormats) 357 { 358 if (assetValue.Contains(videoFormat)) 359 { 360 {@RenderVideoPlayerWrapper(asset, "modal")} @*//CUSTOM*@ 361 } 362 } 363 </div> 364 365 modalAssetNumber++; 366 } 367 } 368 } 369 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 370 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 371 <span class="visually-hidden">@Translate("Previous")</span> 372 </button> 373 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 374 <span class="carousel-control-next-icon" aria-hidden="true"></span> 375 <span class="visually-hidden">@Translate("Next")</span> 376 </button> 377 </div> 378 </div> 379 </div> 380 </div> 381 </div> 382 </div> 383 } 384 else if (Pageview.IsVisualEditorMode) 385 { 386 RatioSettings ratioSettings = GetRatioSettings("desktop"); 387 388 <div class="h-100 @theme"> 389 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 390 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(Translate("Missing image"))"> 391 </div> 392 </div> 393 } 394 } 395 396 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") 397 { 398 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 399 string assetValue = asset.Value; 400 401 <div class="h-100 @(theme)"> 402 @foreach (string format in supportedImageFormats) 403 { //Images 404 if (assetValue.Contains(format)) 405 { 406 {@RenderImage(asset, assetNumber, size)} 407 } 408 } 409 @foreach (string format in supportedVideoFormats) 410 { //Videos 411 if (assetValue.Contains(format)) 412 { 413 if (Model.Item.GetString("OpenVideoInModal") == "true") 414 { 415 {@RenderVideoScreendump(asset, assetNumber, size)} 416 } 417 else 418 { 419 {@RenderVideoPlayerWrapper(asset, size)} @*//CUSTOM*@ 420 } 421 } 422 } 423 @foreach (string format in supportedDocumentFormats) 424 { //Documents 425 if (assetValue.Contains(format)) 426 { 427 {@RenderDocument(asset, assetNumber, size)} 428 } 429 } 430 </div> 431 } 432 433 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") 434 { 435 string productName = product.Name; 436 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 437 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 438 439 RatioSettings ratioSettings = GetRatioSettings(size); 440 441 var parms = new Dictionary<string, object>(); 442 parms.Add("alt", productName); 443 parms.Add("itemprop", "image"); 444 parms.Add("fullwidth", true); 445 parms.Add("columns", Model.GridRowColumnCount); 446 if (!string.IsNullOrEmpty(asset.DisplayName)) 447 { 448 parms.Add("title", asset.DisplayName); 449 } 450 451 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 452 { 453 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 454 } 455 else 456 { 457 parms.Add("cssClass", "mw-100 mh-100"); 458 } 459 460 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 461 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 462 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 463 </div> 464 </a> 465 } 466 467 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") 468 { 469 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 470 471 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 472 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 473 videoScreendumpPath = videoScreendumpPath.Contains("youtu.be") || videoScreendumpPath.Contains("youtube") ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 474 475 string vimeoJsClass = videoScreendumpPath.Contains("vimeo") ? "js-vimeo-video-thumbnail" : ""; 476 videoScreendumpPath = videoScreendumpPath.Contains("vimeo") ? "" : videoScreendumpPath; 477 478 string productName = product.Name; 479 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 480 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 481 482 RatioSettings ratioSettings = GetRatioSettings(size); 483 484 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 485 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 486 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 487 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(productName)" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 488 </div> 489 </div> 490 } 491 492 @*//CUSTOM*@ 493 @helper RenderVideoPlayerWrapper(MediaViewModel asset, string size = "desktop") 494 { 495 if (size == "modal") 496 { 497 <div class="h-100 plyr__wrap plyr__wrap-aspect"> 498 @RenderVideoPlayer(asset, size) 499 </div> 500 } 501 else 502 { 503 var videoObjectFit = Model.Item.GetRawValueString("Custom_VideoObjectFit", "plyr__wrap-aspect"); 504 var videoControlsPosition = Model.Item.GetRawValueString("Custom_VideoControlsPosition", " ").Trim(); 505 videoControlsPosition = videoObjectFit == "plyr__wrap-aspect" && videoControlsPosition == "plyr__wrap-controls" ? "" : videoControlsPosition; 506 videoControlsPosition = videoObjectFit == "plyr__wrap-cover" ? "plyr__wrap-nocontrols" : videoControlsPosition; 507 var videoAspectRatio = GetAspectRatio(Model.Item.GetRawValueString("ImageAspectRatio", "")); 508 videoAspectRatio = videoObjectFit == "plyr__wrap-aspect" ? "auto" : videoAspectRatio; 509 510 <div class="h-100 plyr__wrap @(videoObjectFit) @(videoControlsPosition)" style="aspect-ratio: @(videoAspectRatio);"> 511 @RenderVideoPlayer(asset, size) 512 </div> 513 } 514 } 515 @*//--CUSTOM*@ 516 517 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") 518 { 519 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 520 string assetValue = asset.Value; 521 string assetDescription = !string.IsNullOrEmpty(product.ShortDescription) ? product.ShortDescription : product.Name; 522 DateTime assetUploadDate = product.Created.Value; 523 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 524 string type = assetValue.Contains("youtu.be") || assetValue.Contains("youtube") ? "youtube" : ""; 525 type = assetValue.Contains("vimeo") ? "vimeo" : type; 526 type = assetValue.Contains(".mp4") || assetValue.Contains(".webm") ? "selfhosted" : type; 527 528 string openInModal = Model.Item.GetString("OpenVideoInModal"); 529 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 530 bool onlyAutoPlayInIntersecting = Model.Item.GetBoolean("Custom_VideoOnlyAutoPlayInIntersecting"); 531 bool onlyAutoPlayOnce = Model.Item.GetBoolean("Custom_VideoOnlyAutoPlayOnce"); 532 DateTime uploadDate = product.Created.Value; 533 534 var useSchema = (type != "vimeo"); 535 536 //CUSTOM 537 var videoObjectFit = Model.Item.GetRawValueString("Custom_VideoObjectFit", "plyr__wrap-aspect"); 538 var videoControlsPosition = Model.Item.GetRawValueString("Custom_VideoControlsPosition", " ").Trim(); 539 videoControlsPosition = videoObjectFit == "plyr__wrap-aspect" && videoControlsPosition == "plyr__wrap-controls" ? "" : videoControlsPosition; 540 videoControlsPosition = videoObjectFit == "plyr__wrap-cover" ? "plyr__wrap-nocontrols" : videoControlsPosition; 541 videoControlsPosition = size == "modal" ? "" : videoControlsPosition; 542 //--CUSTOM 543 544 if (useSchema) 545 { 546 if (type == "selfhosted") 547 { 548 var selfhostedFileInfo = new System.IO.FileInfo(Dynamicweb.Core.SystemInformation.MapPath(assetValue)); 549 if (selfhostedFileInfo != null) 550 { 551 assetUploadDate = selfhostedFileInfo.LastWriteTime; 552 } 553 } 554 555 <span class="visually-hidden" itemprop="name">@assetName</span> 556 <span class="visually-hidden" itemprop="contentUrl">@assetValue</span> 557 <span class="visually-hidden" itemprop="thumbnailUrl">@assetValue</span> 558 <span class="visually-hidden" itemprop="description">@assetDescription</span> 559 <span class="visually-hidden" itemprop="uploadDate">@assetUploadDate.ToString("yyyy-MM-dd'T'HH:mm:ss")</span> 560 } 561 562 if (type != "selfhosted") 563 { 564 var playerId = $"player_{Pageview.CurrentParagraph.ID}_{videoId}_{size}"; 565 566 <div id="@(playerId)" 567 class="plyr__video-embed custom" 568 data-plyr-provider="@(type)" 569 data-plyr-embed-id="@videoId" 570 style="--plyr-color-main: var(--swift-foreground-color); color:green;"> 571 </div> 572 573 <script type="module" src="~/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 574 <script type="module"> 575 const player = new Plyr('#@(playerId)', { 576 type: 'video', 577 fullscreen: { 578 enabled: true, 579 iosNative: true 580 }, 581 youtube: { 582 noCookie: true, 583 showinfo: 0 584 }, 585 vimeo: { 586 playsinline: true, 587 }, 588 clickToPlay: false, 589 autopause: false 590 }); 591 592 @*//CUSTOM*@ 593 @if (videoControlsPosition == "plyr__wrap-nocontrols") 594 { 595 <text> 596 player.config.controls = []; 597 </text> 598 } 599 @*//--CUSTOM*@ 600 601 @if (autoPlay && openInModal == "false") 602 { 603 <text> 604 player.config.autoplay = @(!onlyAutoPlayInIntersecting ? "true" : "false"); 605 player.config.muted = true; 606 player.config.volume = 0; 607 player.config.loop = { active: @(!onlyAutoPlayOnce ? "true" : "false") }; 608 609 let useIntersecting = @(onlyAutoPlayInIntersecting ? "true" : "false"); 610 </text> 611 612 if (onlyAutoPlayInIntersecting) 613 { 614 <text> 615 player.on('ready', function (e) { 616 const observer = new IntersectionObserver(entries => { 617 entries.forEach(entry => { 618 if (useIntersecting) { 619 if (entry.isIntersecting) { 620 e.detail.plyr.play(); 621 } 622 else { 623 e.detail.plyr.pause(); 624 } 625 } 626 }); 627 }); 628 observer.observe(e.target.parentElement); 629 }); 630 </text> 631 } 632 633 if (onlyAutoPlayOnce) 634 { 635 <text> 636 player.on('ended', () => { 637 player.stop(); 638 player.restart(); 639 useIntersecting = false; 640 641 var playButton = document.createElement('button'); 642 playButton.type = 'button'; 643 playButton.className = 'plyr__control plyr__control--overlaid'; 644 playButton.setAttribute('aria-plyr', 'play'); 645 playButton.setAttribute('aria-label', 'Play'); 646 playButton.innerHTML = '<svg aria-hidden="true" focusable="false"><use xlink: href="#plyr-play"></use></svg><span class="plyr__sr-only">Play</span>'; 647 playButton.addEventListener('click', function () { 648 this.remove(); 649 player.play(); 650 useIntersecting = true; 651 }); 652 653 player.elements.container.appendChild(playButton); 654 }); 655 </text> 656 } 657 } 658 659 @if (openInModal == "true") { 660 <text> 661 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 662 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 663 player.pause(); 664 }) 665 </text> 666 } 667 668 initPlyrFit(player); @*//CUSTOM*@ 669 </script> 670 } 671 else 672 { 673 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 674 string videoType = Path.GetExtension(assetValue).ToLower(); 675 676 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;"> 677 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 678 </video> 679 } 680 } 681 682 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") 683 { 684 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 685 686 string productName = product.Name; 687 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 688 string imageLinkPath = imagePath; 689 690 RatioSettings ratioSettings = GetRatioSettings(size); 691 692 var parms = new Dictionary<string, object>(); 693 parms.Add("alt", productName); 694 parms.Add("itemprop", "image"); 695 parms.Add("fullwidth", true); 696 parms.Add("columns", Model.GridRowColumnCount); 697 if (!string.IsNullOrEmpty(asset.DisplayName)) 698 { 699 parms.Add("title", asset.DisplayName); 700 } 701 702 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 703 { 704 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 705 } 706 else 707 { 708 parms.Add("cssClass", "mw-100 mh-100"); 709 } 710 711 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Dynamicweb.Core.Encoders.HtmlEncoder.HtmlAttributeEncode(Translate("Download"))"> 712 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 713 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 714 @if (asset.Value.Contains(".pdf")) 715 { 716 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 717 } 718 else 719 { 720 721 } 722 </div> 723 </a> 724 } 725
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsGallery.cshtml" System.ArgumentNullException: Value cannot be null. Parameter name: source at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate) at CompiledRazorTemplates.Dynamic.RazorEngine_4317c919e34d405e950fbf6a0e5debe9.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsGallery.cshtml:line 110 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.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings(string size = "desktop") { 22 var ratioSettings = new RatioSettings(); 23 24 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 25 ratio = ratio != "0" ? ratio : ""; 26 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 27 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 28 cssClass = ratio != "" && ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 29 cssVariable = ratio != "" && ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 30 31 ratioSettings.Ratio = ratio; 32 ratioSettings.CssClass = cssClass; 33 ratioSettings.CssVariable = cssVariable; 34 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 35 36 return ratioSettings; 37 } 38 39 public string GetColumnClass(int total, int assetNumber) { 40 string colClass = total > 1 ? "g-col-lg-6" : "g-col-12"; 41 colClass = galleryLayout == "full-first" && assetNumber == 0 ? "g-col-12" : colClass; 42 colClass = galleryLayout == "full-last" && assetNumber == (total - 1) ? "g-col-12" : colClass; 43 colClass = galleryLayout == "advanced-grid" && assetNumber > 1 ? "g-col-4" : colClass; 44 45 colClass = galleryLayout == "advanced-grid" && total == 1 ? "g-col-12" : colClass; 46 colClass = galleryLayout == "advanced-grid" && total == 3 && assetNumber == 2 ? "g-col-12" : colClass; 47 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 2 ? "g-col-6" : colClass; 48 colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 3 ? "g-col-6" : colClass; 49 colClass = galleryLayout == "advanced-grid" && total == 6 && assetNumber == 5 ? "g-col-12" : colClass; 50 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 5 ? "g-col-6" : colClass; 51 colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 6 ? "g-col-6" : colClass; 52 colClass = galleryLayout == "advanced-grid" && total == 9 && assetNumber == 8 ? "g-col-12" : colClass; 53 54 return colClass; 55 } 56 57 public string GetArrowsColor() 58 { 59 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 60 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 61 return arrowsColor; 62 } 63 64 Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) 65 { 66 var videoParams = new Dictionary<string, object>(); 67 videoParams.Add("AssetName", asset.Name); 68 videoParams.Add("AssetDisplayName", asset.DisplayName); 69 videoParams.Add("AssetValue", asset.Value); 70 videoParams.Add("OpenVideoInModal", Model.Item.GetString("OpenVideoInModal")); 71 videoParams.Add("VideoAutoPlay", Model.Item.GetBoolean("VideoAutoPlay")); 72 videoParams.Add("Size", size); 73 videoParams.Add("Id", Model.ID); 74 return videoParams; 75 } 76 } 77 78 @{ 79 @* Get the product data *@ 80 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 81 { 82 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 83 } 84 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 85 { 86 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 87 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 88 89 if (productList?.Products is object) 90 { 91 product = productList.Products[0]; 92 } 93 } 94 } 95 96 @if (product is object) 97 { 98 @* Supported formats *@ 99 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 100 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 101 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 102 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 103 104 @* Collect the assets *@ 105 var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); 106 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 107 108 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 109 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 110 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 111 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 112 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; 113 assetsList = assetsList.Union(assetsImages); 114 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 115 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 116 117 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 118 119 int totalAssets = 0; 120 foreach (MediaViewModel asset in assetsList) { 121 var assetValue = asset.Value; 122 foreach (string format in allSupportedFormats) { 123 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 124 totalAssets++; 125 } 126 } 127 } 128 129 if (totalAssets == 0) 130 { 131 if (defaultImageFallback) { 132 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 133 totalAssets = 1; 134 } else { 135 assetsList = new List<MediaViewModel>(){ }; 136 totalAssets = 0; 137 } 138 } 139 140 @* Layout settings *@ 141 string spacing = Model.Item.GetRawValueString("Spacing", "4"); 142 spacing = spacing == "none" ? "gap-0" : spacing; 143 spacing = spacing == "small" ? "gap-3" : spacing; 144 spacing = spacing == "large" ? "gap-4" : spacing; 145 146 galleryLayout = Model.Item.GetRawValueString("Layout", "grid"); 147 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 148 149 var badgeParms = new Dictionary<string, object>(); 150 badgeParms.Add("size", "h5"); 151 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 152 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 153 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 154 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 155 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 156 157 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 158 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 159 DateTime createdDate = product.Created.Value; 160 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 161 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 162 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 163 164 165 @* Get assets from selected categories or get all assets *@ 166 if (totalAssets != 0 && assetsList.Count() != 0) { 167 int desktopAssetNumber = 0; 168 int mobileAssetNumber = 0; 169 int mobileAssetThumbnailNumber = 0; 170 int modalAssetNumber = 0; 171 172 @* Desktop: Show the gallery on large screens *@ 173 <div class="d-none d-lg-block h-100 position-relative @theme item_@Model.Item.SystemName.ToLower() desktop"> 174 <div class="grid @spacing"> 175 @foreach (MediaViewModel asset in assetsList) { 176 var assetName = asset.Value; 177 foreach (string format in allSupportedFormats) { 178 if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 179 string size = "desktop"; 180 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 181 string assetValue = asset.Value; 182 183 <div class="@GetColumnClass(totalAssets, desktopAssetNumber)"> 184 <div class="h-100 @(imageTheme)"> 185 @foreach (string imageFormat in supportedImageFormats) 186 { //Images 187 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) 188 { 189 string productName = product.Name; 190 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 191 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 192 193 RatioSettings ratioSettings = GetRatioSettings(size); 194 195 var parms = new Dictionary<string, object>(); 196 parms.Add("alt", productName); 197 parms.Add("itemprop", "image"); 198 if (totalAssets > 1) 199 { 200 parms.Add("columns", 2); 201 } 202 else 203 { 204 parms.Add("columns", 1); 205 } 206 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 207 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 208 209 if (!string.IsNullOrEmpty(asset.DisplayName)) 210 { 211 parms.Add("title", asset.DisplayName); 212 } 213 214 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 215 { 216 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 217 } 218 else 219 { 220 parms.Add("cssClass", "mw-100 mh-100"); 221 } 222 223 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 224 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> 225 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 226 </div> 227 </a> 228 } 229 } 230 @foreach (string videoFormat in supportedVideoFormats) 231 { //Videos 232 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 233 { 234 if (Model.Item.GetString("OpenVideoInModal") == "true") 235 { 236 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 237 238 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 239 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 240 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) != 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 241 242 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 243 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 244 245 string productName = product.Name; 246 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 247 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 248 249 RatioSettings ratioSettings = GetRatioSettings(size); 250 251 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 252 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> 253 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 254 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 255 { 256 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 257 } 258 else 259 { 260 string videoType = Path.GetExtension(asset.Value).ToLower(); 261 string videoPathEncoded = System.Uri.EscapeDataString(assetValue); 262 263 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 264 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 265 </video> 266 } 267 </div> 268 </div> 269 270 <script> 271 function CheckIfVideoThumbnailExist(image) { 272 if (image.width == 120) { 273 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 274 image.src = lowQualityImage; 275 } 276 } 277 </script> 278 } 279 else 280 { 281 Dictionary<string, object> videoParams = GetVideoParams(asset, size); 282 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); 283 } 284 } 285 } 286 @foreach (string documentFormat in supportedDocumentFormats) 287 { //Documents 288 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 289 { 290 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 291 292 string productName = product.Name; 293 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 294 string imageLinkPath = imagePath; 295 296 RatioSettings ratioSettings = GetRatioSettings(size); 297 298 var parms = new Dictionary<string, object>(); 299 parms.Add("alt", productName); 300 parms.Add("itemprop", "image"); 301 parms.Add("fullwidth", true); 302 parms.Add("columns", Model.GridRowColumnCount); 303 if (!string.IsNullOrEmpty(asset.DisplayName)) 304 { 305 parms.Add("title", asset.DisplayName); 306 } 307 308 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 309 { 310 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 311 } 312 else 313 { 314 parms.Add("cssClass", "mw-100 mh-100"); 315 } 316 317 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")"> 318 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 319 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 320 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 321 { 322 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 323 } 324 else 325 { 326 327 } 328 </div> 329 </a> 330 } 331 } 332 </div> 333 </div> 334 desktopAssetNumber++; 335 } 336 } 337 } 338 </div> 339 340 @if (showBadges) { 341 <div class="position-absolute top-0 left-0 p-2 p-lg-3 w-100"> 342 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 343 </div> 344 } 345 </div> 346 347 @* Mobile: Show the thumbs on small screens *@ 348 <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile"> 349 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 350 <div class="carousel-inner h-100"> 351 @foreach (MediaViewModel asset in assetsList) { 352 var assetValue = asset.Value; 353 foreach (string format in allSupportedFormats) { 354 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 355 string activeSlide = mobileAssetNumber == 0 ? "active" : ""; 356 string size = "mobile"; 357 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 358 359 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 360 <div class="h-100 @(imageTheme)"> 361 @foreach (string supportedFormat in supportedImageFormats) 362 { //Images 363 if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) 364 { 365 string productName = product.Name; 366 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 367 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 368 369 RatioSettings ratioSettings = GetRatioSettings(size); 370 371 var parms = new Dictionary<string, object>(); 372 parms.Add("alt", productName); 373 parms.Add("itemprop", "image"); 374 if (totalAssets > 1) 375 { 376 parms.Add("columns", 2); 377 } 378 else 379 { 380 parms.Add("columns", 1); 381 } 382 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 383 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 384 385 if (!string.IsNullOrEmpty(asset.DisplayName)) 386 { 387 parms.Add("title", asset.DisplayName); 388 } 389 390 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 391 { 392 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 393 } 394 else 395 { 396 parms.Add("cssClass", "mw-100 mh-100"); 397 } 398 399 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 400 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> 401 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 402 </div> 403 </a> 404 } 405 } 406 @foreach (string videoFormat in supportedVideoFormats) 407 { //Videos 408 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 409 { 410 if (Model.Item.GetString("OpenVideoInModal") == "true") 411 { 412 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 413 414 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 415 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 416 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) != 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 417 418 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 419 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 420 421 string productName = product.Name; 422 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 423 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 424 425 RatioSettings ratioSettings = GetRatioSettings(size); 426 427 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 428 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> 429 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 430 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 431 { 432 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 433 } 434 else 435 { 436 string videoType = Path.GetExtension(asset.Value).ToLower(); 437 string videoPathEncoded = System.Uri.EscapeDataString(asset.Value); 438 439 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 440 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 441 </video> 442 } 443 </div> 444 </div> 445 446 <script> 447 function CheckIfVideoThumbnailExist(image) { 448 if (image.width == 120) { 449 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 450 image.src = lowQualityImage; 451 } 452 } 453 </script> 454 } 455 else 456 { 457 Dictionary<string, object> videoParams = GetVideoParams(asset, size); 458 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); 459 } 460 } 461 } 462 @foreach (string documentFormat in supportedDocumentFormats) 463 { //Documents 464 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) 465 { 466 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 467 468 string productName = product.Name; 469 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 470 string imageLinkPath = imagePath; 471 472 RatioSettings ratioSettings = GetRatioSettings(size); 473 474 var parms = new Dictionary<string, object>(); 475 parms.Add("alt", productName); 476 parms.Add("itemprop", "image"); 477 parms.Add("fullwidth", true); 478 parms.Add("columns", Model.GridRowColumnCount); 479 if (!string.IsNullOrEmpty(asset.DisplayName)) 480 { 481 parms.Add("title", asset.DisplayName); 482 } 483 484 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") 485 { 486 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 487 } 488 else 489 { 490 parms.Add("cssClass", "mw-100 mh-100"); 491 } 492 493 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")"> 494 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 495 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 496 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 497 { 498 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 499 } 500 else 501 { 502 503 } 504 </div> 505 </a> 506 } 507 } 508 </div> 509 </div> 510 mobileAssetNumber++; 511 } 512 } 513 } 514 </div> 515 </div> 516 517 @if (totalAssets > 1) { 518 <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3"> 519 @foreach (MediaViewModel asset in assetsList) { 520 var assetValue = asset.Value; 521 foreach (string format in allSupportedFormats) { 522 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 523 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 524 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/default.jpg" : imagePath; 525 string imagePathThumb = assetValue.StartsWith("/Files/", StringComparison.OrdinalIgnoreCase) ? imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=160&format=webp" : imagePath : assetValue; 526 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 527 528 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 529 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 530 531 string productName = product.Name; 532 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 533 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 534 535 <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber"> 536 537 @foreach (string videoFormat in supportedVideoFormats) 538 { 539 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) 540 { 541 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 542 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath+"play-circle.svg")</div> 543 </div> 544 } 545 } 546 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 547 { 548 549 <img src="@(imagePathThumb)" class="p-1 @vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" alt="@productName" @assetTitle> 550 551 } 552 else 553 { 554 string videoType = Path.GetExtension(asset.Value).ToLower(); 555 string videoPathEncoded = System.Uri.EscapeDataString(assetValue); 556 557 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 558 <source src="@(videoPathEncoded)#t=0.001" type="video/@videoType.Replace(".", "")"> 559 </video> 560 } 561 562 </div> 563 564 mobileAssetThumbnailNumber++; 565 } 566 } 567 } 568 </div> 569 } 570 571 @if (showBadges) { 572 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 573 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 574 </div> 575 } 576 </div> 577 578 @* Modal with slides *@ 579 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 580 <div class="modal-dialog modal-dialog-centered modal-xl"> 581 <div class="modal-content"> 582 <div class="modal-header visually-hidden"> 583 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 584 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 585 </div> 586 <div class="modal-body p-2 p-lg-3 h-100"> 587 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 588 <div class="carousel-inner h-100 @theme"> 589 @foreach (MediaViewModel asset in assetsList) { 590 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 591 foreach (string format in allSupportedFormats) { 592 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 593 string imagePath = assetValue; 594 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 595 596 var parms = new Dictionary<string, object>(); 597 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 598 parms.Add("columns", Model.GridRowColumnCount); 599 parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); 600 parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); 601 602 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 603 @foreach (string imageFormat in supportedImageFormats) { 604 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 605 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 606 } 607 } 608 609 @foreach (string videoFormat in supportedVideoFormats) { 610 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 611 612 Dictionary<string, object> videoParams = GetVideoParams(asset, "modal"); 613 @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); 614 615 } 616 } 617 </div> 618 619 modalAssetNumber++; 620 } 621 } 622 } 623 <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 624 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 625 <span class="visually-hidden">@Translate("Previous")</span> 626 </button> 627 <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 628 <span class="carousel-control-next-icon" aria-hidden="true"></span> 629 <span class="visually-hidden">@Translate("Next")</span> 630 </button> 631 </div> 632 </div> 633 </div> 634 </div> 635 </div> 636 </div> 637 } else if (Pageview.IsVisualEditorMode) { 638 RatioSettings ratioSettings = GetRatioSettings("desktop"); 639 640 <div class="h-100 @theme"> 641 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 642 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Translate("Missing image")"> 643 </div> 644 </div> 645 } 646 } 647
Eva Solo community
Tag us in your instagram post to be featured in our Eva Solo community.
Use one of the following tags: @evasolo_official, #evasolo, #evatrio or #evasolofurniture
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProducts_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_a7fb577aa3c342d38f2893b012d803d9.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_RelatedProducts_Custom.cshtml:line 186 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb 6 @using Dynamicweb.Core 7 @using Dynamicweb.Core.Encoders 8 @using Dynamicweb.Environment 9 @using Dynamicweb.Ecommerce.ProductCatalog 10 @using Dynamicweb.Ecommerce.Products 11 @using Dynamicweb.Frontend 12 @using Custom.EvaSolo.Ecommerce.Helpers 13 @using Smartpage.EvaSolo.Clerk.Helpers 14 15 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 16 17 @{ 18 ProductViewModel product = null; 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 22 } 23 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 24 { 25 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 26 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 27 28 if (productList?.Products is object) 29 { 30 product = productList.Products[0]; 31 } 32 } 33 34 string title = Model?.Item?.GetRawValueString("Title", Translate("Products")); 35 string campaignValues = Model.Item.GetRawValueString("CampaignBadges", string.Empty); 36 37 //Styling 38 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 39 string subtitleFontSize = Model.Item.GetRawValueString("SubtitleFontSize", "fs-5"); 40 string buttonStyle = Model.Item.GetRawValueString("ButtonStyle", ""); 41 buttonStyle = buttonStyle == "primary" ? " btn-primary" : buttonStyle; 42 buttonStyle = buttonStyle == "secondary" ? " btn-secondary" : buttonStyle; 43 buttonStyle = buttonStyle == "link" ? " btn-link" : buttonStyle; 44 string maxWidth = Model.Item.GetRawValueString("TextReadability", ""); 45 maxWidth = maxWidth == "max-width-on" ? " mw-75ch" : maxWidth; 46 maxWidth = maxWidth == "max-width-off" ? "" : maxWidth; 47 48 string generalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GeneralTheme")) ? " theme " + Model.Item.GetRawValueString("GeneralTheme").Replace(" ", "").Trim().ToLower() : ""; 49 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 50 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 51 52 //Link generation 53 string pageId = !string.IsNullOrEmpty(Model.Item.GetRawValueString("ProductSliderServicePage")) ? Model.Item.GetLink("ProductSliderServicePage").PageId.ToString() : ""; 54 if (string.IsNullOrEmpty(pageId)) 55 { 56 pageId = GetPageIdByNavigationTag("ProductSliderService").ToString(); 57 } 58 59 string url = "/Default.aspx?ID=" + pageId; 60 if (!url.Contains("LayoutTemplate", StringComparison.OrdinalIgnoreCase)) 61 { 62 url += url.Contains("?") ? "&LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml" : "?LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml"; 63 } 64 65 if (Pageview.IsVisualEditorMode) 66 { 67 url += "&VisualEdit=True"; 68 } 69 70 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 71 if (isLazyLoadingForProductInfoEnabled) 72 { 73 url += "&getproductinfo=true"; 74 } 75 76 //Source type 77 string sourceType = Model.Item.GetRawValueString("RelationType", "trending"); 78 IList<string> relateFromGroupIds = new List<string> { }; 79 IList<string> relateFromProductVariantIds = new List<string> { }; 80 IList<string> relateFromProductIds = new List<string> { }; 81 bool hasVariants = false; 82 83 //--- VARIANTS --- 84 if (sourceType == "variants" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateToVariants") is ProductListViewModel productsToRelateToVariants) 85 { 86 foreach (var productSelection in productsToRelateToVariants.Products) 87 { 88 relateFromProductIds.Add(productSelection.Id); 89 } 90 } 91 92 //--- MOST SOLD --- 93 if (sourceType == "most-sold" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToMostSold") is IList<ProductGroupViewModel> groupsToRelateToMostSold) 94 { 95 foreach (var fromGroup in groupsToRelateToMostSold) 96 { 97 relateFromGroupIds.Add(fromGroup.Id); 98 } 99 } 100 101 //--- TRENDING --- 102 if (sourceType == "trending" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToTrending") is IList<ProductGroupViewModel> groupsToRelateToTrending) 103 { 104 foreach (var fromGroup in groupsToRelateToTrending) 105 { 106 relateFromGroupIds.Add(fromGroup.Id); 107 } 108 } 109 110 //--- LATEST --- 111 if (sourceType == "latest" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToLatest") is IList<ProductGroupViewModel> groupsToRelateToLatest) 112 { 113 foreach (var fromGroup in groupsToRelateToLatest) 114 { 115 relateFromGroupIds.Add(fromGroup.Id); 116 } 117 } 118 119 //--- FREQUENTLY BOUGHT --- 120 if (sourceType == "frequently" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo") is ProductListViewModel productsToRelateTo) 121 { 122 foreach (var fromProduct in productsToRelateTo.Products) 123 { 124 relateFromProductIds.Add(fromProduct.Id); 125 } 126 } 127 128 //--- SELECTED PRODUCTS --- 129 if ((sourceType == "selected" || sourceType == "frequently") && Model.Item.GetValue<ProductListViewModel>("Products") is ProductListViewModel products) 130 { 131 hasVariants = products.Products.Any(p => !string.IsNullOrEmpty(p.VariantId)); 132 foreach (var productSelection in products.Products) 133 { 134 if (hasVariants) 135 { 136 if (!string.IsNullOrEmpty(productSelection.VariantId)) 137 { 138 relateFromProductVariantIds.Add($"{productSelection.Id} {productSelection.VariantId}"); 139 } 140 else 141 { 142 relateFromProductVariantIds.Add($"{productSelection.Id}"); 143 } 144 } 145 146 relateFromProductIds.Add($"{productSelection.Id}"); 147 } 148 } 149 150 //--- UPSELL PRODUCTS --- 151 if (sourceType == "upsell-products") 152 { 153 string upSell = product.ProductFields.ContainsKey("Custom_UpSell") ? Converter.ToString(product.ProductFields["Custom_UpSell"].Value) : string.Empty; 154 List<Product> upSellProducts = GetActiveUpSellProducts(upSell); 155 relateFromProductIds = upSellProducts != null ? upSellProducts.Select(p => p.Id).ToList() : new List<string>(); 156 } 157 158 //--- RELATED PRODUCTS --- 159 if (sourceType == "related-products" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo2") is ProductListViewModel selectedRelationProduct) 160 { 161 if (selectedRelationProduct.Products.Any()) 162 { 163 product = selectedRelationProduct.Products.FirstOrDefault(); 164 } 165 166 if (product?.RelatedGroups != null) 167 { 168 foreach (var group in product.RelatedGroups) 169 { 170 foreach (var relatedProduct in group.Products) 171 { 172 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 173 { 174 relateFromProductVariantIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 175 } 176 else 177 { 178 relateFromProductVariantIds.Add($"{relatedProduct.ProductId}"); 179 } 180 } 181 } 182 } 183 } 184 185 //Create group id collection and products id collection strings 186 string groupIds = product is object ? product.PrimaryOrDefaultGroup.Id : string.Join(",", relateFromGroupIds); 187 string productVariantIds = relateFromProductVariantIds.Count > 0 ? string.Join(",", relateFromProductVariantIds) : ""; 188 string productIds = product is object && relateFromProductIds.Count == 0 ? product.Id : string.Join(",", relateFromProductIds); 189 //Set the parameters to the url 190 string linkParameters = ""; 191 linkParameters += sourceType != "related-products" && sourceType != "frequently" && sourceType != "selected" ? "&GroupId=" + groupIds : ""; 192 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType != "most-sold" && sourceType != "trending" && sourceType != "latest" && sourceType != "frequently" && sourceType != "related-products" ? "&MainProductId=" + productIds : ""; 193 linkParameters += !string.IsNullOrEmpty(productVariantIds) && sourceType == "related-products" ? "&ProductVariantId=" + productVariantIds : ""; 194 linkParameters += sourceType == "variants" ? "&IsVariant=true" : ""; 195 linkParameters += sourceType == "latest" ? "&SortBy=Created" : ""; 196 linkParameters += sourceType == "most-sold" ? "&SortBy=OrderCount" : ""; 197 linkParameters += sourceType == "trending" ? "&SortBy=OrderCountGrowth" : ""; 198 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType == "frequently" ? $"&BoughtWithProductIds=[{productIds}]" : ""; 199 var productListPageId = GetPageIdByNavigationTag("Shop"); 200 string link = "/Default.aspx?ID=" + productListPageId + linkParameters; 201 202 // Slider settings (documentation: swiffyslider.com/configuration) 203 string navigationStyle = $"{Model.Item.GetRawValueString("NavigationStyle", "slider-nav-round")}"; 204 string navigationPlacement = $"{Model.Item.GetRawValueString("NavigationPlacement", "slider-nav-on-slides")}"; 205 string indicatorStyle = $"{Model.Item.GetRawValueString("IndicatorStyle", "slider-indicators-hidden")}"; 206 string revealSlides = Model.Item.GetRawValueString("RevealSlides", "no-reveal") == "reveal" ? "slider-item-reveal" : string.Empty; 207 string navigationAlwaysVisible = (Model.Item.GetBoolean("NavigationAlwaysVisible")) ? "slider-nav-visible" : string.Empty; 208 string navigationVisibleOnTouch = (Model.Item.GetBoolean("NavigationVisibleOnTouch")) ? "slider-nav-touch" : string.Empty; 209 string navigationShowScrollbar = (Model.Item.GetBoolean("NavigationShowScrollbar")) ? "slider-nav-scrollbar" : string.Empty; 210 string navigationSmall = (Model.Item.GetBoolean("NavigationSmall")) ? "slider-nav-sm" : string.Empty; 211 string navigationInvertColors = (Model.Item.GetBoolean("NavigationInvertColors")) ? "slider-nav-dark" : string.Empty; 212 string navigationSlideEntirePage = (Model.Item.GetBoolean("NavigationSlideEntirePage")) ? "slider-nav-page" : string.Empty; 213 string navigationNoLoop = (Model.Item.GetBoolean("NavigationNoLoop")) ? "slider-nav-noloop" : string.Empty; 214 string indicatorsOutsideSlider = (Model.Item.GetBoolean("IndicatorsOutsideSlider") && indicatorStyle != string.Empty) ? "slider-indicators-outside" : string.Empty; 215 string indicatorsHighlightActive = (Model.Item.GetBoolean("IndicatorsHighlightActive")) ? "slider-indicators-highlight" : string.Empty; 216 string indicatorsInvertColors = (Model.Item.GetBoolean("IndicatorsInvertedColors")) ? "slider-indicators-dark" : string.Empty; 217 string indicatorsVisibleOnSmallDevices = (Model.Item.GetBoolean("IndicatorsVisibleOnSmallDevices")) ? "slider-indicators-sm" : string.Empty; 218 219 bool productsFound = true; 220 if (sourceType != "clerk" && string.IsNullOrEmpty(groupIds) && string.IsNullOrEmpty(productIds) && string.IsNullOrEmpty(productVariantIds)) //CUSTOM 221 { 222 if (Pageview.IsVisualEditorMode && product != null) //CUSTOM 223 { 224 productIds = product.Id; 225 sourceType = "selected"; 226 } 227 else 228 { 229 productsFound = false; 230 } 231 } 232 else if (sourceType == "upsell-products") 233 { 234 productsFound = relateFromProductIds.Count > 0; 235 } 236 } 237 238 @*//CUSTOM*@ 239 @if (Pageview.IsVisualEditorMode == true && sourceType == "clerk" && PageView.Current().AreaSettings.GetItem("CustomSettings").GetRawValueString("ClerkPublicKey_Custom").IsNullOrEmpty()) 240 { 241 <div class="alert alert-danger" role="alert"> 242 <span>@Translate("Product slider: The Clerk integration requires a public key in the website settings")</span> 243 </div> 244 } 245 @*//--CUSTOM*@ 246 247 @*Container element for the request*@ 248 @if (productsFound) 249 { 250 <form method="post" action="@url" id="RelatedProductsForm_@Model.ID" data-response-target-element="RelatedProducts_@Model.ID" data-preloader="inline" data-update-url="false" class="item_@Model.Item.SystemName.ToLower()"> 251 <input type="hidden" name="ModelID" value="@Model.ID"> 252 <input type="hidden" name="SourceType" value="@sourceType"> 253 254 @*--- SLIDER SETTINGS ---*@ 255 <input type="hidden" name="NavigationStyle" value="@navigationStyle"> 256 <input type="hidden" name="NavigationPlacement" value="@navigationPlacement"> 257 <input type="hidden" name="IndicatorStyle" value="@indicatorStyle"> 258 <input type="hidden" name="RevealSlides" value="@revealSlides"> 259 <input type="hidden" name="NavigationAlwaysVisible" value="@(navigationAlwaysVisible)"> 260 <input type="hidden" name="NavigationVisibleOnTouch" value="@(navigationVisibleOnTouch)"> 261 <input type="hidden" name="NavigationShowScrollbar" value="@(navigationShowScrollbar)"> 262 <input type="hidden" name="NavigationSmall" value="@(navigationSmall)"> 263 <input type="hidden" name="NavigationInvertColors" value="@(navigationInvertColors)"> 264 <input type="hidden" name="NavigationNoLoop" value="@(navigationNoLoop)"> 265 <input type="hidden" name="NavigationSlideEntirePage" value="@(navigationSlideEntirePage)"> 266 <input type="hidden" name="IndicatorsOutsideSlider" value="@(indicatorsOutsideSlider)"> 267 <input type="hidden" name="IndicatorsHighlightActive" value="@(indicatorsHighlightActive)"> 268 <input type="hidden" name="IndicatorsInvertColors" value="@(indicatorsInvertColors)"> 269 <input type="hidden" name="IndicatorsVisibleOnSmallDevices" value="@(indicatorsVisibleOnSmallDevices)"> 270 271 @*--- VARIANTS ---*@ 272 @if (sourceType == "variants") 273 { 274 <input type="hidden" name="isVariant" value="true"> 275 <input type="hidden" name="MainProductID" id="MainProductID_@Model.ID" value="@productIds"> 276 } 277 278 @*--- MOST SOLD ---*@ 279 @if (sourceType == "most-sold") 280 { 281 <input type="hidden" name="SortBy" value="OrderCount"> 282 if (groupIds != "") 283 { 284 <input type="hidden" name="GroupId" value="@groupIds"> 285 } 286 } 287 288 @*--- TRENDING ---*@ 289 @if (sourceType == "trending") 290 { 291 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 292 if (groupIds != "") 293 { 294 <input type="hidden" name="GroupId" value="@groupIds"> 295 } 296 } 297 298 @*--- FREQUENTLY BOUGHT ---*@ 299 @if (sourceType == "frequently" && !string.IsNullOrEmpty(productIds)) 300 { 301 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 302 } 303 @if (sourceType != "frequently" && hasVariants) 304 { 305 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 306 } 307 308 @*--- LATEST ---*@ 309 @if (sourceType == "latest") 310 { 311 <input type="hidden" name="SortBy" value="Created"> 312 <input type="hidden" name="GroupId" value="@groupIds"> 313 } 314 315 @*--- SELECTED PRODUCTS ---*@ 316 @if (sourceType == "selected" && !string.IsNullOrEmpty(productIds) && !hasVariants) 317 { 318 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 319 } 320 @if (sourceType == "selected" && hasVariants) 321 { 322 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 323 } 324 325 @*--- UPSELL PRODUCTS ---*@ 326 @if (sourceType == "upsell-products" && !string.IsNullOrEmpty(productIds) && !hasVariants) 327 { 328 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 329 } 330 @if (sourceType == "upsell-products" && hasVariants) 331 { 332 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 333 } 334 335 @*--- RELATED PRODUCTS ---*@ 336 @if (sourceType == "related-products") 337 { 338 <input type="hidden" name="ProductVariantId" id="MainProductID_@Model.ID" value="@productVariantIds"> 339 } 340 341 @*//CUSTOM*@ 342 @*--- CLERK ---*@ 343 @if (sourceType == "clerk") 344 { 345 // Doc.: https://docs.clerk.io/reference/recommendations-popular 346 347 var clerkEndPoint = Model.Item.GetString("ClerkEndpoint"); 348 var clerkKeywords = Model.Item.GetString("ClerkKeywords"); 349 var clerkQuery = new List<string>(); 350 var clerkFilter = new List<string>(); 351 352 if (!string.IsNullOrEmpty(clerkKeywords)) 353 { 354 switch (clerkEndPoint) 355 { 356 case "recommendations/keywords": 357 clerkQuery.Add("keywords=['" + clerkKeywords + "']"); 358 break; 359 } 360 } 361 362 if (product != null && !string.IsNullOrEmpty(product.Id)) 363 { 364 switch (clerkEndPoint) 365 { 366 case "recommendations/bundle": 367 clerkQuery.Add("product=" + product.Id); 368 break; 369 370 case "recommendations/complementary": 371 case "recommendations/substituting": 372 case "recommendations/most_sold_with": 373 clerkQuery.Add("products=[" + product.Id + "]"); // NOTE: Clerk format id as integer (Data Sync product feed is string) 374 break; 375 } 376 } 377 378 IList<ProductGroupViewModel> clerkGroupsToRelateTo = Model.Item.GetValue<IList<ProductGroupViewModel>>("ClerkGroupsToRelateTo"); 379 switch (clerkEndPoint) 380 { 381 case "recommendations/category/popular": 382 case "recommendations/category/trending": 383 case "recommendations/category/new": 384 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 385 { 386 clerkQuery.Add("category=" + clerkGroupsToRelateTo.First().Id); 387 } 388 else if (Context.Current.Request.HasRequest("GroupID")) 389 { 390 clerkQuery.Add("category=" + Context.Current.Request.GetString("GroupID")); 391 } 392 393 break; 394 395 default: 396 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 397 { 398 clerkFilter.Add("categories in ['" + string.Join("','", clerkGroupsToRelateTo.Select(i => i.Id)) + "']"); 399 } 400 401 break; 402 } 403 404 switch (clerkEndPoint) 405 { 406 case "recommendations/page/substituting": 407 case "recommendations/page/related_products": 408 case "recommendations/page/related_categories": 409 clerkQuery.Add("page=" + Model.PageID); 410 break; 411 } 412 413 IList<ItemViewModel> clerkFilters = Model.Item.GetItems("ClerkFilters"); 414 if (clerkFilters != null) 415 { 416 foreach (var i in clerkFilters) 417 { 418 switch (i.GetString("OperatorType")) 419 { 420 case "=": 421 case "!=": 422 case "contains": 423 case "contains not": 424 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} '{i.GetString("Value")}'"); 425 break; 426 427 case "in": 428 case "not in": 429 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} ['{i.GetString("Value").Replace(",", "','")}']"); 430 break; 431 432 case "= true": 433 case "= false": 434 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")}"); 435 break; 436 437 default: 438 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} {i.GetString("Value")}"); 439 break; 440 } 441 } 442 } 443 444 <input type="hidden" name="ClerkEndpoint" value="@HtmlEncoder.HtmlAttributeEncode(clerkEndPoint)" /> 445 <input type="hidden" name="ClerkQuery" value="@HtmlEncoder.HtmlAttributeEncode(string.Join("&", clerkQuery))" /> 446 <input type="hidden" name="ClerkFilter" value="@HtmlEncoder.HtmlAttributeEncode(string.Join(" and ", clerkFilter))" /> 447 <input type="hidden" name="ClerkLabels" value="@HtmlEncoder.HtmlAttributeEncode(Model.Item.GetString("ClerkLabels"))" /> 448 <input type="hidden" name="ClerkLogArea" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Area.Name} (ID: {Pageview.Area.ID})")" /> 449 <input type="hidden" name="ClerkLogPage" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Page.MenuText} (ID: {Pageview.Page.ID})")" /> 450 <input type="hidden" name="ClerkLogParagraph" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.CurrentParagraph.Header} (ID: {Pageview.CurrentParagraph.ID})")" /> 451 } 452 @*//--CUSTOM*@ 453 454 @* General parameters *@ 455 <input type="hidden" name="Link" value="@link"> 456 <input type="hidden" name="HideTitle" value="@Model.Item.GetString("HideTitle")"> 457 <input type="hidden" name="HidePrices" value="@Model.Item.GetString("CustomHidePrices")" /> @*//CUSTOM*@ 458 459 @if (Model.Item.GetInt32("ProductsCount") != 0) 460 { 461 <input type="hidden" name="PageSize" value="@Model.Item.GetInt32("ProductsCount")"> 462 } 463 <input type="hidden" name="HeadingTitle" id="RelatedProductsTitle_@Model.ID" value="@title"> 464 @if (!string.IsNullOrEmpty(Model.Item.GetString("Subtitle"))) 465 { 466 <input type="hidden" name="Subtitle" value="@Model.Item.GetString("Subtitle")"> 467 } 468 @if (!string.IsNullOrEmpty(Model.Item.GetString("LinkText"))) 469 { 470 <input type="hidden" name="LinkText" value="@Model.Item.GetString("LinkText")"> 471 } 472 @if (!string.IsNullOrEmpty(Model.Item.GetString("ImageAspectRatio"))) 473 { 474 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 475 ratio = ratio != "0" ? ratio : ""; 476 <input type="hidden" name="ImageAspectRatio" value="@ratio"> 477 } 478 @if (!string.IsNullOrEmpty(Model.Item.GetString("Layout"))) 479 { 480 <input type="hidden" name="Layout" value="@Model.Item.GetRawValueString("Layout")"> 481 } 482 @if (titleFontSize != "") 483 { 484 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 485 } 486 @if (subtitleFontSize != "") 487 { 488 <input type="hidden" name="SubtitleFontSize" value="@subtitleFontSize"> 489 } 490 @if (buttonStyle != "") 491 { 492 <input type="hidden" name="ButtonStyle" value="@buttonStyle"> 493 } 494 @if (generalTheme != "") 495 { 496 <input type="hidden" name="GeneralTheme" value="@generalTheme"> 497 } 498 @if (theme != "") 499 { 500 <input type="hidden" name="Theme" value="@theme"> 501 } 502 @if (imageTheme != "") 503 { 504 <input type="hidden" name="ImageTheme" value="@imageTheme"> 505 } 506 @if (!string.IsNullOrEmpty(Model.Item.GetString("ContentPadding"))) 507 { 508 string contentPadding = Model.Item.GetRawValueString("ContentPadding"); 509 <input type="hidden" name="ContentPadding" value="@contentPadding"> 510 } 511 <input type="hidden" name="TextReadability" value="@maxWidth"> 512 <input type="hidden" name="ParentColumnSize" id="ParentColumnSize_@Model.ID" value="12"> 513 514 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 515 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 516 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 517 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 518 519 @if (campaignValues != "") 520 { 521 <input type="hidden" name="CampaignBadgesValues" value="@campaignValues"> 522 } 523 </form> 524 525 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 526 <script> 527 window.addEventListener("load", () => { 528 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 529 }); 530 </script> 531 532 if (Pageview.IsVisualEditorMode) 533 { 534 <div class="alert alert-info" role="alert"> 535 <span>@Translate("Product slider: Edit this column to configure")</span> 536 </div> 537 } 538 539 if (sourceType == "upsell-products") 540 { 541 <div class="w-100 h-100"> 542 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 543 <div id="RelatedProducts_@Model.ID"></div> 544 </div> 545 } 546 else if (sourceType != "related-products") 547 { 548 <div class="w-100 h-100"> 549 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 550 <div id="RelatedProducts_@Model.ID" class="h-100 swift_product_slider_container" data-product-count="@Model.Item.GetInt32("ProductsCount")"></div> @*//CUSTOM*@ 551 </div> 552 } 553 else if (product?.RelatedGroups != null) 554 { 555 @* Create multiple slider containers, if type is Product relation *@ 556 <div class="grid w-100 h-100@(generalTheme)" style="grid-row-gap: 4rem"> 557 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 558 @foreach (var group in product.RelatedGroups) 559 { 560 <div id="RelatedProducts_@(Model.ID)_@group.Id" class="g-col-12 h-100 swift_product_slider_container"></div> 561 } 562 </div> 563 } 564 565 @* Initialize *@ 566 if (sourceType != "related-products") 567 { 568 <script type="module"> 569 if (document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]")) { 570 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]").getAttribute("data-col-size"); 571 } 572 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")).then(function () { 573 setTimeout(function () { 574 const isVisualEditor = @(Converter.ToString(Pageview.IsVisualEditorMode).ToLowerInvariant()); 575 const productSliderContainer = document.querySelector(".swift_product_slider_container"); 576 577 if (productSliderContainer && productSliderContainer.innerHTML !== "") { 578 productSliderContainer.classList.remove("d-none"); 579 } else if (!isVisualEditor && productSliderContainer) { 580 productSliderContainer.closest("[class*=column]").classList.add("d-none"); 581 } 582 custom.UpsellProducts.bindEvents(); 583 }, 150); 584 }); 585 </script> 586 } 587 else if (product?.RelatedGroups != null) 588 { 589 @* Create multiple sliders, if type is Product relation *@ 590 foreach (var group in product.RelatedGroups) 591 { 592 IList<string> fromProductIds = new List<string> { }; 593 594 foreach (var relatedProduct in group.Products) 595 { 596 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 597 { 598 fromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 599 } 600 else 601 { 602 fromProductIds.Add($"{relatedProduct.ProductId}"); 603 } 604 } 605 606 <script type="module"> 607 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@(Model.ID)_@group.Id").closest("[data-col-size]").getAttribute("data-col-size"); 608 document.querySelector("#MainProductID_@Model.ID").value = "@string.Join(",", fromProductIds)"; 609 document.querySelector("#RelatedProductsTitle_@Model.ID").value = "@group.Name"; 610 document.querySelector("#RelatedProductsForm_@Model.ID").setAttribute("data-response-target-element", "RelatedProducts_@(Model.ID)_@group.Id"); 611 612 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")); 613 </script> 614 } 615 } 616 } 617 618 @functions { 619 List<Product> GetActiveUpSellProducts(string productNumbers) 620 { 621 if (!string.IsNullOrEmpty(productNumbers)) 622 { 623 List<Product> upSellProducts = Product.GetProductsByProductIDs(productNumbers.Split(',').Select(x => x.Trim()).ToArray(), true).ToList(); 624 625 if (upSellProducts != null && upSellProducts.Any()) 626 { 627 List<Product> activeUpSellProducts = new List<Product>(); 628 629 foreach (Product upSellProduct in upSellProducts) 630 { 631 if (upSellProduct.Active && GetStock(upSellProduct) > 0 && !upSellProduct.InActive()) 632 { 633 activeUpSellProducts.Add(upSellProduct); 634 } 635 } 636 637 return activeUpSellProducts; 638 } 639 } 640 641 return null; 642 } 643 644 double GetStock(Product product) 645 { 646 double productStock = product.Stock; 647 648 if (Converter.ToBoolean(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_FurnitureWarehouse"))) 649 { 650 productStock = Converter.ToDouble(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_AlternativeStock")); 651 } 652 653 return productStock; 654 } 655 } 656
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProducts_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_a7fb577aa3c342d38f2893b012d803d9.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_RelatedProducts_Custom.cshtml:line 186 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb 6 @using Dynamicweb.Core 7 @using Dynamicweb.Core.Encoders 8 @using Dynamicweb.Environment 9 @using Dynamicweb.Ecommerce.ProductCatalog 10 @using Dynamicweb.Ecommerce.Products 11 @using Dynamicweb.Frontend 12 @using Custom.EvaSolo.Ecommerce.Helpers 13 @using Smartpage.EvaSolo.Clerk.Helpers 14 15 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 16 17 @{ 18 ProductViewModel product = null; 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 22 } 23 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 24 { 25 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 26 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 27 28 if (productList?.Products is object) 29 { 30 product = productList.Products[0]; 31 } 32 } 33 34 string title = Model?.Item?.GetRawValueString("Title", Translate("Products")); 35 string campaignValues = Model.Item.GetRawValueString("CampaignBadges", string.Empty); 36 37 //Styling 38 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 39 string subtitleFontSize = Model.Item.GetRawValueString("SubtitleFontSize", "fs-5"); 40 string buttonStyle = Model.Item.GetRawValueString("ButtonStyle", ""); 41 buttonStyle = buttonStyle == "primary" ? " btn-primary" : buttonStyle; 42 buttonStyle = buttonStyle == "secondary" ? " btn-secondary" : buttonStyle; 43 buttonStyle = buttonStyle == "link" ? " btn-link" : buttonStyle; 44 string maxWidth = Model.Item.GetRawValueString("TextReadability", ""); 45 maxWidth = maxWidth == "max-width-on" ? " mw-75ch" : maxWidth; 46 maxWidth = maxWidth == "max-width-off" ? "" : maxWidth; 47 48 string generalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GeneralTheme")) ? " theme " + Model.Item.GetRawValueString("GeneralTheme").Replace(" ", "").Trim().ToLower() : ""; 49 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 50 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 51 52 //Link generation 53 string pageId = !string.IsNullOrEmpty(Model.Item.GetRawValueString("ProductSliderServicePage")) ? Model.Item.GetLink("ProductSliderServicePage").PageId.ToString() : ""; 54 if (string.IsNullOrEmpty(pageId)) 55 { 56 pageId = GetPageIdByNavigationTag("ProductSliderService").ToString(); 57 } 58 59 string url = "/Default.aspx?ID=" + pageId; 60 if (!url.Contains("LayoutTemplate", StringComparison.OrdinalIgnoreCase)) 61 { 62 url += url.Contains("?") ? "&LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml" : "?LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml"; 63 } 64 65 if (Pageview.IsVisualEditorMode) 66 { 67 url += "&VisualEdit=True"; 68 } 69 70 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 71 if (isLazyLoadingForProductInfoEnabled) 72 { 73 url += "&getproductinfo=true"; 74 } 75 76 //Source type 77 string sourceType = Model.Item.GetRawValueString("RelationType", "trending"); 78 IList<string> relateFromGroupIds = new List<string> { }; 79 IList<string> relateFromProductVariantIds = new List<string> { }; 80 IList<string> relateFromProductIds = new List<string> { }; 81 bool hasVariants = false; 82 83 //--- VARIANTS --- 84 if (sourceType == "variants" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateToVariants") is ProductListViewModel productsToRelateToVariants) 85 { 86 foreach (var productSelection in productsToRelateToVariants.Products) 87 { 88 relateFromProductIds.Add(productSelection.Id); 89 } 90 } 91 92 //--- MOST SOLD --- 93 if (sourceType == "most-sold" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToMostSold") is IList<ProductGroupViewModel> groupsToRelateToMostSold) 94 { 95 foreach (var fromGroup in groupsToRelateToMostSold) 96 { 97 relateFromGroupIds.Add(fromGroup.Id); 98 } 99 } 100 101 //--- TRENDING --- 102 if (sourceType == "trending" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToTrending") is IList<ProductGroupViewModel> groupsToRelateToTrending) 103 { 104 foreach (var fromGroup in groupsToRelateToTrending) 105 { 106 relateFromGroupIds.Add(fromGroup.Id); 107 } 108 } 109 110 //--- LATEST --- 111 if (sourceType == "latest" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToLatest") is IList<ProductGroupViewModel> groupsToRelateToLatest) 112 { 113 foreach (var fromGroup in groupsToRelateToLatest) 114 { 115 relateFromGroupIds.Add(fromGroup.Id); 116 } 117 } 118 119 //--- FREQUENTLY BOUGHT --- 120 if (sourceType == "frequently" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo") is ProductListViewModel productsToRelateTo) 121 { 122 foreach (var fromProduct in productsToRelateTo.Products) 123 { 124 relateFromProductIds.Add(fromProduct.Id); 125 } 126 } 127 128 //--- SELECTED PRODUCTS --- 129 if ((sourceType == "selected" || sourceType == "frequently") && Model.Item.GetValue<ProductListViewModel>("Products") is ProductListViewModel products) 130 { 131 hasVariants = products.Products.Any(p => !string.IsNullOrEmpty(p.VariantId)); 132 foreach (var productSelection in products.Products) 133 { 134 if (hasVariants) 135 { 136 if (!string.IsNullOrEmpty(productSelection.VariantId)) 137 { 138 relateFromProductVariantIds.Add($"{productSelection.Id} {productSelection.VariantId}"); 139 } 140 else 141 { 142 relateFromProductVariantIds.Add($"{productSelection.Id}"); 143 } 144 } 145 146 relateFromProductIds.Add($"{productSelection.Id}"); 147 } 148 } 149 150 //--- UPSELL PRODUCTS --- 151 if (sourceType == "upsell-products") 152 { 153 string upSell = product.ProductFields.ContainsKey("Custom_UpSell") ? Converter.ToString(product.ProductFields["Custom_UpSell"].Value) : string.Empty; 154 List<Product> upSellProducts = GetActiveUpSellProducts(upSell); 155 relateFromProductIds = upSellProducts != null ? upSellProducts.Select(p => p.Id).ToList() : new List<string>(); 156 } 157 158 //--- RELATED PRODUCTS --- 159 if (sourceType == "related-products" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo2") is ProductListViewModel selectedRelationProduct) 160 { 161 if (selectedRelationProduct.Products.Any()) 162 { 163 product = selectedRelationProduct.Products.FirstOrDefault(); 164 } 165 166 if (product?.RelatedGroups != null) 167 { 168 foreach (var group in product.RelatedGroups) 169 { 170 foreach (var relatedProduct in group.Products) 171 { 172 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 173 { 174 relateFromProductVariantIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 175 } 176 else 177 { 178 relateFromProductVariantIds.Add($"{relatedProduct.ProductId}"); 179 } 180 } 181 } 182 } 183 } 184 185 //Create group id collection and products id collection strings 186 string groupIds = product is object ? product.PrimaryOrDefaultGroup.Id : string.Join(",", relateFromGroupIds); 187 string productVariantIds = relateFromProductVariantIds.Count > 0 ? string.Join(",", relateFromProductVariantIds) : ""; 188 string productIds = product is object && relateFromProductIds.Count == 0 ? product.Id : string.Join(",", relateFromProductIds); 189 //Set the parameters to the url 190 string linkParameters = ""; 191 linkParameters += sourceType != "related-products" && sourceType != "frequently" && sourceType != "selected" ? "&GroupId=" + groupIds : ""; 192 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType != "most-sold" && sourceType != "trending" && sourceType != "latest" && sourceType != "frequently" && sourceType != "related-products" ? "&MainProductId=" + productIds : ""; 193 linkParameters += !string.IsNullOrEmpty(productVariantIds) && sourceType == "related-products" ? "&ProductVariantId=" + productVariantIds : ""; 194 linkParameters += sourceType == "variants" ? "&IsVariant=true" : ""; 195 linkParameters += sourceType == "latest" ? "&SortBy=Created" : ""; 196 linkParameters += sourceType == "most-sold" ? "&SortBy=OrderCount" : ""; 197 linkParameters += sourceType == "trending" ? "&SortBy=OrderCountGrowth" : ""; 198 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType == "frequently" ? $"&BoughtWithProductIds=[{productIds}]" : ""; 199 var productListPageId = GetPageIdByNavigationTag("Shop"); 200 string link = "/Default.aspx?ID=" + productListPageId + linkParameters; 201 202 // Slider settings (documentation: swiffyslider.com/configuration) 203 string navigationStyle = $"{Model.Item.GetRawValueString("NavigationStyle", "slider-nav-round")}"; 204 string navigationPlacement = $"{Model.Item.GetRawValueString("NavigationPlacement", "slider-nav-on-slides")}"; 205 string indicatorStyle = $"{Model.Item.GetRawValueString("IndicatorStyle", "slider-indicators-hidden")}"; 206 string revealSlides = Model.Item.GetRawValueString("RevealSlides", "no-reveal") == "reveal" ? "slider-item-reveal" : string.Empty; 207 string navigationAlwaysVisible = (Model.Item.GetBoolean("NavigationAlwaysVisible")) ? "slider-nav-visible" : string.Empty; 208 string navigationVisibleOnTouch = (Model.Item.GetBoolean("NavigationVisibleOnTouch")) ? "slider-nav-touch" : string.Empty; 209 string navigationShowScrollbar = (Model.Item.GetBoolean("NavigationShowScrollbar")) ? "slider-nav-scrollbar" : string.Empty; 210 string navigationSmall = (Model.Item.GetBoolean("NavigationSmall")) ? "slider-nav-sm" : string.Empty; 211 string navigationInvertColors = (Model.Item.GetBoolean("NavigationInvertColors")) ? "slider-nav-dark" : string.Empty; 212 string navigationSlideEntirePage = (Model.Item.GetBoolean("NavigationSlideEntirePage")) ? "slider-nav-page" : string.Empty; 213 string navigationNoLoop = (Model.Item.GetBoolean("NavigationNoLoop")) ? "slider-nav-noloop" : string.Empty; 214 string indicatorsOutsideSlider = (Model.Item.GetBoolean("IndicatorsOutsideSlider") && indicatorStyle != string.Empty) ? "slider-indicators-outside" : string.Empty; 215 string indicatorsHighlightActive = (Model.Item.GetBoolean("IndicatorsHighlightActive")) ? "slider-indicators-highlight" : string.Empty; 216 string indicatorsInvertColors = (Model.Item.GetBoolean("IndicatorsInvertedColors")) ? "slider-indicators-dark" : string.Empty; 217 string indicatorsVisibleOnSmallDevices = (Model.Item.GetBoolean("IndicatorsVisibleOnSmallDevices")) ? "slider-indicators-sm" : string.Empty; 218 219 bool productsFound = true; 220 if (sourceType != "clerk" && string.IsNullOrEmpty(groupIds) && string.IsNullOrEmpty(productIds) && string.IsNullOrEmpty(productVariantIds)) //CUSTOM 221 { 222 if (Pageview.IsVisualEditorMode && product != null) //CUSTOM 223 { 224 productIds = product.Id; 225 sourceType = "selected"; 226 } 227 else 228 { 229 productsFound = false; 230 } 231 } 232 else if (sourceType == "upsell-products") 233 { 234 productsFound = relateFromProductIds.Count > 0; 235 } 236 } 237 238 @*//CUSTOM*@ 239 @if (Pageview.IsVisualEditorMode == true && sourceType == "clerk" && PageView.Current().AreaSettings.GetItem("CustomSettings").GetRawValueString("ClerkPublicKey_Custom").IsNullOrEmpty()) 240 { 241 <div class="alert alert-danger" role="alert"> 242 <span>@Translate("Product slider: The Clerk integration requires a public key in the website settings")</span> 243 </div> 244 } 245 @*//--CUSTOM*@ 246 247 @*Container element for the request*@ 248 @if (productsFound) 249 { 250 <form method="post" action="@url" id="RelatedProductsForm_@Model.ID" data-response-target-element="RelatedProducts_@Model.ID" data-preloader="inline" data-update-url="false" class="item_@Model.Item.SystemName.ToLower()"> 251 <input type="hidden" name="ModelID" value="@Model.ID"> 252 <input type="hidden" name="SourceType" value="@sourceType"> 253 254 @*--- SLIDER SETTINGS ---*@ 255 <input type="hidden" name="NavigationStyle" value="@navigationStyle"> 256 <input type="hidden" name="NavigationPlacement" value="@navigationPlacement"> 257 <input type="hidden" name="IndicatorStyle" value="@indicatorStyle"> 258 <input type="hidden" name="RevealSlides" value="@revealSlides"> 259 <input type="hidden" name="NavigationAlwaysVisible" value="@(navigationAlwaysVisible)"> 260 <input type="hidden" name="NavigationVisibleOnTouch" value="@(navigationVisibleOnTouch)"> 261 <input type="hidden" name="NavigationShowScrollbar" value="@(navigationShowScrollbar)"> 262 <input type="hidden" name="NavigationSmall" value="@(navigationSmall)"> 263 <input type="hidden" name="NavigationInvertColors" value="@(navigationInvertColors)"> 264 <input type="hidden" name="NavigationNoLoop" value="@(navigationNoLoop)"> 265 <input type="hidden" name="NavigationSlideEntirePage" value="@(navigationSlideEntirePage)"> 266 <input type="hidden" name="IndicatorsOutsideSlider" value="@(indicatorsOutsideSlider)"> 267 <input type="hidden" name="IndicatorsHighlightActive" value="@(indicatorsHighlightActive)"> 268 <input type="hidden" name="IndicatorsInvertColors" value="@(indicatorsInvertColors)"> 269 <input type="hidden" name="IndicatorsVisibleOnSmallDevices" value="@(indicatorsVisibleOnSmallDevices)"> 270 271 @*--- VARIANTS ---*@ 272 @if (sourceType == "variants") 273 { 274 <input type="hidden" name="isVariant" value="true"> 275 <input type="hidden" name="MainProductID" id="MainProductID_@Model.ID" value="@productIds"> 276 } 277 278 @*--- MOST SOLD ---*@ 279 @if (sourceType == "most-sold") 280 { 281 <input type="hidden" name="SortBy" value="OrderCount"> 282 if (groupIds != "") 283 { 284 <input type="hidden" name="GroupId" value="@groupIds"> 285 } 286 } 287 288 @*--- TRENDING ---*@ 289 @if (sourceType == "trending") 290 { 291 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 292 if (groupIds != "") 293 { 294 <input type="hidden" name="GroupId" value="@groupIds"> 295 } 296 } 297 298 @*--- FREQUENTLY BOUGHT ---*@ 299 @if (sourceType == "frequently" && !string.IsNullOrEmpty(productIds)) 300 { 301 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 302 } 303 @if (sourceType != "frequently" && hasVariants) 304 { 305 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 306 } 307 308 @*--- LATEST ---*@ 309 @if (sourceType == "latest") 310 { 311 <input type="hidden" name="SortBy" value="Created"> 312 <input type="hidden" name="GroupId" value="@groupIds"> 313 } 314 315 @*--- SELECTED PRODUCTS ---*@ 316 @if (sourceType == "selected" && !string.IsNullOrEmpty(productIds) && !hasVariants) 317 { 318 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 319 } 320 @if (sourceType == "selected" && hasVariants) 321 { 322 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 323 } 324 325 @*--- UPSELL PRODUCTS ---*@ 326 @if (sourceType == "upsell-products" && !string.IsNullOrEmpty(productIds) && !hasVariants) 327 { 328 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 329 } 330 @if (sourceType == "upsell-products" && hasVariants) 331 { 332 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 333 } 334 335 @*--- RELATED PRODUCTS ---*@ 336 @if (sourceType == "related-products") 337 { 338 <input type="hidden" name="ProductVariantId" id="MainProductID_@Model.ID" value="@productVariantIds"> 339 } 340 341 @*//CUSTOM*@ 342 @*--- CLERK ---*@ 343 @if (sourceType == "clerk") 344 { 345 // Doc.: https://docs.clerk.io/reference/recommendations-popular 346 347 var clerkEndPoint = Model.Item.GetString("ClerkEndpoint"); 348 var clerkKeywords = Model.Item.GetString("ClerkKeywords"); 349 var clerkQuery = new List<string>(); 350 var clerkFilter = new List<string>(); 351 352 if (!string.IsNullOrEmpty(clerkKeywords)) 353 { 354 switch (clerkEndPoint) 355 { 356 case "recommendations/keywords": 357 clerkQuery.Add("keywords=['" + clerkKeywords + "']"); 358 break; 359 } 360 } 361 362 if (product != null && !string.IsNullOrEmpty(product.Id)) 363 { 364 switch (clerkEndPoint) 365 { 366 case "recommendations/bundle": 367 clerkQuery.Add("product=" + product.Id); 368 break; 369 370 case "recommendations/complementary": 371 case "recommendations/substituting": 372 case "recommendations/most_sold_with": 373 clerkQuery.Add("products=[" + product.Id + "]"); // NOTE: Clerk format id as integer (Data Sync product feed is string) 374 break; 375 } 376 } 377 378 IList<ProductGroupViewModel> clerkGroupsToRelateTo = Model.Item.GetValue<IList<ProductGroupViewModel>>("ClerkGroupsToRelateTo"); 379 switch (clerkEndPoint) 380 { 381 case "recommendations/category/popular": 382 case "recommendations/category/trending": 383 case "recommendations/category/new": 384 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 385 { 386 clerkQuery.Add("category=" + clerkGroupsToRelateTo.First().Id); 387 } 388 else if (Context.Current.Request.HasRequest("GroupID")) 389 { 390 clerkQuery.Add("category=" + Context.Current.Request.GetString("GroupID")); 391 } 392 393 break; 394 395 default: 396 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 397 { 398 clerkFilter.Add("categories in ['" + string.Join("','", clerkGroupsToRelateTo.Select(i => i.Id)) + "']"); 399 } 400 401 break; 402 } 403 404 switch (clerkEndPoint) 405 { 406 case "recommendations/page/substituting": 407 case "recommendations/page/related_products": 408 case "recommendations/page/related_categories": 409 clerkQuery.Add("page=" + Model.PageID); 410 break; 411 } 412 413 IList<ItemViewModel> clerkFilters = Model.Item.GetItems("ClerkFilters"); 414 if (clerkFilters != null) 415 { 416 foreach (var i in clerkFilters) 417 { 418 switch (i.GetString("OperatorType")) 419 { 420 case "=": 421 case "!=": 422 case "contains": 423 case "contains not": 424 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} '{i.GetString("Value")}'"); 425 break; 426 427 case "in": 428 case "not in": 429 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} ['{i.GetString("Value").Replace(",", "','")}']"); 430 break; 431 432 case "= true": 433 case "= false": 434 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")}"); 435 break; 436 437 default: 438 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} {i.GetString("Value")}"); 439 break; 440 } 441 } 442 } 443 444 <input type="hidden" name="ClerkEndpoint" value="@HtmlEncoder.HtmlAttributeEncode(clerkEndPoint)" /> 445 <input type="hidden" name="ClerkQuery" value="@HtmlEncoder.HtmlAttributeEncode(string.Join("&", clerkQuery))" /> 446 <input type="hidden" name="ClerkFilter" value="@HtmlEncoder.HtmlAttributeEncode(string.Join(" and ", clerkFilter))" /> 447 <input type="hidden" name="ClerkLabels" value="@HtmlEncoder.HtmlAttributeEncode(Model.Item.GetString("ClerkLabels"))" /> 448 <input type="hidden" name="ClerkLogArea" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Area.Name} (ID: {Pageview.Area.ID})")" /> 449 <input type="hidden" name="ClerkLogPage" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Page.MenuText} (ID: {Pageview.Page.ID})")" /> 450 <input type="hidden" name="ClerkLogParagraph" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.CurrentParagraph.Header} (ID: {Pageview.CurrentParagraph.ID})")" /> 451 } 452 @*//--CUSTOM*@ 453 454 @* General parameters *@ 455 <input type="hidden" name="Link" value="@link"> 456 <input type="hidden" name="HideTitle" value="@Model.Item.GetString("HideTitle")"> 457 <input type="hidden" name="HidePrices" value="@Model.Item.GetString("CustomHidePrices")" /> @*//CUSTOM*@ 458 459 @if (Model.Item.GetInt32("ProductsCount") != 0) 460 { 461 <input type="hidden" name="PageSize" value="@Model.Item.GetInt32("ProductsCount")"> 462 } 463 <input type="hidden" name="HeadingTitle" id="RelatedProductsTitle_@Model.ID" value="@title"> 464 @if (!string.IsNullOrEmpty(Model.Item.GetString("Subtitle"))) 465 { 466 <input type="hidden" name="Subtitle" value="@Model.Item.GetString("Subtitle")"> 467 } 468 @if (!string.IsNullOrEmpty(Model.Item.GetString("LinkText"))) 469 { 470 <input type="hidden" name="LinkText" value="@Model.Item.GetString("LinkText")"> 471 } 472 @if (!string.IsNullOrEmpty(Model.Item.GetString("ImageAspectRatio"))) 473 { 474 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 475 ratio = ratio != "0" ? ratio : ""; 476 <input type="hidden" name="ImageAspectRatio" value="@ratio"> 477 } 478 @if (!string.IsNullOrEmpty(Model.Item.GetString("Layout"))) 479 { 480 <input type="hidden" name="Layout" value="@Model.Item.GetRawValueString("Layout")"> 481 } 482 @if (titleFontSize != "") 483 { 484 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 485 } 486 @if (subtitleFontSize != "") 487 { 488 <input type="hidden" name="SubtitleFontSize" value="@subtitleFontSize"> 489 } 490 @if (buttonStyle != "") 491 { 492 <input type="hidden" name="ButtonStyle" value="@buttonStyle"> 493 } 494 @if (generalTheme != "") 495 { 496 <input type="hidden" name="GeneralTheme" value="@generalTheme"> 497 } 498 @if (theme != "") 499 { 500 <input type="hidden" name="Theme" value="@theme"> 501 } 502 @if (imageTheme != "") 503 { 504 <input type="hidden" name="ImageTheme" value="@imageTheme"> 505 } 506 @if (!string.IsNullOrEmpty(Model.Item.GetString("ContentPadding"))) 507 { 508 string contentPadding = Model.Item.GetRawValueString("ContentPadding"); 509 <input type="hidden" name="ContentPadding" value="@contentPadding"> 510 } 511 <input type="hidden" name="TextReadability" value="@maxWidth"> 512 <input type="hidden" name="ParentColumnSize" id="ParentColumnSize_@Model.ID" value="12"> 513 514 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 515 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 516 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 517 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 518 519 @if (campaignValues != "") 520 { 521 <input type="hidden" name="CampaignBadgesValues" value="@campaignValues"> 522 } 523 </form> 524 525 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 526 <script> 527 window.addEventListener("load", () => { 528 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 529 }); 530 </script> 531 532 if (Pageview.IsVisualEditorMode) 533 { 534 <div class="alert alert-info" role="alert"> 535 <span>@Translate("Product slider: Edit this column to configure")</span> 536 </div> 537 } 538 539 if (sourceType == "upsell-products") 540 { 541 <div class="w-100 h-100"> 542 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 543 <div id="RelatedProducts_@Model.ID"></div> 544 </div> 545 } 546 else if (sourceType != "related-products") 547 { 548 <div class="w-100 h-100"> 549 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 550 <div id="RelatedProducts_@Model.ID" class="h-100 swift_product_slider_container" data-product-count="@Model.Item.GetInt32("ProductsCount")"></div> @*//CUSTOM*@ 551 </div> 552 } 553 else if (product?.RelatedGroups != null) 554 { 555 @* Create multiple slider containers, if type is Product relation *@ 556 <div class="grid w-100 h-100@(generalTheme)" style="grid-row-gap: 4rem"> 557 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 558 @foreach (var group in product.RelatedGroups) 559 { 560 <div id="RelatedProducts_@(Model.ID)_@group.Id" class="g-col-12 h-100 swift_product_slider_container"></div> 561 } 562 </div> 563 } 564 565 @* Initialize *@ 566 if (sourceType != "related-products") 567 { 568 <script type="module"> 569 if (document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]")) { 570 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]").getAttribute("data-col-size"); 571 } 572 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")).then(function () { 573 setTimeout(function () { 574 const isVisualEditor = @(Converter.ToString(Pageview.IsVisualEditorMode).ToLowerInvariant()); 575 const productSliderContainer = document.querySelector(".swift_product_slider_container"); 576 577 if (productSliderContainer && productSliderContainer.innerHTML !== "") { 578 productSliderContainer.classList.remove("d-none"); 579 } else if (!isVisualEditor && productSliderContainer) { 580 productSliderContainer.closest("[class*=column]").classList.add("d-none"); 581 } 582 custom.UpsellProducts.bindEvents(); 583 }, 150); 584 }); 585 </script> 586 } 587 else if (product?.RelatedGroups != null) 588 { 589 @* Create multiple sliders, if type is Product relation *@ 590 foreach (var group in product.RelatedGroups) 591 { 592 IList<string> fromProductIds = new List<string> { }; 593 594 foreach (var relatedProduct in group.Products) 595 { 596 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 597 { 598 fromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 599 } 600 else 601 { 602 fromProductIds.Add($"{relatedProduct.ProductId}"); 603 } 604 } 605 606 <script type="module"> 607 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@(Model.ID)_@group.Id").closest("[data-col-size]").getAttribute("data-col-size"); 608 document.querySelector("#MainProductID_@Model.ID").value = "@string.Join(",", fromProductIds)"; 609 document.querySelector("#RelatedProductsTitle_@Model.ID").value = "@group.Name"; 610 document.querySelector("#RelatedProductsForm_@Model.ID").setAttribute("data-response-target-element", "RelatedProducts_@(Model.ID)_@group.Id"); 611 612 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")); 613 </script> 614 } 615 } 616 } 617 618 @functions { 619 List<Product> GetActiveUpSellProducts(string productNumbers) 620 { 621 if (!string.IsNullOrEmpty(productNumbers)) 622 { 623 List<Product> upSellProducts = Product.GetProductsByProductIDs(productNumbers.Split(',').Select(x => x.Trim()).ToArray(), true).ToList(); 624 625 if (upSellProducts != null && upSellProducts.Any()) 626 { 627 List<Product> activeUpSellProducts = new List<Product>(); 628 629 foreach (Product upSellProduct in upSellProducts) 630 { 631 if (upSellProduct.Active && GetStock(upSellProduct) > 0 && !upSellProduct.InActive()) 632 { 633 activeUpSellProducts.Add(upSellProduct); 634 } 635 } 636 637 return activeUpSellProducts; 638 } 639 } 640 641 return null; 642 } 643 644 double GetStock(Product product) 645 { 646 double productStock = product.Stock; 647 648 if (Converter.ToBoolean(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_FurnitureWarehouse"))) 649 { 650 productStock = Converter.ToDouble(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_AlternativeStock")); 651 } 652 653 return productStock; 654 } 655 } 656
Related content
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProducts_Custom.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_a7fb577aa3c342d38f2893b012d803d9.Execute() in E:\Solutions\Eva Solo\Files\Templates\Designs\Swift\Paragraph\Swift_RelatedProducts_Custom.cshtml:line 153 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 System 3 @using System.Collections.Generic 4 @using System.Linq 5 @using Dynamicweb 6 @using Dynamicweb.Core 7 @using Dynamicweb.Core.Encoders 8 @using Dynamicweb.Environment 9 @using Dynamicweb.Ecommerce.ProductCatalog 10 @using Dynamicweb.Ecommerce.Products 11 @using Dynamicweb.Frontend 12 @using Custom.EvaSolo.Ecommerce.Helpers 13 @using Smartpage.EvaSolo.Clerk.Helpers 14 15 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 16 17 @{ 18 ProductViewModel product = null; 19 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 20 { 21 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 22 } 23 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 24 { 25 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 26 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 27 28 if (productList?.Products is object) 29 { 30 product = productList.Products[0]; 31 } 32 } 33 34 string title = Model?.Item?.GetRawValueString("Title", Translate("Products")); 35 string campaignValues = Model.Item.GetRawValueString("CampaignBadges", string.Empty); 36 37 //Styling 38 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 39 string subtitleFontSize = Model.Item.GetRawValueString("SubtitleFontSize", "fs-5"); 40 string buttonStyle = Model.Item.GetRawValueString("ButtonStyle", ""); 41 buttonStyle = buttonStyle == "primary" ? " btn-primary" : buttonStyle; 42 buttonStyle = buttonStyle == "secondary" ? " btn-secondary" : buttonStyle; 43 buttonStyle = buttonStyle == "link" ? " btn-link" : buttonStyle; 44 string maxWidth = Model.Item.GetRawValueString("TextReadability", ""); 45 maxWidth = maxWidth == "max-width-on" ? " mw-75ch" : maxWidth; 46 maxWidth = maxWidth == "max-width-off" ? "" : maxWidth; 47 48 string generalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GeneralTheme")) ? " theme " + Model.Item.GetRawValueString("GeneralTheme").Replace(" ", "").Trim().ToLower() : ""; 49 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 50 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 51 52 //Link generation 53 string pageId = !string.IsNullOrEmpty(Model.Item.GetRawValueString("ProductSliderServicePage")) ? Model.Item.GetLink("ProductSliderServicePage").PageId.ToString() : ""; 54 if (string.IsNullOrEmpty(pageId)) 55 { 56 pageId = GetPageIdByNavigationTag("ProductSliderService").ToString(); 57 } 58 59 string url = "/Default.aspx?ID=" + pageId; 60 if (!url.Contains("LayoutTemplate", StringComparison.OrdinalIgnoreCase)) 61 { 62 url += url.Contains("?") ? "&LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml" : "?LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml"; 63 } 64 65 if (Pageview.IsVisualEditorMode) 66 { 67 url += "&VisualEdit=True"; 68 } 69 70 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 71 if (isLazyLoadingForProductInfoEnabled) 72 { 73 url += "&getproductinfo=true"; 74 } 75 76 //Source type 77 string sourceType = Model.Item.GetRawValueString("RelationType", "trending"); 78 IList<string> relateFromGroupIds = new List<string> { }; 79 IList<string> relateFromProductVariantIds = new List<string> { }; 80 IList<string> relateFromProductIds = new List<string> { }; 81 bool hasVariants = false; 82 83 //--- VARIANTS --- 84 if (sourceType == "variants" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateToVariants") is ProductListViewModel productsToRelateToVariants) 85 { 86 foreach (var productSelection in productsToRelateToVariants.Products) 87 { 88 relateFromProductIds.Add(productSelection.Id); 89 } 90 } 91 92 //--- MOST SOLD --- 93 if (sourceType == "most-sold" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToMostSold") is IList<ProductGroupViewModel> groupsToRelateToMostSold) 94 { 95 foreach (var fromGroup in groupsToRelateToMostSold) 96 { 97 relateFromGroupIds.Add(fromGroup.Id); 98 } 99 } 100 101 //--- TRENDING --- 102 if (sourceType == "trending" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToTrending") is IList<ProductGroupViewModel> groupsToRelateToTrending) 103 { 104 foreach (var fromGroup in groupsToRelateToTrending) 105 { 106 relateFromGroupIds.Add(fromGroup.Id); 107 } 108 } 109 110 //--- LATEST --- 111 if (sourceType == "latest" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToLatest") is IList<ProductGroupViewModel> groupsToRelateToLatest) 112 { 113 foreach (var fromGroup in groupsToRelateToLatest) 114 { 115 relateFromGroupIds.Add(fromGroup.Id); 116 } 117 } 118 119 //--- FREQUENTLY BOUGHT --- 120 if (sourceType == "frequently" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo") is ProductListViewModel productsToRelateTo) 121 { 122 foreach (var fromProduct in productsToRelateTo.Products) 123 { 124 relateFromProductIds.Add(fromProduct.Id); 125 } 126 } 127 128 //--- SELECTED PRODUCTS --- 129 if ((sourceType == "selected" || sourceType == "frequently") && Model.Item.GetValue<ProductListViewModel>("Products") is ProductListViewModel products) 130 { 131 hasVariants = products.Products.Any(p => !string.IsNullOrEmpty(p.VariantId)); 132 foreach (var productSelection in products.Products) 133 { 134 if (hasVariants) 135 { 136 if (!string.IsNullOrEmpty(productSelection.VariantId)) 137 { 138 relateFromProductVariantIds.Add($"{productSelection.Id} {productSelection.VariantId}"); 139 } 140 else 141 { 142 relateFromProductVariantIds.Add($"{productSelection.Id}"); 143 } 144 } 145 146 relateFromProductIds.Add($"{productSelection.Id}"); 147 } 148 } 149 150 //--- UPSELL PRODUCTS --- 151 if (sourceType == "upsell-products") 152 { 153 string upSell = product.ProductFields.ContainsKey("Custom_UpSell") ? Converter.ToString(product.ProductFields["Custom_UpSell"].Value) : string.Empty; 154 List<Product> upSellProducts = GetActiveUpSellProducts(upSell); 155 relateFromProductIds = upSellProducts != null ? upSellProducts.Select(p => p.Id).ToList() : new List<string>(); 156 } 157 158 //--- RELATED PRODUCTS --- 159 if (sourceType == "related-products" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo2") is ProductListViewModel selectedRelationProduct) 160 { 161 if (selectedRelationProduct.Products.Any()) 162 { 163 product = selectedRelationProduct.Products.FirstOrDefault(); 164 } 165 166 if (product?.RelatedGroups != null) 167 { 168 foreach (var group in product.RelatedGroups) 169 { 170 foreach (var relatedProduct in group.Products) 171 { 172 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 173 { 174 relateFromProductVariantIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 175 } 176 else 177 { 178 relateFromProductVariantIds.Add($"{relatedProduct.ProductId}"); 179 } 180 } 181 } 182 } 183 } 184 185 //Create group id collection and products id collection strings 186 string groupIds = product is object ? product.PrimaryOrDefaultGroup.Id : string.Join(",", relateFromGroupIds); 187 string productVariantIds = relateFromProductVariantIds.Count > 0 ? string.Join(",", relateFromProductVariantIds) : ""; 188 string productIds = product is object && relateFromProductIds.Count == 0 ? product.Id : string.Join(",", relateFromProductIds); 189 //Set the parameters to the url 190 string linkParameters = ""; 191 linkParameters += sourceType != "related-products" && sourceType != "frequently" && sourceType != "selected" ? "&GroupId=" + groupIds : ""; 192 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType != "most-sold" && sourceType != "trending" && sourceType != "latest" && sourceType != "frequently" && sourceType != "related-products" ? "&MainProductId=" + productIds : ""; 193 linkParameters += !string.IsNullOrEmpty(productVariantIds) && sourceType == "related-products" ? "&ProductVariantId=" + productVariantIds : ""; 194 linkParameters += sourceType == "variants" ? "&IsVariant=true" : ""; 195 linkParameters += sourceType == "latest" ? "&SortBy=Created" : ""; 196 linkParameters += sourceType == "most-sold" ? "&SortBy=OrderCount" : ""; 197 linkParameters += sourceType == "trending" ? "&SortBy=OrderCountGrowth" : ""; 198 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType == "frequently" ? $"&BoughtWithProductIds=[{productIds}]" : ""; 199 var productListPageId = GetPageIdByNavigationTag("Shop"); 200 string link = "/Default.aspx?ID=" + productListPageId + linkParameters; 201 202 // Slider settings (documentation: swiffyslider.com/configuration) 203 string navigationStyle = $"{Model.Item.GetRawValueString("NavigationStyle", "slider-nav-round")}"; 204 string navigationPlacement = $"{Model.Item.GetRawValueString("NavigationPlacement", "slider-nav-on-slides")}"; 205 string indicatorStyle = $"{Model.Item.GetRawValueString("IndicatorStyle", "slider-indicators-hidden")}"; 206 string revealSlides = Model.Item.GetRawValueString("RevealSlides", "no-reveal") == "reveal" ? "slider-item-reveal" : string.Empty; 207 string navigationAlwaysVisible = (Model.Item.GetBoolean("NavigationAlwaysVisible")) ? "slider-nav-visible" : string.Empty; 208 string navigationVisibleOnTouch = (Model.Item.GetBoolean("NavigationVisibleOnTouch")) ? "slider-nav-touch" : string.Empty; 209 string navigationShowScrollbar = (Model.Item.GetBoolean("NavigationShowScrollbar")) ? "slider-nav-scrollbar" : string.Empty; 210 string navigationSmall = (Model.Item.GetBoolean("NavigationSmall")) ? "slider-nav-sm" : string.Empty; 211 string navigationInvertColors = (Model.Item.GetBoolean("NavigationInvertColors")) ? "slider-nav-dark" : string.Empty; 212 string navigationSlideEntirePage = (Model.Item.GetBoolean("NavigationSlideEntirePage")) ? "slider-nav-page" : string.Empty; 213 string navigationNoLoop = (Model.Item.GetBoolean("NavigationNoLoop")) ? "slider-nav-noloop" : string.Empty; 214 string indicatorsOutsideSlider = (Model.Item.GetBoolean("IndicatorsOutsideSlider") && indicatorStyle != string.Empty) ? "slider-indicators-outside" : string.Empty; 215 string indicatorsHighlightActive = (Model.Item.GetBoolean("IndicatorsHighlightActive")) ? "slider-indicators-highlight" : string.Empty; 216 string indicatorsInvertColors = (Model.Item.GetBoolean("IndicatorsInvertedColors")) ? "slider-indicators-dark" : string.Empty; 217 string indicatorsVisibleOnSmallDevices = (Model.Item.GetBoolean("IndicatorsVisibleOnSmallDevices")) ? "slider-indicators-sm" : string.Empty; 218 219 bool productsFound = true; 220 if (sourceType != "clerk" && string.IsNullOrEmpty(groupIds) && string.IsNullOrEmpty(productIds) && string.IsNullOrEmpty(productVariantIds)) //CUSTOM 221 { 222 if (Pageview.IsVisualEditorMode && product != null) //CUSTOM 223 { 224 productIds = product.Id; 225 sourceType = "selected"; 226 } 227 else 228 { 229 productsFound = false; 230 } 231 } 232 else if (sourceType == "upsell-products") 233 { 234 productsFound = relateFromProductIds.Count > 0; 235 } 236 } 237 238 @*//CUSTOM*@ 239 @if (Pageview.IsVisualEditorMode == true && sourceType == "clerk" && PageView.Current().AreaSettings.GetItem("CustomSettings").GetRawValueString("ClerkPublicKey_Custom").IsNullOrEmpty()) 240 { 241 <div class="alert alert-danger" role="alert"> 242 <span>@Translate("Product slider: The Clerk integration requires a public key in the website settings")</span> 243 </div> 244 } 245 @*//--CUSTOM*@ 246 247 @*Container element for the request*@ 248 @if (productsFound) 249 { 250 <form method="post" action="@url" id="RelatedProductsForm_@Model.ID" data-response-target-element="RelatedProducts_@Model.ID" data-preloader="inline" data-update-url="false" class="item_@Model.Item.SystemName.ToLower()"> 251 <input type="hidden" name="ModelID" value="@Model.ID"> 252 <input type="hidden" name="SourceType" value="@sourceType"> 253 254 @*--- SLIDER SETTINGS ---*@ 255 <input type="hidden" name="NavigationStyle" value="@navigationStyle"> 256 <input type="hidden" name="NavigationPlacement" value="@navigationPlacement"> 257 <input type="hidden" name="IndicatorStyle" value="@indicatorStyle"> 258 <input type="hidden" name="RevealSlides" value="@revealSlides"> 259 <input type="hidden" name="NavigationAlwaysVisible" value="@(navigationAlwaysVisible)"> 260 <input type="hidden" name="NavigationVisibleOnTouch" value="@(navigationVisibleOnTouch)"> 261 <input type="hidden" name="NavigationShowScrollbar" value="@(navigationShowScrollbar)"> 262 <input type="hidden" name="NavigationSmall" value="@(navigationSmall)"> 263 <input type="hidden" name="NavigationInvertColors" value="@(navigationInvertColors)"> 264 <input type="hidden" name="NavigationNoLoop" value="@(navigationNoLoop)"> 265 <input type="hidden" name="NavigationSlideEntirePage" value="@(navigationSlideEntirePage)"> 266 <input type="hidden" name="IndicatorsOutsideSlider" value="@(indicatorsOutsideSlider)"> 267 <input type="hidden" name="IndicatorsHighlightActive" value="@(indicatorsHighlightActive)"> 268 <input type="hidden" name="IndicatorsInvertColors" value="@(indicatorsInvertColors)"> 269 <input type="hidden" name="IndicatorsVisibleOnSmallDevices" value="@(indicatorsVisibleOnSmallDevices)"> 270 271 @*--- VARIANTS ---*@ 272 @if (sourceType == "variants") 273 { 274 <input type="hidden" name="isVariant" value="true"> 275 <input type="hidden" name="MainProductID" id="MainProductID_@Model.ID" value="@productIds"> 276 } 277 278 @*--- MOST SOLD ---*@ 279 @if (sourceType == "most-sold") 280 { 281 <input type="hidden" name="SortBy" value="OrderCount"> 282 if (groupIds != "") 283 { 284 <input type="hidden" name="GroupId" value="@groupIds"> 285 } 286 } 287 288 @*--- TRENDING ---*@ 289 @if (sourceType == "trending") 290 { 291 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 292 if (groupIds != "") 293 { 294 <input type="hidden" name="GroupId" value="@groupIds"> 295 } 296 } 297 298 @*--- FREQUENTLY BOUGHT ---*@ 299 @if (sourceType == "frequently" && !string.IsNullOrEmpty(productIds)) 300 { 301 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 302 } 303 @if (sourceType != "frequently" && hasVariants) 304 { 305 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 306 } 307 308 @*--- LATEST ---*@ 309 @if (sourceType == "latest") 310 { 311 <input type="hidden" name="SortBy" value="Created"> 312 <input type="hidden" name="GroupId" value="@groupIds"> 313 } 314 315 @*--- SELECTED PRODUCTS ---*@ 316 @if (sourceType == "selected" && !string.IsNullOrEmpty(productIds) && !hasVariants) 317 { 318 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 319 } 320 @if (sourceType == "selected" && hasVariants) 321 { 322 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 323 } 324 325 @*--- UPSELL PRODUCTS ---*@ 326 @if (sourceType == "upsell-products" && !string.IsNullOrEmpty(productIds) && !hasVariants) 327 { 328 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 329 } 330 @if (sourceType == "upsell-products" && hasVariants) 331 { 332 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 333 } 334 335 @*--- RELATED PRODUCTS ---*@ 336 @if (sourceType == "related-products") 337 { 338 <input type="hidden" name="ProductVariantId" id="MainProductID_@Model.ID" value="@productVariantIds"> 339 } 340 341 @*//CUSTOM*@ 342 @*--- CLERK ---*@ 343 @if (sourceType == "clerk") 344 { 345 // Doc.: https://docs.clerk.io/reference/recommendations-popular 346 347 var clerkEndPoint = Model.Item.GetString("ClerkEndpoint"); 348 var clerkKeywords = Model.Item.GetString("ClerkKeywords"); 349 var clerkQuery = new List<string>(); 350 var clerkFilter = new List<string>(); 351 352 if (!string.IsNullOrEmpty(clerkKeywords)) 353 { 354 switch (clerkEndPoint) 355 { 356 case "recommendations/keywords": 357 clerkQuery.Add("keywords=['" + clerkKeywords + "']"); 358 break; 359 } 360 } 361 362 if (product != null && !string.IsNullOrEmpty(product.Id)) 363 { 364 switch (clerkEndPoint) 365 { 366 case "recommendations/bundle": 367 clerkQuery.Add("product=" + product.Id); 368 break; 369 370 case "recommendations/complementary": 371 case "recommendations/substituting": 372 case "recommendations/most_sold_with": 373 clerkQuery.Add("products=[" + product.Id + "]"); // NOTE: Clerk format id as integer (Data Sync product feed is string) 374 break; 375 } 376 } 377 378 IList<ProductGroupViewModel> clerkGroupsToRelateTo = Model.Item.GetValue<IList<ProductGroupViewModel>>("ClerkGroupsToRelateTo"); 379 switch (clerkEndPoint) 380 { 381 case "recommendations/category/popular": 382 case "recommendations/category/trending": 383 case "recommendations/category/new": 384 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 385 { 386 clerkQuery.Add("category=" + clerkGroupsToRelateTo.First().Id); 387 } 388 else if (Context.Current.Request.HasRequest("GroupID")) 389 { 390 clerkQuery.Add("category=" + Context.Current.Request.GetString("GroupID")); 391 } 392 393 break; 394 395 default: 396 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 397 { 398 clerkFilter.Add("categories in ['" + string.Join("','", clerkGroupsToRelateTo.Select(i => i.Id)) + "']"); 399 } 400 401 break; 402 } 403 404 switch (clerkEndPoint) 405 { 406 case "recommendations/page/substituting": 407 case "recommendations/page/related_products": 408 case "recommendations/page/related_categories": 409 clerkQuery.Add("page=" + Model.PageID); 410 break; 411 } 412 413 IList<ItemViewModel> clerkFilters = Model.Item.GetItems("ClerkFilters"); 414 if (clerkFilters != null) 415 { 416 foreach (var i in clerkFilters) 417 { 418 switch (i.GetString("OperatorType")) 419 { 420 case "=": 421 case "!=": 422 case "contains": 423 case "contains not": 424 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} '{i.GetString("Value")}'"); 425 break; 426 427 case "in": 428 case "not in": 429 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} ['{i.GetString("Value").Replace(",", "','")}']"); 430 break; 431 432 case "= true": 433 case "= false": 434 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")}"); 435 break; 436 437 default: 438 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} {i.GetString("Value")}"); 439 break; 440 } 441 } 442 } 443 444 <input type="hidden" name="ClerkEndpoint" value="@HtmlEncoder.HtmlAttributeEncode(clerkEndPoint)" /> 445 <input type="hidden" name="ClerkQuery" value="@HtmlEncoder.HtmlAttributeEncode(string.Join("&", clerkQuery))" /> 446 <input type="hidden" name="ClerkFilter" value="@HtmlEncoder.HtmlAttributeEncode(string.Join(" and ", clerkFilter))" /> 447 <input type="hidden" name="ClerkLabels" value="@HtmlEncoder.HtmlAttributeEncode(Model.Item.GetString("ClerkLabels"))" /> 448 <input type="hidden" name="ClerkLogArea" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Area.Name} (ID: {Pageview.Area.ID})")" /> 449 <input type="hidden" name="ClerkLogPage" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Page.MenuText} (ID: {Pageview.Page.ID})")" /> 450 <input type="hidden" name="ClerkLogParagraph" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.CurrentParagraph.Header} (ID: {Pageview.CurrentParagraph.ID})")" /> 451 } 452 @*//--CUSTOM*@ 453 454 @* General parameters *@ 455 <input type="hidden" name="Link" value="@link"> 456 <input type="hidden" name="HideTitle" value="@Model.Item.GetString("HideTitle")"> 457 <input type="hidden" name="HidePrices" value="@Model.Item.GetString("CustomHidePrices")" /> @*//CUSTOM*@ 458 459 @if (Model.Item.GetInt32("ProductsCount") != 0) 460 { 461 <input type="hidden" name="PageSize" value="@Model.Item.GetInt32("ProductsCount")"> 462 } 463 <input type="hidden" name="HeadingTitle" id="RelatedProductsTitle_@Model.ID" value="@title"> 464 @if (!string.IsNullOrEmpty(Model.Item.GetString("Subtitle"))) 465 { 466 <input type="hidden" name="Subtitle" value="@Model.Item.GetString("Subtitle")"> 467 } 468 @if (!string.IsNullOrEmpty(Model.Item.GetString("LinkText"))) 469 { 470 <input type="hidden" name="LinkText" value="@Model.Item.GetString("LinkText")"> 471 } 472 @if (!string.IsNullOrEmpty(Model.Item.GetString("ImageAspectRatio"))) 473 { 474 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 475 ratio = ratio != "0" ? ratio : ""; 476 <input type="hidden" name="ImageAspectRatio" value="@ratio"> 477 } 478 @if (!string.IsNullOrEmpty(Model.Item.GetString("Layout"))) 479 { 480 <input type="hidden" name="Layout" value="@Model.Item.GetRawValueString("Layout")"> 481 } 482 @if (titleFontSize != "") 483 { 484 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 485 } 486 @if (subtitleFontSize != "") 487 { 488 <input type="hidden" name="SubtitleFontSize" value="@subtitleFontSize"> 489 } 490 @if (buttonStyle != "") 491 { 492 <input type="hidden" name="ButtonStyle" value="@buttonStyle"> 493 } 494 @if (generalTheme != "") 495 { 496 <input type="hidden" name="GeneralTheme" value="@generalTheme"> 497 } 498 @if (theme != "") 499 { 500 <input type="hidden" name="Theme" value="@theme"> 501 } 502 @if (imageTheme != "") 503 { 504 <input type="hidden" name="ImageTheme" value="@imageTheme"> 505 } 506 @if (!string.IsNullOrEmpty(Model.Item.GetString("ContentPadding"))) 507 { 508 string contentPadding = Model.Item.GetRawValueString("ContentPadding"); 509 <input type="hidden" name="ContentPadding" value="@contentPadding"> 510 } 511 <input type="hidden" name="TextReadability" value="@maxWidth"> 512 <input type="hidden" name="ParentColumnSize" id="ParentColumnSize_@Model.ID" value="12"> 513 514 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 515 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 516 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 517 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 518 519 @if (campaignValues != "") 520 { 521 <input type="hidden" name="CampaignBadgesValues" value="@campaignValues"> 522 } 523 </form> 524 525 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 526 <script> 527 window.addEventListener("load", () => { 528 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 529 }); 530 </script> 531 532 if (Pageview.IsVisualEditorMode) 533 { 534 <div class="alert alert-info" role="alert"> 535 <span>@Translate("Product slider: Edit this column to configure")</span> 536 </div> 537 } 538 539 if (sourceType == "upsell-products") 540 { 541 <div class="w-100 h-100"> 542 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 543 <div id="RelatedProducts_@Model.ID"></div> 544 </div> 545 } 546 else if (sourceType != "related-products") 547 { 548 <div class="w-100 h-100"> 549 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 550 <div id="RelatedProducts_@Model.ID" class="h-100 swift_product_slider_container" data-product-count="@Model.Item.GetInt32("ProductsCount")"></div> @*//CUSTOM*@ 551 </div> 552 } 553 else if (product?.RelatedGroups != null) 554 { 555 @* Create multiple slider containers, if type is Product relation *@ 556 <div class="grid w-100 h-100@(generalTheme)" style="grid-row-gap: 4rem"> 557 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 558 @foreach (var group in product.RelatedGroups) 559 { 560 <div id="RelatedProducts_@(Model.ID)_@group.Id" class="g-col-12 h-100 swift_product_slider_container"></div> 561 } 562 </div> 563 } 564 565 @* Initialize *@ 566 if (sourceType != "related-products") 567 { 568 <script type="module"> 569 if (document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]")) { 570 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]").getAttribute("data-col-size"); 571 } 572 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")).then(function () { 573 setTimeout(function () { 574 const isVisualEditor = @(Converter.ToString(Pageview.IsVisualEditorMode).ToLowerInvariant()); 575 const productSliderContainer = document.querySelector(".swift_product_slider_container"); 576 577 if (productSliderContainer && productSliderContainer.innerHTML !== "") { 578 productSliderContainer.classList.remove("d-none"); 579 } else if (!isVisualEditor && productSliderContainer) { 580 productSliderContainer.closest("[class*=column]").classList.add("d-none"); 581 } 582 custom.UpsellProducts.bindEvents(); 583 }, 150); 584 }); 585 </script> 586 } 587 else if (product?.RelatedGroups != null) 588 { 589 @* Create multiple sliders, if type is Product relation *@ 590 foreach (var group in product.RelatedGroups) 591 { 592 IList<string> fromProductIds = new List<string> { }; 593 594 foreach (var relatedProduct in group.Products) 595 { 596 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 597 { 598 fromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 599 } 600 else 601 { 602 fromProductIds.Add($"{relatedProduct.ProductId}"); 603 } 604 } 605 606 <script type="module"> 607 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@(Model.ID)_@group.Id").closest("[data-col-size]").getAttribute("data-col-size"); 608 document.querySelector("#MainProductID_@Model.ID").value = "@string.Join(",", fromProductIds)"; 609 document.querySelector("#RelatedProductsTitle_@Model.ID").value = "@group.Name"; 610 document.querySelector("#RelatedProductsForm_@Model.ID").setAttribute("data-response-target-element", "RelatedProducts_@(Model.ID)_@group.Id"); 611 612 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")); 613 </script> 614 } 615 } 616 } 617 618 @functions { 619 List<Product> GetActiveUpSellProducts(string productNumbers) 620 { 621 if (!string.IsNullOrEmpty(productNumbers)) 622 { 623 List<Product> upSellProducts = Product.GetProductsByProductIDs(productNumbers.Split(',').Select(x => x.Trim()).ToArray(), true).ToList(); 624 625 if (upSellProducts != null && upSellProducts.Any()) 626 { 627 List<Product> activeUpSellProducts = new List<Product>(); 628 629 foreach (Product upSellProduct in upSellProducts) 630 { 631 if (upSellProduct.Active && GetStock(upSellProduct) > 0 && !upSellProduct.InActive()) 632 { 633 activeUpSellProducts.Add(upSellProduct); 634 } 635 } 636 637 return activeUpSellProducts; 638 } 639 } 640 641 return null; 642 } 643 644 double GetStock(Product product) 645 { 646 double productStock = product.Stock; 647 648 if (Converter.ToBoolean(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_FurnitureWarehouse"))) 649 { 650 productStock = Converter.ToDouble(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_AlternativeStock")); 651 } 652 653 return productStock; 654 } 655 } 656
Sorry. There is nothing to view here