ASP.NET DataIslandGrid Control

By Fons Sonnemans (January 2003)

Download DataIslandGrid.zip

Introduction

The standard ASP.NET DataGrid control is a great control. You can use it for many things, it even supports Paging and Column Sorting. Those last two option although work using postbacks to the server.

Internet Explorer 5.0 (and higher) support XML Client-side Data-Binding. This is a powerful DHTML feature which is not used in the .NET Framework. It allows.

The DataIslandGrid control is an ASP.NET grid which is bound to a DataTable in a DataSet. The DataSet is serialized and rendered to an Xml DataIsland. The Grid uses Tabular Data-Binding to the Xml DataIsland. This makes it possible to support client-side Column Sorting and Paging. The Column Sorting is implemented using a JavaScript and a StyleSheet in a second Xml DataIsland.


Example

The following grid is a DataIslandGrid which is bound to the Authors table of the SQLServer pubs database.

172-32-1176WhiteJohnson408 496-7223
10932 Bigge Rd.
SteinCA94025true
213-46-8915GreenMarjorie415 986-7020
309 63rd St. #411
OaklandCA94618true
238-95-7766CarsonRony415 548-7723
589 Darwin Ln.
BerkeleyCA94705true
267-41-2394O'LearyMichael408 286-2428
22 Cleveland Av. #14
San JoseCA95128true
274-80-9391StraightDean415 834-2919
5420 College Av.
OaklandCA94609true
341-22-1782SmithMeander913 843-0462
10 Mississippi Dr.
LawrenceKS66044false
409-56-7008BennettAbrahamfdfs415 658-9932
6223 Bateman
BerkeleyCL94705true
427-17-2319DullAnn415 836-7128
3410 Blonde St.
Palo AltoCA94301true
472-27-2349GringlesbyBurt707 938-6445
PO Box 792
CoveloCA95428true
486-29-1786LocksleyCharlene415 585-4620
18 Broadway Av.
San FranciscoCA94130true
527-72-3246GreeneMorningstar615 297-2723
22 Graybar House Rd.
NashvilleTN37215false
648-92-1872Blotchet-HallsReginald503 745-6402
55 Hillsdale Bl..
CorvallisOR97334true
672-71-3249YokomotoPete415 935-4228
3 Silver Ct.
Walnut CreekCA94595true
712-45-1867del CastilloInnes2615 996-8275
2286 Cram Pl. #86
Ann ArborMI48105true
722-51-5454DeFranceMichel219 547-9982
3 Balding Pl.
GaryIN46403true
724-08-9931StringerDirk415 843-2991
5420 Telegraph Av.
OaklandCA94609true
724-80-9391MacFeatherStearns415 354-7128
44 Upland Hts.
OaklandCA94612true
756-30-7391KarsensLivia415 534-9219
5720 McAuley St.
OaklandCA94609true
807-91-6654PanteleySylvia301 946-8853
1956 Arlington Pl.
RockvilleMD20853true
846-92-7186HunterSheryl415 836-7128
3410 Blonde St.
Palo AltoCA94301true
893-72-1158McBaddenHeather707 448-4982
301 Putnam
VacavilleCA95688false
899-46-2035RingerAnne801 826-0752
67 Seventh Av.
Salt Lake CityUT84152true
998-72-3567RingerAlbert801 826-0752
67 Seventh Av.
Salt Lake CityUT84152true
ID Firstname Lastname Address City
abc
abc
abc
abc
abc
<< < > >>

The Html of this page must have redirective to the DataIslandGridProject. Then you can add the DataIslandGrid control to the Page. The best way to do this is by adding the assembly to your Toolbox. You then can drag&drop the DataIslandGrid from your Toolbox onto your Page.

<%@ Register TagPrefix="rit" Namespace="ReflectionIT.Web.UI.WebControls"  Assembly="DataIslandGridProject" %>
.
.
<rit:DataIslandGrid id="DataIslandGrid1" runat="server" CellSpacing="0" BorderWidth="1px"
AllowSorting="true" CssClass="clsTest" width="80%" BorderColor="WhiteSmoke">
   <rit:BoundColumn CssClass="clsColumnID" Width="120px" DataField="au_id" HeaderText="ID" />
   <rit:BoundColumn DataField="au_fname" HeaderText="Firstname"></rit:BoundColumn>
   <rit:BoundColumn DataField="au_lname" HeaderText="Lastname"></rit:BoundColumn>
   <rit:BoundColumn DataField="address" HeaderText="Address"></rit:BoundColumn>
   <rit:BoundColumn DataField="city" HeaderText="City"></rit:BoundColumn>
</rit:DataIslandGrid>
.
.

You add Columns to the datagrid using the BoundColumn Collection Editor.

The following code binds the DataGrid to the Authors table. I have converted the SQLServer table to an MS Access database to make testing easier.

public class WebForm1 : System.Web.UI.Page
{
    protected ReflectionIT.Web.UI.WebControls.DataIslandGrid DataIslandGrid1;

    private void Page_Load(object sender, System.EventArgs e)
    {
        string connectString = string.Format(
         @"Provider=Microsoft.Jet.OLEDB.4.0;Password=;User ID=Admin;Data Source={0};",
         Server.MapPath("pubs.mdb"));

        using (OleDbConnection con = new OleDbConnection(connectString)) {
            con.Open();
            using (OleDbCommand cmd = new OleDbCommand("select * from Authors", con)) {
                OleDbDataAdapter da = new OleDbDataAdapter(cmd);
                DataSet ds = new DataSet();
                da.Fill(ds);

                // Bind the DataGrid
                DataIslandGrid1.DataSet = ds;
            }
        }
    }
}

DataIslandGrid Control

The DataIslandGrid class is a WebControl with a lot of attributes. The ParseChilderenAttribute is set to the Columns property. It enables a control to specify how the page parser interprets nested elements within the control's tags when the control is used declaratively on an ASP.NET page. Columns is a property of type BoundColumnsCollection and holds items of the BoundColumn type

[ToolboxData("<{0}:DataIslandGrid runat=server></{0}:DataIslandGrid>")]
[ParseChildren(true, "Columns")]
[PersistChildren(false)]
[Designer(typeof(ReflectionIT.Web.UI.WebControls.Design.DataIslandGridDesigner))]
[DefaultProperty("Columns")]
public class DataIslandGrid : System.Web.UI.WebControls.WebControl

The class has a set of public properties which allows you to configure it. The real magic is done in the Render() and OnPreRender() methods:

  • JavaScript code for client-side sorting is added to the Page.
  • The DataTable from the DataSet is rendered to an Xml DataIsland using the WriteXml() method of the DataSet. 
  • An Xml StyleSheet is rendered in a second Xml DataIsland. This StyleSheet is used by the JavaScript to do the sorting. It uses a xsl:for-each loop with a order-by attribute. The value of this attribute is set in the JavaScript.
  • A <table> tag is rendered with a datasrc attribute which is set to the ID of first XmlDataIsland
  • For each Column a table cell (<td>) is rendered within a <thead> section. In this Cell a hyperlink is rendered with a NavigateUrl property to the JavaScript which does the client-side sorting. The Text of the hyperlink is set to the HeaderText property.
  • For each Column a table cell (<td>) is rendered within a <tbody> section. In this Cell a <div> tag is rendered with a datafld attribute which is set to the DataField property.
  • A <tfoot> section is rendered with four hyperlinks for paging: MoveFirst, MovePrevious, MoveNext and MoveLast.
  • All tags are closed.
/// <summary>
/// Add a JavaScript to the Page to sort a column
/// </summary>
/// <param name="e"></param>
override protected void OnPreRender(EventArgs e) {

     if (this.Page.Request.Browser.JavaScript == true) {
         // Build JavaScript        
         System.Text.StringBuilder s = new System.Text.StringBuilder();
         s.Append("\n<script type='text/javascript' language='JavaScript'>\n");
         s.Append("<!--\n");
         s.Append("function sortColumn(xmldoc, xsldoc, sortcol) {\n");
         s.Append(" xsldoc.selectSingleNode(\"//xsl:for-each\").setAttribute(\"order-by\", sortcol);\n");
         s.Append(" xmldoc.documentElement.transformNodeToObject(xsldoc.documentElement,xmldoc);\n");
         s.Append("}\n");
         s.Append("// -->\n");
         s.Append("</script>\n");

         // Add the Script to the Page
         this.Page.RegisterClientScriptBlock("SortDataIslandGrid", s.ToString());
     }
}

/// <summary>
/// Sends server control content to a provided HtmlTextWriter object, which writes
/// the content to be rendered on the client.
/// </summary>
/// <param name="writer">The HtmlTextWriter object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {

     if (_dataSet != null) {


         // XML
         string oldNS = DataSet.Namespace;
         DataSet.Namespace = "";

         writer.AddAttribute("id", "xml" + this.ClientID);
         writer.RenderBeginTag("XML");

         DataSet.WriteXml(writer, XmlWriteMode.IgnoreSchema);
         writer.RenderEndTag(); //XML

         DataSet.Namespace = oldNS ;

         // XSL (sorting)
         if (this.AllowSorting) {
             writer.AddAttribute("id", "xsl" + this.ClientID);
             writer.RenderBeginTag("XML");

             writer.RenderBeginTag(DataSet.DataSetName);
             writer.AddAttribute("order-by", "?");
             writer.AddAttribute("select", DataTable.TableName);
             writer.AddAttribute("xmlns:xsl", "http://www.w3.org/TR/WD-xsl");
             writer.RenderBeginTag("xsl:for-each");
             writer.WriteBeginTag(DataTable.TableName);
             writer.Write(HtmlTextWriter.TagRightChar);

             foreach (DataColumn col in DataTable.Columns) {
                 writer.WriteBeginTag(col.ColumnName);
                 writer.Write(HtmlTextWriter.TagRightChar);
                 writer.WriteBeginTag("xsl:value-of");
                 writer.WriteAttribute("select", col.ColumnName);
                 writer.Write(HtmlTextWriter.SlashChar);
                 writer.Write(HtmlTextWriter.TagRightChar);
                 writer.WriteEndTag(col.ColumnName);
             }

             writer.WriteEndTag(DataTable.TableName);
             writer.RenderEndTag(); //xsl:for-each"
             writer.RenderEndTag();

             writer.RenderEndTag(); //XML
         }
     }

     bool design = (this.Site != null && this.Site.DesignMode);
     if (_dataSet != null | design) {
         // Table
         writer.AddAttribute("datasrc", "#xml" + this.ClientID);
         writer.AddAttribute("id", this.ClientID);
         writer.AddAttribute("CellPadding", this.CellPadding.ToString());
         writer.AddAttribute("CellSpacing", this.CellSpacing.ToString());
         if (this.AllowPaging) {
             writer.AddAttribute("dataPageSize", PageSize.ToString());
         }
         if (!this.BorderWidth.IsEmpty) {
             writer.AddAttribute("border", BorderWidth.ToString());
         }
         if (this.CssClass != string.Empty) {
             writer.AddAttribute("class", CssClass);
         }

         if (this.ControlStyleCreated && this.ControlStyle != null) {
             ControlStyle.AddAttributesToRender(writer);
         }
         writer.RenderBeginTag("table");

         // Header
         writer.RenderBeginTag("thead");
         writer.RenderBeginTag("tr");

         foreach (ReflectionIT.Web.UI.WebControls.BoundColumn bc in _columns) {
             if (!bc.Width.IsEmpty) {
                 writer.AddAttribute("width", bc.Width.ToString());
             }
             writer.RenderBeginTag("th");

             if (this.AllowSorting & bc.Sortable) {
                 this.WriteSort(writer, bc.DataField, bc.HeaderText);
             } else {
                 writer.Write(bc.HeaderText);
             }

             writer.RenderEndTag();
         }

         writer.RenderEndTag(); // tr
         writer.RenderEndTag(); // thead

         // body
         writer.RenderBeginTag("tbody");

         int t = design ? (AllowPaging ? PageSize : 4) : 1;
         for (int x = 0; x < t; x++) {
             writer.RenderBeginTag("tr");

             foreach (ReflectionIT.Web.UI.WebControls.BoundColumn bc in _columns) {
                 bc.Render(writer);
             }
             writer.RenderEndTag(); //tr
         }
         writer.RenderEndTag(); // tbody

         // Footer
         if (AllowPaging) {
             writer.RenderBeginTag("tfoot");
             writer.RenderBeginTag("tr");
             writer.AddAttribute("colspan", _columns.Count.ToString());
             writer.RenderBeginTag("th");

             this.WritePaging(writer, ".firstPage();", "<<");
             this.WritePaging(writer, ".previousPage();", "<");
             this.WritePaging(writer, ".nextPage();", ">");
             this.WritePaging(writer, ".lastPage();", ">>");

             writer.RenderEndTag(); //th
             writer.RenderEndTag(); //tr
             writer.RenderEndTag(); //tfoot
         }

         writer.RenderEndTag(); // table
     }
}

protected virtual void WritePaging(HtmlTextWriter writer, string function, string text) {
     writer.AddAttribute("href", "javascript:" + this.ClientID + function);
     writer.RenderBeginTag("a");
     writer.Write(HttpUtility.HtmlEncode(text));
     writer.RenderEndTag(); //a
     writer.Write(HtmlTextWriter.SpaceChar);
}

protected virtual void WriteSort(HtmlTextWriter writer, string dataField, string headerText) {
     writer.AddAttribute("href", "javascript:sortColumn(xml" + this.ClientID +
         ".XMLDocument, xsl" + this.ClientID + ".XMLDocument, '+" + dataField + "');");
     writer.RenderBeginTag("a");
     writer.Write(HttpUtility.HtmlEncode(headerText));
     writer.RenderEndTag(); //a
     writer.Write(HtmlTextWriter.SpaceChar);
}

You can configure the appearance of the DataGrid easily by assigning a stylesheet classname to the CssClass property of the DataGrid and its BoundColumns. I have also included a DataIslandGridDesigner class which renders the design-time html.

Links:

I have used the following articles to create this control and to write this article:

Conclusion

The Internet Explorer features for Data-Binding to Xml DataIslands are very powerful. The DataIslandGrid control makes it easy to use them in a ASP.NET application. It demonstrates the power of .NET controls.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl