Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET

ASP.NET e-Commerce GridView with Product Comparisons

Rate me:
Please Sign up or sign in to vote.
4.86/5 (21 votes)
4 Oct 2010CPOL5 min read 100.3K   6.3K   63   15
Demonstrates an e-commerce style grid with product comparison checkboxes, sorting, images in a database, expandible/collapsible product summary, selectable row display, and product comparison page.

Introduction

e-Commerce web applications are everywhere these days, and many share a common set of functionality. In this article, I will show how to use the GridView and ListView controls to build a powerful product page with many of the features found on today's e-commerce sites. We'll build a bicycle store product grid using some free clip art bicycle images. The example files are user controls which can be easily added to a page. We're only using three images here to keep the size of the sample application small.

Product Grid

Background

This article assumes the reader has some familiarity with data binding the GridView and ListView controls using the ObjectDataSource control.

The Database

A product page that does product comparisons requires at least three tables: one to hold the product data, and two to support the product comparison functionality. In this example, the product images are stored in the database, and an image handler is used to retrieve them. The images must be inserted in the database table from an application; you cannot add an image to a table from the SQL console. The example project includes a console app that will put three images in the database. Generally, there are at least two images necessary: a small one for the grid and comparison page, and a larger image for the product detail page. The rest of the table rows will consist of whatever information is desired for the product details page.

The product comparison functionality requires two tables: one to hold a unique key to distinguish sessions, and another to hold the user's selected items associated with the unique key. The domain-layer code uses ADO.NET to invoke Stored Procedures to display the products using pagination. The product comparison is handled through JavaScript, which counts up all the checkboxes in the checked state, creates a cookie to hold them, and invokes the comparison page with a query string of IDs. The ObjectDataSource on the comparison page has a "SelectMethod" attribute that points to a method which is called when the page is loaded. The ObjectDataSource.Select method invokes a Stored Procedure to retrieve the information for the compared products, and handles the paging of the items if the item count exceeds the ListView's GroupItemCount. To run the example, first execute the database script, "BicycleDatabase.sql", found in the "Database Script" folder of the DatabaseImageInsert project in SQL Server. Then set the DatabaseImageInsert project as the StartUp project and execute it. This will put the product images in the database.

The Database Image Application

There are many examples which show how to select an image from disk and store it in a database. I found the code for that approach didn't work when the images are read directly from a directory; i.e., I couldn't read the image back with the image handler. Reading images from disk requires that a Bitmap object be instantiated and read into a MemoryStream. From the Stream, you get a byte array which is given to the SqlCommand object.

The Product Grid

The product grid uses a GridView with an ObjectDataSource implemented in a user control. We must implement two methods in our domain class for SelectCountMethod and SelectMethod. The grid will support paging and sorting, so those properties are set to true. We set the DataKeyNames to ProductID for the primary key field of our data source, and DataSourceID to the ID of our ObjectDataSource. The img element is filled by an image handler which is given the ProductID of an item.

ASP.NET
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductGrid.ascx.cs" 
       Inherits="eCommerceProductGrid.UserControls.ProductGrid" %>
<%@ Register assembly="System.Web.Extensions, Version=3.5.0.0, 
            Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       namespace="System.Web.UI.WebControls" tagprefix="asp" %>

<div style="float: left;">
  <asp:Label ID="PageResultsLabel" CssClass="resultPage" runat="server" 
    Width="100%" EnableViewState="False"></asp:Label>
</div>
<br />
<div style="padding-top: 5px; padding-bottom: 5px;">
  <asp:Button ID="btnCompare" runat="server" CssClass="buttonStyle" 
      Text="Compare Checked Items" Height="25" Width="190" EnableViewState="False" />
  <asp:Label ID="ComparisonError" runat="server" OnLoad="ComparisonError_Load" 
      CssClass="errorMessage" EnableViewState="False" />
    <div style="float: right; margin-right: 20%; border: 1px solid black;">
      <asp:DropDownList ID="GridPageNumberList" runat="server" Enabled="true" 
           AutoPostBack="True" 
           OnSelectedIndexChanged="GridPageNumberList_SelectedIndexChanged">
        <asp:ListItem Value="5">Display 5 per page</asp:ListItem>
        <asp:ListItem Value="7">Display 7 per page</asp:ListItem>
      </asp:DropDownList>
  </div>
</div>

<asp:ObjectDataSource ID="SourceBicycles"
                      runat="server"
                      EnablePaging="True"
                      SelectCountMethod="CountBicycles"
                      SelectMethod="GetBicycleGridDetails"
                      SortParameterName="SortExpression"
                      TypeName="eCommereProductGrid.Domain.BicycleProductDB">
</asp:ObjectDataSource>

<asp:GridView ID="gridBicycles"
              runat="server"
              AllowPaging="True"
              AllowSorting="True"
              AutoGenerateColumns="False"
              BackColor="White"
              CellPadding="0"
              DataKeyNames="ProductID"
              DataSourceID="SourceBicycles"
              EmptyDataText="No data returned from query"
              EnableViewState="false"
              Font-Names="Verdana"
              Font-Size="Small"
              GridLines="Horizontal"
              OnDataBound="GridBicycles_DataBound"
              OnPageIndexChanging="GridBicycles_PageIndexChanging" 
              OnRowDataBound="GridBicycles_RowDataBound"
              PageSize="5"
              Width="80%">
  <FooterStyle BackColor="GhostWhite" BorderColor="Gainsboro" ForeColor="Blue" />
  <HeaderStyle BackColor="GhostWhite" BorderColor="Gainsboro" 
      ForeColor="Blue" HorizontalAlign="Center" VerticalAlign="Middle" />
  <PagerSettings Position="TopAndBottom" FirstPageText="First" 
      LastPageText="Last" Mode="NumericFirstLast" />
  <PagerStyle BackColor="GhostWhite" BorderColor="Gainsboro" 
      ForeColor="Black" HorizontalAlign="Center" VerticalAlign="Middle"/>
  <RowStyle BackColor="White" ForeColor="Black" />

  <Columns>
    <asp:BoundField DataField="ProductID" />

    <asp:TemplateField ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Middle">
      <ItemTemplate>
        <input type="checkbox" id="checkBox" name="myCheckBox" runat="server" onclick="" 
                value='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>' />
        <br />
        <b>Compare</b>
      </ItemTemplate>
      <ItemStyle HorizontalAlign="Center" VerticalAlign="Middle" Width="65px" />
    </asp:TemplateField>

    <asp:TemplateField>
      <ItemTemplate>
        <div style="padding-left: 15px; padding-right: 15px;">
          <a href='<%# DataBinder.Eval(Container.DataItem, 
                 "ProductGridLinkPage") %>?ProductID=
                 <%# DataBinder.Eval(Container.DataItem, "ProductID") %>'>
              <img src='DatabaseImageHandler.ashx?ID=<%# Eval("ProductID") %>' 
                 alt='<%# DataBinder.Eval(Container.DataItem, "BrandName") %> 
                      <%# DataBinder.Eval(Container.DataItem, "ModelName") %>'
                 border="0" />
          </a>
        </div>
      </ItemTemplate>
    </asp:TemplateField>

    <asp:TemplateField HeaderText="Sort" SortExpression="LinkTitle">
      <ItemTemplate>
        <table>
          <tr>
            <td>
              <a href='<%# DataBinder.Eval(Container.DataItem, 
                 "ProductGridLinkPage") %>;?productID=<%# DataBinder.Eval(
                    Container.DataItem, "ProductID") %>'>
              <%# DataBinder.Eval(Container.DataItem, "LinkTitle")%>
            </td>
          </tr>
          <tr>
            <td>
              <div id="summary" runat="server">
                <div id="expandedDiv" runat="server" style="display: none">
                  <asp:Label ID="summaryExpanded" Runat="server" 
                        Text='<%# Eval("Summary") %>'></asp:Label>
                </div>
                <div id="collapsedDiv" runat="server">
                  <asp:Label ID="summaryCollapsed" Runat="server" 
                       Text='<%# Eval("Summary") %>'></asp:Label>
                </div>
                <span id="genericControl" runat="server"></span>
            </div>
            </td>
          </tr>
          <tr>
              <td>
                <asp:Label ID="standardCost" Runat="server" 
                   CssClass="gridStandardPrice" 
                   Text='<%# Eval("MSRP", "Price: {0:C}") %>'></asp:Label>
              </td>
          </tr>
          <tr>
              <td>
                <asp:Label ID="listPrice" Runat="server" 
                  Text='<%# Eval("ListPrice", "MSRP: {0:C}") %>'></asp:Label>
              </td>
          </tr>
        </table>
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>

The Page_Load event for the ProductGrid calls the JavaScript function getCheckedBoxes() which collects the IDs of the CheckBoxes that are checked and stores them in a cookie. The Compare button calls the JavaScript function compareButtonClicked() which reads the checked items from the cookie and calls the ProductComparison.aspx page with a query string consisting of each of the checked item IDs. The GridView's RowDataBound event handler sets up the dynamic product summary text DIVs to expand and contract. The GridView DataBound event handler sets up everything to do the product comparisons by assigning the JavaScript method to the "Compare" button and setting up the page number label.

C#
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using eCommereProductGrid.Domain;

namespace eCommerceProductGrid.UserControls
{
    public partial class ProductGrid : UserControl
    {
        #region Fields
        private const string CHECKBOX_ATTRIBUTE = "ID";
        public const string CHECKBOX_STATE_SCRIPT = 
           "<script type='text/javascript'>getCheckedBoxes()</script>";
        public const string COMPARE_ITEMS_FUNCTION = "compareCheckedItems(this,'{0}')";
        public const string COMPARISON_RESULTS_TEXT = "Results {0} - {1} of {2}";
        private const int PRODUCT_SUMMARY_TEXT_WIDTH = 75;
        private int pagingIndex;
        #endregion

        #region Event Handlers
        protected void Page_Load(object sender, EventArgs e)
        {
            ClientScriptManager csm = Page.ClientScript;
            if (!csm.IsStartupScriptRegistered(GetType(), "CheckBoxState"))
            {
                csm.RegisterStartupScript(GetType(), "CheckBoxState",
                    CHECKBOX_STATE_SCRIPT);
            }
        }

        protected void GridPageNumberList_SelectedIndexChanged(object sender, EventArgs e)
        {
            gridBicycles.PageSize = Convert.ToInt32(GridPageNumberList.SelectedValue);
            gridBicycles.DataBind();
        }

        protected void ComparisonError_Load(object sender, EventArgs e)
        {   // Get a reference to the Comparison Error Message Label
        // to issue error message if less than 2 items are checked
            ComparisonError = sender as Label;
        }

        protected void GridBicycles_DataBound(object sender, EventArgs e)
        {
            foreach (GridViewRow row in gridBicycles.Rows)
            {
                // The ID column is necessary
                // for the comparisons checkbox. Remove it from view.
                row.Cells[0].Visible = false;
            }

            btnCompare.Attributes.Add("onclick", "compareButtonClicked();return false;");
            
            int maxRecords = GridRecordCount();
            int pageRecordCount = (pagingIndex + 1) * gridBicycles.PageSize;

            if (pageRecordCount > maxRecords)
            {
                pageRecordCount = maxRecords;
            }

            PageResultsLabel.Text = String.Format(COMPARISON_RESULTS_TEXT,
                pagingIndex * gridBicycles.PageSize + 1, pageRecordCount, maxRecords);
        }

        protected void GridBicycles_PageIndexChanging(object sender,
            GridViewPageEventArgs e)
        {
            pagingIndex = e.NewPageIndex;
        }

        protected void GridBicycles_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                TableCell cell = e.Row.Cells[2];
                Label summaryExpanded = (Label)cell.FindControl("summaryExpanded");
                HtmlGenericControl theCollapsedDiv = 
                   (HtmlGenericControl)cell.FindControl("collapsedDiv");
                HtmlGenericControl theExpandedDiv = 
                   (HtmlGenericControl)cell.FindControl("expandedDiv");

                string scriptCollapse =
                    "( <span class=\"caretback\">«</span><" + 
                    "script language="\""javascript\" type=\"text/javascript\">" +
                    "if(document.getElementById){document.writeln('<a " + 
                    "href=\"javascript:void(0)\" onclick=\"return" +
                    " toggleDisplay(' + \"'" + theCollapsedDiv.ClientID + 
                    "'\" + ')\"> less</a>');}</script>)";

                string summaryExpandedText = summaryExpanded.Text;
                summaryExpanded.Text = summaryExpandedText + scriptCollapse;

                Label summaryCollapsed = (Label)cell.FindControl("summaryCollapsed");
                string originalText = summaryCollapsed.Text;

                if (originalText.Length > PRODUCT_SUMMARY_TEXT_WIDTH)
                {
                    String text = originalText.Substring(
                        PRODUCT_SUMMARY_TEXT_WIDTH - 1, 15);
                    int indexOfFirstBlank = text.IndexOf(' ');
                    if (indexOfFirstBlank != -1)
                    {
                        string truncatedText = originalText.Substring(0, 
                               PRODUCT_SUMMARY_TEXT_WIDTH + indexOfFirstBlank - 1);
                        string scriptExpand = "( <span class=\"caret" + 
                            "back\">»</span><script language="\""javascript\" " +
                            "type=\"text/javascript\">if(document" + 
                            ".getElementById){document.writeln('<a" +
                            " href=\"javascript:void(0)\" onclick=\"return " + 
                            "toggleDisplay(' + \"'" +
                            theExpandedDiv.ClientID + "'\" + 
                            ')\"> more</a>');}</script>)";
                        summaryCollapsed.Text = truncatedText + "..." + scriptExpand;
                    }
                }
                HtmlGenericControl regGeneric = 
                  (HtmlGenericControl)cell.FindControl("genericControl");
                regGeneric.InnerHtml = 
                  "<script language="\""javascript\" type" + 
                  "=\"text/javascript\">registerToggle('" +
                  theCollapsedDiv.ClientID + "', '" + 
                  theExpandedDiv.ClientID.ToString() + "');</script>";

                // Add product ID to the row's checkBox so it can be uniquely identified
                HtmlInputCheckBox cb = (HtmlInputCheckBox)cell.FindControl("checkBox");
                eCommereProductGrid.Domain.BicycleProduct gp = 
                   (e.Row.DataItem as eCommereProductGrid.Domain.BicycleProduct);
                cb.Attributes.Add(CHECKBOX_ATTRIBUTE, gp.ProductID.ToString());
                cb.Attributes.Add("onclick", 
                   String.Format(COMPARE_ITEMS_FUNCTION, gp.ProductID));
            }
        }
        #endregion

        #region Properties
        #endregion

        #region Private Methods
        private int GridRecordCount()
        {
            return BicycleProductDB.CountBicycles();
        }
        #endregion
    }
}

When the Compare Items button is clicked, the function compareButtonClicked() is called which creates a cookie to hold the CheckBoxes in the checked state.

C#
function createCookie(name, value, days)
{
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = "; expires="+date.toGMTString();
    }
    else {
        var expires = "";
    }
    document.cookie = name+"="+value+expires+"; path=/";
}

function getCookie(name)
{
    var nameFormatted = name+"=";
    var cookies = document.cookie.split(';');
    
    for(var i=0; i < cookies.length; i++) {
        var cookie = cookies[i];
        while (cookie.charAt(0) == ' ')
            cookie = cookie.substring(1, cookie.length);

        if (cookie.indexOf(nameFormatted) == 0)
            return cookie.substring(nameFormatted.length, cookie.length);
    }
    
    return null;
}

function compareCheckedItems(checkBox, itemno)
{
    var compareCookie = getCookie('compareCookie');
    var comparedItems = new Array();

    if (checkBox.checked == true) {
        if (compareCookie) {
            comparedItems = compareCookie.split(',');
            comparedItems.unshift(itemno);

            if (comparedItems.length > 12) {
                comparedItems.pop();
            }

            compareCookie = comparedItems.join(',');
            createCookie('compareCookie', compareCookie, 0);
        }
        else {
            createCookie('compareCookie', itemno, 0);
        }
    }
    else if (checkBox.checked == false) {
        comparedItems = compareCookie.split(',');

        for(var i = 0; i <= comparedItems.length-1; i++) 
        {
            if (comparedItems[i] == itemno) {
                comparedItems.splice(i,1);
                compareCookie = comparedItems.join(',');
                createCookie('compareCookie', compareCookie, 0);
            }
        }
    }
}

function compareButtonClicked()
{
    var compareCookie = getCookie('compareCookie');
    var isValidSelection = false;

    if (compareCookie) {
        var comparedItems = new Array();
        comparedItems = compareCookie.split(',');

        if (comparedItems.length > 1) {
            isValidSelection = true;
        }
    }

    if (isValidSelection) {
        var queryString = "";
        for (var i = 0; i < comparedItems.length; i++) {
            var item = "ID=" + comparedItems[i];
            if (i != comparedItems.length - 1)
                queryString = queryString + item + "&";
            else
                queryString = queryString + item;
        }

        if (document.domain == "localhost")
          window.location.href = 'http://localhost:49625/ComparePage.aspx?' + 
                                 queryString;
        else
            window.location.href = 'http://' + document.domain + 
                                   '/ComparePage.aspx?' + queryString;
    }
    else
    {
        alert('Please select at least two items to compare.');
    }
}

function makeNewCookie() {
    var compareCookie = "";
    createCookie('compareCookie', compareCookie, 0);
}

function getCheckedBoxes() {
    var mc = getCookie('compareCookie');
    if (mc) {
        var checkedItems = new Array();
        checkedItems = mc.split(',');
        var checkBoxes = new Array();
        checkBoxes = document.getElementsByTagName('input');
        for (var i = 0; i <= checkedItems.length-1; i++) {
            for (var x = 0; x <= checkBoxes.length-1; x++) {
                if (checkedItems[i] == checkBoxes[x].value) {
                    checkBoxes[x].checked = true;
                }
            }
        }
    }
}

Here's the domain class for the grid's ObjectDataSource. The class BicycleProduct is merely a class with an overloaded constructor and automatic public properties for each of the fields, and is omitted here for the sake of brevity.

C#
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
using System.Collections.Generic;

namespace eCommereProductGrid.Domain
{
    public class BicycleProductDB
    {
        public static int CountBicycles()
        {
            int nRows;
            using (SqlConnection connection = 
                new SqlConnection(WebConfigurationManager.
                    ConnectionStrings["BicycleDatabase"].ConnectionString))
            {
                SqlCommand command = connection.CreateCommand();
                command.Connection = connection;
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = "CountBicycleProducts";
                connection.Open();
                nRows = (int)command.ExecuteScalar();
            }
            return nRows;
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public List<BicycleProduct> GetBicycleGridDetails(
                   string SortExpression, int startRowIndex, int maximumRows)
        {
            List<BicycleProduct> list = new List<BicycleProduct>();
            DataSet dataSet = new DataSet();

            using (SqlConnection connection = 
                   new SqlConnection(WebConfigurationManager.
                       ConnectionStrings["BicycleDatabase"].ConnectionString))
            {
                using (SqlCommand command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.StoredProcedure;
                    command.CommandText = "GetAllProductsPaged";
                    command.Parameters.Add(new SqlParameter("@Start", SqlDbType.Int, 4));
                    command.Parameters["@Start"].Value = startRowIndex;
                    command.Parameters.Add(new SqlParameter("@Count", SqlDbType.Int, 4));
                    command.Parameters["@Count"].Value = maximumRows;
                    SqlDataAdapter adapter = new SqlDataAdapter(command);
                    connection.Open();
                    adapter.Fill(dataSet, "Bicycles");
                }
            }

            DataView view = dataSet.Tables[0].DefaultView;
            view.Sort = SortExpression;

            foreach (DataRowView row in view)
            {
                BicycleProduct displayDetails = new BicycleProduct(
                    (string) row["BrandName"],
                    (string)row["Color"],
                    (string)row["GridPageImageName"],
                    (string)row["LinkTitle"],
                    (decimal)row["ListPrice"],
                    (string)row["ModelName"],
                    (decimal)row["MSRP"],
                    (int)row["PercentSaved"],
                    (int)row["ProductID"],
                    (string)row["ProductPageImageName"],
                    (string)row["ProductGridLinkPage"],
                    (decimal)row["Savings"],
                    (string)row["Summary"],
                    (decimal)row["Weight"]);

                list.Add(displayDetails);
            }
            return list;
        }

        public static void GetProductDetailsFor(int ProductID, DataSet displaySet)
        {
            using (SqlConnection connection = 
                new SqlConnection(WebConfigurationManager.
                        ConnectionStrings["BicycleDatabase"].ConnectionString))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = "GetProductDetailsByProductID";
                command.Parameters.Add(new SqlParameter("@ProductID", SqlDbType.Int, 4));
                command.Parameters["@ProductID"].Value = ProductID;
                SqlDataAdapter adapter = new SqlDataAdapter(command);
                connection.Open();
                adapter.Fill(displaySet, "ProductDetails");
            }
        }
    }
}

Our image handler class receives the ID of the item to display, retrieves it from the database, and serves it up to the GridView.

C#
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Web;

namespace eCommerceProductGrid
{
    public class DatabaseImageHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.QueryString["ID"] == null)
                throw new ArgumentException("No parameter specified");

            using (SqlConnection connection = 
                   new SqlConnection(ConfigurationManager.
                       ConnectionStrings["BicycleDatabase"].ConnectionString))
            {
                string sql = "SELECT GridPageImage, " + 
                   "ImageContentType FROM BicycleProduct WHERE ProductID=@ID";
                SqlCommand sqlCommand = new SqlCommand(sql, connection);
                sqlCommand.CommandType = CommandType.Text;
                sqlCommand.Parameters.Add("@ID", SqlDbType.Int, 4);
                sqlCommand.Parameters["@ID"].Value = context.Request.QueryString["ID"];

                connection.Open();
                SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.Default);
                reader.Read();

                try
                {
                    MemoryStream stream = new MemoryStream((
                        byte[])reader["GridPageImage"]);
                    byte[] buffer = new byte[stream.Length];
                    int byteSeq = stream.Read(buffer, 0, (int)stream.Length);
                    context.Response.ContentType = reader["ImageContentType"].ToString();

                    while (byteSeq > 0)
                    {
                        context.Response.OutputStream.Write(buffer, 0, byteSeq);
                        byteSeq = stream.Read(buffer, 0, (int)stream.Length);
                    }
                }
                catch (SqlException ex)
                {
                    context.Response.Write(ex.Message);
                }
                finally
                {
                    reader.Close();
                }
           }
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

The Product Comparison Page

The comparison page uses a ListView and an ObjectDataSource that's identical to the GridView except without the SortParameterName attribute. The ListView is set to display four items per page with paging enabled.

ComparisonGrid

ASP.NET
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductComparison.ascx.cs" 
         Inherits="eCommerceProductGrid.UserControls.ProductComparison" %>

<asp:ObjectDataSource ID="sourceBicycles"
                      runat="server"
                      EnablePaging="True"
                      SelectCountMethod="CountBicycles"
                      SelectMethod="CompareBicycleDetails"
                      TypeName="eCommereProductGrid.Domain.CompareBicycleProductsDB">
  <SelectParameters>
    <asp:QueryStringParameter Name="QueryValues" QueryStringField="ID" />
  </SelectParameters>
</asp:ObjectDataSource>

<asp:ListView ID="ListView"
              Runat="server"
              DataSourceID="sourceBicycles"
              GroupItemCount="4">
  <LayoutTemplate>
    <table id="LayoutTable" runat="server" border="1">
        <tr id="groupPlaceholder" runat="server">
        </tr>
    </table>
    <asp:DataPager runat="server" ID="ContactsDataPager" PageSize="4">
        <Fields>
          <asp:TemplatePagerField>
            <PagerTemplate>
              <div class="productComparisonPager">
                <b>
                Page
                <asp:Label runat="server" ID="CurrentPageLabel" Font-Size="Small"
                    Text="<%# Container.TotalRowCount>0 ? 
                    (Container.StartRowIndex / Container.PageSize) + 1 : 0 %>" />
                of
                <asp:Label runat="server" ID="TotalPagesLabel" Font-Size="Small"
                    Text="<%# Math.Ceiling ((double)Container.TotalRowCount / 
                                      Container.PageSize) %>" />
                <br />
                </b>
              </div>
            </PagerTemplate>
          </asp:TemplatePagerField>

          <asp:NumericPagerField PreviousPageText="< Prev 4" NextPageText="Next 4 >" />
        </Fields>
    </asp:DataPager>
  </LayoutTemplate>

  <GroupTemplate>
    <tr><td runat="server" id="itemPlaceholder" valign="top" /></tr>
  </GroupTemplate>

  <ItemTemplate>
    <td>
      <table class="comparisonTable" id="rptProduct" runat="server" border="0">
        <tr>
          <td style="height: 50px;font-size: small">
              <a href='<%# DataBinder.Eval(Container.DataItem, 
                 "ProductGridLinkPage") %>?productID=<%# DataBinder.Eval(
                 Container.DataItem, "ProductID") %>' />
              <%# DataBinder.Eval(Container.DataItem, "LinkTitle")%>  </td>
        </tr>
        <tr>
          <td style="height: 50px;">
            <a href='<%# DataBinder.Eval(Container.DataItem, 
                 "ProductGridLinkPage") %>?ProductID=<%# DataBinder.Eval(
                 Container.DataItem, "ProductID") %>' />
              <img src='DatabaseImageHandler.ashx?ID=<%# DataBinder.Eval(
                           Container.DataItem, "ProductID") %>'
                   alt="<%# DataBinder.Eval(Container.DataItem, 
                       "BrandName") %> <%# DataBinder.Eval(Container.DataItem, 
                       "ModelName") %>" border="0" />
          </td>
        </tr>
        <tr>
          <td><b>Brand:</b> <%# DataBinder.Eval(Container.DataItem, "BrandName")%></td>
        </tr>
        <tr>
          <td><b>Model:</b> <%# DataBinder.Eval(Container.DataItem, "ModelName")%></td>
        </tr>
        <tr>
          <td><b>Color:</b> <%# DataBinder.Eval(Container.DataItem, "Color")%></td>
        </tr>
        <tr>
          <td><b>Weight:</b> <%# DataBinder.Eval(Container.DataItem, "Weight")%></td>
        </tr>
        <tr class="comparisonPrice">
          <td><b>List Price:</b> 
            <%# DataBinder.Eval(Container.DataItem, "ListPrice", "{0:C}")%></td>
        </tr>
        <tr>
          <td><b>MSRP:</b> <%# DataBinder.Eval(Container.DataItem, "MSRP", "{0:C}")%>
	 </td>
        </tr>
        <tr>
          <td><b>Savings:</b> <%# DataBinder.Eval(Container.DataItem, "Savings", "{0:C}")%>
              (<%# DataBinder.Eval(Container.DataItem, "PercentSaved")%>%)</td>
        </tr>
        <tr>
          <td style="height: 35px" align="center">
            <asp:Button ID="RemoveItemButton" runat="server" CommandName="REMOVE"
                CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>'
                 oncommand="ImageButton_Command" Text="Remove" />
          </td>
        </tr>
        <tr>
          <td style="height: 35px" align="center">
            <asp:Button ID="DisplayItemButton1" runat="server" CommandName="DISPLAY"
                CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>'
                Text="Display" oncommand="ImageButton_Command" />
          </td>
        </tr>
        </tr>
      </table>
    </td>
  </ItemTemplate>
</asp:ListView>

The Page_Load event handler in the code-behind parses and stores the IDs in the query string so if there are more than two items in the comparison, the user can remove them. The button click handler simply executes the given command to either remove an item or display the product page for an item.

C#
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using eCommereProductGrid.Domain;

namespace eCommerceProductGrid.UserControls
{
    public partial class ProductComparison : UserControl
    {
        #region Fields
        private const string DISPLAY_ITEM = "DISPLAY";
        private const string REMOVE_ITEM = "REMOVE";
        private List<int> queryArgs = new List<int>();
        #endregion

        #region Event Handlers

        protected void Page_Load(object sender, EventArgs e)
        {
            NameValueCollection coll = Request.QueryString;
            String[] key = coll.AllKeys;

            for (int loop1 = 0; loop1 < key.Length; loop1++)
            {
                String[] value = coll.GetValues(key[loop1]);

                if (value != null && value.Length > 0)
                {
                    for (int ix = 0; ix < value.Length; ix++)
                    {
                        int id = Convert.ToInt32(Server.HtmlEncode(value[ix]));
                        queryArgs.Add(id);
                    }
                }
            }
        }

        protected void ImageButton_Command(object sender, CommandEventArgs e)
        {
            if (e.CommandName == DISPLAY_ITEM)
            {
                int productID = Convert.ToInt32(e.CommandArgument);
                string pageName = CompareBicycleProductsDB.GetProductPageName(productID);
                Response.Redirect("~/" + pageName + "?ProductID=" + productID, true);
            }
            else if (e.CommandName == REMOVE_ITEM)
            {
                if (ListView.Items.Count > 2)
                {
                    queryArgs.Remove(Convert.ToInt32((string)e.CommandArgument));
                    Response.Redirect("~/ComparePage.aspx?" + ParseQueryArgs());
                }
            }
        }

        #endregion

        #region Private Methods
        
        private string ParseQueryArgs()
        {
            StringBuilder query = new StringBuilder();

            foreach (int parm in queryArgs)
            {
                if (query.Length != 0)
                {
                    query.Append("&");
                }
                query.AppendFormat("ID={0}", parm);
            }
            return query.ToString();
        }
 
        #endregion
    }
}

Running the Sample Code

The sample code is a Visual Studio 2008 solution and uses SQL Server 2008. When executing the solution under localhost, the port number that localhost uses must be the same port number in the JavaScript function compareButtonClicked() that executes the comparison page. I added the port 49625 as a property of the solution, but your machine may require a different port. If so, change the properties of the web project to your port number and change line 92 of ProductComparison.js to include the new port number.

  1. Build the solution
  2. Set the DatabaseImageInsert project as the startup project
  3. Execute the SQL script in the Database Script folder in SQL Server
  4. Execute the DatabaseImageInsert project
  5. Set eCommerceProductGrid as the startup project and run it

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Mike is a software developer working in the Dallas area. His early experience was in Unix/C++ development in the telecommunications field, and has done Windows programming since 1999. His interests are in object-oriented analysis & design, .NET, C#, SQL Server, ASP.NET, CSS, HTML, Javascript and JQuery. He is also a classically trained and accomplished classical guitarist who also enjoys bass fishing and working on his Ranger bass boat in his spare time.

Comments and Discussions

 
QuestionUnable to cast object of type 'System.DBNull' to type 'System.String'. Pin
ali iran25-Oct-15 21:41
ali iran25-Oct-15 21:41 
QuestionError Pin
Member 115838906-Apr-15 5:23
Member 115838906-Apr-15 5:23 
AnswerRe: Error Pin
Mike Barreyre8-Apr-15 10:13
Mike Barreyre8-Apr-15 10:13 
AnswerRe: Error datafeedware Pin
datafeedware26-Jun-15 0:14
datafeedware26-Jun-15 0:14 
QuestionExcepton Pin
Member 1052261414-Jan-14 0:01
Member 1052261414-Jan-14 0:01 
AnswerRe: Excepton Pin
Mike Barreyre30-Jan-15 7:05
Mike Barreyre30-Jan-15 7:05 
QuestionSame problem Pin
lucian Gore12-Dec-13 11:01
lucian Gore12-Dec-13 11:01 
AnswerRe: Same problem Pin
Mike Barreyre30-Jan-15 7:05
Mike Barreyre30-Jan-15 7:05 
QuestionNOT supporting!!! Pin
Manu kumar4-Jul-13 0:56
Manu kumar4-Jul-13 0:56 
AnswerRe: NOT supporting!!! Pin
Mike Barreyre30-Jan-15 7:06
Mike Barreyre30-Jan-15 7:06 
QuestionVB version? Pin
andy_Symo1-Nov-10 4:34
andy_Symo1-Nov-10 4:34 
AnswerRe: VB version? hey the post is very interesting. Pin
Member 1007418429-May-13 1:32
Member 1007418429-May-13 1:32 
GeneralMy vote of 5 Pin
Abhinav S7-Oct-10 7:42
Abhinav S7-Oct-10 7:42 
GeneralMy vote of 5 Pin
thatraja6-Oct-10 23:31
professionalthatraja6-Oct-10 23:31 
GeneralMy vote of 5 Pin
Marc Leger29-Sep-10 8:23
Marc Leger29-Sep-10 8:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.