ASP.NET DataIslandGrid Control

By Fons Sonnemans, 1-1-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.

IDFirstnameLastnameAddressCity
<< < > >>
172-32-1176
Johnson
White
abc
Stein
213-46-8915
Marjorie
Green
abc
Oakland
238-95-7766
Rony
Carson
abc
Berkeley
267-41-2394
Michael
O'Leary
abc
San Jose
274-80-9391
Dean
Straight
abc
Oakland
341-22-1782
Meander
Smith
abc
Lawrence
409-56-7008
Abrahamfdfs
Bennett
abc
Berkeley
427-17-2319
Ann
Dull
abc
Palo Alto
472-27-2349
Burt
Gringlesby
abc
Covelo
486-29-1786
Charlene
Locksley
abc
San Francisco

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;

    privatevoid Page_Load(objectsender, System.EventArgs e)
    {
        stringconnectString=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")]
publicclass 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>
protectedoverridevoid Render(HtmlTextWriter writer) {

     if(_dataSet!=null) {


         // XML
         stringoldNS= 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 colin 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 bcin_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 bcin_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
     }
}

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

protectedvirtualvoid WriteSort(HtmlTextWriter writer, stringdataField,stringheaderText){
     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

Tags: Web, ASP.NET

Leave a Comment

Leave a Comment
Name
Comment
6 + 3 =

0 Comments

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.