So... I rewrote ProductTemplate.SingleVariant.cshtml to work with multiple product variants.
If more than one product variant exists it shows a drop-down list with product variant names... when one is selected it changes the image, price, Add to Cart button, and other variant information using jQuery
If only one product variant exists then it doesn't show the drop-down list.
This is my first code for nopCommerce... if anyone would like to use it and/or improve on it that would be great.
If someone would like to make it a plugin or add it to nopCommerce itself as a new ProductTemplate called "MultipleVariant" or something... even better (as this is beyond my current skill at the moment).
- Brian
Note: updated code with some bug fixes 4/1/2013 6:06pm CST
@model ProductDetailsModel
@using Nop.Core.Domain.Seo;
@using Nop.Core.Infrastructure;
@using Nop.Web.Models.Catalog;
<script type="text/javascript">
function showHideProductVariant() {
var name = '';
$('#product-variant-combobox option:selected').each(function () {
$('#product-variant-combobox').data("prev", $(this).val());
var str = $(this).val();
var arr = str.split('~');
name = '.variant-specific-name-' + arr[0];
});
if (!$(name).is(":visible")) {
$('.variant-specific').hide();
$(name).show();
}
$('#product-variant-combobox option:selected').each(function () {
$('#product-variant-combobox').data("prev", $(this).val());
});
}
$(document).ready(function () {
$('#product-variant-combobox').change(function (data) {
// move quantity over to new variant
var prev = $(this).data("prev");
var next = '';
$('#product-variant-combobox option:selected').each(function () {
next = $(this).val();
});
var prevId = '#addtocart_' + prev.split('~')[1] + '_EnteredQuantity';
var nextId = '#addtocart_' + next.split('~')[1] + '_EnteredQuantity';
$(nextId).val($(prevId).val());
// do the change
showHideProductVariant();
});
showHideProductVariant(); // firefox cache's selections... so update in case not default value
});
</script>
@functions{
public int GetProductVariantIndexFromSeoName(IList<ProductDetailsModel.ProductVariantModel> productVariantModels, string seoName) {
int n = 0;
foreach (var variant in productVariantModels)
{
if (Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) == seoName)
{
return n;
}
n++;
}
return -1;
}
public int GetProductVariantIdFromSeoName(IList<ProductDetailsModel.ProductVariantModel> productVariantModels, string seoName)
{
foreach (var variant in productVariantModels)
{
if (Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) == seoName)
{
return variant.Id;
}
}
return -1;
}
public string SelectedIfEqual(object obj1, object obj2)
{
if (obj1.Equals(obj2))
return " selected";
return "";
}
}
@{
Layout = "~/Views/Shared/_ColumnsTwo.cshtml";
//title, meta
Html.AddTitleParts(!String.IsNullOrEmpty(Model.MetaTitle) ? Model.MetaTitle : Model.Name);
Html.AddMetaDescriptionParts(Model.MetaDescription);
Html.AddMetaKeywordParts(Model.MetaKeywords);
var canonicalUrlsEnabled = EngineContext.Current.Resolve<SeoSettings>().CanonicalUrlsEnabled;
if (canonicalUrlsEnabled)
{
var productUrl = Url.RouteUrl("Product", new { SeName = Model.SeName }, this.Request.Url.Scheme);
Html.AddCanonicalUrlParts(productUrl);
}
Html.AddScriptParts("~/Scripts/slimbox2.js");
int productPerRow = 6;
}
@{
var variantIndex = GetProductVariantIndexFromSeoName(Model.ProductVariantModels, Request["variant"]);
if (variantIndex < 0)
{
variantIndex = 0;
}
var defaultProductVariant = Model.ProductVariantModels.Count > 0 ? Model.ProductVariantModels[variantIndex] : null;
}
<!--product breadcrumb-->
@Html.Action("ProductBreadcrumb", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
<div class="page product-details-page">
<div class="page-body">
@Html.Widget("productdetails_top")
@using (Html.BeginRouteForm("Product", new { SeName = Model.SeName }, FormMethod.Post, new { id = "product-details-form" }))
{
<div class="product-essential">
@Html.Widget("productdetails_before_pictures")
@if (Model.ProductVariantModels.Count == 1)
{
@Html.Partial("_ProductDetailsPictures", Model) // replace with our code for each variant
}
else
{
foreach (var variant in Model.ProductVariantModels)
{
if (variant == defaultProductVariant)
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "'>");
}
else
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "' style='display:none'>");
}
<!--product pictures-->
<div class="picture">
@if (Model.DefaultPictureZoomEnabled)
{
<a href="@variant.PictureModel.FullSizeImageUrl" data-gallery="lightbox-pd" title="@Model.Name">
<img alt="@variant.PictureModel.AlternateText" src="@variant.PictureModel.FullSizeImageUrl" title="@variant.PictureModel.Title" />
</a>
}
else
{
<img alt="@variant.PictureModel.AlternateText" src="@variant.PictureModel.FullSizeImageUrl" title="@variant.PictureModel.Title" />
}
@if (Model.PictureModels.Count > 1)
{
<table class="picture-thumbs">
@for (int i = 0; i < Model.PictureModels.Count; i++)
{
var picture = Model.PictureModels[i];
if (i % productPerRow == 0)
{
@Html.Raw("<tr>")
}
<td class="a-left">
<a href="@picture.FullSizeImageUrl" data-gallery="lightbox-p" title="@Model.Name">
<img src="@picture.ImageUrl" alt="@picture.AlternateText" title="@picture.Title" />
</a>
</td>
if ((i % productPerRow == (productPerRow - 1)) ||
//last image
(i == (Model.PictureModels.Count - 1)))
{
@Html.Raw("</tr>")
}
}
</table>
}
</div>
@Html.Raw("</div>");
}
}
@Html.Widget("productdetails_after_pictures")
<div class="overview">
<h1 class="product-name">
@Model.Name
</h1>
<div class="short-description">
@Html.Raw(Model.ShortDescription)
</div>
<div class="clear">
</div>
@Html.Widget("productdetails_overview_top")
@foreach (var variant in Model.ProductVariantModels)
{
if (variant == defaultProductVariant)
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "'>");
}
else
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "' style='display:none'>");
}
<!--product SKU, manufacturer part number, stock info-->
@Html.Partial("_ProductVariant_SKU_Man_Stock", variant)
<div class="clear">
</div>
<!--Back in stock subscription-->
@Html.Partial("_ProductVariantBackInStockSubscription", variant)
<div class="clear">
</div>
@Html.Raw("</div>");
}
<!--product manufacturers-->
@Html.Action("ProductManufacturers", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
<!--product reviews-->
@Html.Action("ProductReviewOverview", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
@if (Model.ProductVariantModels.Count > 1)
{
<div id="product-variant-combobox" class="product-variant-combobox"><strong>@Html.Raw(T("admin.telerik.gridlocalization.select")): </strong>
<select>
@foreach (var variant in Model.ProductVariantModels)
{
<option value="@Html.Raw(Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name))[email protected](variant.Id)"@Html.Raw(SelectedIfEqual(defaultProductVariant, variant))>@Html.Raw(variant.Name)</option>
}
</select>
</div>
<div class="clear">
</div>
}
@foreach (var variant in Model.ProductVariantModels)
{
if (variant == defaultProductVariant)
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "'>");
}
else
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "' style='display:none'>");
}
<!--sample download-->
@Html.Partial("_DownloadSample", variant)
<div class="clear">
</div>
var dataDictPrice = new ViewDataDictionary();
dataDictPrice.TemplateInfo.HtmlFieldPrefix = string.Format("price_{0}", variant.Id);
@Html.Partial("_ProductVariantPrice", variant.ProductVariantPrice, dataDictPrice)
var dataDictAddToCart = new ViewDataDictionary();
dataDictAddToCart.TemplateInfo.HtmlFieldPrefix = string.Format("addtocart_{0}", variant.Id);
@Html.Partial("_ProductVariantAddToCart", variant.AddToCart, dataDictAddToCart)
@Html.Raw("</div>");
}
@Html.Action("ProductEmailAFriendButton", "Catalog", new { productId = Model.Id })
@Html.Action("CompareProductsButton", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
@Html.Action("ShareButton", "Catalog")
@Html.Widget("productdetails_overview_bottom")
</div>
<div class="full-description">
@Html.Raw(Model.FullDescription)
</div>
</div>
<div class="clear">
</div>
<div class="product-collateral">
@foreach (var variant in Model.ProductVariantModels)
{
if (variant == defaultProductVariant)
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "'>");
}
else
{
@Html.Raw("<div class='variant-specific variant-specific-name-" + Nop.Services.Seo.SeoExtensions.GetSeName(variant.Name) + "' style='display:none'>");
}
<div class="product-variant-line">
<!--product tier prices-->
@Html.Action("ProductTierPrices", "Catalog", new { productVariantId = variant.Id })
<div class="clear">
</div>
@{
var dataDictAttributes = new ViewDataDictionary();
dataDictAttributes.TemplateInfo.HtmlFieldPrefix = string.Format("attributes_{0}", variant.Id);
@Html.Partial("_ProductAttributes", variant.ProductVariantAttributes, dataDictAttributes)
}
<div class="clear">
</div>
@{
var dataDictGiftCard = new ViewDataDictionary();
dataDictGiftCard.TemplateInfo.HtmlFieldPrefix = string.Format("giftcard_{0}", variant.Id);
@Html.Partial("_GiftCardInfo", variant.GiftCard, dataDictGiftCard)
}
</div>
@Html.Raw("</div>")
}
@Html.Action("ProductSpecifications", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
@Html.Action("ProductTags", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
@Html.Action("ProductsAlsoPurchased", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
@Html.Action("RelatedProducts", "Catalog", new { productId = Model.Id })
<div class="clear">
</div>
</div>
}
@Html.Widget("productdetails_bottom")
</div>
</div>