Reordering columns without binding data

Published 13 January 04 09:46 AM | despos
In my last post, I say if you want reorder DataGrid columns on the client (e.g., using drag-and-drop) then you must rebind the data source on the server to have the changes persisted at least across the session. The persistence wouldn't work across application/session restarts because no disk persistence is implemented.
 
However, the following comment made me thinking a little more about the whole subject. (Basically, I spent 90% of my yesterday workday solving this issue.)
 
I still think that the concept of rebinding in order to persist column position is wrong. The column position is only a form of a presentation. It should not be related in any way, to my opinion, to its underlying data (unlike sorting, which, as you mentioned  in your (great) blog, is better done on the server).

I don't think we would ever come to an agreed point if we start a thread about the interpretation of column reordering. As a matter of fact, ASP.NET suggests to use the IPostBackDataHandler interface whenever client-side changes affect the state (including presentation) of a server control. This is definitely the case, I believe, with grid columns. In light of this, I firmly believe that implementing that interface is the correct way to go and is respectful of the currently set programming rules.
 
If we proceed this way, rebinding is necessary to have the grid taking into account the new column order. By firing a server-side event, we reduce this to the very minimum. However, a challenging question is still lying in the above comment. Can we do better and eliminate the necessity to rebind?
 
I haven't tried it yet but I think that another way might exist--storing the ColumnOrder value (the comma-separated string with the new order of columns) out of the viewstate in a separate hidden field. If you look at my source code, you probably recognize out of commented lines that I originally wrote it using a separate hidden field. When the DataGrid is initialized, the sequence of events is:
  • OnInit
  • CreateColumnSet called to create the grid without binding (i.e., from columns viewstate)
  • LoadViewState (ColumnOrder is empty and gets updated only at THIS time)
  • Page_Load
With ColumnOrder stored in the viewstate and automatically restored using the recommended IPostBackDataHandler interface, the earlier you get involved is after LoadViewStateColumnOrder gets its updated value too late to affect the column order in the grid. If you rebind, you fix it.
 
However, since CreateColumnSet is still called before LoadViewState is invoked, if you figure out a way to have ColumnOrder set with the client value in OnInit that might work. ASP.NET doesn't allow you to modify the viewstate, but if you store ColumnOrder in a separate hidden field, you can read it at any time using the plain old Page.Request method. If you come up with the following sequence of events, it should work without rebinding.
  • OnInit
  •     ColumnOrder = Page.Request["YourHiddenField"]
  • CreateColumnSet called to create the grid without binding (i.e., from columns viewstate)
  • LoadViewState
  • Page_Load
Of course, for this to happen you need to modify the HTC accordingly.

Comments

# Joe said on January 13, 2004 05:29 AM:

Here's how I reorder columns in a custom DataGrid. I have a custom DataGrid which selectively renders columns in a user-defined order:

- Persist the column order (column separated list) somewhere appropriate depending on how long you want it to persist (ViewState, Session, Cookie or server-side database).

- Don't change the column order of the DataGrid.

- Override RenderChildren to render the columns in the required order. When RenderChildren is called, the controls collection will contain a Table, which in turn contains a collection of DataGridItems. So the code looks something like the following (actually a bit more complex: e.g. I always render in the standard order when in design mode).

protected override void RenderChildren(HtmlTextWriter writer)
{
Table table = Controls[0] as Table;
if (table == null)
{
base.RenderChildren (writer);
return;
}
table.RenderBeginTag(writer);
foreach (Control ctl in table.Controls)
{
DataGridItem dgi = ctl as DataGridItem;
if (dgi == null)
{
ctl.RenderControl(writer);
continue;
}
if (dgi.ItemType == ListItemType.Pager)
{
dgi.RenderControl(writer);
}
ctl.RenderBeginTag(writer); // tr tag
// ctl's Controls collection contains a collection of cells. Render them
// in the required order
...
ctl.RenderEndTag(writer); // /tr tag
}
table.RenderEndTag(writer);
}

With a rendering solution like this the column order does not change on the server side: i.e. no need for rebinding, also it ensures that the control tree stays the same across postbacks.

# Troy Dedmon said on January 15, 2004 10:31 PM:

A very slick solution I must say. This worked great for my grid.

# Troy Dedmon said on January 21, 2004 10:10 PM:

The more and more I worked with this method I discovered its impossible to use it with viewstate. Here is why. First off you have to know in the control whether or not it is being restored from the viewstate. You want to reorder in the RenderChildren only if the data is not being restored from a postback. Why? Because you have to know how the columns are ordered on the initial databind because each time the page postbacks the grid will be restored based upon the original bind. So why can't I save the initial bind in a hidden field? Because this could change at any time say you enable sorting in which the sort function you have to rebind the data, well now your data is update and each postback from now on will use viewstate to resotre the sorted order. If there is a way to tell whether a control is being restored from its viewstate then this method will work. However, I think thats why microsoft chose to make you rebind when you use server side sorting but I may be wrong..

Leave a Comment

(required) 
(required) 
(optional)
(required)