ASP.NET AJAX ReorderListによりドラッグアンドドロップでリスト並べ替えを行う

ASP.NET AJAXを使ったサンプル。

ASP.NET AJAX Control Toolkitにはいろんなコントロールが含まれていますが、最初は「おおっスゲー!」と思っても、かゆいところに手が届かない仕様によって案外簡単には使えない場合が多く感じます。
ReorderListもそのひとつで、SQLDataSourceとかObjectDataSourceを使用してあらかじめデータソースと連結させて使うことを想定しているらしく、簡単に並べ替え結果の取得が出来ないっぽいです。

並べ替えのたびにDBアクセスを行い、データをコミットされると使いにくいので、なんとか画面内だけでデータを保持したまま並べ替え結果を取得するサンプルを作ってみました。

Session、Application、Cacheなどのサーバ側でデータを保持することが可能な領域にリストデータを確保しておき、非同期で呼び出される並べ替えイベント内で確保したデータを自力で適宜変更し、ReorderListに再バインドを繰り返すことで、一応はObjectDataSourceなどを使用せずに並べ替え結果を取得できるようです。

以下、サンプルコード。

[Default.aspx]から一部抜粋

<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <cc1:ReorderList ID="ReorderList1" runat="server"
         SortOrderField="Sort"
         AllowReorder="true"
         DataKeyField="ID"
         OnItemReorder="ReorderList1_ItemReorder"
         OnItemCommand="ReorderListCommand"
        >
            <ItemTemplate>
                <div style="width:400px; background-color:#E9E0FF;">
                    <table cellspacing="0" cellpadding="0" style="width:100%;">
                        <tr>
                            <td>
                                <asp:Label ID="Label1" runat="server" 
Text='<%# DataBinder.Eval(Container.DataItem, "Title") %>'></asp:Label>
                            </td>
                            <td style="width:40px;">
                                <asp:LinkButton ID="LinkButton1" runat="server" 
CommandName="Delete" 
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ID") %>' >
削除</asp:LinkButton>
                            </td>
                        </tr>
                    </table>
                </div>
            </ItemTemplate>
            <DragHandleTemplate>
                <div style=" background-color:#9999FF; cursor:move;width:15px;height:20px;">
 </div>
            </DragHandleTemplate>
            <ReorderTemplate>
                <div style="border:1px solid blue;width:400px;"> </div>
            </ReorderTemplate>
            
        </cc1:ReorderList>
    </ContentTemplate>
</asp:UpdatePanel>
<asp:Button ID="Button1" runat="server" 
OnClick="Button1_Click" Text="確定" />
</div>

[Default.aspx.cs]

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;

public partial class test_Default : System.Web.UI.Page
{
    /// <summary>
    /// ReorderListにバインドするデータ
    /// </summary>
    public DataTable SessionData
    {
        get { return (DataTable)Session["DataSource"]; }
        set { Session["DataSource"] = value; }
    }

    
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            //Sessionに格納するSessionDataと、ReorderListのDataSourceに初期データをセット
            DataTable dt = getdummydata();
            DataView dv = dt.DefaultView;
            dv.Sort = "Sort";
            this.SessionData = dt;
            this.ReorderList1.DataSource = dv;
            this.ReorderList1.DataBind();
        }
    }

    /// <summary>
    /// 初回の表示データを取得する
    /// </summary>
    /// <returns>とりあえず適当なデータを固定で用意</returns>
    private DataTable getdummydata()
    {
//バインドするデータには、少なくとも一意Key列と並べ替え用Sort連番列が必要
        DataTable dt = new DataTable();
        dt.Columns.Add("ID",typeof(int));
        dt.Columns.Add("Title", typeof(string));
        dt.Columns.Add("Sort", typeof(int));

        dt.Rows.Add(new String[] { "1", "Have product idea - Figure out opportunities", "1" });
        dt.Rows.Add(new String[] { "2", "Talk to customers - Make sure they want it", "2" });
        dt.Rows.Add(new String[] { "3", 
"Design product - Figure out the features and architecture", "3" });
        dt.Rows.Add(new String[] { "4", "Build prototype - Work out the issues", "4" });
        dt.Rows.Add(new String[] { "5", 
"Test features - Use TDD and automated testing", "5" });
        dt.Rows.Add(new String[] { "6", 
"Build production version - Make it fast and robust", "6" });
        dt.Rows.Add(new String[] { "7", "Fix bugs - Make sure it works", "7" });
        dt.Rows.Add(new String[] { "8", "Ship - Ship it! ", "8" });

        return dt;

    }

    /// <summary>
    /// 並べ替え実行イベント(非同期呼び出し)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e">
///ユーザが画面上でドラッグアンドドロップ操作した後に毎回呼び出される。
///イベント引数から並べ替えたアイテムのインデックスが取得できるので、Session内のデータを変更する
///</param>
    protected void ReorderList1_ItemReorder
(object sender, AjaxControlToolkit.ReorderListItemReorderEventArgs e)
    {
        DataTable dt = this.SessionData;
        DataView dv = dt.DefaultView;
        dv.Sort = "Sort";

        //ArrayListにSortOrderFieldでソートした結果を確保
        ArrayList rows = new ArrayList();
        for(int i = 0; i < dv.Count - 1; i++)
        {
            rows.Add(dv[i].Row);
        }
            
        //順序を入れ替える
        int NewListOrder = (int)dv[e.NewIndex]["Sort"];

        if(e.OldIndex < e.NewIndex)
        {
            //item moved down
            for(int i =e.OldIndex + 1 ;i< e.NewIndex;i++)
            {
                ((DataRow)rows[i])["Sort"] = (int)((DataRow)rows[i])["Sort"] - 1;
            }
        }else{
            //item moved up
            for(int i = e.NewIndex ; i < e.OldIndex; i++)
            {
                ((DataRow)rows[i])["Sort"] = (int)((DataRow)rows[i])["Sort"] + 1;
            }
        }

        ((DataRow)rows[e.OldIndex])["Sort"] = e.NewIndex + 1;

        dv = dt.DefaultView;
        dv.Sort = "Sort";
        //Sessionに編集後のデータを確保
        this.SessionData = dt;
        //ReorderList再表示
        this.ReorderList1.DataSource = dv;
        this.ReorderList1.DataBind();
    }

    /// <summary>
    /// 削除実行(非同期呼び出し)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e">
///削除リンクボタンをクリックした際に呼び出されるイベント。イベント引数から一意キー値をとり、Session変数内のデータを変更する
///</param>
    protected void ReorderListCommand
(object sender, AjaxControlToolkit.ReorderListCommandEventArgs e)
    {
        DataTable dt = this.SessionData;
        DataView dv = dt.DefaultView;
        dv.RowFilter = "ID=" + e.CommandArgument;

        if( dv.Count == 1)
        {
            //選択行を削除する
            dt.Rows.Remove(dv[0].Row);
        }
        dv.RowFilter = null;


        //Sort列の連番が抜けるので、1から順に振りなおす

        //ArrayListにSortOrderFieldでソートした結果を確保
        ArrayList rows = new ArrayList();
        for (int i = 0; i < dv.Count - 1; i++)
        {
            rows.Add(dv[i].Row);
        }
        //連番を振りなおす
        for (int i = 0; i < rows.Count; i++)
        {
            ((DataRow)rows[i])["Sort"] = i+1;
        }


        dv = dt.DefaultView;
        dv.Sort = "Sort";
        //Sessionに編集後のデータを確保
        this.SessionData = dt;
        //ReorderList再表示
        this.ReorderList1.DataSource = dv;
        this.ReorderList1.DataBind();
    }

    /// <summary>
    /// 確定ボタン
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Button1_Click(object sender, EventArgs e)
    {
        //ここで順序変更を確定し、DBを更新する処理を行う。
        //hoge.Update(this.SessionData);
    }
}