Click here to Skip to main content
15,881,742 members
Articles / Web Development / HTML

SmartPager: a Flickr-style pager control with go-to-page popup layer

Rate me:
Please Sign up or sign in to vote.
4.76/5 (22 votes)
8 Jan 2007CPOL10 min read 167.2K   2.2K   117  
ASP.NET pager control similar to Flickr's paging interface, but with tooltips and go-to-page popup layer allowing you to enter the required page number.
/*
 * Copyright � 2006, Ashley van Gerven - ashley._v_g_@gmail.com (NB: remove '_' chars)
 * All rights reserved.
 *
 * Use of this script, with or without modification, is permitted
 * provided that the above copyright notice and disclaimer below is not removed.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */


#define CLR_v1  // << N.B. Comment this line if compiling with .NET 2.0 or later


using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Drawing;


#if !CLR_v1
[assembly: WebResource("Avg.Controls.SmartPager.js", "text/javascript")]
#endif


namespace Avg.Controls
{
	/// <summary>
	/// Flickr-style pager control with a go-to-page feature
	/// </summary>
	[ToolboxData("<{0}:SmartPager runat=\"server\" />")]
	[ToolboxBitmap(typeof(SmartPager), "SmartPagerIcon.bmp")]
	public class SmartPager : Control, ICloneable, IPostBackDataHandler
	{
		#region Public Properties
		/// <summary>
		/// Gets or sets the number of page numbers to display in the list
		/// </summary>
		[Category("Appearance"), DefaultValue(9), Description("Number of page numbers to display in the list")]
		public int Display
		{
			get { return display; }
			set { display = value; }
		}

		/// <summary>
		/// Gets or sets the number of pages available in the data source
		/// </summary>
		[Category("Misc"), Description("Number of pages available in the data source")]
		public int PageCount
		{
			get { return pageCount; }
			set { pageCount = value; }
		}

		/// <summary>
		/// Gets or sets the current page number
		/// </summary>
		[Browsable(false)]
		public int CurrentPage
		{
			get { return currentPage; }
			set
			{
				currentPage = value;
				ViewState[ClientID + "_CurrentPage"] = currentPage;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to output the Next & Previous links
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public bool OutputNextPrevLinks
		{
			get { return outputNextPrevLinks; }
			set { outputNextPrevLinks = value; }
		}

		/// <summary>
		/// Gets or sets the style for non-clickable Next & Previous links
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string DisabledNextPrevStyle
		{
			get { return disabledNextPrevStyle; }
			set { disabledNextPrevStyle = value; }
		}

		/// <summary>
		/// Gets or sets the text for the 'Previous' link
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string NavigatePreviousText
		{
			get { return navigatePreviousText; }
			set { navigatePreviousText = value; }
		}

		/// <summary>
		/// Gets or sets the text for the 'Next' link
		/// </summary>
		[Category("Appearance"), Description("Number of pages available in the data source")]
		public string NavigateNextText
		{
			get { return navigateNextText; }
			set { navigateNextText = value; }
		}

		/// <summary>
		/// Gets or sets the style attribute for the main table
		/// </summary>
		[Category("Appearance"), DefaultValue(""), Description("Number of pages available in the data source")]
		public string MainTableStyle
		{
			get { return mainTableStyle; }
			set { mainTableStyle = value; }
		}

		/// <summary>
		/// Gets or sets the location of SmartPager.js
		/// </summary>
		[Category("Misc"), DefaultValue(null), Description("Location of SmartPager.js")]
		public string ScriptPath
		{
			get { return scriptPath; }
			set { scriptPath = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether to output the first and last page links
		/// </summary>
		[Category("Appearance"), Description("Determines whether to output the first and last page links")]
		public bool OutputFirstAndLastLinks
		{
			get { return outputFirstAndLastLinks; }
			set { outputFirstAndLastLinks = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether clicking the ellipses should display the Go-to-page layer
		/// </summary>
		[Category("Behavior"), Description("Determines whether clicking the ellipses should display the Go-to-page layer")]
		public bool EnableGoToPage
		{
			get { return enableGoToPage; }
			set { enableGoToPage = value; }
		}

		/// <summary>
		/// Gets or sets the font size for page links
		/// </summary>
		[Category("Appearance")]
		public string FontSize
		{
			get { return fontSize; }
			set { fontSize = value; }
		}

		/// <summary>
		/// Gets or sets the color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkForeColor
		{
			get { return pageLinkForeColor; }
			set { pageLinkForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the background color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkBackColor
		{
			get { return pageLinkBackColor; }
			set { pageLinkBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the hover color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkHoverForeColor
		{
			get { return pageLinkHoverForeColor; }
			set { pageLinkHoverForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the hover background color for page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkHoverBackColor
		{
			get { return pageLinkHoverBackColor; }
			set { pageLinkHoverBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the color for selected page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkSelectedForeColor
		{
			get { return pageLinkSelectedForeColor; }
			set { pageLinkSelectedForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the background color for selected page links
		/// </summary>
		[Category("Appearance")]
		public string PageLinkSelectedBackColor
		{
			get { return pageLinkSelectedBackColor; }
			set { pageLinkSelectedBackColor = value; }
		}

		/// <summary>
		/// Gets or sets the amount of padding in pixels of page number boxes
		/// </summary>
		[Category("Appearance")]
		public int PageNumberBoxPadding
		{
			get { return pageNumberBoxPadding; }
			set { pageNumberBoxPadding = value; }
		}

		/// <summary>
		/// Gets or sets the border width in pixels of page number boxes
		/// </summary>
		[Category("Appearance")]
		public int PageNumberBoxBorderWidth
		{
			get { return pageNumberBoxBorderWidth; }
			set { pageNumberBoxBorderWidth = value; }
		}

		/// <summary>
		/// Gets or sets the border color of page number boxes
		/// </summary>
		[Category("Appearance")]
		public string PageNumberBoxBorderColor
		{
			get { return pageNumberBoxBorderColor; }
			set { pageNumberBoxBorderColor = value; }
		}

		/// <summary>
		/// Gets or sets the text to indicate page numbers that are omitted
		/// </summary>
		[Category("Appearance")]
		public string EllipsisText
		{
			get { return ellipsisText; }
			set { ellipsisText = value; }
		}

		/// <summary>
		/// Gets or sets the name of the JavaScript function to handle the page-change (overriding the default Postback behavior)
		/// </summary>
		[Category("Behavior"), Description("Specify a JS function to handle the page-change (overriding the default Postback behavior)")]
		public string ClientPageChanged
		{
			get { return clientPageChanged; }
			set { clientPageChanged = value; }
		}

		/// <summary>
		/// Gets or sets the text for the textbox-label on the Go-to-page layer
		/// </summary>
		[Category("Appearance")]
		public string PageLabelText
		{
			get { return pageLabelText; }
			set { pageLabelText = value; }
		}

		/// <summary>
		/// Gets or sets the text for the GO button on the Go-to-page layer
		/// </summary>
		[Category("Appearance")]
		public string GoButtonText
		{
			get { return goButtonText; }
			set { goButtonText = value; }
		}
		#endregion


		#region Public Events
		/// <summary>
		/// Occurs when the user navigates to a page on this control
		/// </summary>
		public event EventHandler PageChanged;
		#endregion


		#region Private Members
		private int display = 9;
		private int pageCount = 20;
		private int currentPage = 1;
		private bool outputNextPrevLinks = true;
		private string fontSize = null;
		private bool enableGoToPage = true;
		private bool outputFirstAndLastLinks = true;
		private string scriptPath = "";
		private string mainTableStyle = "";
		private string navigateNextText = "Next &#187;";
		private string navigatePreviousText = "&#171; Previous";
		private string disabledNextPrevStyle = "display:none";
		private string pageLinkForeColor = null;
		private string pageLinkBackColor = null;
		private string pageLinkHoverForeColor = null;
		private string pageLinkHoverBackColor = null;
		private string pageLinkSelectedForeColor = "red";
		private string pageLinkSelectedBackColor = null;
		private int pageNumberBoxPadding = 5;
		private int pageNumberBoxBorderWidth = 1;
		private string pageNumberBoxBorderColor = "#ccc";
		private string ellipsisText = " &#133; ";
		private string clientPageChanged = null;
		private string pageLabelText = "Page:";
		private string goButtonText = "GO";

		private string[] tooltips = null;
		#endregion



		protected override void OnInit(EventArgs e)
		{
			this.Load += new EventHandler(SmartPager_Load);
			base.OnInit(e);
		}


		private void SmartPager_Load(object sender, EventArgs e)
		{
#if CLR_v1
            Page.RegisterClientScriptBlock("SmartPager_js", "<script language=JavaScript src=\"" + scriptPath + "SmartPager.js\"></script>");
			Page.RegisterClientScriptBlock("SmartPager_js2", string.Format("<script language=JavaScript>document.getElementById('smartPagerPageLabel').innerHTML='{0}'; document.getElementById('smartPagerGoLabel').innerHTML='{1}'</script>", pageLabelText, goButtonText));
#else
			bool foundRsrc = false;
			foreach (string rsrcName in this.GetType().Assembly.GetManifestResourceNames())
			{
				if (rsrcName.EndsWith(".SmartPager.js"))
				{
					foundRsrc = true;
					break;
				}
			}
			string scriptUrl = (foundRsrc) ? Page.ClientScript.GetWebResourceUrl(this.GetType(), "Avg.Controls.SmartPager.js") : scriptPath + "SmartPager.js";
			Page.ClientScript.RegisterClientScriptInclude("SmartPager_js", scriptUrl);
			
			Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "SmartPager_js2", string.Format("<script language=JavaScript>document.getElementById('smartPagerPageLabel').innerHTML='{0}'; document.getElementById('smartPagerGoLabel').innerHTML='{1}'</script>", pageLabelText, goButtonText));
#endif
		}


		protected override void Render(HtmlTextWriter writer)
		{
			if (PageCount <= 1)
				Display = -1;

			int[] links;

			if (Display == -1)
			{
				links = new int[PageCount];
				for (int i = 0; i < links.Length; i++)
					links[i] = i + 1;
			}
			else
			{
				links = new int[Display];

				// current page in the middle of our range
				int middle = (int)Math.Ceiling(Display / 2.0) - 1;
				links[middle] = currentPage;

				// pages preceding current page
				for (int i = middle - 1; i >= 0; i--)
					links[i] = links[i + 1] - 1;

				// pages following current page
				for (int i = middle + 1; i < links.Length; i++)
					links[i] = links[i - 1] + 1;


				// Get rid of page numbers exceeding PageCount ("shift" page numbers to the right)
				while (links[links.Length - 1] > PageCount)
				{
					for (int i = 0; i < links.Length; i++)
						links[i]--;
				}

				// Get rid of 0 or negative pages ("shift" page numbers to the left)
				while (links[0] <= 0)
				{
					for (int i = 0; i < links.Length; i++)
						links[i]++;
				}

				// assign -1 to pages over PageCount
				for (int i = links.Length - 1; i >= 0; i--)
				{
					if (links[i] > PageCount)
						links[i] = -1;
					else
						break;
				}
			}


			// javascript tooltips array
			StringBuilder tooltipsJsArr = new StringBuilder();
			if (tooltips != null)
			{
				foreach (string s in tooltips)
					tooltipsJsArr.AppendFormat("\"{0}\",", Page.Server.HtmlEncode(s));
				if (tooltipsJsArr.Length > 0)
					tooltipsJsArr.Remove(tooltipsJsArr.Length - 1, 1);
			}


			// ** HTML output begins **

			// JS variables for go-to-page popup
			writer.WriteLine("<script>var {0}$pagerPageCount = {1}; var {0}$tooltipsArr = [{2}]; var {0}$callJS = '{3}'</script>", ClientID, PageCount, tooltipsJsArr, clientPageChanged);


			// CSS
			string userAgent = "MSIE";
			try 
			{
				userAgent = Page.Request.UserAgent; // VS designer doesn't have a Request
			}
			catch { }
			string MsIEOnly = (userAgent != null && userAgent.IndexOf("MSIE") != -1) ? "background-color:{2};" : "";

			writer.WriteLine("<style type=text/css>");
			writer.WriteLine("a.pagerLink_{0} {{ color:{1}; text-decoration:none; font-size:{3}; }}", this.ClientID, pageLinkForeColor, pageLinkBackColor, fontSize);  // background-color:{2}; 
			writer.WriteLine("a.pagerLink_{0} span {{ background-color:{1}; }}", this.ClientID, pageLinkBackColor);
			writer.WriteLine("a.pagerLink_{0}:hover {{ color:{1}; " + MsIEOnly + "}}", this.ClientID, pageLinkHoverForeColor, pageLinkHoverBackColor);
			writer.WriteLine("a.pagerLink_{0}:hover span {{ background-color:{1}; }}", this.ClientID, pageLinkSelectedBackColor); // This has no effect in IE
			writer.WriteLine("a.pagerLinkSel_{0} {{ color:{1}; text-decoration:none; font-weight:bold; font-size:{3}; }}", this.ClientID, pageLinkSelectedForeColor, pageLinkSelectedBackColor, fontSize); // background-color:{2}; 
			writer.WriteLine("a.pagerLinkSel_{0} span {{ background-color:{1}; }}", this.ClientID, pageLinkSelectedBackColor);
			writer.WriteLine("span.pagerPageNo_{0} {{ height:1; cursor:hand; padding:{1}px; margin-left:2px; margin-right:2px; border:{2}px solid {3}; }}", this.ClientID, pageNumberBoxPadding, pageNumberBoxBorderWidth, pageNumberBoxBorderColor);
			writer.WriteLine("</style>");


			// main table
			writer.WriteLine("<table border=0 cellpadding=0 cellspacing=0 style=\"" + mainTableStyle + "\"><tr>");


			if (outputNextPrevLinks)
			{
				// previous link
				writer.Write("<td nowrap><nobr>");
				if (currentPage > 1)
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class=pagerLink_{0} title=\"{2}\">&#171; Previous</a> &nbsp;", ClientID, currentPage - 1, getTooltipText(currentPage - 2));
				else
					writer.Write("<div style=\"{0}\">{1} &nbsp;</div>", disabledNextPrevStyle, navigatePreviousText);
				writer.WriteLine("</nobr></td>");
			}


			// page numbers
			writer.Write("<td align=center style='padding:3 0 3 0'>");


			if (links[0] != 1)
			{
				if (outputFirstAndLastLinks)
					// output link to first page number
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a>", ClientID, 1, (1 == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(0));

				if (links[0] != 2)
				{
					// output ellipsis
					if (enableGoToPage)
						writer.Write("<a href=# onclick=\"SmartPagerID='{0}';showPager(this);return false\" class=pagerLink_{0} style='position:relative;background-color:transparent;'>{1}</a>", ClientID, ellipsisText);
					else
						writer.Write(ellipsisText);
				}
			}


			// output page number links
			for (int i = 0; i < links.Length; i++)
			{
				if (links[i] == -1)
					break;

				writer.WriteLine("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a", ClientID, links[i], (links[i] == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(links[i] - 1));
				writer.Write(">"); // span the </a> tag accross two lines so no line break between tags in HTML

				if (Display == -1)
					writer.Write(" "); // write a space so page numbers can wrap if displaying ALL page links
			}


			if (links[links.Length - 1] != -1 && links[links.Length - 1] != PageCount)
			{
				if (links[links.Length - 1] != PageCount - 1)
				{
					// output ellipsis
					if (enableGoToPage)
						writer.Write("<a href=#o onclick=\"SmartPagerID='{0}';showPager(this);return false\" class=pagerLink_{0} style='position:relative;background-color:transparent;'>{1}</a>", ClientID, ellipsisText);
					else
						writer.Write(ellipsisText);
				}

				if (outputFirstAndLastLinks)
					// output link to last page number
					writer.Write("<a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class={2}_{0} title=\"{3}\"><span class=pagerPageNo_{0}>{1}</span></a>", ClientID, PageCount, (PageCount == currentPage) ? "pagerLinkSel" : "pagerLink", getTooltipText(PageCount - 1));
			}
			writer.WriteLine("</div></td>");


			if (outputNextPrevLinks)
			{
				// next link
				writer.Write("<td nowrap align=right><nobr>");
				if (currentPage < PageCount)
					writer.Write("&nbsp; <a href=\"javascript:SmartPagerSelectPage('{0}', '{1}')\" class=pagerLink_{0} title=\"{2}\">Next &#187;</a>", ClientID, currentPage + 1, getTooltipText(currentPage));
				else
					writer.Write("<div style=\"{0}\">&nbsp; {1}</div>", disabledNextPrevStyle, navigateNextText);
				writer.WriteLine("</nobr></td>");
			}

			writer.WriteLine("</tr></table>");

			writer.WriteLine("<input type=hidden name='{0}' id='hdn{1}'>", UniqueID, ClientID);
		}


		// Check postback data (IPostBackDataHandler required method)
		public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
		{
			// get the previously selected page from the viewstate
			if (ViewState[ClientID + "_CurrentPage"] != null)
				currentPage = Convert.ToInt32(ViewState[ClientID + "_CurrentPage"]);

			bool changed = (postCollection[postDataKey] != "");
			if (changed)
				CurrentPage = Convert.ToInt32(postCollection[postDataKey]);

			return changed;
		}


		// Raise the PageChanged event (IPostBackDataHandler required method)
		public virtual void RaisePostDataChangedEvent()
		{
			OnPageChanged(EventArgs.Empty);
		}


		protected virtual void OnPageChanged(EventArgs e)
		{
			if (PageChanged != null)
				PageChanged(this, e);
		}


		/// <summary>
		/// Specify the tooltips for the page numbers
		/// </summary>
		/// <param name="tooltips">String array of tooltip values</param>
		public void SetTooltips(string[] tooltips)
		{
			this.tooltips = new string[PageCount];
			Array.Copy(tooltips, 0, this.tooltips, 0, tooltips.Length);
		}


		private string getTooltipText(int index)
		{
			if (tooltips == null)
				return "";
			else
				return Page.Server.HtmlEncode(tooltips[index]);
		}


		/// <summary>
		/// Clones the urrent instance of the control
		/// </summary>
		/// <returns>Clone of this control instance</returns>
		public object Clone()
		{
			SmartPager inst = new SmartPager();

			inst.display = display;
			inst.pageCount = pageCount;
			inst.currentPage = currentPage;
			inst.scriptPath = scriptPath;
			inst.mainTableStyle = mainTableStyle;
			inst.outputNextPrevLinks = outputNextPrevLinks;
			inst.navigatePreviousText = navigatePreviousText;
			inst.navigateNextText = navigateNextText;
			inst.outputFirstAndLastLinks = outputFirstAndLastLinks;
			inst.enableGoToPage = enableGoToPage;
			inst.fontSize = fontSize;
			inst.pageLinkForeColor = pageLinkForeColor;
			inst.pageLinkBackColor = pageLinkBackColor;
			inst.pageLinkHoverForeColor = pageLinkHoverForeColor;
			inst.pageLinkHoverBackColor = pageLinkHoverBackColor;
			inst.pageLinkSelectedForeColor = pageLinkSelectedForeColor;
			inst.pageLinkSelectedBackColor = pageLinkSelectedBackColor;
			inst.pageNumberBoxPadding = pageNumberBoxPadding;
			inst.pageNumberBoxBorderWidth = pageNumberBoxBorderWidth;
			inst.pageNumberBoxBorderColor = pageNumberBoxBorderColor;
			inst.ellipsisText = ellipsisText;
			inst.disabledNextPrevStyle = disabledNextPrevStyle;
			inst.tooltips = tooltips;

			return inst;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Australia Australia
Ash is a C# developer (MCAD) with a background developing e-commerce and content management solutions. His current role includes working with VOIP systems, integration and maintenance of business and billing apps. His personal projects include the ScrollingGrid web control to enable cross-browser freeze-header 2-way scrolling of DataGrids. His other interests include travel, cinema, Squash, photography, Muay Thai.

Comments and Discussions