I was working on a new project recently and unlike many of the older projects i’ve been working on this new project was built on the .Net 3.5 framework. Meaning now, should i choose, i could use LINQ, or any other number of ‘new’ features available to me. I decided to start with a few basic updates to the project the first of which was to add a ‘paging’ ability to a data listing that previously had a screen limitation of 22 items max per group otherwise the items would not display in the visible screen area.
So the first thing that I decided to do was check out the ASP.Net 3.5 ListView control. For the longest time to create any number of data lists I've used a conjunction of data-bound readers with template fields or DataGrid lists. Going in using the ListView i was sold on two points, the first is the ability to create both a layout template, and the ability to great a group item template. For added icing there was also the ability to assign a DataPager to the ListView right out of the box, what fun!
What I ideally wanted to create was a data listing that had a previous, next, first, and last buttons on both the top and bottom of the data list. Along with this i wanted to be able to display the current row position, and have the movement controls show/hide based on the current index without having to implement my own data paging logic. Trust me i’ve had to create my own paging logic way more times than i can count, and have plenty of ‘code snippets’ to paste into place to provide it. The major draw to ListView was the data pager, and template support.
After looking around for a way to create the pager style i was looking for i came back highly dissatisfied. All i found was the same out-of-box Microsoft snippets displayed on the umpteen number of blog sites with wordy diatribes that only sufficed to say the same verbiage found on the Microsoft site with a few ‘neat’ and ‘look at what i did’ comments. So needless to say i had to pretty much just default to the plug and play method to make out what i wanted.
So after a number of hours reading, prodding, and poking here is what I came up with:
<asp:ListView ID="lvCartUsers" ItemPlaceholderID="itemPlaceholder" runat="server"
OnPreRender="lvCartUsers_PreRender">
<LayoutTemplate>
<div class="cartUserNavControls">
<asp:DataPager runat="server" ID="dpProductTopPager" PagedControlID="lvCartUsers"
PageSize="5">
<Fields>
<asp:TemplatePagerField OnPagerCommand="cartUsers_OnPagerCommand">
<PagerTemplate>
<div class="navMoveFirst">
<asp:ImageButton CommandName="First" ID="ibFirst" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && (((Container.StartRowIndex / Container.PageSize) + 1) > 1) %>'
ImageUrl='<%# GetFirstImagePath() %>' runat="server" />
</div>
<div class="navMovePrevious">
<asp:ImageButton CommandName="Previous" ID="ibPrevious" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && (((Container.StartRowIndex / Container.PageSize) + 1) > 1) %>'
ImageUrl='<%# GetPreviousImagePath() %>' runat="server" />
</div>
<div class="navMoveLast">
<asp:ImageButton CommandName="Last" ID="ibLast" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && ((Container.StartRowIndex + Container.PageSize) < Container.TotalRowCount) %>'
ImageUrl='<%# GetLastImagePath() %>' runat="server" />
</div>
<div class="navMoveNext">
<asp:ImageButton CommandName="Next" ID="ibNext" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && ((Container.StartRowIndex + Container.PageSize) < Container.TotalRowCount) %>'
ImageUrl='<%# GetNextImagePath() %>' runat="server" />
</div>
<div class="navPagerCount">
Displaying Records
<asp:Label runat="server" ID="CurrentPageLabel" Text="<%# Container.StartRowIndex %>" />
-
<asp:Label runat="server" ID="TotalPagesLabel" Text="<%# Container.StartRowIndex+Container.PageSize %>" />
( of
<asp:Label runat="server" ID="TotalItemsLabel" Text="<%# Container.TotalRowCount%>" />
records.)
</div>
</PagerTemplate>
</asp:TemplatePagerField>
</Fields>
</asp:DataPager>
</div>
<div id="cartGrid">
<div id="cartListHeader">
<div id="headerFistName">
<asp:Literal ID="lblFirstHeader" runat="server" Text="First" />
</div>
<div id="headerLastName">
<asp:Literal ID="lblLastHeader" runat="server" Text="Last" />
</div>
<div id="headerCartNumber">
<asp:Literal ID="lblCartNumberHeader" runat="server" Text="Cart #" />
</div>
<div id="headerCartStatus">
<asp:Literal ID="lblCartStatusHeader" runat="server" Text="Status" />
</div>
</div>
<div runat="server" id="itemPlaceHolder">
</div>
</div>
<br style="clear: both;" />
<div class="cartUserNavControls">
<asp:DataPager runat="server" ID="dpProductBottomPager" PagedControlID="lvCartUsers"
PageSize="5">
<Fields>
<asp:TemplatePagerField OnPagerCommand="cartUsers_OnPagerCommand">
<PagerTemplate>
<div class="navMoveFirst">
<asp:ImageButton CommandName="First" ID="ibFirst" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && (((Container.StartRowIndex / Container.PageSize) + 1) > 1) %>'
ImageUrl='<%# GetFirstImagePath() %>' runat="server" />
</div>
<div class="navMovePrevious">
<asp:ImageButton CommandName="Previous" ID="ibPrevious" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && (((Container.StartRowIndex / Container.PageSize) + 1) > 1) %>'
ImageUrl='<%# GetPreviousImagePath() %>' runat="server" />
</div>
<div class="navMoveLast">
<asp:ImageButton CommandName="Last" ID="ibLast" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && ((Container.StartRowIndex + Container.PageSize) < Container.TotalRowCount) %>'
ImageUrl='<%# GetLastImagePath() %>' runat="server" />
</div>
<div class="navMoveNext">
<asp:ImageButton CommandName="Next" ID="ibNext" Visible='<%# (Math.Ceiling(System.Convert.ToDouble(Container.TotalRowCount) / Container.PageSize) > 1) && ((Container.StartRowIndex + Container.PageSize) < Container.TotalRowCount) %>'
ImageUrl='<%# GetNextImagePath() %>' runat="server" />
</div>
</PagerTemplate>
</asp:TemplatePagerField>
</Fields>
</asp:DataPager>
</div>
</LayoutTemplate>
<EmptyDataTemplate>
<asp:Label ID="lblNoItems" runat="server" />
</EmptyDataTemplate>
<ItemTemplate>
<div class="cartListRow">
<div class="rowFistName">
<asp:Literal ID="lblFirstName" runat="server" Text='<%# Eval("FirstName") %>' />
</div>
<div class="rowLastName">
<asp:Literal ID="lblLastName" runat="server" Text='<%# Eval("LastName") %>' />
</div>
<div class="rowCartNumber">
<a class="cartListRowCartNumber" href='<%# Request.Path + "?CartId=" + Eval("CartId") %>'>
<%# Eval("CartId") %></a>
</div>
<div class="rowCartStatus">
<asp:Literal ID="lblStatus" runat="server" Text='<%# Eval("Status") %>' />
</div>
</div>
</ItemTemplate>
</asp:ListView>
Code-behind:
public partial class CartUserList : System.Web.UI.UserControl
{
#region Properties
private int _pageSize = 5;
public int PageSize
{
get { return _pageSize; }
set { _pageSize = value; }
}
private DataSet CartDataSet
{
get
{
DataSet dsCarts = null;
if (ViewState["DataSet"] != null)
{
dsCarts = (DataSet)ViewState["DataSet"];
}
return dsCarts;
}
set { ViewState["DataSet"] = value; }
}
#endregion // </Properties>
#region Events
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (!IsPostBack)
{
<Call RebindCartList here>
}
}
catch (Exception ex)
{
}
}
protected void cartUsers_OnPagerCommand(object sender, DataPagerCommandEventArgs e)
{
try
{
int position = e.Item.Pager.StartRowIndex + e.Item.Pager.PageSize;
switch (e.CommandName)
{
case "First":
e.NewStartRowIndex = 0;
e.NewMaximumRows = e.Item.Pager.MaximumRows;
break;
case "Last":
e.NewStartRowIndex = (e.TotalRowCount - e.Item.Pager.PageSize);
e.NewMaximumRows = e.Item.Pager.MaximumRows;
break;
case "Next":
if (position <= e.TotalRowCount)
{
e.NewStartRowIndex = position;
e.NewMaximumRows = e.Item.Pager.MaximumRows;
}
break;
case "Previous":
e.NewStartRowIndex = e.Item.Pager.StartRowIndex - e.Item.Pager.PageSize;
e.NewMaximumRows = e.Item.Pager.MaximumRows;
break;
}
}
catch (Exception ex)
{
}
}
protected void lvCartUsers_PreRender(object sender, EventArgs e)
{
if (IsPostBack)
{
<Call RebindCartList here>
}
}
#endregion //</Events>
#region Functions
private void RebindCartList(int customerID, int userID)
{
try
{
DataTable tblCarts = null;
if (CartDataSet == null)
{
CartDataSet = <BLL get DataSet>
if (CartDataSet.Tables.Count > 0)
{
tblCarts = CartDataSet.Tables[0];
}
}
lvCartUsers.DataSource = tblCarts;
lvCartUsers.DataBind();
}
catch (Exception ex)
{
}
}
#endregion //</Functions>
The first thing you’ll see is the LayoutTemplate. This is the core to the ListView control as the entire structure of the ‘list’ is formatted within this template area. If one was building a HTML table the beginning tag for <table> and end tag </table> would be contained within this template. Unlike many other controls there is no header template and footer template instead what represents the header and footer of the template is defined within the LayoutTemplate. However, the actual data being repeated, ie the body of the data, is defined in the ItemTemplate.
Since I wanted the DataPager to appear above and below the list of data I have to create the two new DataPager controls above and below my ItemTemplate area so that they again appear above and below the data being paged. In order to get the look that I was going for I also had to implement my own TemplatePagerFields with the PagerTemplate set to display my custom ASP.Net ImageButtons with the selected first, next, previous, and last images to display instead of text. This is another benefit to using the TemplatePagerFields is that instead of having to use the built in text or button movement controls I can create my own by simply providing my own ASP.Net ImageButton controls with a custom CommandName. By using a custom command name I can just handle the event for OnPagerCommand to set the pagers index position based on the control clicked by the user. The code-behind snippet will show the OnPagerCommand being used to page the data. The thing to note here, as I saw in many blogs about paging problems, is that after a paging action is performed the data has to be rebound. It seems like out of the box the control should hold a temporary cache/session data when paging, but it doesn’t. That’s why on my example i handle the OnPreRender of the ListView control to re-bind the data when IsPostBack.
Getting back to the ItemTemplate that is sandwiched between the two DataPager controls. Within the ItemTemplate the bound data can be formatted as this will be the template repeated for each row of data found within the DataSource used to bind with the ListView. Here is where using Eval() the fields can be formatted for display applying any HTML elements or ASP.Net controls within the ItemTemplate area.
So from our perspective the layout looks much like this:
<LayoutTemplate>
<DataPager />
<ItemTemplate>
</ItemTemplate>
<DataPager/>
</LayoutTemplate>
The best part is that by setting the PagedControlID of the DataPager controls to the ListView control I can simply add a few functions to check whether to display the paging controls based on the current PagerSize of the DataPager and I no longer need to handle calculating when to display the controls on the code-behind. You can see the functions for this on the Visible property of the DataPager’s PagerTemplate controls.
All in all not bad. The control is easy enough to use, but when searching for how to use the ListView i did come across many blogs where users couldn’t figure out why after a paging action occurred the ListView was empty. Easy enough, when it posts back the user still is responsible for rebinding the control with the data source. If a SqlDataSource control is used then a user can take advantage of the caching ability of the SqlDataSource otherwise on the post back the ListView must be re-bound with data. Keep that in mind and this is one sweet control.
There is one thing I've used but didn’t touch on and that's the GroupTemplate. The GroupTemplate sits within the LayoutTemplate where ItemTemplate would be displayed in my hierarchy above, but now the GroupTemplate itself contains a definition of the ItemTemplate within it. So what you’d get is the ability to span items, using <div> tags with float set, across columns, essentially creating a table arrangement across columns instead of just rows. Below is a rough hierarchy of how the GroupTemplate becomes involved. There is actually a great article here detailing how to use the ListView with a great snippet on setting up a GroupTemplate here http://msdn.microsoft.com/en-us/library/bb398790.aspx.
<LayoutTemplate>
<DataPager />
<GroupTemplate>
<ItemTemplate>
</ItemTemplate>
</GroupTemplate>
<DataPager/>
</LayoutTemplate>
For now I’ve just started using the control and I'm sure I'll find all kinds of nifty little tricks to perform this that and the other. Either way it goes I've found my new favorite control for quickly creating both grouped and lists of bound data in a way i can quickly put together using CSS to create quick lists of data that is both paging and dynamically syllable.
ASP.Net, Programming, .Net