使用ASP.NET AJAX Control Toolkit中的ReorderList控件实现用鼠标拖动改变条目顺序
本文来自我即将出版的《ASP.NET AJAX程序设计 第I卷 服务器端ASP.NET AJAX Extensions与ASP.NET AJAX Control Toolkit》第10章第4节。请各位朋友不吝给出建议和意见。
10.4 ReorderList:用鼠标拖动改变条目顺序
ASP.NET AJAX Control Toolkit中的ReorderList控件将在页面中呈现出一个由数据绑定自动生成的条目列表。用户可以通过鼠标拖动某一项来直接改变该列表中条目彼此之间的相对位置关系,且在拖动的过程中,ReorderList控件提供了丰富的、可定制的视觉效果。当用户在某个位置放开鼠标之后,ReorderList控件也将同样会自动通知与其绑定的数据源控件,以Ajax的异步或整页回送的同步方式更新服务器端数据。
10.4.1 应用场景
列表是一切GUI应用程序中都非常常见的元素,而根据用户喜好改变列表中条目的排列顺序也就自然地成为了一个常见的需求。实现这个需求最直观的方法非直接用鼠标拖动莫属,图10-18即显示了在Windows的“开始”菜单中通过鼠标拖动改变其中条目排列顺序时的界面样式。
图10-18 在Windows的“开始”菜单中通过鼠标拖动改变其中条目排列顺序
而对于传统的Web应用程序来讲,这种对用户最为直观的方式似乎并不是那么易于实现。所以,很多时候我们都会采用在每个条目中添加一对“上移”和“下移”按钮、或添加一个用来输入次序值的文本框来实现,如图10-19所示的Mambo CMS(http://www.mamboserver.com/)的菜单管理界面。这种设计的缺点有很多,包括“上移”和“下移”按钮每次只能将条目移动一个位置,逐一填写次序值文本框非常麻烦,浪费宝贵页面空间等,而唯一的优势就是实现起来相对简单。
图10-19 管理列表中的“上移”和“下移”按钮和用来输入次序值的文本框
在当今流行的Web 2.0应用程序中,这种完全忽视易用性的设计正逐渐地被抛弃,取而代之的是越来越类似桌面应用程序的操作方式。例如流行的在线RSS阅读网站Bloglines(http://www.bloglines.com/),即允许用户用类似维护Windows开始菜单一样的方式通过鼠标拖动改变列表条目的位置,如图10-20所示。
图10-20 在Web页面中实现通过鼠标拖动改变列表中条目排列顺序
使用ASP.NET AJAX Control Toolkit中的ReorderList扩展器控件,我们也可以方便地实现这样的功能。
10.4.2 声明语法以及常用属性
ReorderList是一个全功能的数据绑定控件,即能够用统一的模版以列表的方式显示多个拥有共同类型的数据条目,比如DataTable中的某一行、Array中的某一项等,并支持多种模版,例如ItemTemplate、EditItemTemplate、InsertItemTemplate等。若是其绑定的数据条目中有一个类似Order或Priority之类的表示排序信息的整数类型字段,那么即可将该字段设置为ReorderList的排序字段。这样,当用户拖动某一项改变其在列表中的位置之后,ReorderList将根据当前的条目次序自动请求服务器,并修改需要更新的条目的排序字段。我们需要做的只是提供数据源的更新方法以及列表条目的排序字段而已,至于如何增加、减少并且协调列表中各个条目中排序字段的值,都将由ReorderList自动代为处理。
对于用户对列表条目进行的排序操作,ReorderList既可以通过整页的回送,也可以通过Ajax方式的异步回送来通知服务器。而若是需要对某个列表条目进行编辑或删除,则必须使用传统的整页回送模型,当然,我们仍然可以用UpdatePanel将该ReorderList包围起来以得到我们所期望的Ajax行为。
声明ReorderList扩展器控件的语法将类似如下所示:
<ajaxToolkit:ReorderList
ID="myReorderList"
runat="server"
DataSourceID="myObjectDataSource"
DataKeyField="ItemId"
AllowReorder="true"
SortOrderField="Order"
DragHandleAlignment="Left"
PostBackOnReorder="false"
EditItemIndex="3"
ItemInsertLocation="Beginning"
ShowInsertItem="true">
<ItemTemplate>...</ItemTemplate>
<ReorderTemplate>...</ReorderTemplate>
<DragHandleTemplate>...</DragHandleTemplate>
<InsertItemTemplate>...</InsertItemTemplate>
<EditItemTemplate>...</EditItemTemplate>
<EmptyListTemplate>...</EmptyListTemplate>
</ajaxToolkit:ReorderList>
ReorderList控件继承于System.Web.UI.WebControls.CompositeDataBoundControl,也就自然拥有了CompositeDataBoundControl数据绑定控件的所有属性/方法/事件,声明ReorderList控件时所常用的属性标签如表10-5所示。
10-5 声明ReorderList控件时的常用属性标签
- DataSourceID:页面中某个DataSource控件的ID,用于通过数据绑定自动生成列表项目。
- DataKeyField:数据源中键字段的名称,该字段中的值应该在所右记录中是唯一且不变的,ReorderList将用条目中该字段的值作为记录的标志,将在更新/删除中使用。
- AllowReorder:是否允许用户对列表中的项目进行重新排序,若指定了<ReorderTemplate>,则该属性将自动被设置为true。
- SortOrderField:数据源中作为排序字段的名称。在用户进行重新排序之后,ReorderList将自动修改需要更新的条目的该字段
- DragHandleAlignment:条目的可拖动区域与条目之间的相对位置关系。可选Top(上部)、Bottom(下部)、Left(左边)和Right(右边)。
- PostBackOnReorder:若设置该属性值为true,则当用户对列表中的项目进行重新排序之后,将自动引发一次整页的回送。否则将以异步回调的方式向服务器端发送请求。
- EditItemIndex:列表中当前处于编辑模式下的项目的索引值。
- ShowInsertItem:若该属性值为true,则列表中将显示出一个用来添加新条目的特殊行,即<InsertItemTemplate>模版中定义的内容。
- ItemInsertLocation:插入的新行在整个列表中的位置。可选Beginning(第一项)或End(最后一项)。
- <ItemTemplate>:该标签内将定义列表中普通条目的模版。
- <DragHandleTemplate>:该标签内将定义列表条目中可拖放区域的模版。用户只有在该区域中拖拽才能够对该条目进行重排序。
- <ReorderTemplate>:该标签内将定义拖动列表条目时列表中可投放区域的模版。
- <InsertItemTemplate>:该标签内将定义用来添加新条目的特殊行的模版。
- <EditItemTemplate>:该标签内将定义处于编辑状态中的列表条目的模版。
- <EmptyListTemplate>:该标签内将定义空列表的模版。若列表中没有任何条目,则将显示出该模版中定义的内容。
需要特别注意的是,ReorderList控件并不会在客户端自动根据SortOrderField对条目进行排序,因此我们在进行数据绑定的时候,要保证条目已经按照SortOrderField进行了排序。事实上,SortOrderField属性也仅仅在更新排序信息时使用。换句话说,ReorderList将只负责更新条目的SortOrderField属性,而并不负责根据该属性对项目排序。
对于PostBackOnReorder属性,若该ReorderList控件将只提供对项目重新排序的功能,则应该将其设置为false,这样可以充分利用ReorderList自带的异步回调功能。而若是该ReorderList控件同样支持对条目的添加、删除、编辑等复杂功能,则应该将PostBackOnReorder属性设置为true,并用UpdatePanel包围该ReorderList,以期得到Ajax的异步回送功能。
10.4.3 示例程序:可排序的音乐列表
在本示例程序中,我们将基于前面Rating控件的音乐列表,使用ReorderList控件让用户可以根据喜好通过简单直观的鼠标拖动改变某个音乐在列表中的位置。
回到图10-16中所示的MusicEntry类的类图上,可以看到MusicEntry类包含了一个名为Order的Int类型属性,该属性即将用来存储音乐在列表中的次序。
图10-16 MusicEntry类的类图
在初始化10条肖邦的名曲时,我们已经依照顺序分别指定了其Order属性为0-9。但由于ReorderList控件将在运行时改变某些MusicEntry的Order属性,所以在MusicData类中的用来返回所有音乐列表的GetMusicList()方法中,我们还要首先对现有的MusicEntry根据其Order属性进行排序。修改后的GetMusicList()方法如下:
[DataObjectMethod(DataObjectMethodType.Select)]
public static List<MusicEntry> GetMusicList()
{
data.Sort(CompareMusicEntry);
return data;
}
注意其中第一行调用了List<>类型的Sort()方法,对其中的条目进行排序。在调用Sort()方法时,我们传递了一个名为CompareMusicEntry()的静态方法,该方法将用来比较并返回两个MusicEntry对象的排列次序关系:
private static int CompareMusicEntry(MusicEntry x, MusicEntry y)
{
return x.Order.CompareTo(y.Order);
}
从上述CompareMusicEntry()代码中可以看到,MusicEntry对象的顺序将由其Order属性决定。
然后,在MusicData类中添加一个用来更新MusicEntry的方法,该方法用来更新集合中的某个MusicEntry对象,也将被指定为页面中ObjectDataSource控件的Update方法,并由ReorderList控件通过ObjectDataSource控件进行调用:
[DataObjectMethod(DataObjectMethodType.Update)]
public static void UpdateMusic(MusicEntry music)
{
for (int i = 0; i < data.Count; ++i)
{
if (data[i].Id == music.Id)
{
m_data[i] = music;
break;
}
}
}
让我们举一个例子说明ReorderList是如何通过ObjectDataSource调用该UpdateMusic()方法的。假如我们有了a、b、c、d四条音乐,它们的Order属性分别为1、2、3、4。现在用户将第4条,即d拖动到了a、b之间,拖动后的四条音乐的顺序即为a、d、b、c。这时,ReorderList将首先调用该UpdateMusic()方法将拖动条目,即d的Order属性设置为其前面一条——a的Order属性加1,即2。然后再调用两次UpdateMusic()方法,分别设定c和d的Order属性为其当前值加1,即3和4。这样,在ReorderList完成了这3次UpdateMusic()的调用之后,a、b、c、d四条音乐的Order属性就分别变为了1、3、4、2,按照Order进行排序,四条音乐的顺序将变成我们所期望的a、d、b、c,这样也就完成了一次调整顺序的全过程。
搞清楚ReorderList的实现方法之后,让我们接下来完成这个示例程序:新建一个ASP.NET页面并添加ScriptManager控件,然后添加一个ObjectDataSource控件:
<asp:ObjectDataSource ID="musicDataSource" runat="server" DataObjectTypeName=
"Music Entry" SelectMethod="GetMusicList" TypeName="MusicData" UpdateMethod=
"UpdateMusic"></asp: ObjectDataSource>
可以看到,除了设定UpdateMethod属性为前面刚刚添加到MusicData类中的UpdateMusic()方法之外,该ObjectDataSource控件的所有声明都和前面Rating控件示例程序中的ObjectDataSource完全相同。
接下来添加ReorderList控件,为了简单起见,我们的示例程序将只演示ReorderList的改变条目顺序功能。诸如添加/删除/修改条目的实现与我们熟悉的其他模版化数据绑定控件的实现方式完全相同,这里不赘。ReorderList控件的代码如下:
<ajaxToolkit:ReorderList ID="musicList" CssClass="musicList"
DragHandleAlignment="Left" PostBackOnReorder="false" DataSourceID="musicDataSource"
DataKeyField= "Id" Sort OrderField="Order" runat="server">
<ItemTemplate>
...
</ItemTemplate>
<ReorderTemplate>
...
</ReorderTemplate>
<DragHandleTemplate>
...
</DragHandleTemplate>
</ajaxToolkit:ReorderList>
在上面的定义中,我们指定了DragHandleAlignment属性为Left,即将可拖动区域置于条目的左边。因为这里只需要改变条目顺序的功能,所以PostBackOnReorder属性设置为false,让ReorderList控件使用其内建的异步回调功能。DataSourceID属性指向了前面定义的ID为musicDataSource的ObjectDataSource。DataKeyField和SortOrderField属性分别设置为MusicEntry类的Id和Order属性,让ReorderList能够根据Id属性更新相应的MusicEntry对象的Order属性。
因为要用到ReorderList的鼠标拖动排序功能,所以<ItemTemplate>、<ReorderTemplate>和<DragHandleTemplate>模版都是必须指定的,让我们从<ItemTemplate>开始:
<ItemTemplate>中将定义列表中普通条目,即每一条音乐的模版。这里我们只显示其评级信息,同样使用了Rating控件。这部分代码如下:
<ItemTemplate>
<ajaxToolkit:Rating ID="rating" runat="server" CssClass="rating"
StarCssClass="ratingStar" FilledStarCssClass="filledRatingStar" EmptyStarCssClass="emptyRatingStar" CurrentRating='<%# Bind("Rating") %>'
MaxRating="5" ReadOnly="true">
</ajaxToolkit:Rating>
</ItemTemplate>
我们希望将音乐的评级信息显示在条目的右边,所以Rating控件的CssClass属性所指定的rating的定义将如下所示:
.musicList .rating
{
float: right;
}
对于<ReorderTemplate>,则相对来说比较简单:
<ReorderTemplate>
<div class="dragDue">
Drop Here!
</div>
</ReorderTemplate>
其中仅仅定义了一个包含“Drop Here!”字样的<div>元素,该<div>元素应用的CSS Class的定义如下:
.musicList .dragDue
{
margin: 1px 5px;
padding: 2px;
border: 1px dashed #555;
color: #555;
text-align: center;
}
可以看到,“Drop Here!”将居中显示,且整个<div>将显示出灰色的虚线边框。
<DragHandleTemplate>内将定义列表条目中可拖放区域的模版,用户只有在该区域中拖拽才能够对该条目进行重排序。这里我们通过数据绑定让其显示本条音乐的名称,即让用户在音乐名称上拖动即可对该条音乐进行重新排序:
<DragHandleTemplate>
<asp:Label ID="lbTitle" CssClass="dragHandle" ToolTip="Drag Me!" runat="server"
Text='<%# Bind("Name") %>'></asp:Label>
</DragHandleTemplate>
注意到该Label应用的CSS Class为dragHandle,该CSS Class的定义如下,注意其中粗体部分定义的鼠标指针样式:
.musicList .dragHandle
{
width: 300px;
margin: 1px 5px;
padding: 2px;
cursor: move;
display: block;
border: 1px solid #fff;
}
这样即完成了整个ReorderList的定义,该ReorderList的完整代码如下:
<ajaxToolkit:ReorderList ID="musicList" CssClass="musicList"
DragHandleAlignment ="Left" PostBackOnReorder="false" DataSourceID="musicDataSource"
DataKeyField="Id" SortOrderField="Order" runat="server">
<ItemTemplate>
<ajaxToolkit:Rating ID="rating" runat="server" CssClass="rating"
StarCssClass = "ratingStar" FilledStarCssClass="filledRatingStar"
EmptyStarCssClass="emptyRatingStar" CurrentRating='<%# Bind("Rating") %>'
MaxRating="5"ReadOnly="true">
</ajaxToolkit:Rating>
</ItemTemplate>
<ReorderTemplate>
<div class="dragDue">
Drop Here!
</div>
</ReorderTemplate>
<DragHandleTemplate>
<asp:Label ID="lbTitle" CssClass="dragHandle" ToolTip="Drag Me!"
runat="server" Text='<%# Bind("Name") %>'></asp:Label>
</DragHandleTemplate>
</ajaxToolkit:ReorderList>
编译并在浏览器中查看该页面,将看到如图10-21所示的音乐列表。
图10-21 可改变项目次序的音乐列表界面
图10-21中同样可以看到,当鼠标悬停到某条音乐的名称上时,鼠标指针将变成移动的样式。在这十首曲目中,我最喜欢目前位于倒数第二位的“Fantaisie-Impromptu in C-Sharp Minor, Op. 66” (《幻想即兴曲》),它让我想起了儿时练习钢琴时那段美好兼痛苦的时光。将鼠标移动到该曲目之上,按下左键开始拖动,如图10-22所示。
图10-22 拖动倒数第二项:“Fantaisie-Impromptu in C-Sharp Minor, Op. 66”
可以看到,随着拖动该项在列表中移动,所有鼠标当前位置上的音乐条目均纷纷为其让位,并显示出定义在<ReorderTemplate>中定义的带有虚线边框的“Drop Here!”文字。
继续拖动直至第一位后放开鼠标左键,“Fantaisie-Impromptu in C-Sharp Minor, Op. 66”这个条目就这样被提到了最前面。由于ReorderList已经将列表中音乐的当前次序保存到了服务器端,即使刷新页面或另开窗口访问该页面,当前的排序信息将依然保留,如图10-23所示。
图10-23 将“Fantaisie-Impromptu in C-Sharp Minor, Op. 66”拖放至列表第一位