Product Variant Attributes for a Book Store

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
11 лет назад
I'm putting together an online book store and I was hoping to user Product Variant Attributes for Format (Hardcover, Paperback, PDF, Kindle, etc...) and for Language (English, Spanish, etc...).

However it seems that since one format is downloadable and another is not... that I can't do this... because the downloadable checkbox is at the Product Variant level and not the Product Variant Attribute level.

And... further... I can't even add two Product Variants, say Print and Digital... because if I have Language as a Product Variant Attribute... this would be fine for print... but for digital I can't do this... because the file upload is at the Product Variant and not the Product Variant Attribute level.

So I would have to add the following variants to a product:

Print    ... this would have Format (Paperback and Hardcover) and Language (English and Spanish) attributes
PDF - English
PDF - Spanish
Kindle - English
Kindle - Spanish
... and any additional digital formats

Am I correct in all the above... if so, this is really a shame, because it makes my store very awkward with many, many variants.

Any thoughts?
11 лет назад
It would even help if there was another Product template in addition to "variants in grid" and "single product variant" something like "choose product variant" where it would give you a combobox of all the product variants.
11 лет назад
BrianARice wrote:
It would even help if there was another Product template in addition to "variants in grid" and "single product variant" something like "choose product variant" where it would give you a combobox of all the product variants.

In your case you should be using product variants and not attributes. Unfortunately there is no template available for displaying the variants in combo box.
11 лет назад
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>
11 лет назад
Also, this code will handle an optional url parameter "variant" which will use the SEO friendly string of the variant, for example:


http://site.com/en/you-can-overcome-fear?variant=pdf-spanish

where "You Can Overcome Fear!" is the product name.

and "PDF - Spanish" is the variant name.
11 лет назад
It looks great! I don't have enough coding experience, but make sure it would not be affected if you use the old price, special price or discounts on product variants or categories.
11 лет назад
Those should work... I wrapped everything that was using defaultProductVariant I wrapped with:

@foreach (var variant in Model.ProductVariantModels)
{
}

and used variant instead of defaultProductVariant... and then have classes that hide and show the correct variant types.
10 лет назад
THANK YOU SO MUCH !!!!!!!!!!!!
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.