I’ve been mucking around with the ReorderList control in the ATLAS Control Toolkit. I’ve been meaning to add some ‘visual resorting’ content to a couple of my applications and I’m trying to figure out whether this control might fit the bill in this and several other applications as a somewhat generic re-arranging mechanism.
The control basically allows you to bind a list or data to the control and then lets you re-arrange that list by dragging and dropping, which is very cool from a UI perspective. I’ve built sorting functionality in a few applications before using AJAX (non-ATLAS) and while I got it to work it was a bit of a pain to build. Here’s an example of one of the apps that does this:
http://www.west-wind.com/webstoresandbox/admin/SpecialsManager.aspx
It works but it wasn’t exactly trivial to build this thing, and functionally it’s really not nearly as nice as being able to drag and drop items around. It's also not resusable in anyway.
So the ATLAS Toolkit Reoderlist certainly sounds interesting if for nothing else to come up with a good pattern for handling reorder lists in application, even if it's using that functionality on a separate form.
Unfortunately, I’ve not had much luck with getting this to work well. Here’s what I found.
I installed the release control that is on ATLAS site, which is dated June 20th. That control actually worked correctly, but it was completely posting back the page on every drag and drop operation which was definitely not what I want to do. While still pretty cool, the fact that the D&D operation causes the page to resubmit kinda defeats the whole purpose of a reorder list. Reordering is a task that really needs to be reasonably quick and interactive and hopefully leave the state of the page intact. Alternately you have to move the operation off to its own page which is an option, but still the whole PostBack thing kind feels funny when it comes to an ‘ATLAS’ control. But it works.
Anyway, after some more looking around I found that the latest versions on CodePlex no longer do full POSTBacks to the server. Unfortunately, now with the client side management of the sorting I can no longer seem to keep the lists in sync between client and server. The version I’m using now is dated July 27th (1943) but I saw the same behavior with earlier builds (July 20th and forward).
With the move to client side sorting of the control it seems the behavior has drastically changed and I’ve run into major problems with the server staying in sync with the client side. The control exhibits some ‘unconventional’ behaviors. For example, it’s a list control yet it doesn’t manage the list content in ViewState. It also appears that the control has some issues in passing ViewState back properly to the page if you manage your own Viewstate.
To check out the control I created a very simple example, that takes a generic list of color strings and stores them to the list and then sorts them. I purposefully didn’t want to use data bound values because using lists like this might provide a way to provide a somewhat generic mechanism for sorting items. I think if databound data is used this sort of thing would actually work ok as the data would get updated into a database and then rebound on Page_Load().
In this case however the content of the output is managed on the page view ViewState.
Here’s what I ended up with:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SortList.aspx.cs" Inherits="AjaxAspNet.SortList" %>
<%@ Register Assembly="AtlasControlToolkit" Namespace="AtlasControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Reorder List with List</title>
<link href="../WestWind.css" rel="stylesheet" type="text/css" />
<style type="text/css">
.itemdiv { background: steelblue;
color: cornsilk;
border: solid 1px black;
padding: 5px;
}
#lstCustomers li
{
list-style-type: none;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager" runat="server" EnableScriptComponents="true" EnablePartialRendering="true" >
</atlas:ScriptManager>
<div>
<h1>ATLAS Control ToolKit Sort List</h1>
<a href="..\default.aspx">Home</a> | <a href="SortList.aspx">Reload Page</a><small> | Rendered: <%= DateTime.Now %></small>
<br />
<br />
<div style="width:200px;">
<cc1:ReorderList ID="lstCustomers" runat="server" OnItemReorder="lstCustomers_ItemReorder"
LayoutType="Table" AllowReorder="true" >
<ItemTemplate>
<div class="itemdiv" style="background:<%# Container.DataItem %>">
<%# Container.DataItem %>
</div>
</ItemTemplate>
<ReorderTemplate>
<div class="itemdiv" style="border: dashed 2px navy;font-weight:bold;background: Yellow; color: Red">Move Item here...</div>
</ReorderTemplate>
</cc1:ReorderList>
<atlas:UpdatePanel runat="server" id="MessagePanel" Mode="Always" >
<ContentTemplate>
<hr />
<asp:Button runat="server" ID="btnAccept" Text="Apply" OnClick="btnAccept_Click" />
</div>
<asp:Label runat="server" ID="lblMessage" enableviewstate="false"></asp:Label><br />
</ContentTemplate>
</atlas:UpdatePanel>
<br />
<hr />
</form>
</body>
</html>
The code beside then looks like this:
public partial class SortList : System.Web.UI.Page
{
const string STR_SortString = "SortString_Val";
protected List<string> SortString = null;
protected void Page_Load(object sender, EventArgs e)
{
SortString = ViewState[STR_SortString] as List<string>;
if (SortString == null)
{
SortString = new List<string>();
SortString.Add("Red");
SortString.Add("Green");
SortString.Add("Purple");
SortString.Add("DarkBlue");
SortString.Add("Orange");
SortString.Add("Pink");
SortString.Add("Brown");
SortString.Add("GoldenRod");
}
this.lstCustomers.DataSource = SortString;
this.lstCustomers.AllowReorder = true;
this.lstCustomers.DataBind();
}
protected void lstCustomers_ItemReorder(object sender, AtlasControlToolkit.ReorderListItemReorderEventArgs e)
{
//string New = SortString[e.NewIndex];
//string Old = SortString[e.OldIndex];
//if (e.NewIndex > e.OldIndex)
//{
// SortString.RemoveAt(e.NewIndex);
// SortString.Insert(e.NewIndex, Old);
//}
//else
//{
// SortString.Insert(e.NewIndex, Old);
// SortString.RemoveAt(e.NewIndex + 1);
//}
//SortString[e.OldIndex] = New;
//*** Have to rebind to show the changes
this.lstCustomers.DataSource = SortString;
this.lstCustomers.DataBind();
ViewState[STR_SortString] = SortString;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ViewState[STR_SortString] = SortString;
}
protected void btnAccept_Click(object sender, EventArgs e)
{
ShowSortItems();
}
protected void ShowSortItems()
{
StringBuilder sb = new StringBuilder();
foreach (string Color in this.SortString)
{
sb.Append(Color + " | ");
}
this.lblMessage.Text = "<br /> You're sorted choices: <br /><b>" + sb.ToString() + "</b>";
}
}
Here’s what happens:
The control renders the color list fine. On Reorder the list rebinds and then the Reorder event fires. The Control accurately reorders the SortItems inside of the event, so without making any manual changes to the list values (the commented out code which I originally wrote but isn’t necessary) the list is automatically re-ordered for you. That part is very cool! Very little work to get this far.
After re-sorting I stick the newly sorted list back into ViewState for the next request, and I also rebind the list so that the list reflects the latest values. On the first hit all looks OK.
However, on the next reorder, the value retrieved from ViewState is no longer correct. The list comes back in its original state, which seems to suggest the Reorder event callback did not update ViewState. That sucks and breaks this whole scheme.
But what sucks even more is that the list now has the wrong values on the server, yet displays the ‘right’ order on the client, and consecutive re-orders only mangle the list even further. The problem is that the
Ok, so for kicks I switched to using the Session object, which then persists on the server. Now I get the first move to work Ok and the Session object correctly returns my now sorted list and I can rebind the sorted list. However, now when a ItemReorder event occurs the list order is completely screwed up and out of whack. I get the item I just moved in the right position but the rest of the list is all wrong.
The problem here is that it’s not clear exactly on what should be bound to the control.
The control doesn’t do ViewState internally, so it has to be rebound every time. If you don’t rebind, the list doesn’t show. So you need to rebind every time. But what do you rebind? The original list? The resorted list? I’ve tried both and neither seems to result in the right output.
To check this out I changed ViewState to Session state. When I use Session with stored values I see the first sorting operation work correctly but any subsequent sort operations completely mangle the list. It seems that the last sorted item always ends up in the right spot but the rest of the list simply ‘migrates’ around (I can’t seem to see a pattern in how)…
I’d be curious to hear if anybody has used this control with recent builds and if you have, have you used anything but direct data binding as they are doing in the samples which use data source controls to perform the updates automatically…