Using a DataPager with both a QueryStringField and RenderDisabledButtonsAsLabels
May 31, 2009 at 2:38 pm 2 comments
The ASP.NET 3.5 DataPager control has two useful features which alter its default behaviour. Firstly, we can specify a non-empty string for the QueryStringField property, causing the DataPager to use HTTP GET to navigate through the pages, rather than JavaScript initiated postbacks which are not very SEO friendly. Secondly, we can set the RenderDisabledButtonsAsLabels property to true, which means that disabled buttons (for example, the Previous button when we are already on the first page), are rendered as simple labels, rather than as links with the disabled attribute set, which isn’t valid xhtml.
Unfortunately, these two features are mutually exclusive – if we assign a value to the QueryStringField, then the RenderDisabledButtonsAsLabels property is ignored.
To address this problem, we need to override the NextPreviousPagerField control which is used by the DataPager to render the buttons.
Using Reflector, we can see that the CreateDataPagers method calls CreateDataPagersForQueryString if QueryStringField is set. This, in turn, calls CreateLink for each of the pager’s buttons.
It’s this CreateLink method which we need to change, adding the code from CreateControl which tests for RenderDisabledButtonsAsLabels, and creates a Label if appropriate:
public class MyNextPreviousPagerField : NextPreviousPagerField
{
private Control CreateLink(string buttonText, int pageIndex, string imageUrl, bool enabled)
{
int pageNumber = pageIndex + 1;
if (!enabled && this.RenderDisabledButtonsAsLabels)
{
Label label = new Label
{
Text = buttonText
};
if (!string.IsNullOrEmpty(this.ButtonCssClass))
{
label.CssClass = this.ButtonCssClass;
}
return label;
}
As CreateLink is a private method, it’s not possible to override it, we have to hide the original, and do the same with a number of other methods too. I’ve done this in the attached code.
Finally, I used tag mapping to make ASP.NET use my version of the NextPreviousPagerField rather then the supplied one. This involves adding a tagMapping section to the web.config file:
<pages> <tagMapping> <add tagType="System.Web.UI.WebControls.NextPreviousPagerField" mappedTagType="GSEJ.MyNextPreviousPagerField"/> </tagMapping> </pages>
With this done, I’ve got exactly what I wanted – SEO friendly links from my DataPager, with xhtml compliant labels for disabled buttons.
using System.Web.UI;
using System.Web.UI.WebControls;
namespace GSEJ
{
public class MyNextPreviousPagerField : NextPreviousPagerField
{
private int _startRowIndex;
private int _maximumRows;
private int _totalRowCount;
public override void CreateDataPagers(DataPagerFieldItem container, int startRowIndex, int maximumRows, int totalRowCount, int fieldIndex)
{
this._startRowIndex = startRowIndex;
this._maximumRows = maximumRows;
this._totalRowCount = totalRowCount;
if (string.IsNullOrEmpty(base.DataPager.QueryStringField))
{
base.CreateDataPagers(container, startRowIndex, maximumRows, totalRowCount, fieldIndex);
}
else
{
this.CreateDataPagersForQueryString(container);
}
}
private Control CreateLink(string buttonText, int pageIndex, string imageUrl, bool enabled)
{
int pageNumber = pageIndex + 1;
if (!enabled && this.RenderDisabledButtonsAsLabels)
{
Label label = new Label
{
Text = buttonText
};
if (!string.IsNullOrEmpty(this.ButtonCssClass))
{
label.CssClass = this.ButtonCssClass;
}
return label;
}
HyperLink link = new HyperLink
{
Text = buttonText,
NavigateUrl = base.GetQueryStringNavigateUrl(pageNumber),
ImageUrl = imageUrl,
Enabled = enabled
};
if (!string.IsNullOrEmpty(this.ButtonCssClass))
{
link.CssClass = this.ButtonCssClass;
}
return link;
}
private void AddNonBreakingSpace(DataPagerFieldItem container)
{
if (this.RenderNonBreakingSpacesBetweenControls)
{
container.Controls.Add(new LiteralControl(" "));
}
}
private void CreateDataPagersForQueryString(DataPagerFieldItem container)
{
bool flag = false;
if (!base.QueryStringHandled)
{
int num;
base.QueryStringHandled = true;
if (int.TryParse(base.QueryStringValue, out num))
{
num--;
int num1 = this._startRowIndex / this._maximumRows;
int num2 = (this._totalRowCount - 1) / this._maximumRows;
if ((num >= 0) && (num <= num2))
{
this._startRowIndex = num * this._maximumRows;
flag = true;
}
}
}
if (this.ShowFirstPageButton)
{
container.Controls.Add(this.CreateLink(this.FirstPageText, 0, this.FirstPageImageUrl, this.EnablePreviousPage));
this.AddNonBreakingSpace(container);
}
if (this.ShowPreviousPageButton)
{
int pageIndex = (this._startRowIndex / this._maximumRows) - 1;
container.Controls.Add(this.CreateLink(this.PreviousPageText, pageIndex, this.PreviousPageImageUrl, this.EnablePreviousPage));
this.AddNonBreakingSpace(container);
}
if (this.ShowNextPageButton)
{
int num4 = (this._startRowIndex + this._maximumRows) / this._maximumRows;
container.Controls.Add(this.CreateLink(this.NextPageText, num4, this.NextPageImageUrl, this.EnableNextPage));
this.AddNonBreakingSpace(container);
}
if (this.ShowLastPageButton)
{
int num5 = (this._totalRowCount / this._maximumRows) - (((this._totalRowCount % this._maximumRows) == 0) ? 1 : 0);
container.Controls.Add(this.CreateLink(this.LastPageText, num5, this.LastPageImageUrl, this.EnableNextPage));
this.AddNonBreakingSpace(container);
}
if (flag)
{
base.DataPager.SetPageProperties(this._startRowIndex, this._maximumRows, true);
}
}
private bool EnableNextPage
{
get
{
return ((this._startRowIndex + this._maximumRows) < this._totalRowCount);
}
}
private bool EnablePreviousPage
{
get
{
return (this._startRowIndex > 0);
}
}
}
}
1. DotNetShoutout | June 3, 2009 at 12:10 pm
Using a DataPager with both a QueryStringField and RenderDisabledButtonsAsLabels « Gareth Erskine-Jones…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
2.
devstuff | September 28, 2009 at 5:21 am
Excellent Gareth, solved an annoying issue for me – I just spent way too long in Reflector tracking down this bug before googling for your solution.
The tagMapping option is good too, I’d forgotten about that one.