Paging Products and/or Categries. * IMPORTANT *

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
15 years ago
hi guys,

is the productinlist and the productsingrid templates are meant not to have pagers or is this something missing.

i don't think it's pretty to have a 100 or more products long page . this is besides the fact that paging can and should improve site performance by intelligently handling the amount of data requested from the DB and needed to display, a kind of load on demand or as we click on "next page"...

if it is there and i am missing it, please advice where it is. otherwise i'd think this should be a top priority fix.

Please comment as this is becoming a real issue.


Thank you.
15 years ago
Can't see the option myself for paging, in fact the:


         //TODO implement paging
            if (!Page.IsPostBack)
            {
                BindData();
            }


would suggest perhaps this did not make it into this release.

Obviously, the ideal solution is to perform the paging at the stored proc level for best performance.

As I am sure this is being worked on already, I started working on a quick solution but could do with some help finishing it off.

Paging using the asp:DataPager control

This will look better but as stated isn't the best performing solution. I got as far as the below:

1) Make a copy of /Templates/Categories/ProductsInGrid.ascx. Call it NewProductsInGrid.ascx

2) Open up NewProductsInGrid.ascx and replace:


    <div class="ProductGrid">  
        <asp:DataList ID="dlProducts" runat="server" RepeatColumns="2" RepeatDirection="Horizontal"
            RepeatLayout="Table" ItemStyle-CssClass="ItemBox">
            <ItemTemplate>
                <nopCommerce:ProductBox1 ID="ctrlProductBox" Product='<%# Container.DataItem %>' runat="server" />
            </ItemTemplate>
        </asp:DataList>
    </div>


with:


    <div class="ProductGrid">
        <asp:ListView ID="lvProducts" runat="server" GroupItemCount="2" ItemPlaceholderID="phItems"
            GroupPlaceholderID="phGroups" OnPagePropertiesChanging="lvProducts_PagePropertiesChanging">
            <LayoutTemplate>
                <table>
                    <asp:PlaceHolder runat="server" ID="phGroups"></asp:PlaceHolder>
                </table>
            </LayoutTemplate>
            <GroupTemplate>
                <tr>
                    <asp:PlaceHolder runat="server" ID="phItems"></asp:PlaceHolder>
                </tr>
            </GroupTemplate>
            <ItemTemplate>
                <td>
                    <nopCommerce:ProductBox1 ID="ctrlProductBox" Product='<%# Container.DataItem %>'
                        runat="server" />
                </td>
            </ItemTemplate>
        </asp:ListView>
        <div style="text-align: center;">
            <asp:DataPager ID="dpProducts" runat="server" PageSize="4" PagedControlID="lvProducts"
                QueryStringField="p">
                <Fields>
                    <asp:NumericPagerField />
                </Fields>
            </asp:DataPager>
        </div>
    </div>


(here we have effectively replaced the datalist with a listview control - we use the GroupItemCount property to set the number of columns to display)

3) Open up NewProductsInGrid.ascx.cs and replace all occurrences of "dlProducts" with "lvProducts":


            if (productCollection.Count > 0)
            {
                lvProducts.DataSource = productCollection;
                lvProducts.DataBind();
            }
            else
                lvProducts.Visible = false;


4) Add the following method to the code behind:


        protected void lvProducts_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e)
        {
            dpProducts.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);
            BindData();
        }


5) Save the files then go into Administration and create a new template pointing this new control. Then set a category of your choice to use this new template.

If you try and browse via the normal category link you will get an exception.

To fix I had to change:


<add name="CategoryDetailsRewrite" virtualUrl="^~/Category/([0-9]*)-([\w-]*)\.aspx(?:\?(.*))?"
           rewriteUrlParameter="ExcludeFromClientQueryString"
           destinationUrl="~/Category.aspx?CategoryID=$1&amp;$3"
           ignoreCase="true" />


in web.config to:


      <add name="CategoryDetailsRewrite" virtualUrl="^~/Category/([0-9]*)-([\w-]*)\.aspx"
           rewriteUrlParameter="ExcludeFromClientQueryString"
           destinationUrl="~/Category.aspx?CategoryID=$1"
           ignoreCase="true" />


(I am not sure of the relevance of the "(?:\?(.*))?" at the end of the existing regular expression?)

Also add the following rewrite rule:


      <add name="CategoryDetailsRewriteWithPaging" virtualUrl="^~/Category/([0-9]*)-([\w-]*)\.aspx&amp;p=([0-9]*)"
           rewriteUrlParameter="ExcludeFromClientQueryString"
           destinationUrl="~/Category.aspx?CategoryID=$1&amp;p=$3"
           ignoreCase="true" />


If you browse to the category page it should now load okay. However, clicking on the page numbers will direct back to default.aspx (because of the way the datapager links are created).

However if you append "&p=2" to your rewritten url the paging works fine e.g:

http://localhost:1453/NopCommerceWeb/Category/23-computers.aspx?p=2

The problem is that the DataPager seems to pick up the CategoryID querystring parameter when creating the links e.g:

http://localhost:1453/NopCommerceWeb/Category/23-computers.aspx?CategoryID=23&p=1

So the Url is not correctly rewritten.

I suppose to fix this you could either

a) Create another rule to map "/Category/23-computers.aspx?CategoryID=23&p=1" to "/Category/23-computers.aspx&p=1"

b) Override the numeric pager link urls in the correct format.

If someone wouldn't mind looking into doing this then we could at least have temporary paging until the next release.

Cheers,

Ben
15 years ago
thank you Ben for your suggestions. i'll give it a try. otherwise, i have another component to use instead of the repeater control and it does automatic paging based on the number of row and columns set to display.

for the sake of sharing and keeping a uniform coding, i did not want to introduce a new component. but that's the way to i'll do it if there is no other quick fix for this.

i'll share the code either way.

Souheil.
15 years ago
Souheil,

Did you get any further with this?

Ben
15 years ago
Here's a temporary solution. Remember that it will be fully rewritten in the next release as it's not SEO friendly.

It has been mplemented for "Products in Lines 1" category template. I got as far as the below:

1. Open /Templates/Categories/ProductsInLines1.ascx and replace:

<div class="ProductList1">
        <asp:ListView ID="lvCatalog" runat="server">
            <LayoutTemplate>
                <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
            </LayoutTemplate>
            <ItemTemplate>
                <div class="ItemBox">
                    <nopCommerce:ProductBox2 ID="ctrlProductBox" Product='<%# Container.DataItem %>'
                        runat="server" />
                </div>
            </ItemTemplate>
        </asp:ListView>
    </div>


with:

<div class="ProductList1">
        <asp:ListView ID="lvCatalog" runat="server" OnPagePropertiesChanging="lvCatalog_PagePropertiesChanging">
            <LayoutTemplate>
                <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
            </LayoutTemplate>
            <ItemTemplate>
                <div class="ItemBox">
                    <nopCommerce:ProductBox2 ID="ctrlProductBox" Product='<%# Container.DataItem %>'
                        runat="server" />
                </div>
            </ItemTemplate>
        </asp:ListView>
    </div>
    <div style="text-align: center;">
        <asp:DataPager ID="dpProducts" runat="server" PageSize="3" PagedControlID="lvCatalog">
            <Fields>
                <asp:NumericPagerField />
            </Fields>
        </asp:DataPager>
    </div>


2. Open /Templates/Categories/ProductsInLines1.ascx and add the following method:

        
protected void lvCatalog_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e)
        {
            dpProducts.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);
            BindData();
        }



3. Open /ModulesProductBox2.ascx.cs and add the following method:


        public override void DataBind()
        {
            base.DataBind();
            this.BindData();
        }
15 years ago
Can confirm this works for the ProductsInGrid as per my original post.

support | a.m. wrote:
Here's a temporary solution. Remember that it will be fully rewritten in the next release as it's not SEO friendly.


This is similar to what I had tried originally but as you say, it's not search engine friendly. You could always provide a laundry list of products to the search engine or as I have done in the past, dynamically produced a sitemap file and submitted this to google.

If you can get it working using the QueryStringField property this will be SEO'd as it won't use javascript for paging. However, this does break the Url rewriting as stated before.

Presume next release will make use of paging at the db level rather than in the UI?

Thanks,

Ben
15 years ago
Hi all,

Regarding the SEO of the paging if you use this code it will make the data pager SEO friendly.

Add this QueryStringPagerField.cs to you APP_Code Directory:

[quote]
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Globalization;
using System.Text;
using System.Web;
using System.Web.Resources;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace PagingControl.WebControls {
    public class QueryStringPagerField : NumericPagerField {
        private const string QueryStringField = "CurrentPageIndex";
        public QueryStringPagerField() {
        }

        protected override DataPagerField CreateField() {
            return new QueryStringPagerField();
        }
        
        private HyperLink CreateNumericButton(int pageIndex) {
            HyperLink link = new HyperLink();
            StringBuilder sb = new StringBuilder();
            
            sb.Append(DataPager.Page.Request.AppRelativeCurrentExecutionFilePath);
            sb.Append("?");
            sb.Append(QueryStringField);
            sb.Append("=");
            sb.Append(pageIndex.ToString(CultureInfo.InvariantCulture));

            link.Text = (pageIndex + 1).ToString(CultureInfo.InvariantCulture);
            link.NavigateUrl = sb.ToString();

            if (!String.IsNullOrEmpty(NumericButtonCssClass)) {
                link.CssClass = NumericButtonCssClass;
            }

            return link;
        }

        private HyperLink CreateNextPrevButton(string buttonText, int pageIndex, string imageUrl) {
            HyperLink link = new HyperLink();
            StringBuilder sb = new StringBuilder();
            
            sb.Append(DataPager.Page.Request.AppRelativeCurrentExecutionFilePath);
            sb.Append("?");
            sb.Append(QueryStringField);
            sb.Append("=");
            sb.Append(pageIndex.ToString(CultureInfo.InvariantCulture));

            link.Text = buttonText;
            link.NavigateUrl = sb.ToString();
            link.ImageUrl = imageUrl;

            WebControl webControl = link as WebControl;
            if (!String.IsNullOrEmpty(NextPreviousButtonCssClass)) {
                link.CssClass = NextPreviousButtonCssClass;
            }

            return link;
        }

        public override void CreateDataPagers(DataPagerFieldItem container, int startRowIndex, int maximumRows, int totalRowCount, int fieldIndex) {            

            int currentPageIndex = startRowIndex / DataPager.PageSize;
            object currentPageObj = DataPager.Page.Request.QueryString[QueryStringField];
            short currentQSPageIndex;
            bool resetProperties = false;
            if (currentPageObj != null) {
                bool parsed = Int16.TryParse(currentPageObj.ToString(), out currentQSPageIndex);
                if (parsed) {
                    int highestPageIndex = (totalRowCount - 1)/maximumRows;
                    if (currentPageIndex != currentQSPageIndex && currentQSPageIndex <= highestPageIndex) {
                        currentPageIndex = currentQSPageIndex;
                        startRowIndex = (currentPageIndex * DataPager.PageSize);
                        resetProperties = true;
                    }
                }
            }

            int firstButtonIndex = (startRowIndex / (ButtonCount * DataPager.PageSize)) * ButtonCount;
            int lastButtonIndex = firstButtonIndex + ButtonCount - 1;
            int lastRecordIndex = ((lastButtonIndex + 1) * DataPager.PageSize) - 1;

            if (firstButtonIndex != 0) {
                container.Controls.Add(CreateNextPrevButton(PreviousPageText, firstButtonIndex - 1, PreviousPageImageUrl));
                if (RenderNonBreakingSpacesBetweenControls) {
                    container.Controls.Add(new LiteralControl("&nbsp;"));
                }
            }

            for (int i = 0; i < ButtonCount && totalRowCount > ((i + firstButtonIndex) * DataPager.PageSize); i++) {
                if (i + firstButtonIndex == currentPageIndex) {
                    Label pageNumber = new Label();
                    pageNumber.Text = (i + firstButtonIndex + 1).ToString(CultureInfo.InvariantCulture);
                    if (!String.IsNullOrEmpty(CurrentPageLabelCssClass)) {
                        pageNumber.CssClass = CurrentPageLabelCssClass;
                    }
                    container.Controls.Add(pageNumber);
                }
                else {
                    container.Controls.Add(CreateNumericButton(i + firstButtonIndex));
                }
                if (RenderNonBreakingSpacesBetweenControls) {
                    container.Controls.Add(new LiteralControl("&nbsp;"));
                }
            }

            if (lastRecordIndex < totalRowCount - 1) {
                if (RenderNonBreakingSpacesBetweenControls) {
                    container.Controls.Add(new LiteralControl("&nbsp;"));
                }
                container.Controls.Add(CreateNextPrevButton(NextPageText, firstButtonIndex + ButtonCount, NextPageImageUrl));
                if (RenderNonBreakingSpacesBetweenControls) {
                    container.Controls.Add(new LiteralControl("&nbsp;"));
                }
            }
            
            if (resetProperties) {
                DataPager.SetPageProperties(startRowIndex, maximumRows, true);
            }
        }

        public override void HandleEvent(CommandEventArgs e) {
            // Not needed because Hyperlinks don't generate bubbled events.
        }

        // Required for design-time support (DesignerPagerStyle)
        public override bool Equals(object o) {
            QueryStringPagerField field = o as QueryStringPagerField;
            if (field != null) {
                return base.Equals(o);
            }
            return false;
        }

        public override int GetHashCode() {
            return base.GetHashCode();
        }
    }
}
[/quote]

and then using the code support | a.m. posted add this to ProductInLines1.cs

Reference the code namespace by adding a reference at the top of the page:

<%@ Register TagPrefix="PagingControl" Namespace="PagingControl.WebControls" %>

and add this datapager control:

        <asp:DataPager ID="dpProducts" runat="server" PageSize="3" PagedControlID="lvCatalog">
            <Fields>
                <PagingControl:QueryStringPagerField />
            </Fields>
        </asp:DataPager>

and I believe this will fix the problem for the meantime.

If it does not do correct me

Mike..
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.