使用 ASP.NET 2.0 和 Atlas 实现拖放






4.77/5 (40投票s)
2006 年 6 月 15 日
14分钟阅读

465578

3279
本教程深入探讨了 Atlas 中声明式编程和命令式编程之间的关系,以及如何利用它们在 Web 客户端中创建拖放功能。
引言
本教程旨在帮助读者理解 Microsoft 新的 AJAX Extensions 技术的一些方面是如何工作的。AJAX Extensions 旨在简化 AJAX 风格功能的开发。然而,与所有技术一样,要很好地使用一个工具,了解 Atlas 抽象的底层技术至关重要。ASP.NET AJAX 的一个关键抽象是新开发的 XML 标记语法,它使使用 AJAX 编码更加容易(最初包含在核心 Atlas 文件中,XML 标记现在是 CTP 中称为 AJAX Futures 的一部分)。使用 XML 标记,开发人员可以声明式地修改他们的代码。但是,有时开发人员可能希望能够以编程方式更改其代码,为了实现这一点,他们需要了解在标记抽象的底层,他们实际上是在处理普通的 JavaScript 和 Microsoft 开发的一些自定义 JavaScript 库。为了演示 Atlas 声明式模型与命令式模型之间的关系,我将通过一系列示例,其中同一任务将以声明式和命令式两种方式完成。我将演示如何使用 PreviewDragDrop 库文件执行基本的拖放操作以及设置放置区。
背景
在我写这篇文章的时候,Microsoft 已经在 Beta 2 中对 ASP.NET AJAX 进行了一些重要的更改,这些更改不幸地破坏了大部分原始 Atlas 实现,并需要对原始示例进行一些返工。这些修订后的示例适用于 ASP.NET AJAX 的 Beta 2。AJAX Extensions 的未来版本可能会影响本教程的准确性。我将尝试在新版本的 AJAX Extensions 可用时更新代码。AJAX Extensions 与 .NET 2.0 配合使用,并且将与即将发布的 Orcas 配合使用。
一、声明式拖放
第一个任务是使用 XML 标记为 div
标签添加拖放行为。通过拖放,我只是意味着能够拖动一个对象,然后让它停留在你放置它的任何地方。让对象在放置在指定的放置目标上时实际执行某些操作的更复杂行为将在本教程的后面讨论。要配置您的网页以使用 ASP.NET AJAX,您需要将 Microsoft.Web.Extensions.dll 安装到全局程序集缓存中。 您还需要对 Microsoft.Web.Preview.dll 库的引用。 最后,您需要使用以下条目配置您的 web.config 文件:
<system.web>
<pages>
<controls>
<add tagPrefix="asp" namespace="Microsoft.Web.UI"
assembly="Microsoft.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add tagPrefix="asp" namespace="Microsoft.Web.UI.Controls"
assembly="Microsoft.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add tagPrefix="asp" namespace="Microsoft.Web.Preview.UI"
assembly="Microsoft.Web.Preview" />
</controls>
</pages>
</system.web>
您需要向您的 .aspx 页面添加一个 Atlas Script Manager 控件,并配置它使用 PreviewDragDrop 库文件。
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js" />
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewDragDrop.js" />
</Scripts>
</asp:ScriptManager>
添加您希望使其可拖动的 div
对象,并确保它有一个拖动手柄。
<div style="background-color:Red;height:800px;width:600px;">
<div id="draggableDiv"
style="height:100px;width:100px;background-color:Blue;">
<div id="handleBar"
style="height:20px;width:auto;background-color:Green;">
</div>
</div>
</div>
最后,添加将使您的 div
可拖动的标记脚本。
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<control id="draggableDiv">
<behaviors>
<floatingBehavior handle="handleBar"/>
</behaviors>
</control>
</components>
</page>
</script>
这样,您应该拥有一个可拖动的 div
标签。该示例展示了使用 AJAX Extensions 的声明式模型的简单性和易用性。在 AJAX Futures 引入的术语中,您刚刚使用声明式标记为 HTML 元素添加了浮动行为。
二、命令式拖放
使用命令式模型完成同样的事情需要更多的代码,但不多。重要的是要理解,当您在页面中添加 AJAX Extensions Script Manager 组件时,您实际上是在指示将 AJAX Extensions JavaScript 库加载到您的页面中。AJAX Extensions 库(除其他外)提供了扩展 DOM 的客户端类,并为您提供了允许您以浏览器无关的方式进行编码的工具(尽管目前仍存在 Safari 兼容性问题)。这些客户端类还允许您向 HTML 元素添加行为。
要切换到命令式模型,您需要用两个 JavaScript 函数替换 XML 标记。第一个是用于向 HTML 元素添加浮动行为的通用脚本。它利用 AJAX Extensions 客户端类来完成此操作。
<script type="text/javascript">
function addFloatingBehavior(ctrl, ctrlHandle){
$create(Sys.Preview.UI.FloatingBehavior,
{'handle': ctrlHandle},null, null, ctrl);
}
</script>
该函数接受两个参数值:您希望使其可拖动的 HTML 元素,以及作为拖放行为拖动手柄的 HTML 元素。 新的 $create
函数封装了行为的实例化和初始化例程。addFloatingBehavior
实用函数将在本教程的其余部分中使用。
现在,您需要在页面加载时调用 "addFloatingBehavior(...)
" 函数。令人惊讶的是,这是编写此示例代码中最困难的部分。Script Manager 并不简单地创建一个对 AJAX Extensions JavaScript 库的引用,我读到有人猜测它实际上会将库脚本加载到 DOM 中。无论如何,这意味着库仅在页面上的所有其他内容加载后才加载。那么,问题是,没有标准的方法可以使我们添加浮动行为的代码在库加载后运行;如果我们尝试在库加载之前运行它,我们只会生成 JavaScript 错误,因为我们调用的所有 AJAX Extensions 方法都找不到。
实际上有几种解决方法,但最简单的一种是使用自定义的 AJAX Extensions 事件 "pageLoad()
",它只在库加载后调用。要在页面首次加载时(但在库脚本加载后)为您的 div
标签添加浮动行为,您只需要编写以下代码:
<script type="text/javascript">
function pageLoad(){
addFloatingBehavior(document.getElementById('draggableDiv'),
document.getElementById('handleBar'));
}
</script>
这可以写成这样,使用 AJAX Extensions 脚本的简写,它用 "$get()
" 替换 "document.getElementById()
"。
<script type="text/javascript">
function pageLoad(){
addFloatingBehavior($get('draggableDiv'),$get('handleBar'));
}
</script>
再次,您拥有一个可拖动的 div
,其行为与您使用声明式模型编写的可拖动 div
完全相同。
三、动态拖放
由于声明式模型比命令式模型更简洁,为什么您还要编写自己的 JavaScript 来处理 AJAX Extensions 行为?如果您想动态添加行为,您可能需要自己编写 JavaScript。声明式模型的一个限制是,您只能处理最初在页面上的对象。如果您开始动态向页面添加对象,您就无法使用声明式模型为它们添加浮动行为。另一方面,使用命令式模型,您可以。
在前一个示例的基础上,您将把 "pageLoad()
" 函数替换为一个按需创建浮动 div
的函数。以下 JavaScript 函数将创建一个 div
标签,其中嵌入另一个 div
标签作为手柄,然后将 div
标签插入当前页面,最后向 div
标签添加浮动行为。
function createDraggableDiv() {
var panel= document.createElement("div");
panel.style.height="100px";
panel.style.width="100px";
panel.style.backgroundColor="Blue";
var panelHandle = document.createElement("div");
panelHandle.style.height="20px";
panelHandle.style.width="auto";
panelHandle.style.backgroundColor="Green";
panel.appendChild(panelHandle);
var target = $get('containerDiv').appendChild(panel);
addFloatingBehavior(panel, panelHandle);
}
然后,您只需要在页面上添加一个调用 "createDraggableDiv()
" 函数的按钮。新的 HTML body 应该如下所示:
<input type="button" value="Add Floating Div" onclick="createDraggableDiv();" />
<div id="containerDiv" style="background-color:Purple;height:800px;width:600px;"/>
这将允许您根据需要向页面添加尽可能多的可拖动元素,从而展示一旦您理解了使用 AJAX Extensions 的声明式方法和命令式方法之间的关系,就可以获得的强大功能和灵活性。作为参考,以下是动态拖放示例的完整代码:
<%@ Page Language="C#" %>
<!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>Imperative Drag and Drop II</title>
<script type="text/javascript">
function createDraggableDiv() {
var panel = document.createElement("div");
panel.style.height="100px";
panel.style.width="100px";
panel.style.backgroundColor="Blue";
var panelHandle = document.createElement("div");
panelHandle.style.height="20px";
panelHandle.style.width="auto";
panelHandle.style.backgroundColor="Green";
panel.appendChild(panelHandle);
var target = $get('containerDiv').appendChild(panel);
addFloatingBehavior(panel, panelHandle);
}
function addFloatingBehavior(ctrl, ctrlHandle){
$create(Sys.Preview.UI.FloatingBehavior,
{'handle':
ctrlHandle}, null,
null, ctrl);
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript.js"
/>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewDragDrop.js"
/>
</Scripts>
</asp:ScriptManager>
<h2>Imperative Drag and Drop Code with javascript:
demonstrate dynamic loading of behaviors</h2>
<input type="button" value="Add Floating Div"
onclick="createDraggableDiv();"
/>
<div id="containerDiv"
style="background-color:Purple;height:800px;width:600px;"/>
</form>
</body>
</html>
四、声明式放置区
能够将 HTML 元素拖动到页面上并让它们停留在原处是很有趣的。然而,为了使这种行为真正有用,当放置发生时应该会引发一个事件。此外,引发的事件应该取决于放置的位置。换句话说,需要有一种行为可以添加到给定的 HTML 元素上,将其变成一个 "放置区" 或 "放置目标",就像浮动行为可以添加到 HTML div
标签上一样,将其变成一个拖放元素。
在以下示例中,我将展示 Atlas 如何支持放置区的概念。在其当前状态下,Atlas 并不能像为浮动元素那样开箱即用地支持创建放置元素。但是,它实现了 dragdroplist
元素和 draggablelistitem
元素的行为,这两个元素一起使用,允许您创建可以通过拖放重新排序的列表。如果您想进一步探索此功能,网上有几个使用 dragDropList
行为的优秀示例,例如,Atlas 拖放入门。
dragdropzone 行为的主要缺点是它只能与被 DragDropList
行为装饰的项一起工作。这项功能相当具体。要获得我上面描述的那种开放式的放置区功能,并且它也能与预定义的浮动行为一起工作,您将需要自己编写放置区行为类。幸运的是,这并不难。
Atlas 为 JavaScript 添加了几个 OOP 扩展,以使其更强大,例如命名空间、抽象类和接口。您将在编写自己的放置区行为时利用这些扩展。如果您窥探幕后,查看 PreviewDragDrop.js 文件中的源代码(位于 C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\ScriptLibrary\Debug 目录中),您会发现其中定义了几个接口,包括 Sys.UI.DragSource
和 Sys.UI.DropTarget
。事实上,FloatingBehavior
类和 DraggableListItem
类都实现了 Sys.UI.DragSource
接口,而 DragDropList
类实现了 Sys.UI.DropTarget
。这两个接口的代码如下:
Sys.Preview.UI.IDragSource = function Sys$Preview$UI$IDragSource() {
}
Sys.Preview.UI.IDragSource.prototype = {
get_dragDataType: Sys$Preview$UI$IDragSource$get_dragDataType,
getDragData: Sys$Preview$UI$IDragSource$getDragData,
get_dragMode: Sys$Preview$UI$IDragSource$get_dragMode,
onDragStart: Sys$Preview$UI$IDragSource$onDragStart,
onDrag: Sys$Preview$UI$IDragSource$onDrag,
onDragEnd: Sys$Preview$UI$IDragSource$onDragEnd
}
Sys.Preview.UI.IDragSource.registerInterface('Sys.Preview.UI.IDragSource');
Sys.Preview.UI.IDropTarget = function Sys$Preview$UI$IDropTarget() {}
Sys.Preview.UI.IDropTarget.prototype = {
get_dropTargetElement: Sys$Preview$UI$IDropTarget$get_dropTargetElement,
canDrop: Sys$Preview$UI$IDropTarget$canDrop,
drop: Sys$Preview$UI$IDropTarget$drop,
onDragEnterTarget: Sys$Preview$UI$IDropTarget$onDragEnterTarget,
onDragLeaveTarget: Sys$Preview$UI$IDropTarget$onDragLeaveTarget,
onDragInTarget: Sys$Preview$UI$IDropTarget$onDragInTarget
}
Sys.Preview.UI.IDropTarget.registerInterface('Sys.Preview.UI.IDropTarget');
为什么您需要实现这些接口而不是简单地写全新的类来支持拖动、放置和放置区?秘密在于,在后台,一个名为 DragDropManager
的第三个类实际上正在协调可拖动元素和放置区元素之间的交互,它只知道如何处理实现 IDragSource
或 IDropTarget
的类。DragDropManager
类会注册哪些放置区是每个可拖动元素的合法目标,处理 MouseOver
事件以确定何时放置区上有一个可拖动元素,以及您不想自己做的其他一百件事。事实上,它做得如此之好,以至于您即将编写的放置区行为非常简洁。首先,创建一个名为 DropZoneBehavior.js 的新 JavaScript 文件。我将我的 JavaScript 文件放在名为 scriptLibrary 的子目录中,但这对于放置区行为的工作不是必需的。接下来,将以下代码复制到您的文件中:
Type.registerNamespace('Custom.UI');
Custom.UI.DropZoneBehavior = function(value) {
Custom.UI.DropZoneBehavior.initializeBase(this, [value]);
initialize: function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this, 'initialize');
// Register ourselves as a drop target.
Sys.Preview.UI.DragDropManager.registerDropTarget(this);
},
dispose: function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this, 'dispose');
},
getDescriptor: function() {
var td = Custom.UI.DropZoneBehavior.callBaseMethod(this,
'getDescriptor');
return td;
},
// IDropTarget members.
get_dropTargetElement: function() {
return this.get_element();
},
drop: function(dragMode, type, data) {
alert('dropped');
},
canDrop: function(dragMode, dataType) {
return true;
},
onDragEnterTarget: function(dragMode, type, data) {
},
onDragLeaveTarget: function(dragMode, type, data) {
},
onDragInTarget: function(dragMode, type, data) {
}
}
Custom.UI.DropZoneBehavior.registerClass('Custom.UI.DropZoneBehavior',
Sys.UI.Behavior, Sys.Preview.UI.IDragSource,
Sys.Preview.UI.IDropTarget, Sys.IDisposable);
if(typeof(Sys) != "undefined") {Sys.Application.notifyScriptLoaded();}
我需要从后往前解释这个类。首先值得注意的是倒数第二行 "Custom.UI.DropZoneBehavior.registerClass
"。这就是上面定义的 DropZoneBehavior
类与 AJAX Extensions 注册的地方。registerClass
方法的第一个参数是类的名称。第二个参数是基类。其余参数是新类实现的接口。紧随其后的行抛出一个自定义事件,指示脚本已成功加载(这对于支持 Safari 是必需的,Safari 不支持此功能)。现在回到顶部,"Type.registerNamespace
" 方法允许您注册自定义命名空间。下一行使用匿名方法语法声明我们的新类。这是一种我不太熟悉的编写 JavaScript 的方式,但它对于使 JavaScript 面向对象非常重要,并且对于设计 Atlas 行为至关重要。在匿名方法中,类方法 initialize
、dispose
和 getDescriptor
是所有行为类使用的标准方法,在此实现中,您只需要调用基类方法(即,此代码示例的倒数第二行中指定的基类的该方法)。您唯一特别要做的事情是在 initialize
方法中将放置目标注册到 Sys.Preview.UI.DragDropManager
。这是实现大部分拖放魔术的动作。
接下来,您实现 IDropTarget
方法。在此示例中,您只实现了两个方法:"this.canDrop
" 和 "this.drop
"。对于 "canDrop
",您只会返回 true
。可以在此处放置更复杂的逻辑,以确定哪些浮动 div
标签实际上可以放置在给定的目标上,甚至可以确定哪些浮动 div
放置时会执行什么操作,但在本例中,您只想实现一个允许任何浮动 div
放置在其上的 IDropTarget
的基本实现。您对 "drop" 方法的实现也同样非常基础。当浮动元素被放置在您的放置目标之一上时,将弹出一个警报消息,指示发生了某事。就是这样。您现在拥有一个与前面示例中使用的浮动行为兼容的放置行为。
您现在应该编写一个页面来展示您新的自定义放置区行为。您可以基于前面的示例来完成此操作。在 Script Manager 中,除了注册 PreviewDragDrop 脚本外,您还将希望注册您的新 DropZoneBehavior 脚本。
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript" />
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewDragDrop" />
<asp:ScriptReference
Path="scriptLibrary/DropZoneBehavior.js" />
</Scripts>
</asp:ScriptManager>
接下来,您将希望在 HTML body 中添加一个新的 div
标签,该标签可用作放置目标。
<div style="background-color:Red;height:200px;width:200px;">
<div id="draggableDiv"
style="height:100px;width:100px;background-color:Blue;">
<div id="handleBar"
style="height:20px;width:auto;background-color:Green;">
</div>
</div>
</div>
<div id="dropZone" style="background-color:cornflowerblue;
height:200px;width:200px;">
Drop Zone
</div>
最后,您需要添加一个声明式标记元素,将您的自定义 DropZone 行为添加到您打算用作放置区元素的 div
中。XML 标记应如下所示:
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005"
xmlns:JavaScript="Custom.UI">
<components>
<control id="dropZone">
<behaviors>
<JavaScript:DropZoneBehavior/>
</behaviors>
</control>
<control id="draggableDiv">
<behaviors>
<floatingBehavior handle="handleBar"/>
</behaviors>
</control>
</components>
</page>
</script>
您刚刚编写的代码基本上应该为原始的声明式拖放示例添加一个放置区。当您将拖动元素放在放置区上时,现在应该会出现一个警报消息。您可以扩展此代码,使您的自定义放置区行为的 drop 方法执行更多有趣的操作,例如触发当前页面上的其他 JavaScript 事件,甚至调用 ASP.NET AJAX 的 Web 服务,然后该服务将为您处理服务器端代码。
五、命令式放置区
要使用 JavaScript 而不是声明式脚本创建放置区,只需添加以下 JavaScript 函数即可初始化您的放置元素及其自定义放置行为。
function addDropZoneBehavior(ctrl){
$create(Custom.UI.DropZoneBehavior, {}, null, null, ctrl);
}
要完成所有连接,请从 ASP.NET AJAX 的 pageLoad()
方法调用此 addDropZoneBehavior
函数,就像您在早期示例中为 addFloatingBehavior
函数所做的那样。这将把适当的行为附加到其各自的 HTML 元素上,并复制您上面使用声明式标记创建的拖放和放置区功能。如果您想使其动态工作,只需添加您为先前动态示例编写的 createDraggableDiv()
函数即可。作为参考,以下是创建命令式放置区的完整代码:
<%@ Page Language="C#" %>
<!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 id="Head1" runat="server">
<title>Imperative Drop Targets</title>
<script type="text/javascript">
function addFloatingBehavior(ctrl, ctrlHandle){
$create(Sys.Preview.UI.FloatingBehavior,
{'handle': ctrlHandle}, null, null,
ctrl);
}
function addDropZoneBehavior(ctrl){
$create(Custom.UI.DropZoneBehavior,
{}, null, null, ctrl);
}
function pageLoad(){
addDropZoneBehavior($get('dropZone'));
addFloatingBehavior($get('draggableDiv'),$get('handleBar'));
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewScript"/>
<asp:ScriptReference
Name="Microsoft.Web.Resources.ScriptLibrary.PreviewDragDrop"/>
<asp:ScriptReference
Path="scriptLibrary/DropZoneBehavior.js"/>
</Scripts>
</asp:ScriptManager>
<h2>Imperative Drop Targets with javacript</h2>
<div style="background-color:Red;height:200px;width:200px;">
<div id="draggableDiv"
style="height:100px;width:100px;background-color:Blue;">
<div id="handleBar"
sstyle="height:20px;width:auto;background-color:Green;">
</div>
</div>
</div>
<div id="dropZone" style="background-color:cornflowerblue;
height:200px;width:200px;">Drop Zone</div>
</form>
</body>
</html>
除了放置区行为外,您可能还想编写自己的浮动行为。例如,默认情况下,带有浮动行为的元素在您放置它们的位置就会停住。您可能希望将其扩展,以便您的浮动 div
在您将其放置在放置区之外时会弹回其原始位置。此外,您可能希望更改拖动元素在拖动时的外观,方法是使其透明、更改其颜色或完全替换拖动图像。所有这些都可以通过创建实现 IDragSource
接口的行为来完成,就像您创建实现 IDropTarget
接口的自定义类一样。
本教程在很大程度上是原始 Atlas 教程的直接翻译,该教程是我针对四月 CTP 编写的。尽管 Atlas 的许多概念仍然保留在 AJAX Extensions 中,但有些概念已经经过了微调,以至于原始教程中曾经贴切和准确的内容现在已不再如此。例如,在原始 Atlas 教程中,我可以将 XML 脚本和其他 ASP.NET AJAX 功能作为一个技术来讨论,而现在它们是两个不同的技术,在 Microsoft 的支持度和兴趣度上有所不同。存在更细微的差异,我认为这些差异使当前版本的教程在某种程度上过时了,就好像我在用略带口音的语调说话一样;换句话说,虽然我坚持本教程的准确性,但我认为它在翻译中失去了一些原有的优雅。我相信本教程对于那些试图开始使用 Microsoft 的 AJAX 实现的人来说仍然有用,尽管它在这一点上的主要实用性可能在于那些习惯了 Atlas 工作方式并需要一个参考点来查看该技术的语义如何变化的人。我希望这些示例能够帮助您克服一些成长的烦恼,就像它帮助我克服我的烦恼一样。