Monday, September 19, 2005

Repeater - ITemplate

Simple code to demonstrate how a custom control that has repeater as its child control defines its template.
Three classes, MyHeaderTemplate, MyItemTemplate and MyFooterTemplate derive from Itemplate class and hence implement the InstantiateIn method.

The Repeater’s DataBind() method calls the InstantiateIn methods of the HeaderTemplate, ItemTemplate(once for each row of data in the DataTable) and FoorterTemplate respectively.

The implementation of the ItemTemplate’s InstantiateIn method creates a TableRow and a TableCell for each Column of the DataTable and adds it to the repeater control’s RepeaterItem (passed in to the InstantiateIn method).
The BindRow method handles the DataBinding event of each of the rows. It is called for each row after the InstantiateIn method returns.
The implementation of this method adds some client side attributes to the current row and updates the format of the DateTime columns.


public class MyItemTemplate : ITemplate
{
//DataTable is supplied by the custom control and has a set of rows.
private DataTable dt = null;
private WebControl webControl;

public MyItemTemplate(DataTable dt, WebControl webControl)
{
this.dt = dt;
this.webControl = webControl;
}

public void InstantiateIn(Control container)
{
TableRow _row = new TableRow();

for(int i=0;i < dt.Columns.Count;++i)
{
TableCell _column = new TableCell();
_row.Cells.Add(_column);
}
_row.DataBinding+=new EventHandler(BindRow);
container.Controls.Add(_row);
}

public void BindRow(object sender, EventArgs e)
{
TableRow row = (TableRow)sender;
RepeaterItem container = (RepeaterItem)
row.NamingContainer;
DataRowView _dataRow = (DataRowView) container.DataItem;

StringBuilder sbKeyValuePairs = new StringBuilder();
for(int i=0;i < _dataRow.DataView.Table.Columns.Count;++i)
{
string columnName = _dataRow.DataView.Table.Columns[i].Caption;
if(i>0)
{
sbKeyValuePairs.Append(",");
}
sbKeyValuePairs.Append("'");
sbKeyValuePairsString.Append(columnName);
sbKeyValuePairs.Append("','");
sbKeyValuePairs.Append(Convert.ToString(_dataRow[i]));
sbKeyValuePairs.Append("'");
}
row.Attributes.Add("KeyValuePairs",sbKeyValuePairs.ToString());
row.Attributes.Add("ondblclick","javascript:webControl.Page.GetPostBackEventReference(webControl,"DblClick-" + _dataRow[0].ToString()) );

for(int i=0;i < row.Cells.Count;++i)
{
TableCell cell = (TableCell) row.Cells[i];
cell.Wrap = false;
string cellText = _dataRow[i].ToString();
try
{
DateTime dColumnValue = Convert.ToDateTime(cellText);
cellText = dColumnValue.ToString("yyyy-MM-dd");
}
catch{}
cell.Text = cellText;
}
}
}

//Our repeater is essentially a table (You can have very complex HTML here. We used table for simplicity)
public class MyHeaderTemplate : ITemplate
{
public void InstantiateIn(Control container)
{
LiteralControl lcTABLE = new LiteralControl("");
container.Controls.Add(lcTABLE);
}
}

//As our repeater is a table we close it in the footer
public class MyFooterTemplate : ITemplate
{
public void InstantiateIn(Control container)
{
LiteralControl lcTABLE = new LiteralControl("
");
container.Controls.Add(lcTABLE);
}
}

The following is the code in the custom control that assigns the above Templates to the repeater control.

protected override void CreateChildControls()
{
Controls.Clear();

Controls.Add(repeater);

repeater.HeaderTemplate = new MyHeaderTemplate();
repeater.ItemTemplate = new MyItemTemplate(dt,this);
repeater.FooterTemplate = new MyFooterTemplate();

// Prevent child controls from being created again.
ChildControlsCreated = true;

//call the parent method
base.CreateChildControls();
}

protected override void OnPreRender(EventArgs e)
{
// Populate the repeater control with the Items DataSet
repeater.DataSource=dt;
// Bind the data to the repeater
repeater.DataBind();

base.OnPreRender (e);
}

Paging using PagedDataSource class

PagedDataSource objPagedDataSource = new PagedDataSource();
objPagedDataSource.DataSource = //All rows - some datasource (like DataTable, DataView, DataSet's Dtatable, DataReader, Array etc.)

objPagedDataSource.AllowPaging = true;
objPagedDataSource.CurrentPageIndex = 0;
objPagedDataSource.PageSize = 5;

repeater.DataSource = objPagedDataSource;
repeater.DataBind();


Now you create your own navigation buttons First, Prev, Next, Last.
In their server side event handlers, you update bind the repeater with other set of rows as follows


PagedDataSource objPagedDataSource = new PagedDataSource();
objPagedDataSource.DataSource = //ALL rows - some datasource (like DataTable, DataView, DataSet's Dtatable, DataReader, Array etc.)

objPagedDataSource.AllowPaging = true;
objPagedDataSource.CurrentPageIndex = //new value depending on which navigational button is clicked
objPagedDataSource.PageSize = 5;

repeater.DataSource = objPagedDataSource;
repeater.DataBind();


When AllowCustomPaging is set to true, PagedDataSource looks at the total record count of the DataSource, PageSize and CurrentPageIndex properties to set its IsFirstPage, IsLastPage and other properties.

When the repeater's DataBind method is called, it accesses the objPagedDataSource's DataSource, CurrentPageIndex and PageSize properties and loads itself with the appropriate set of rows.
Here All of the rows are being accessed from the database.
So, if this data is stored in the viewstate, then the size of the file sent back and forth the browser becomes very large and is unnecessary.

You can hide/show the navigational buttons depending on the PagedDataSource class's IsLastPage, IsFirstPage properties.

CUSTOM PAGING



PagedDataSource objPagedDataSource = new PagedDataSource();
objPagedDataSource.DataSource = //Only first set of rows - some datasource (like DataTable, DataView, DataSet's Dtatable, DataReader, Array etc.)

objPagedDataSource.AllowPaging = true;
objPagedDataSource.AllowCustomPaging = true;
objPagedDataSource.VirtualCount = //TOTAL number of rows
objPagedDataSource.CurrentPageIndex = 0;
objPagedDataSource.PageSize = 5;

repeater.DataSource = objPagedDataSource;
repeater.DataBind();


Now you create your own navigation buttons First, Prev, Next, Last.
In their server side event handlers, you update bing the repeater with other set of rows as follows

PagedDataSource objPagedDataSource = new PagedDataSource();
objPagedDataSource.DataSource = //only the required set of rows - some datasource (like DataTable, DataView, DataSet's Datatable, DataReader, Array etc.)

objPagedDataSource.AllowPaging = true;
objPagedDataSource.AllowCustomPaging = true;
objPagedDataSource.VirtualCount = //TOTAL number of rows
objPagedDataSource.CurrentPageIndex = //new value depending on which navigational button is clicked
objPagedDataSource.PageSize = 5;

repeater.DataSource = objPagedDataSource;
repeater.DataBind();

When AllowCustomPaging is set to true, PagedDataSource looks at the VirtualCount, PageSize and CurrentPageIndex properties to set its IsFirstPage, IsLastPage and other properties.

When the repeater's DataBind method is called, it accesses the objPagedDataSource's DataSource, CurrentPageIndex and PageSize properties and loads itself with the appropriate set of rows.
Here only the required set of the rows are accessed from the database for each page.
So, if this data is stored in the viewstate, then the size of the file sent back and forth the browser is minimized.

You can hide/show the navigational buttons depending on the PagedDataSource class's IsLastPage, IsFirstPage properties.