Af: Marie Hertz | Til 4 personer
Jeg elsker porrer. De er utrolig anvendelige og kan sagtens spille hovedrollen på en tallerken. Denne ret har været min families hof-forret hele mit liv. Både fordi vi altid har haft utrolig mange porrer i køkkenhaven, men også fordi den smager virkelig godt. I denne version koges porrerne, men du kan også grille dem helt sorte, hvorefter det brændte lag pilles af, eller du kan bage dem i ovnen.
Ingredienser
- 4 mellemstore danske økologiske porrer
- krydder- eller æbleeddike
- 1 tsk dijonsennep
- 1 dl olivenolie
- 1 skalotteløg
- salt og peber
- 1 tsk honning
- 250 g kogte belugalinser, rørt med lidt olivenolie og salt
- frisk kørvel, vasket og plukket småt
Fremgangsmåde
Vask og rens porrerne. Skær den øverste grønneste del fra og gem til en grønsagsfond.
Dæk bunden af en større gryde med et par centimeter vand og lidt salt og kog op. Kom porerne i, læg låg på og lad dem dampe sig møre over 6-8 minutter – vend dem gerne rundt undervejs. Tag porrerne op, læg dem på en tallerken og lad dem dampe af.
Rør imens vinaigretten. Hak skalotteløget fint. Kom sennep og eddike i en lille skål og rør sammen med salt, peber og honning. Tilsæt olivenolien i en tynd stråle, så vinaigretten samler sig. Tilsæt det finthakkede skalotteløg og lad vinaigretten stå i 10 minutter – og smag den så til igen med salt, peber og honning.
Halver de dampede porrer og anret dem på en smuk tallerken. Dryp vinaigretten over med en ske og fordel linserne rundt på tallerkenen. Pynt til sidst med kørvel og server retten som en forret, eller som én af flere småretter.
Error executing template "Designs/Swift/Paragraph/Swift_RelatedProducts_Custom.cshtml" System.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData() at System.Data.SqlClient.SqlDataReader.get_MetaData() at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) at Dynamicweb.Data.Database.CreateDataReader(IDbCommand command, CommandBehavior behavior) at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, CommandBehavior behavior, Int32 commandTimeout) at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout) at Dynamicweb.Ecommerce.Products.ProductRepository.Dynamicweb.Ecommerce.Products.IProductRepository.GetProductKeysByGroupId(String groupId, Boolean useOrderBy, Boolean includeVariants, String productLanguageId, Boolean doRefactoring, Boolean useAssortments) at Dynamicweb.Ecommerce.Orders.Discounts.Discount.ProcessProductSelections(HashSet`1& productKeys, HashSet`1& includedQueries, HashSet`1& excludedQueries) at Dynamicweb.Ecommerce.Orders.Discounts.Discount.GetRelevantProductKeys() at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.StatelessSetLookup(Discount discount, ConcurrentDictionary`2 lookup) at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.InitializeLookup() at System.Lazy`1.CreateValue() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Lazy`1.get_Value() at Dynamicweb.Ecommerce.Orders.Discounts.DiscountService.GetDiscounts(DiscountApplyType[] discountTypes, String[] productKeys, User user) at Dynamicweb.Ecommerce.Orders.Discounts.DiscountInfoCollection.LoadDiscounts() at Smartpage.EvaSolo.CampaignPrices.Helpers.DiscountHelper.GetDiscountInfo(Product product, Currency currency, Country country, Shop shop, User user, String format) in D:\a\1\s\Smartpage.EvaSolo.CampaignPrices\Helpers\DiscountHelper.cs:line 48 at Custom.EvaSolo.CampaignPrices.FieldTypeProviders.OnSaleProvider.GetProductValue(Product product, Object fieldValue, String languageId, Dictionary`2 settings) in D:\a\1\s\Smartpage.EvaSolo.CampaignPrices\FieldTypeProviders\OnSaleProvider.cs:line 27 at Dynamicweb.Ecommerce.Products.ProductFieldTypeProvider.GetProductValue(Product product, FieldType fieldType, Object fieldValue) at Dynamicweb.Ecommerce.Products.ProductFieldValueCollection.RefreshProviderFields(Product product) at Dynamicweb.Ecommerce.Products.ProductRepository.ExtractProduct(IDataReader dataReader, Nullable`1& groupProductRelationSortingExists) at Dynamicweb.Ecommerce.Products.ProductRepository.GetProductById(String productId, String productVariantId, String productLanguageId) at Dynamicweb.Ecommerce.Products.ProductService.FetchMissingProductsInternal(IProductRepository repo, IEnumerable`1 keys) at Dynamicweb.Caching.ServiceCache`2.GetCache(IEnumerable`1 keys) at Dynamicweb.Caching.ServiceCache`2.GetCache(TKey key) at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, User user, Boolean showUntranslated) at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, Boolean useAssortments) at Dynamicweb.Ecommerce.Content.Items.Editors.ProductEditor.ParseValue(IEnumerable`1 value) at Dynamicweb.Ecommerce.Content.Items.Editors.ProductCatalogEditor.GetViewModelValue(Object value) at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at Dynamicweb.Frontend.ItemFieldViewModel.GetValue[T]() at Dynamicweb.Frontend.ItemViewModel.GetValue[T](String systemName) at CompiledRazorTemplates.Dynamic.RazorEngine_66f8b4a1ddb743b0a2aba6f698ce0e70.Execute() in D:\dynamicweb.net\Solutions\twodayco3\evasolo.cloud.dynamicweb-cms.com\Files\Templates\Designs\Swift\Paragraph\Swift_RelatedProducts_Custom.cshtml:line 128 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:077a6f4a-d841-4c19-b87e-820b384f100a Error Number:-2,State:0,Class:11
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 Smartpage.EvaSolo.Clerk.Helpers 13 14 @* CUSTOMIZED STANDARD SWIFT (v1.25.0) TEMPLATE *@ 15 16 @{ 17 ProductViewModel product = null; 18 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 19 { 20 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 21 } 22 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 23 { 24 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 25 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 26 27 if (productList?.Products is object) 28 { 29 product = productList.Products[0]; 30 } 31 } 32 33 string title = Model?.Item?.GetRawValueString("Title", Translate("Products")); 34 string campaignValues = Model.Item.GetRawValueString("CampaignBadges", string.Empty); 35 36 //Styling 37 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 38 string subtitleFontSize = Model.Item.GetRawValueString("SubtitleFontSize", "fs-5"); 39 string buttonStyle = Model.Item.GetRawValueString("ButtonStyle", ""); 40 buttonStyle = buttonStyle == "primary" ? " btn-primary" : buttonStyle; 41 buttonStyle = buttonStyle == "secondary" ? " btn-secondary" : buttonStyle; 42 buttonStyle = buttonStyle == "link" ? " btn-link" : buttonStyle; 43 string maxWidth = Model.Item.GetRawValueString("TextReadability", ""); 44 maxWidth = maxWidth == "max-width-on" ? " mw-75ch" : maxWidth; 45 maxWidth = maxWidth == "max-width-off" ? "" : maxWidth; 46 47 string generalTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("GeneralTheme")) ? " theme " + Model.Item.GetRawValueString("GeneralTheme").Replace(" ", "").Trim().ToLower() : ""; 48 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 49 string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 50 51 //Link generation 52 string pageId = !string.IsNullOrEmpty(Model.Item.GetRawValueString("ProductSliderServicePage")) ? Model.Item.GetLink("ProductSliderServicePage").PageId.ToString() : ""; 53 if (string.IsNullOrEmpty(pageId)) 54 { 55 pageId = GetPageIdByNavigationTag("ProductSliderService").ToString(); 56 } 57 58 string url = "/Default.aspx?ID=" + pageId; 59 if (!url.Contains("LayoutTemplate", StringComparison.OrdinalIgnoreCase)) 60 { 61 url += url.Contains("?") ? "&LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml" : "?LayoutTemplate=Designs/Swift/Swift_PageClean.cshtml"; 62 } 63 64 if (Pageview.IsVisualEditorMode) 65 { 66 url += "&VisualEdit=True"; 67 } 68 69 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Core.Converter.ToBoolean(Dynamicweb.Context.Current.Items["IsLazyLoadingForProductInfoEnabled"]); 70 if (isLazyLoadingForProductInfoEnabled) 71 { 72 url += "&getproductinfo=true"; 73 } 74 75 //Source type 76 string sourceType = Model.Item.GetRawValueString("RelationType", "trending"); 77 IList<string> relateFromGroupIds = new List<string> { }; 78 IList<string> relateFromProductVariantIds = new List<string> { }; 79 IList<string> relateFromProductIds = new List<string> { }; 80 bool hasVariants = false; 81 82 //--- VARIANTS --- 83 if (sourceType == "variants" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateToVariants") is ProductListViewModel productsToRelateToVariants) 84 { 85 foreach (var productSelection in productsToRelateToVariants.Products) 86 { 87 relateFromProductIds.Add(productSelection.Id); 88 } 89 } 90 91 //--- MOST SOLD --- 92 if (sourceType == "most-sold" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToMostSold") is IList<ProductGroupViewModel> groupsToRelateToMostSold) 93 { 94 foreach (var fromGroup in groupsToRelateToMostSold) 95 { 96 relateFromGroupIds.Add(fromGroup.Id); 97 } 98 } 99 100 //--- TRENDING --- 101 if (sourceType == "trending" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToTrending") is IList<ProductGroupViewModel> groupsToRelateToTrending) 102 { 103 foreach (var fromGroup in groupsToRelateToTrending) 104 { 105 relateFromGroupIds.Add(fromGroup.Id); 106 } 107 } 108 109 //--- LATEST --- 110 if (sourceType == "latest" && Model.Item.GetValue<IList<ProductGroupViewModel>>("GroupsToRelateToLatest") is IList<ProductGroupViewModel> groupsToRelateToLatest) 111 { 112 foreach (var fromGroup in groupsToRelateToLatest) 113 { 114 relateFromGroupIds.Add(fromGroup.Id); 115 } 116 } 117 118 //--- FREQUENTLY BOUGHT --- 119 if (sourceType == "frequently" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo") is ProductListViewModel productsToRelateTo) 120 { 121 foreach (var fromProduct in productsToRelateTo.Products) 122 { 123 relateFromProductIds.Add(fromProduct.Id); 124 } 125 } 126 127 //--- SELECTED PRODUCTS --- 128 if ((sourceType == "selected" || sourceType == "frequently") && Model.Item.GetValue<ProductListViewModel>("Products") is ProductListViewModel products) 129 { 130 hasVariants = products.Products.Any(p => !string.IsNullOrEmpty(p.VariantId)); 131 foreach (var productSelection in products.Products) 132 { 133 if (hasVariants) 134 { 135 if (!string.IsNullOrEmpty(productSelection.VariantId)) 136 { 137 relateFromProductVariantIds.Add($"{productSelection.Id} {productSelection.VariantId}"); 138 } 139 else 140 { 141 relateFromProductVariantIds.Add($"{productSelection.Id}"); 142 } 143 } 144 145 relateFromProductIds.Add($"{productSelection.Id}"); 146 } 147 } 148 149 //--- UPSELL PRODUCTS --- 150 if (sourceType == "upsell-products") 151 { 152 string upSell = product.ProductFields.ContainsKey("Custom_UpSell") ? Converter.ToString(product.ProductFields["Custom_UpSell"].Value) : string.Empty; 153 List<Product> upSellProducts = GetActiveUpSellProducts(upSell); 154 relateFromProductIds = upSellProducts != null ? upSellProducts.Select(p => p.Id).ToList() : new List<string>(); 155 } 156 157 //--- RELATED PRODUCTS --- 158 if (sourceType == "related-products" && Model.Item.GetValue<ProductListViewModel>("ProductsToRelateTo2") is ProductListViewModel selectedRelationProduct) 159 { 160 if (selectedRelationProduct.Products.Any()) 161 { 162 product = selectedRelationProduct.Products.FirstOrDefault(); 163 } 164 165 if (product?.RelatedGroups != null) 166 { 167 foreach (var group in product.RelatedGroups) 168 { 169 foreach (var relatedProduct in group.Products) 170 { 171 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 172 { 173 relateFromProductVariantIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 174 } 175 else 176 { 177 relateFromProductVariantIds.Add($"{relatedProduct.ProductId}"); 178 } 179 } 180 } 181 } 182 } 183 184 //Create group id collection and products id collection strings 185 string groupIds = product is object ? product.PrimaryOrDefaultGroup.Id : string.Join(",", relateFromGroupIds); 186 string productVariantIds = relateFromProductVariantIds.Count > 0 ? string.Join(",", relateFromProductVariantIds) : ""; 187 string productIds = product is object && relateFromProductIds.Count == 0 ? product.Id : string.Join(",", relateFromProductIds); 188 //Set the parameters to the url 189 string linkParameters = ""; 190 linkParameters += sourceType != "related-products" && sourceType != "frequently" && sourceType != "selected" ? "&GroupId=" + groupIds : ""; 191 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType != "most-sold" && sourceType != "trending" && sourceType != "latest" && sourceType != "frequently" && sourceType != "related-products" ? "&MainProductId=" + productIds : ""; 192 linkParameters += !string.IsNullOrEmpty(productVariantIds) && sourceType == "related-products" ? "&ProductVariantId=" + productVariantIds : ""; 193 linkParameters += sourceType == "variants" ? "&IsVariant=true" : ""; 194 linkParameters += sourceType == "latest" ? "&SortBy=Created" : ""; 195 linkParameters += sourceType == "most-sold" ? "&SortBy=OrderCount" : ""; 196 linkParameters += sourceType == "trending" ? "&SortBy=OrderCountGrowth" : ""; 197 linkParameters += !string.IsNullOrEmpty(productIds) && sourceType == "frequently" ? $"&BoughtWithProductIds=[{productIds}]" : ""; 198 var productListPageId = GetPageIdByNavigationTag("Shop"); 199 string link = "/Default.aspx?ID=" + productListPageId + linkParameters; 200 201 // Slider settings (documentation: swiffyslider.com/configuration) 202 string navigationStyle = $"{Model.Item.GetRawValueString("NavigationStyle", "slider-nav-round")}"; 203 string navigationPlacement = $"{Model.Item.GetRawValueString("NavigationPlacement", "slider-nav-on-slides")}"; 204 string indicatorStyle = $"{Model.Item.GetRawValueString("IndicatorStyle", "slider-indicators-hidden")}"; 205 string revealSlides = Model.Item.GetRawValueString("RevealSlides", "no-reveal") == "reveal" ? "slider-item-reveal" : string.Empty; 206 string navigationAlwaysVisible = (Model.Item.GetBoolean("NavigationAlwaysVisible")) ? "slider-nav-visible" : string.Empty; 207 string navigationVisibleOnTouch = (Model.Item.GetBoolean("NavigationVisibleOnTouch")) ? "slider-nav-touch" : string.Empty; 208 string navigationShowScrollbar = (Model.Item.GetBoolean("NavigationShowScrollbar")) ? "slider-nav-scrollbar" : string.Empty; 209 string navigationSmall = (Model.Item.GetBoolean("NavigationSmall")) ? "slider-nav-sm" : string.Empty; 210 string navigationInvertColors = (Model.Item.GetBoolean("NavigationInvertColors")) ? "slider-nav-dark" : string.Empty; 211 string navigationSlideEntirePage = (Model.Item.GetBoolean("NavigationSlideEntirePage")) ? "slider-nav-page" : string.Empty; 212 string navigationNoLoop = (Model.Item.GetBoolean("NavigationNoLoop")) ? "slider-nav-noloop" : string.Empty; 213 string indicatorsOutsideSlider = (Model.Item.GetBoolean("IndicatorsOutsideSlider") && indicatorStyle != string.Empty) ? "slider-indicators-outside" : string.Empty; 214 string indicatorsHighlightActive = (Model.Item.GetBoolean("IndicatorsHighlightActive")) ? "slider-indicators-highlight" : string.Empty; 215 string indicatorsInvertColors = (Model.Item.GetBoolean("IndicatorsInvertedColors")) ? "slider-indicators-dark" : string.Empty; 216 string indicatorsVisibleOnSmallDevices = (Model.Item.GetBoolean("IndicatorsVisibleOnSmallDevices")) ? "slider-indicators-sm" : string.Empty; 217 218 bool productsFound = true; 219 if (sourceType != "clerk" && string.IsNullOrEmpty(groupIds) && string.IsNullOrEmpty(productIds) && string.IsNullOrEmpty(productVariantIds)) //CUSTOM 220 { 221 if (Pageview.IsVisualEditorMode && product != null) //CUSTOM 222 { 223 productIds = product.Id; 224 sourceType = "selected"; 225 } 226 else 227 { 228 productsFound = false; 229 } 230 } 231 else if (sourceType == "upsell-products") 232 { 233 productsFound = relateFromProductIds.Count > 0; 234 } 235 } 236 237 @*//CUSTOM*@ 238 @if (Pageview.IsVisualEditorMode == true && sourceType == "clerk" && PageView.Current().AreaSettings.GetItem("CustomSettings").GetRawValueString("ClerkPublicKey_Custom").IsNullOrEmpty()) 239 { 240 <div class="alert alert-danger" role="alert"> 241 <span>@Translate("Product slider: The Clerk integration requires a public key in the website settings")</span> 242 </div> 243 } 244 @*//--CUSTOM*@ 245 246 @*Container element for the request*@ 247 @if (productsFound) 248 { 249 <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()"> 250 <input type="hidden" name="ModelID" value="@Model.ID"> 251 <input type="hidden" name="SourceType" value="@sourceType"> 252 253 @*--- SLIDER SETTINGS ---*@ 254 <input type="hidden" name="NavigationStyle" value="@navigationStyle"> 255 <input type="hidden" name="NavigationPlacement" value="@navigationPlacement"> 256 <input type="hidden" name="IndicatorStyle" value="@indicatorStyle"> 257 <input type="hidden" name="RevealSlides" value="@revealSlides"> 258 <input type="hidden" name="NavigationAlwaysVisible" value="@(navigationAlwaysVisible)"> 259 <input type="hidden" name="NavigationVisibleOnTouch" value="@(navigationVisibleOnTouch)"> 260 <input type="hidden" name="NavigationShowScrollbar" value="@(navigationShowScrollbar)"> 261 <input type="hidden" name="NavigationSmall" value="@(navigationSmall)"> 262 <input type="hidden" name="NavigationInvertColors" value="@(navigationInvertColors)"> 263 <input type="hidden" name="NavigationNoLoop" value="@(navigationNoLoop)"> 264 <input type="hidden" name="NavigationSlideEntirePage" value="@(navigationSlideEntirePage)"> 265 <input type="hidden" name="IndicatorsOutsideSlider" value="@(indicatorsOutsideSlider)"> 266 <input type="hidden" name="IndicatorsHighlightActive" value="@(indicatorsHighlightActive)"> 267 <input type="hidden" name="IndicatorsInvertColors" value="@(indicatorsInvertColors)"> 268 <input type="hidden" name="IndicatorsVisibleOnSmallDevices" value="@(indicatorsVisibleOnSmallDevices)"> 269 270 @*--- VARIANTS ---*@ 271 @if (sourceType == "variants") 272 { 273 <input type="hidden" name="isVariant" value="true"> 274 <input type="hidden" name="MainProductID" id="MainProductID_@Model.ID" value="@productIds"> 275 } 276 277 @*--- MOST SOLD ---*@ 278 @if (sourceType == "most-sold") 279 { 280 <input type="hidden" name="SortBy" value="OrderCount"> 281 if (groupIds != "") 282 { 283 <input type="hidden" name="GroupId" value="@groupIds"> 284 } 285 } 286 287 @*--- TRENDING ---*@ 288 @if (sourceType == "trending") 289 { 290 <input type="hidden" name="SortBy" value="OrderCountGrowth"> 291 if (groupIds != "") 292 { 293 <input type="hidden" name="GroupId" value="@groupIds"> 294 } 295 } 296 297 @*--- FREQUENTLY BOUGHT ---*@ 298 @if (sourceType == "frequently" && !string.IsNullOrEmpty(productIds)) 299 { 300 <input type="hidden" name="BoughtWithProductIds" value="[@productIds]"> 301 } 302 @if (sourceType != "frequently" && hasVariants) 303 { 304 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 305 } 306 307 @*--- LATEST ---*@ 308 @if (sourceType == "latest") 309 { 310 <input type="hidden" name="SortBy" value="Created"> 311 <input type="hidden" name="GroupId" value="@groupIds"> 312 } 313 314 @*--- SELECTED PRODUCTS ---*@ 315 @if (sourceType == "selected" && !string.IsNullOrEmpty(productIds) && !hasVariants) 316 { 317 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 318 } 319 @if (sourceType == "selected" && hasVariants) 320 { 321 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 322 } 323 324 @*--- UPSELL PRODUCTS ---*@ 325 @if (sourceType == "upsell-products" && !string.IsNullOrEmpty(productIds) && !hasVariants) 326 { 327 <input type="hidden" name="MainProductId" id="MainProductID_@Model.ID" value="@productIds"> 328 } 329 @if (sourceType == "upsell-products" && hasVariants) 330 { 331 <input type="hidden" name="ProductVariantId" value="@productVariantIds"> 332 } 333 334 @*--- RELATED PRODUCTS ---*@ 335 @if (sourceType == "related-products") 336 { 337 <input type="hidden" name="ProductVariantId" id="MainProductID_@Model.ID" value="@productVariantIds"> 338 } 339 340 @*//CUSTOM*@ 341 @*--- CLERK ---*@ 342 @if (sourceType == "clerk") 343 { 344 // Doc.: https://docs.clerk.io/reference/recommendations-popular 345 346 var clerkEndPoint = Model.Item.GetString("ClerkEndpoint"); 347 var clerkKeywords = Model.Item.GetString("ClerkKeywords"); 348 var clerkQuery = new List<string>(); 349 var clerkFilter = new List<string>(); 350 351 if (!string.IsNullOrEmpty(clerkKeywords)) 352 { 353 switch (clerkEndPoint) 354 { 355 case "recommendations/keywords": 356 clerkQuery.Add("keywords=['" + clerkKeywords + "']"); 357 break; 358 } 359 } 360 361 if (product != null && !string.IsNullOrEmpty(product.Id)) 362 { 363 switch (clerkEndPoint) 364 { 365 case "recommendations/bundle": 366 clerkQuery.Add("product=" + product.Id); 367 break; 368 369 case "recommendations/complementary": 370 case "recommendations/substituting": 371 case "recommendations/most_sold_with": 372 clerkQuery.Add("products=[" + product.Id + "]"); // NOTE: Clerk format id as integer (Data Sync product feed is string) 373 break; 374 } 375 } 376 377 IList<ProductGroupViewModel> clerkGroupsToRelateTo = Model.Item.GetValue<IList<ProductGroupViewModel>>("ClerkGroupsToRelateTo"); 378 switch (clerkEndPoint) 379 { 380 case "recommendations/category/popular": 381 case "recommendations/category/trending": 382 case "recommendations/category/new": 383 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 384 { 385 clerkQuery.Add("category=" + clerkGroupsToRelateTo.First().Id); 386 } 387 else if (Context.Current.Request.HasRequest("GroupID")) 388 { 389 clerkQuery.Add("category=" + Context.Current.Request.GetString("GroupID")); 390 } 391 392 break; 393 394 default: 395 if (clerkGroupsToRelateTo != null && clerkGroupsToRelateTo.Any()) 396 { 397 clerkFilter.Add("categories in ['" + string.Join("','", clerkGroupsToRelateTo.Select(i => i.Id)) + "']"); 398 } 399 400 break; 401 } 402 403 switch (clerkEndPoint) 404 { 405 case "recommendations/page/substituting": 406 case "recommendations/page/related_products": 407 case "recommendations/page/related_categories": 408 clerkQuery.Add("page=" + Model.PageID); 409 break; 410 } 411 412 IList<ItemViewModel> clerkFilters = Model.Item.GetItems("ClerkFilters"); 413 if (clerkFilters != null) 414 { 415 foreach (var i in clerkFilters) 416 { 417 switch (i.GetString("OperatorType")) 418 { 419 case "=": 420 case "!=": 421 case "contains": 422 case "contains not": 423 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} '{i.GetString("Value")}'"); 424 break; 425 426 case "in": 427 case "not in": 428 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} ['{i.GetString("Value").Replace(",", "','")}']"); 429 break; 430 431 case "= true": 432 case "= false": 433 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")}"); 434 break; 435 436 default: 437 clerkFilter.Add($"{i.GetString("Field")} {i.GetString("OperatorType")} {i.GetString("Value")}"); 438 break; 439 } 440 } 441 } 442 443 <input type="hidden" name="ClerkEndpoint" value="@HtmlEncoder.HtmlAttributeEncode(clerkEndPoint)" /> 444 <input type="hidden" name="ClerkQuery" value="@HtmlEncoder.HtmlAttributeEncode(string.Join("&", clerkQuery))" /> 445 <input type="hidden" name="ClerkFilter" value="@HtmlEncoder.HtmlAttributeEncode(string.Join(" and ", clerkFilter))" /> 446 <input type="hidden" name="ClerkLabels" value="@HtmlEncoder.HtmlAttributeEncode(Model.Item.GetString("ClerkLabels"))" /> 447 <input type="hidden" name="ClerkLogArea" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Area.Name} (ID: {Pageview.Area.ID})")" /> 448 <input type="hidden" name="ClerkLogPage" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.Page.MenuText} (ID: {Pageview.Page.ID})")" /> 449 <input type="hidden" name="ClerkLogParagraph" value="@HtmlEncoder.HtmlAttributeEncode($"{Pageview.CurrentParagraph.Header} (ID: {Pageview.CurrentParagraph.ID})")" /> 450 } 451 @*//--CUSTOM*@ 452 453 @* General parameters *@ 454 <input type="hidden" name="Link" value="@link"> 455 <input type="hidden" name="HideTitle" value="@Model.Item.GetString("HideTitle")"> 456 <input type="hidden" name="HidePrices" value="@Model.Item.GetString("CustomHidePrices")" /> @*//CUSTOM*@ 457 458 @if (Model.Item.GetInt32("ProductsCount") != 0) 459 { 460 <input type="hidden" name="PageSize" value="@Model.Item.GetInt32("ProductsCount")"> 461 } 462 <input type="hidden" name="HeadingTitle" id="RelatedProductsTitle_@Model.ID" value="@title"> 463 @if (!string.IsNullOrEmpty(Model.Item.GetString("Subtitle"))) 464 { 465 <input type="hidden" name="Subtitle" value="@Model.Item.GetString("Subtitle")"> 466 } 467 @if (!string.IsNullOrEmpty(Model.Item.GetString("LinkText"))) 468 { 469 <input type="hidden" name="LinkText" value="@Model.Item.GetString("LinkText")"> 470 } 471 @if (!string.IsNullOrEmpty(Model.Item.GetString("ImageAspectRatio"))) 472 { 473 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 474 ratio = ratio != "0" ? ratio : ""; 475 <input type="hidden" name="ImageAspectRatio" value="@ratio"> 476 } 477 @if (!string.IsNullOrEmpty(Model.Item.GetString("Layout"))) 478 { 479 <input type="hidden" name="Layout" value="@Model.Item.GetRawValueString("Layout")"> 480 } 481 @if (titleFontSize != "") 482 { 483 <input type="hidden" name="TitleFontSize" value="@titleFontSize"> 484 } 485 @if (subtitleFontSize != "") 486 { 487 <input type="hidden" name="SubtitleFontSize" value="@subtitleFontSize"> 488 } 489 @if (buttonStyle != "") 490 { 491 <input type="hidden" name="ButtonStyle" value="@buttonStyle"> 492 } 493 @if (generalTheme != "") 494 { 495 <input type="hidden" name="GeneralTheme" value="@generalTheme"> 496 } 497 @if (theme != "") 498 { 499 <input type="hidden" name="Theme" value="@theme"> 500 } 501 @if (imageTheme != "") 502 { 503 <input type="hidden" name="ImageTheme" value="@imageTheme"> 504 } 505 @if (!string.IsNullOrEmpty(Model.Item.GetString("ContentPadding"))) 506 { 507 string contentPadding = Model.Item.GetRawValueString("ContentPadding"); 508 <input type="hidden" name="ContentPadding" value="@contentPadding"> 509 } 510 <input type="hidden" name="TextReadability" value="@maxWidth"> 511 <input type="hidden" name="ParentColumnSize" id="ParentColumnSize_@Model.ID" value="12"> 512 513 <input type="hidden" name="SaleBadgeType" value="@Model.Item.GetRawValue("SaleBadgeType")"> 514 <input type="hidden" name="SaleBadgeCssClassName" value="@Model.Item.GetRawValue("SaleBadgeDesign")"> 515 <input type="hidden" name="NewBadgeCssClassName" value="@Model.Item.GetRawValue("NewBadgeDesign")"> 516 <input type="hidden" name="NewPublicationDays" value="@Model.Item.GetInt32("NewPublicationDays")"> 517 518 @if (campaignValues != "") 519 { 520 <input type="hidden" name="CampaignBadgesValues" value="@campaignValues"> 521 } 522 </form> 523 524 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/swiffy-slider.js"></script> 525 <script> 526 window.addEventListener("load", () => { 527 swift.AssetLoader.Load('/Files/Templates/Designs/Swift/Assets/css/swiffy-slider.min.css', 'css'); 528 }); 529 </script> 530 531 if (Pageview.IsVisualEditorMode) 532 { 533 <div class="alert alert-info" role="alert"> 534 <span>@Translate("Product slider: Edit this column to configure")</span> 535 </div> 536 } 537 538 if (sourceType == "upsell-products") 539 { 540 <div class="w-100 h-100"> 541 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 542 <div id="RelatedProducts_@Model.ID"></div> 543 </div> 544 } 545 else if (sourceType != "related-products") 546 { 547 <div class="w-100 h-100"> 548 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 549 <div id="RelatedProducts_@Model.ID" class="h-100 swift_product_slider_container" data-product-count="@Model.Item.GetInt32("ProductsCount")"></div> @*//CUSTOM*@ 550 </div> 551 } 552 else if (product?.RelatedGroups != null) 553 { 554 @* Create multiple slider containers, if type is Product relation *@ 555 <div class="grid w-100 h-100@(generalTheme)" style="grid-row-gap: 4rem"> 556 <div id="@Model.ID" class="user-select-none" style="scroll-margin-top:var(--header-height,150px)"></div> 557 @foreach (var group in product.RelatedGroups) 558 { 559 <div id="RelatedProducts_@(Model.ID)_@group.Id" class="g-col-12 h-100 swift_product_slider_container"></div> 560 } 561 </div> 562 } 563 564 @* Initialize *@ 565 if (sourceType != "related-products") 566 { 567 <script type="module"> 568 if (document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]")) { 569 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@Model.ID").closest("[data-col-size]").getAttribute("data-col-size"); 570 } 571 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")).then(function () { 572 setTimeout(function () { 573 const isVisualEditor = @(Converter.ToString(Pageview.IsVisualEditorMode).ToLowerInvariant()); 574 const productSliderContainer = document.querySelector(".swift_product_slider_container"); 575 576 if (productSliderContainer && productSliderContainer.innerHTML !== "") { 577 productSliderContainer.classList.remove("d-none"); 578 } else if (!isVisualEditor && productSliderContainer) { 579 productSliderContainer.closest("[class*=column]").classList.add("d-none"); 580 } 581 custom.UpsellProducts.bindEvents(); 582 }, 150); 583 }); 584 </script> 585 } 586 else if (product?.RelatedGroups != null) 587 { 588 @* Create multiple sliders, if type is Product relation *@ 589 foreach (var group in product.RelatedGroups) 590 { 591 IList<string> fromProductIds = new List<string> { }; 592 593 foreach (var relatedProduct in group.Products) 594 { 595 if (!string.IsNullOrEmpty(relatedProduct.VariantId)) 596 { 597 fromProductIds.Add($"{relatedProduct.ProductId} {relatedProduct.VariantId}"); 598 } 599 else 600 { 601 fromProductIds.Add($"{relatedProduct.ProductId}"); 602 } 603 } 604 605 <script type="module"> 606 document.querySelector("#ParentColumnSize_@Model.ID").value = document.querySelector("#RelatedProducts_@(Model.ID)_@group.Id").closest("[data-col-size]").getAttribute("data-col-size"); 607 document.querySelector("#MainProductID_@Model.ID").value = "@string.Join(",", fromProductIds)"; 608 document.querySelector("#RelatedProductsTitle_@Model.ID").value = "@group.Name"; 609 document.querySelector("#RelatedProductsForm_@Model.ID").setAttribute("data-response-target-element", "RelatedProducts_@(Model.ID)_@group.Id"); 610 611 swift.PageUpdater.Update(document.querySelector("#RelatedProductsForm_@Model.ID")); 612 </script> 613 } 614 } 615 } 616 617 @functions { 618 List<Product> GetActiveUpSellProducts(string productNumbers) 619 { 620 if (!string.IsNullOrEmpty(productNumbers)) 621 { 622 List<Product> upSellProducts = Product.GetProductsByProductIDs(productNumbers.Split(',').Select(x => x.Trim()).ToArray(), true).ToList(); 623 624 if (upSellProducts != null && upSellProducts.Any()) 625 { 626 List<Product> activeUpSellProducts = new List<Product>(); 627 628 foreach (Product upSellProduct in upSellProducts) 629 { 630 if (upSellProduct.Active && GetStock(upSellProduct) > 0 && !upSellProduct.IsProductDisabledInClerkSync()) 631 { 632 activeUpSellProducts.Add(upSellProduct); 633 } 634 } 635 636 return activeUpSellProducts; 637 } 638 } 639 640 return null; 641 } 642 643 double GetStock(Product product) 644 { 645 double productStock = product.Stock; 646 647 if (Converter.ToBoolean(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_FurnitureWarehouse"))) 648 { 649 productStock = Converter.ToDouble(Dynamicweb.Ecommerce.Services.Products.GetProductFieldValue(product, "Custom_AlternativeStock")); 650 } 651 652 return productStock; 653 } 654 } 655