using System; using System.Linq; using System.Web.UI; using System.ComponentModel; using Westwind.Utilities; using System.Collections; using System.Collections.Generic; using System.Data; namespace Westwind.Web.Controls { /// /// The Pager class provides a standalone pager that can be added /// on any page and doesn't require a DataSource or related control. The /// control can be manually assigned values to display or automatically /// infer values using one of the FilterXXX methods to filter a data source. /// /// <<img src="images/pager1.png" />> /// Using the Pager Web Control to Page Content and Data Results /// public class Pager : Control { /// /// Total number of pages for this pager /// [Description("Total number of pages for this pager")] [Category("Pager"), DefaultValue(0)] public int TotalPages { get { return _TotalPages; } set { _TotalPages = value; } } private int _TotalPages = 0; /// /// The page to display. Values are 1 based. /// [Description("The page to display. Values are 1 based.")] [Category("Pager"), DefaultValue(1)] public int ActivePage { get { return _ActivePage; } set { _ActivePage = value; } } private int _ActivePage = 1; /// /// The number of items on the page /// [Description("The number of items on the page.")] [Category("Pager"), DefaultValue(10)] public int PageSize { get { return _PageSize; } set { _PageSize = value; } } private int _PageSize = 10; /// /// Total number of items available - must be set manually or via one of the filter methods /// [Browsable(false)] public int TotalItems { get { return _TotalItems; } set { _TotalItems = value; } } private int _TotalItems = 0; /// /// The base Url for each of the paging links. /// If left blank the control will use the current /// page Url and append. /// [Description("The base Url for the paging links. If empty automatically uses current page Url.")] [Category("Pager"), DefaultValue("")] public string BaseUrl { get { return _BaseUrl; } set { _BaseUrl = value; } } private string _BaseUrl = ""; /// /// Query string key name for the Page variable /// [Description("Query string key name for the page to display")] [Category("Pager"), DefaultValue("page")] public string QueryStringPageField { get { return _QueryStringPageField; } set { _QueryStringPageField = value; } } private string _QueryStringPageField = "page"; /// /// The CSS class used for the immediate pager control. By default this /// is rendered as a div tag. Default styling floats it right. /// /// Use RenderPagerContainerDiv and PagerContainerCssClass to specify an /// 'outer' container if desired. /// [Description("CSS Class for the immediate pager. Note there's an additional Pager Container that can also be rendered. Renders on a
tag that by default is styled to float right.")] [Category("Pager Display"), DefaultValue("pager")] public string CssClass { get { return _CssClass; } set { _CssClass = value; } } private string _CssClass = "pager"; /// /// CSS Class used for page links /// [Description("CSS Class used for page links")] [Category("Pager Display"), DefaultValue("pagerbutton")] public string PageLinkCssClass { get { return _PageLinkCssClass; } set { _PageLinkCssClass = value; } } private string _PageLinkCssClass = "pagerbutton"; /// /// CSS class used for the selected page /// [Description("CSS class used for the selected page")] [Category("Pager Display"), DefaultValue("pagerbutton-selected")] public string SelectedPageCssClass { get { return _SelectedPageCssClass; } set { _SelectedPageCssClass = value; } } private string _SelectedPageCssClass = "pagerbutton-selected"; /// /// Pages: text string /// [Localizable(true)] [Description("Pages: text")] [Category("Pager Text"), DefaultValue("Pages: ")] public string PagesText { get { return _PagesText; } set { _PagesText = value; } } private string _PagesText = "Pages: "; /// /// Pages: text /// [Description("Pages: text CSS class")] [Category("Pager Display"), DefaultValue("pagertext")] public string PagesTextCssClass { get { return _PagesTextCssClass; } set { _PagesTextCssClass = value; } } private string _PagesTextCssClass = "pagertext"; /// /// The text displayed for the previous button. If this /// text is empty the button is not displayed. /// [Localizable(true)] [Description("The text displayed for the previous button. Empty = no button")] [Category("Pager Text"), DefaultValue("Prev")] public string PreviousText { get { return _PreviousText; } set { _PreviousText = value; } } private string _PreviousText = "Prev"; /// /// The text displayed for the next button. If empty /// this button isn't displayed. /// [Localizable(true)] [Description("The text displayed for the next button. Empty = no button")] [Category("Pager Text"), DefaultValue("Next")] public string NextText { get { return _NextText; } set { _NextText = value; } } private string _NextText = "Next"; /// /// /// [Description("The maximum number of pages to display around hte active page")] [Category("Pager Navigation"), DefaultValue(10)] public int MaxPagesToDisplay { get { return _MaxPagesToDisplay; } set { _MaxPagesToDisplay = value; } } private int _MaxPagesToDisplay = 10; /// /// Determines whether the 1... and ...n page links are shown /// before and after the displayed pages /// /// Only shown if there are more pages than MaxPagesToDisplay /// [Description("Determines whether First and Last Page links are rendered. Only shown if there are more Pages than MaxPagesToDisplay")] [Category("Pager Navigation"), DefaultValue(true)] public bool ShowFirstAndLastPageLinks { get { return _ShowFirstAndLastPageLinks; } set { _ShowFirstAndLastPageLinks = value; } } private bool _ShowFirstAndLastPageLinks = true; /// /// Determines whether the Previous and Next buttons are displayed /// [Description("Determines whether the Previous and Next buttons are displayed")] [Category("Pager Navigation"), DefaultValue(true)] public bool ShowPreviousNextLinks { get { return _ShowPreviousNextLinks; } set { _ShowPreviousNextLinks = value; } } private bool _ShowPreviousNextLinks = true; /// /// Determines whether a container div tag is generated. /// Useful to allow nothing to be rendered if there are less than 2 pages /// as it hides the container. Alternately you can render the container /// through your markup but in that case you may end up with an empty container /// if there's no data or only a single page. /// [Description("Determines whether a container div tag is generated. Useful to allow nothing to be rendered if there are less than 2 pages.")] [Category("Pager Container"), DefaultValue(false)] public bool RenderContainerDiv { get { return _RenderContainerDiv; } set { _RenderContainerDiv = value; } } private bool _RenderContainerDiv = false; /// /// Determines whether a br clear='all' is rendered inside of the container div to break content. /// [Description("Determines whether a br clear='all' is rendered inside of the container div to break content.")] [Category("Pager Container"), DefaultValue(true)] public bool RenderContainerDivBreak { get { return _RenderContainerDivBreak; } set { _RenderContainerDivBreak = value; } } private bool _RenderContainerDivBreak = true; /// /// The CSS Class used for the container div /// [Description("The CSS Class used for the container div if enabled")] [Category("Pager Container"), DefaultValue("pagercontainer")] public string ContainerDivCssClass { get { return _ContainerDivCssClass; } set { _ContainerDivCssClass = value; } } private string _ContainerDivCssClass = "pagercontainer"; /// /// Internally used to hold the first page to render when max pages is exceeded /// private int _startPage = 1; /// /// Internally used to hold the last page to render when max pages are exceeded /// private int _endPage = 0; /// /// overridden to handle custom pager rendering for runtime and design time /// /// protected override void Render(HtmlTextWriter writer) { base.Render(writer); if (TotalPages == 0 && TotalItems > 0) TotalPages = CalculateTotalPagesFromTotalItems(); // calculate based on totalitems if (DesignMode) TotalPages = 10; // don't render pager if there's only one page if (TotalPages < 2) return; if (RenderContainerDiv) { if (!string.IsNullOrEmpty(ContainerDivCssClass)) writer.AddAttribute("class", ContainerDivCssClass); writer.RenderBeginTag("div"); } // main pager wrapper writer.WriteBeginTag("div"); writer.AddAttribute("id", this.ClientID); if (!string.IsNullOrEmpty(CssClass)) writer.WriteAttribute("class", this.CssClass); //writer.Write(HtmlTextWriter.TagRightChar + "\r\n"); // Pages Text writer.WriteBeginTag("span"); if (!string.IsNullOrEmpty(PagesTextCssClass)) writer.WriteAttribute("class", PagesTextCssClass); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(this.PagesText); writer.WriteEndTag("span"); // if the base url is empty use the current URL FixupBaseUrl(); // set _startPage and _endPage ConfigurePagesToRender(); // write out first page link if (ShowFirstAndLastPageLinks && _startPage != 1) { writer.WriteBeginTag("a"); string pageUrl = StringUtils.SetUrlEncodedKey(BaseUrl, QueryStringPageField, (1).ToString()); writer.WriteAttribute("href", pageUrl); if (!string.IsNullOrEmpty(PageLinkCssClass)) writer.WriteAttribute("class", PageLinkCssClass + " " + PageLinkCssClass + "-first"); writer.Write(HtmlTextWriter.SelfClosingTagEnd); writer.Write("1"); writer.WriteEndTag("a"); } // write out all the page links for (int i = _startPage; i < _endPage + 1; i++) { if (i == ActivePage) { writer.WriteBeginTag("span"); if (!string.IsNullOrEmpty(SelectedPageCssClass)) writer.WriteAttribute("class", SelectedPageCssClass); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(i.ToString()); writer.WriteEndTag("span"); } else { writer.WriteBeginTag("a"); string pageUrl = StringUtils.SetUrlEncodedKey(BaseUrl, QueryStringPageField, i.ToString()).TrimEnd('&'); writer.WriteAttribute("href", pageUrl); if (!string.IsNullOrEmpty(PageLinkCssClass)) writer.WriteAttribute("class", PageLinkCssClass); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(i.ToString()); writer.WriteEndTag("a"); } writer.Write("\r\n"); } // write out last page link if (ShowFirstAndLastPageLinks && _endPage < TotalPages) { writer.WriteBeginTag("a"); string pageUrl = StringUtils.SetUrlEncodedKey(BaseUrl, QueryStringPageField, TotalPages.ToString()); writer.WriteAttribute("href", pageUrl); if (!string.IsNullOrEmpty(PageLinkCssClass)) writer.WriteAttribute("class", PageLinkCssClass + " " + PageLinkCssClass + "-last"); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(TotalPages.ToString()); writer.WriteEndTag("a"); writer.Write("\r\n"); } // Previous link if (ShowPreviousNextLinks && !string.IsNullOrEmpty(PreviousText) && ActivePage > 1) { writer.WriteBeginTag("a"); string pageUrl = StringUtils.SetUrlEncodedKey(BaseUrl, QueryStringPageField, (ActivePage - 1).ToString()); writer.WriteAttribute("href", pageUrl); if (!string.IsNullOrEmpty(PageLinkCssClass)) writer.WriteAttribute("class", PageLinkCssClass + " " + PageLinkCssClass + "-prev"); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(PreviousText); writer.WriteEndTag("a"); writer.Write("\r\n"); } // Next link if (ShowPreviousNextLinks && !string.IsNullOrEmpty(NextText) && ActivePage < TotalPages) { writer.WriteBeginTag("a"); string pageUrl = StringUtils.SetUrlEncodedKey(BaseUrl, QueryStringPageField, (ActivePage + 1).ToString()); writer.WriteAttribute("href", pageUrl); if (!string.IsNullOrEmpty(PageLinkCssClass)) writer.WriteAttribute("class", PageLinkCssClass + " " + PageLinkCssClass + "-next"); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(NextText); writer.WriteEndTag("a"); writer.Write("\r\n"); } writer.WriteEndTag("div"); if (RenderContainerDiv) { if (RenderContainerDivBreak) writer.Write("
\r\n"); writer.WriteEndTag("div"); writer.Write("\r\n"); } } /// /// Determines the startpage and endpage which are the first /// and last page numbers that are rendered. /// private void ConfigurePagesToRender() { // figure out which page counts should be displayed (10 typically) _startPage = 1; _endPage = TotalPages; if (_endPage - _startPage > MaxPagesToDisplay) { int halfOfMaxPages = (MaxPagesToDisplay / 2); if (ActivePage > 2) { _startPage = ActivePage - halfOfMaxPages; if (_startPage < 1) _startPage = 1; } _endPage = ActivePage + halfOfMaxPages; // add pages not used in beginning to end if (_startPage == 1) _endPage += halfOfMaxPages - ActivePage; if (_endPage > TotalPages) _endPage = TotalPages; if (_endPage == TotalPages) _startPage = _endPage - MaxPagesToDisplay; if (_startPage < 1) _startPage = 1; } } /// /// Tries to retrieve the Page url if one wasn't provided /// private void FixupBaseUrl() { // Fix up the Url for page links if (string.IsNullOrEmpty(BaseUrl) && !DesignMode) { BaseUrl = Page.Request.Url.ToString(); if (!BaseUrl.Contains("?")) BaseUrl += "?"; else BaseUrl += "&"; } } /// /// Updates the internal settings based on the url /// protected override void OnInit(EventArgs e) { base.OnInit(e); int page = ActivePage; if (!DesignMode) { int.TryParse(Page.Request.Params[QueryStringPageField] ?? "1", out page); ActivePage = page; } } /// /// /// Queries the database for the ActivePage applied manually /// or from the Request["page"] variable. This routine /// figures out and sets TotalPages, ActivePage and /// returns a filtered subset IQueryable that contains /// only the items from the ActivePage. /// /// /// /// The page you want to display. Sets the ActivePage property when passed. /// Pass 0 or smaller to use ActivePage setting. /// /// public IQueryable FilterIQueryable(IQueryable query, int activePage) where T : class, new() { ActivePage = activePage < 1 ? ActivePage : activePage; if (ActivePage < 1) ActivePage = 1; TotalItems = query.Count(); if (TotalItems <= PageSize) { ActivePage = 1; TotalPages = 1; return query; } int skip = ActivePage - 1; if (skip > 0) query = query.Skip(skip * PageSize); _TotalPages = CalculateTotalPagesFromTotalItems(); return query.Take(PageSize); } /// /// Queries the database for the ActivePage applied manually /// or from the Request["page"] variable. This routine /// figures out and sets TotalPages, ActivePage and /// returns a filtered subset IQueryable that contains /// only the items from the ActivePage. /// /// /// /// public IQueryable FilterIQueryable(IQueryable query) where T : class, new() { return FilterIQueryable(query, 0); } /// /// /// /// /// /// /// public IEnumerable FilterIEnumerable(IEnumerable items, int activePage) where T: class, new() { return FilterIQueryable(items.AsQueryable(), activePage); } /// /// Filters a data table for an ActivePage. /// /// Note: Modifies the data set permanently by remove DataRows /// /// Full result DataTable /// Page to display. 0 to use ActivePage property /// public DataTable FilterDataTable(DataTable dt, int activePage) { ActivePage = activePage < 1 ? ActivePage : activePage; if (ActivePage < 1) ActivePage = 1; TotalItems = dt.Rows.Count; if (TotalItems <= PageSize) { ActivePage = 1; TotalPages = 1; return dt; } int skip = ActivePage - 1; if (skip > 0) { for (int i = 0; i < skip * PageSize; i++ ) dt.Rows.RemoveAt(0); } while(dt.Rows.Count > PageSize) dt.Rows.RemoveAt(PageSize); return dt; } /// /// Calculates total pages from TotalItems /// /// private int CalculateTotalPagesFromTotalItems() { int totalPages = Convert.ToInt32(Math.Floor(Convert.ToDecimal(TotalItems) / Convert.ToDecimal(PageSize))); int rest = TotalItems % PageSize; if (rest != 0) totalPages++; return totalPages; } } }