Important Notice from AspDotNetStorefront
It is with dismay that we report that we have been forced, through the action of hackers, to shut off write-access to this forum. We are keen to leave the wealth of material available to you for research. We have opened a new forum from which our community of users can seek help, support and advice from us and from each other. To post a new question to our community, please visit: http://forums.vortx.com
Results 1 to 3 of 3

Thread: Dynamic AJAX Product Page Pricing

  1. #1
    Rob is offline Senior Member
    Join Date
    Aug 2004
    Posts
    3,037

    Default Dynamic AJAX Product Page Pricing

    As a simple exercise, we added dynamic AJAX pricing to all product Page XmlPackages. This thread shows the mods we had to do, so it could be used for those developers customizing the cart in similar areas. This feature will show up out of the box also in future builds.

    The goal: As a size or color option is changed, if it has a price modifier on it, change the actual price shown on the product page.

    The complications: support all pricing models (regular, sale, extended, customer levels, wholesale, retail), multi-lingual fields and prompts, and multiple currencies, and still honor VAT inclusive and exclusive display mods.

    Chosen solution: Add AJAX callback with onChange handlers to Size & Color options on the product add to cart forms.

    So first, we had to add this onChange handlers to the Size & Color select lists on the addtocart forms (in ShoppingCart.cs):

    Code:
    ...onChange=\"if(typeof(getPricing) == 'function'){getPricing(" + ProductID.ToString() + "," + VariantID.ToString() + ")}\"...
    We then added a new getPricing routine to our /jscripts/revAjax.js AJAX handler:

    Code:
    function getPricing(ProductID,VariantID)
    {
    	//alert('VariantID=' + VariantID);
    	if(ProductID == undefined || VariantID == undefined)
    	{
    		return;
    	}
    
    	var ChosenSize = "";
    	var ChosenSizeList = document.getElementById('Size');
    	if(ChosenSizeList != undefined)
    	{
    		ChosenSize = ChosenSizeList.options[ChosenSizeList.selectedIndex].text;
    	}
    
    	var ChosenColor = "";
    	var ChosenColorList = document.getElementById('Color');
    	if(ChosenColorList != undefined)
    	{
    		ChosenColor = ChosenColorList.options[ChosenColorList.selectedIndex].text;
    	}
    
        var url = "ajaxPricing.aspx?ProductID=" + ProductID + "&VariantID=" + VariantID + "&size=" + escape(ChosenSize) + "&color=" + escape(ChosenColor);
    
        //alert("Ajax Url=" + url);
        makeHttpRequest(url,undefined,'pricing');
    }
    and updated the loadXml routine to handle the new 'pricing' callbacks:

    Code:
    function loadXML(xml,calltype)
    {
    	if(calltype == 'shipping')
    	{
    		var string = '';
    		var root = xml.getElementsByTagName('Shipping')[0];
    		for (i = 0; i < root.childNodes.length; i++)
    		{
        		var node = root.childNodes[i].tagName;
    		    string += root.getElementsByTagName(node)[0].childNodes[0].nodeValue + "<br />";
    		}
    		if (document.getElementById('ShipQuote'))
    		{
    			document.getElementById('ShipQuote').innerHTML = string;
    		}
    	}
    	if(calltype == 'pricing')
    	{
    		var prnode = xml.getElementsByTagName('PriceHTML')[0];
    		var variantnode = xml.getElementsByTagName('VariantID')[0];
    		var NewPrice = "Not Found";
    		var VariantID = "0";
    		if(prnode != undefined)
    		{
    			NewPrice = xml.getElementsByTagName('PriceHTML')[0].firstChild.data
    		}
    		if(variantnode != undefined)
    		{
    			VariantID = xml.getElementsByTagName('VariantID')[0].firstChild.data
    		}
    		//alert("VariantID=" + VariantID + ", NewPrice=" + NewPrice);
    		if (document.getElementById('VariantPrice_' + VariantID))
    		{
    			document.getElementById('VariantPrice_' + VariantID).innerHTML = NewPrice;
    		}
    	}
    }
    We then added this new HTML container around all pricing output in the product XmlPackages:

    Code:
    <span>
    <xsl:attribute name="id">VariantPrice_<xsl:value-of select="VariantID"/></xsl:attribute>
    <xsl:value-of select="aspdnsf:GetVariantPrice(VariantID, number(HidePriceUntilCart), Price, SalePrice, ExtendedPrice, Points, $pSalesPromptName, TaxClassID)" disable-output-escaping="yes" />
    </span>
    the only new part in that is the <span> tag so we can find the text to update that contains the price on the page. Each package is a little different, but the span elements are almost identical.

    Now the harder part, how to compute the price and factor in all those requirements (VAT INC, EX, Sale Prices, customer level wholesale pricing, multi-lingual, string resource changes to the sales prompts, and price prompts, etc) WITHOUT having to reinvent all that logic.

    We had to use the GetVariantPrice routine in XSLTExtensionBase.cs. That is the heavy lifter, and we don't want to reinvent all that stuff. To do this, we therefore added a new overload in XSLTExtensionbase.cs, to take in a "chosen attribute" price modifier:

    Code:
            public virtual string GetVariantPrice(String sVariantID, String sHidePriceUntilCart, string sPrice, string sSalePrice, string sExtPrice, String sPoints, string sSalesPromptName, String sShowpricelabel, string sTaxClassID, String sChosenAttributesPriceDelta)
            {
    ...
                Decimal ChosenAttributesPriceDelta = IV.ValidateDecimal("AttributesPriceDelta", sChosenAttributesPriceDelta);
    
                StringBuilder results = new StringBuilder(1024);
    
                Price += ChosenAttributesPriceDelta;
    ..
    }
    That's the only logic change we added to the new overload, and we now have the other routines calling this new "master" pricing routine, passing "0.00" as a value for attribute price deltas. That means, all prior XmlPackages will still work unchanged.

    The final piece was to add "ajaxPricing.aspx/cs" to the solution, which is the actual callback doing the work.

    Code:
    <%@ Page language="c#" Inherits="AspDotNetStorefront.ajaxPricing" CodeFile="ajaxPricing.aspx.cs" %>
    nothing really needed there, we're just sending back a small Xml doc to the ajax js handler.

    Next we just implement that page as a thin call to the existing XSLTExtensionBase routines, and we are pretty much done:

    Code:
    // ------------------------------------------------------------------------------------------
    // Copyright AspDotNetStorefront.com, 1995-2007.  All Rights Reserved.
    // http://www.aspdotnetstorefront.com
    // For details on this license please visit  the product homepage at the URL above.
    // THE ABOVE NOTICE MUST REMAIN INTACT. 
    // ------------------------------------------------------------------------------------------
    using System;
    using System.Web;
    using System.Data;
    using System.Xml;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Globalization;
    using AspDotNetStorefrontCommon;
    
    namespace AspDotNetStorefront
    {
        /// <summary>
        /// Summary description for search.
        /// </summary>
        public partial class ajaxPricing : System.Web.UI.Page
        {
    
            protected void Page_Load(object sender, System.EventArgs e)
            {
                Customer ThisCustomer = ((AspDotNetStorefrontPrincipal)Context.User).ThisCustomer;
    
                int ProductID = CommonLogic.QueryStringUSInt("ProductID");
                int VariantID = CommonLogic.QueryStringUSInt("VariantID");
                if (ProductID == 0)
                {
                    ProductID = AppLogic.GetVariantProductID(VariantID);
                }
                String ChosenSize = Regex.Replace(CommonLogic.QueryString("Size"), "\\s+", "");
                String ChosenSizeSimple = String.Empty;
                String ChosenColor = Regex.Replace(CommonLogic.QueryString("Color"), "\\s+", "");
                String ChosenColorSimple = String.Empty;
                Decimal SizePriceModifier = System.Decimal.Zero;
                Decimal ColorPriceModifier = System.Decimal.Zero;
    
                if (ChosenSize.IndexOf("[") != -1)
                {
                    int i1 = ChosenSize.IndexOf("[");
                    int i2 = ChosenSize.IndexOf("]");
                    if (i1 != -1 && i2 != -1)
                    {
                        SizePriceModifier = Localization.ParseNativeDecimal(ChosenSize.Substring(i1 + 1, i2 - i1 - 1).Replace("$","").Replace("+","").Replace("-",""));
                    }
                    ChosenSizeSimple = ChosenSize.Substring(0, i1);
                }
                if (ChosenColor.IndexOf("[") != -1)
                {
                    int i1 = ChosenColor.IndexOf("[");
                    int i2 = ChosenColor.IndexOf("]");
                    if (i1 != -1 && i2 != -1)
                    {
                        ColorPriceModifier = Localization.ParseNativeDecimal(ChosenColor.Substring(i1 + 1, i2 - i1 - 1).Replace("$", "").Replace("+", "").Replace("-", ""));
                    }
                    ChosenColorSimple = ChosenSize.Substring(0, i1);
                }
    
                Decimal FinalPriceDelta = SizePriceModifier + ColorPriceModifier;
    
                String NewPRText = "Not Available";
    
                XSLTExtensionBase xslte = new XSLTExtensionBase(ThisCustomer, ThisCustomer.SkinID);
    
                IDataReader rs = DB.GetRS("select p.*, pv.VariantID, pv.name VariantName, pv.Price, pv.Description VariantDescription, isnull(pv.SalePrice, 0) SalePrice, isnull(SkuSuffix, '') SkuSuffix, pv.Dimensions, pv.Weight, isnull(pv.Points, 0) Points, sp.name SalesPromptName, case when pcl.productid is null then 0 else isnull(e.Price, 0) end ExtendedPrice FROM Product p join productvariant pv on p.ProductID = pv.ProductID join SalesPrompt sp on p.SalesPromptID = sp.SalesPromptID left join ExtendedPrice e on pv.VariantID=e.VariantID and e.CustomerLevelID=" + ThisCustomer.CustomerLevelID.ToString() + " left join ProductCustomerLevel pcl with (NOLOCK) on p.ProductID = pcl.ProductID and pcl.CustomerLevelID = " + ThisCustomer.CustomerLevelID.ToString() + " WHERE p.ProductID = " + ProductID.ToString() + " and p.Deleted = 0 and pv.Deleted = 0 and p.Published = 1 and pv.Published = 1 ORDER BY p.ProductID, pv.DisplayOrder, pv.Name");
                if (rs.Read())
                {
                    NewPRText = xslte.GetVariantPrice(VariantID.ToString(),
                        CommonLogic.IIF(DB.RSFieldBool(rs, "HidePriceUntilCart"), "1", "0"),
                        Localization.DecimalStringForDB(DB.RSFieldDecimal(rs, "Price")),
                        Localization.DecimalStringForDB(DB.RSFieldDecimal(rs, "SalePrice")),
                        Localization.DecimalStringForDB(DB.RSFieldDecimal(rs, "ExtendedPrice")),
                        DB.RSFieldInt(rs, "Points").ToString(),
                        DB.RSField(rs, "SalesPromptName"),
                        "1",
                        DB.RSFieldInt(rs, "TaxClassID").ToString(),
                        Localization.DecimalStringForDB(FinalPriceDelta));
                }
                rs.Close();
    
                Response.Expires = -1;
                Response.ContentType = "application/xml";
                XmlWriter writer = new XmlTextWriter(Response.OutputStream, System.Text.Encoding.UTF8);
                writer.WriteStartDocument();
                writer.WriteStartElement("root");
    
                writer.WriteElementString("CustomerID", ThisCustomer.CustomerID.ToString());
                writer.WriteElementString("CustomerLevelID", ThisCustomer.CustomerLevelID.ToString());
                writer.WriteElementString("ProductID", ProductID.ToString());
                writer.WriteElementString("VariantID", VariantID.ToString());
    
                writer.WriteStartElement("Size");
                writer.WriteElementString("Input", ChosenSize);
                writer.WriteElementString("Text", ChosenSizeSimple);
                writer.WriteElementString("Delta", Localization.DecimalStringForDB(SizePriceModifier));
                writer.WriteEndElement();
    
                writer.WriteStartElement("Color");
                writer.WriteElementString("Input", ChosenColor);
                writer.WriteElementString("Text", ChosenColorSimple);
                writer.WriteElementString("Delta", Localization.DecimalStringForDB(ColorPriceModifier));
                writer.WriteEndElement();
    
                writer.WriteElementString("ChosenAttributesPriceDelta", Localization.DecimalStringForDB(FinalPriceDelta));
                writer.WriteElementString("PriceHTML", NewPRText);
                writer.WriteEndElement();
                writer.WriteEndDocument();
                writer.Flush();
            }
    
        }
    }
    There were a few other minor mods, but that's about it...Hope this gives others insight on how we do customizations/new feature additions.

    Maybe next up is our AJAX add to cart handler
    Last edited by Rob; 03-25-2007 at 06:24 PM.

  2. #2
    cablesforless is offline Member
    Join Date
    Oct 2006
    Posts
    32

    Default Additional Method using jQuery

    if you use a javascript library you can get the methods much easier. Here is an example that we are currently developing.

    First, download and include jquery in your template.ascx file:
    Code:
    <script language="javascript" src="skins/skin_1/jquery.js"></script>
    Then, include the following below your call for the jQuery file.
    Code:
    <script language="javascript">
    $(document).ready(function(){
        	$('.ProductNameText').after('<br/><div id="ShipRates">ZipCode:&nbsp;<input type="input" id="InputShipRates"/><br /><input type="button" id="BtnShipRates" value="Calculate Shipping"/></div>');
    	$('#BtnShipRates').click(function(){
    		var qty = $('#Quantity').val();
    		$.ajax({
    			type: "GET",
    			url: "ajaxShipping.aspx",
    			data:	"ProductID=" + $('#ProductID').val() + "&PostalCode=" + $('#InputShipRates').val() + "&Quantity=" + qty,
    			dataType: "xml",
    			success: function(xml){
    				//This function will loop through results.
    				$('#ShipRates').html('<i>Shipping for this product only.</i><br />');
    				
    				var Methods = $("Shipping",xml).children();
    			
    				$(Methods).each(function(){
    					$('#ShipRates').append($(this).text()+"<br />");
    				});
    		}});
    	}); 
    });
    </script>
    Last edited by cablesforless; 03-10-2008 at 02:20 PM. Reason: Update to code

  3. #3
    smoreo is offline Member
    Join Date
    Nov 2009
    Location
    Brooklyn, NY
    Posts
    36

    Question Change Image also?

    Hi, i am looking for something that will allow me to click on the variant icons and change the product image(on product page) to the variant's image (color in this case). Do you think approach this would work for that as well?
    Last edited by smoreo; 04-06-2010 at 06:25 AM.