Saturday, March 29, 2008

AjaxControlToolkit - Using the ReorderList with simple collections

Download Sample Project

Recently I've been working with the AjaxControlToolkit and Microsoft's Ajax technologies in .net 3.5. While I love the AjaxToolkit, I find the documentation and code-sample is somewhat lacking.

The ReorderList is also one of the lest intuitive controls in the Toolkit.

There are several other samples around the net, but all the ones I found assumed your were binding to a DataSource control of some sort, and most didn't demonstrate persisting changes to an underlying data store.

The few that showed persistence assumed a SqlDataSoruce control that would automate persistence with two-way binding (outside of demo-land, this is a a rare case  in my experience).

So when I first tried out this control, I was surprised at how difficult it was to use it with a simple collection such as an array, or a generic list.

Here is the basic markup you need in your page:

<ajaxToolkit:ReorderList ID="OrderList" runat="server" AllowReorder="true"
            LayoutType="Table" OnItemReorder="OrderList_ItemReorder" 
            PostBackOnReorder="true">
            <ItemTemplate>
                <span style="cursor: pointer;">
                    <asp:Label ID="ItemName" runat="server" 
                               Text='<%# Container.DataItem %>' />
                </span>
    </ItemTemplate>
</ajaxToolkit:ReorderList>

We're setting the text of the label control to the Container.DataItem directly rather than using an Eval or Bind method. That's because simple collections don't have properties that Eval and Bind can use, so you put the content of the data item directly in your output. This is the same with almost all bindable controls with simple collections, but is also one of those things the documentation just sort of "assumes" you already know... which is probably why I coded asp.net for about 4 years before I discovered this little trick.

On a postback or callback, there is no reliable way to get at the data items in their re-arranged state. If you put a button on the page to postback to the server and try to look at the items in the control, you will only be able to see them in the order they had been in when the control was first bound to the collection.

This means you can't just let the user make changes on the client and just submit the page back when they are done. The only reliable way to keep up with the user made reordering is to let the control postback each time the user makes a change, then manually keep track of the changes yourself.

Annoyingly, if you let the control post back to the server as the users makes their changes, the control will re-render itself with the original items in their original order. This will cause the list to reset after each postback effectively undoing the user's changes.

The only way around that seems to be to manually rebind the control yourself on each postback after you've updated the collection that you are using to manually track the changes.

To get all this to work together, the trick is to cache a copy of the items. When a user reorders the list you rearrange your copy of the items, recache it, and re-bind the ReorderList control to your updated copy.

How you choose to cache the items might depend on how much information is in the collection. For small collections you can just use ViewState, but for larger collections you'd probably want to cache them on the server with the Cache API or session variable.

This example will use ViewState since it assumes small and simple collection.

Here is the code that does all this:

protected void Page_PreRender(object sender, EventArgs e)
{
    // doing this at PreRender so we don't have to worry about when/if 
    // we should bind based on if it's a postback or callback and what not.
    OrderList.DataSource = OrderItems;
    OrderList.DataBind();
}

protected void SaveOrder_Click(object sender, EventArgs e)
{
    //normally you'd save to reordered list to the DB or whatever
    foreach(string s in OrderItems)
    {
        // output the re-ordered values to page
        Message.Text = Message.Text + s + "
";
    }
}

protected void OrderList_ItemReorder(object sender, ReorderListItemReorderEventArgs e)
{
    string[] items = OrderItems;
    List list = new List(OrderItems);
    //using a list for the reordering (convienience)
    string itemToMove = list[e.OldIndex];
    list.Remove(itemToMove);
    list.Insert(e.NewIndex, itemToMove);
    OrderItems = list.ToArray();
    //you could save this to the DB now, but this example uses a 
    //save button to batch up changes
}

As you can see, this is pretty straight forward. We maintain our own copy of the items collection putting it into ViewState between server trip. When the user makes a change the event handler just syncs our copy of the items collection. On PreRender we rebind the ReorderList to our collection, which should always be in sync with the client's copy. And finally, the save button can just commit the changes using our copy.

No comments:

Post a Comment