Product Maximum and Minimums in Sitefinity Part 2: Shopping Cart

By in
No comments

We previously looked at how you can enforce minimum and maximum product quantities on the Sitefinity product details page. However, users can still override the quantities in the shopping cart, defeating the restrictions.

Fortunately, the shopping cart template also includes a validation control for each item. By mapping this control to an external User Control template as well, we can tap into this validator with custom code just as we did before.

Shopping Cart Template

Once again, we need to map an external template from the shopping cart, which can be retrieved from the Sitefinity SDK. Instead of using the ViewMap like in the previous post, this time we’ll map the widget control directly to the external template file.

Sitefinity-Ecommerce-Shopping-Cart-Template

In addition to setting the LayoutTemplatePath, be sure to delete the TemplateKey property so that it does not select the default internal template instead of your custom one.

Also remember that, instead of copying the shopping cart template, you should create a full UserControl so you can use the code-behind for your custom code.

Sitefinity-Ecommerce-Custom-User-Control-Template

Custom Code-Behind

Also unlike last time, the validation control we need to hook into is embedded in a RadGrid, so we cannot access it directly from the code-behind. Instead, we need to handle the ItemDataBound event of the grid so that we can modify the validation control for each item in the cart.

First, we’ll modify the front end to point the grid to our custom event. Note: the template for this control may change over time, so be sure you are using the latest version from the SDK!

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ShoppingCart.ascx.cs" Inherits="SitefinityWebApp.Templates.ShoppingCart" %>
<%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
<%@ Register Assembly="Telerik.Sitefinity.Ecommerce" Namespace="Telerik.Sitefinity.Modules.Ecommerce.Catalog.Web.UI.Fields"
	TagPrefix="sfCatalog" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="orders" Namespace="Telerik.Sitefinity.Modules.Ecommerce.Orders.Web.UI"
	Assembly="Telerik.Sitefinity.Ecommerce" %>
<%@ OutputCache Duration="1" VaryByParam="*" %>
<%@ Import Namespace="Telerik.Sitefinity.Ecommerce" %>
<%@ Import Namespace="Telerik.Sitefinity.Modules.Ecommerce" %>
<div class="sfshoppingCartWrp">
	<!-- This container is used to display warning messages about set up of the widget;
         when control is set correctly, this container is invisible -->
	<div id="widgetStatus" runat="server" visible="false" class="sfshoppingCartStatus">
		<asp:Label ID="widgetStatusMessage" runat="server" />
	</div>
	<asp:PlaceHolder ID="widgetBody" runat="server">
		<h1 class="sfshoppingCartTitle" id="widgetHeading" runat="server">
			<asp:Literal ID="Literal1" runat="server" Text='<%$Resources:OrdersResources, ShoppingCart %>' />
		</h1>
		<sf:Message runat="server" ID="cartUpdateMessage" />
		<asp:PlaceHolder runat="server" ID="itemsCountPlaceholder">
			<div class="sfProductsInCart">
				<asp:Literal ID="productsCountLabel" runat="server" />&nbsp;<asp:Literal 
					runat="server" Text='<%$Resources:OrdersResources, items %>' />
			</div>
		</asp:PlaceHolder>
		<telerik:RadGrid ID="shoppingCartGrid" runat="server" Skin="Basic" ShowFooter="False"
			EnableEmbeddedBaseStylesheet="false" EnableEmbeddedSkins="false" OnItemDataBound="shoppingCartGrid_ItemDataBound">
			<MasterTableView AutoGenerateColumns="false" DataKeyNames="Id">
				<Columns>
					<telerik:GridTemplateColumn HeaderText='' UniqueName="ProductImage" ItemStyle-CssClass="sfItmTmbCol"
						HeaderStyle-CssClass="sfItmTmbCol">
						<ItemTemplate>
							<div class="sfproductTmbWrp">
								<img src='<%# HttpUtility.HtmlAttributeEncode(Eval("ThumbnailUrl") as string) %>'
									alt='<%# Eval("ThumbnailAlternativeText") %>' class="sfproductTmb" />
							</div>
						</ItemTemplate>
					</telerik:GridTemplateColumn>
					<telerik:GridTemplateColumn HeaderText='<%$Resources:OrdersResources, ProductDescription %>'
						UniqueName="ProductDescription" ItemStyle-CssClass="sfItmTitleCol" HeaderStyle-CssClass="sfItmTitleCol">
						<ItemTemplate>
							<div class="sfItmTitleWrp">
								<asp:HyperLink ID="productTitleLink" runat="server" Text='<%# Eval("Title") %>' CssClass="sfItmTitle" />
							</div>
							<asp:Label ID="OutOfStock" CssClass="sfItemOutOfStockMessage" Text="<%$Resources:OrdersResources, OutOfStock %>"
								Visible="false" runat="server" />
							<asp:Label ID="InventoryChange" CssClass="sfItmTitleInventoryChangeCol" Text="<%$Resources:OrdersResources, InventoryChange %>"
								Visible="false" runat="server" />
							<div class="sfItmLnksWrp">
								<asp:LinkButton ID="removeButton" runat="server" Text='<%$Resources:OrdersResources, Remove %>'
									CommandName="remove" CssClass="sfItmRemove" />
							</div>
						</ItemTemplate>
					</telerik:GridTemplateColumn>
					<telerik:GridTemplateColumn HeaderText='<%$Resources:OrdersResources, ProductOptions %>'
						UniqueName="Options" ItemStyle-CssClass="sfItmOptionsCol" HeaderStyle-CssClass="sfItmOptionsCol">
						<ItemTemplate>
							<div>
								<%# Eval("Options")%>
							</div>
						</ItemTemplate>
					</telerik:GridTemplateColumn>
					<telerik:GridTemplateColumn UniqueName="BasePrice" ItemStyle-CssClass="sfSingleItmPriceCol"
						HeaderStyle-CssClass="sfSingleItmPriceCol">
						<ItemTemplate>
							<sfCatalog:DisplayPriceField ID="displayPriceField" ObjectType="Cart" ObjectId='<%# Eval("Id") %>'
								runat="server" />
						</ItemTemplate>
					</telerik:GridTemplateColumn>
					<telerik:GridTemplateColumn UniqueName="ProductQuantity" HeaderText="<%$Resources:OrdersResources, Quantity %>"
						ItemStyle-CssClass="sfItmQuantityCol" HeaderStyle-CssClass="sfItmQuantityCol">
						<ItemTemplate>
							<span>x</span>
							<asp:HiddenField ID="cartDetailId" runat="server" />
							<asp:TextBox ID="quantity" runat="server" Text='<%# Eval("Quantity") %>' CssClass="sfTxt" />
							<asp:RangeValidator ID="quantityValidator" runat="server" MinimumValue="0" MaximumValue="9999"
								ControlToValidate="quantity" Type="Integer" Display="Dynamic" CssClass="sfErrorWrp">
								<span class="sfError">
									<asp:Literal ID="Literal2" runat="server" Text="<%$Resources: OrdersResources, ProductQuantityIsInvalidInShoppingCart %>" />
								</span>
							</asp:RangeValidator>
						</ItemTemplate>
					</telerik:GridTemplateColumn>
					<telerik:GridTemplateColumn UniqueName="NewPrice" HeaderText='<%$Resources:OrdersResources, Price %>'
						ItemStyle-CssClass="sfItmPriceCol" FooterStyle-CssClass="sfItmPriceCol" HeaderStyle-CssClass="sfItmPriceCol">
						<ItemTemplate>
							<asp:Label ID="newPriceLabel" runat="server" Text='<%# EcommerceControlExtensions.ToCurrencyString((decimal)Eval("DisplayTotal")) %>'
								CssClass="sfTxtLbl" />
						</ItemTemplate>
					</telerik:GridTemplateColumn>
				</Columns>
			</MasterTableView>
		</telerik:RadGrid>
		<asp:Panel ID="shoppingCartGridFooter" runat="server" CssClass="sfShoppingCartGridFooter sfClearfix">
			<asp:UpdatePanel runat="server" UpdateMode="Conditional" ID="outerCouponCodeUpdatePanel">
				<ContentTemplate>
					<div class="sfShoppingCartCouponEntryField">
						<orders:couponcodeentryview id="couponCodeEntryView" runat="server" ischangemode="False" />
					</div>
				</ContentTemplate>
			</asp:UpdatePanel>
			<asp:UpdatePanel ID="UpdatePanel1" runat="server">
				<ContentTemplate>
					<div class="sfShoppingCartTotal">
						<table class="sfShoppingCartDiscountList">
							<tbody>
								<tr runat="server" id="beforeDiscountRow">
									<th>
										<asp:Label ID="Label1" runat="server" Text="<%$ Resources:OrdersResources, BeforeDiscounts %>"
											CssClass="sfTxtLbl" />:
									</th>
									<td>
										<asp:Label ID="totalPrice" runat="server" Text="" CssClass="sfTxtLbl" />
									</td>
								</tr>
								<orders:discountlist runat="server" id="discountRows" />
							</tbody>
						</table>
					</div>
					<div class="sfTotalRowWrp">
						<asp:Label ID="productTotalQuantity" runat="server" />
						<asp:Label ID="subTotalLabel" runat="server" Text='<%$Resources:OrdersResources, Subtotal %>'
							CssClass="sfTxtLbl" />:&nbsp; <strong class="sfPriceTotal">
								<asp:Label ID="afterDiscountPrice" runat="server" Text="" CssClass="sfTxtLbl" /></strong>
					</div>
				</ContentTemplate>
			</asp:UpdatePanel>
			<asp:LinkButton ID="updateButton" Text="<%$Resources:OrdersResources, Update %>"
				runat="server" CssClass="sfshoppingCartUpdateLnk" />
		</asp:Panel>
		<asp:Panel ID="noProductsInShoppingCartPanel" runat="server" Visible="false" CssClass="sfNoProductsInCartMsg">
			<asp:Literal ID="Literal3" runat="server" Text='<%$Resources:OrdersResources, NoProductsInShoppingCart %>' />
		</asp:Panel>
		<div class="sfshoppingCartBtnsWrp sfClearfix">
			<asp:HyperLink ID="continueShoppingLink" runat="server" Text='<%$Resources:OrdersResources, ContinueShopping %>'
				NavigateUrl="#" CssClass="sfBackBtn" />
			<div id="checkoutButtonDiv" runat="server">
				<asp:Button ID="checkoutButton" runat="server" Text='<%$Resources:OrdersResources, Checkout %>'
					CssClass="sfCheckoutBtn" />
			</div>
		</div>
	</asp:PlaceHolder>
</div>

View Code Sample on Gist.

Next, add the event handler which extracts the CartDetail DataItem. Just like before, we need to make sure it is the correct type, and if so, check the Minimum and Maximum custom field properties, passing them to the validator to ensure that the quantities are enforced.

Here is the full source for the code-behind of the custom template.

	public partial class ShoppingCart : System.Web.UI.UserControl
	{
		protected void shoppingCartGrid_ItemDataBound(object sender, Telerik.Web.UI.GridItemEventArgs e)
		{
			if (e.Item.ItemType == Telerik.Web.UI.GridItemType.Item || e.Item.ItemType == Telerik.Web.UI.GridItemType.AlternatingItem)
			{
				var item = e.Item.DataItem as CartDetail;
				if (item == null) return;

				// Get the actual product
				var manager = CatalogManager.GetManager();
				var product = manager.GetProduct(item.ProductId);
				if (product == null) return;

				// only memberships have min/max
				var type = product.GetType().Name;
				if (!type.Contains("generalproduct")) return;

				var validation = e.Item.FindControl("quantityValidator") as RangeValidator;
				if (validation == null) return;

				validation.MinimumValue = product.GetValue("Minimum").ToString();
				validation.MaximumValue = product.GetValue("Maximum").ToString();
			}
		}
	}

View Code Sample on Gist.

Finally, build and restart the project, and your new template should enforce the validation just like the product page.

Sitefinity-Ecommerce-Shopping-Cart-Invalid-Quantity
The following two tabs change content below.

selaromdotnet

Senior Developer at iD Tech
Josh loves all things Microsoft and Windows, and develops solutions for Web, Desktop and Mobile using the .NET Framework, Azure, UWP and everything else in the Microsoft Stack. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom. His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom.