asp.net|程式|滑鼠
摘要 本文將詳細探討Atlas中的宣告式程式設計與強制性編程之間的關係,及如何用之在一個web用戶端實現拖放功能。下圖為本文相應樣本程式運行結果快照。
一. 簡介
本文旨在協助讀者理解微軟的Atlas技術的某些方面的工作原理。Atlas的實現宗旨是簡化AJAX風格的Web程式開發。然而,就象所有其它技術一樣,為了更好地使用這個工具,你需要深入理解Atlas抽象的內在技術。Atlas抽象的一個關鍵之處是新的XML標記文法,目的是實現利用Atlas進行編程更為容易。藉助於XML標記,開發人員能夠以聲明性方式修改他們的代碼。然而,有時當一個開發人員可能想以編程方式來改變其代碼時,他需要理解,在標記抽象層下面,他實際在與一些JavaScript及一些微軟開發的定製JavaScript庫打交道。為了展示Atlas聲明性模型與可程式化模型之間的關係,我們將討論幾個例子,在這些樣本中,我們將使用兩種模型來實現相同的功能。我將向你展示如何使用AtlasUIDragDrop庫檔案來執行基本的拖放操作以及建立放置區。
[背景]
當我寫作本文時,Atlas仍處於其beta測試階段,並且在繼續修改中。本文中的這些樣本適用於Atlas的四月CTP版本;因此,Atlas的更新的發行版本可能會影響本文的精確性。另外,還要注意,Atlas僅能與.NET 2.0一起工作。
二. 聲明性拖放
為了把拖允許存取為添加到一個div標籤,第一項任務是使用Atlas標記。通過拖放,我僅想實現能夠拖放一個對象並且讓它位於你想把它放置的地方。當把一個對象放置到一個指定的點時,在實際開發中所表現出的動作將在後面討論。為了配置你的網頁以便使用Atlas,你需要從微軟網站把Microsoft.Web.Atlas.dll檔案下載到你的bin檔案夾下並且使用下列入口配置你的web.config檔案:
<system.web>
<pages>
<controls>
<add namespace="Microsoft.Web.UI"
assembly="Microsoft.Web.Atlas" tagPrefix="atlas"/>
<add namespace="Microsoft.Web.UI.Controls"
assembly="Microsoft.Web.Atlas" tagPrefix="atlas"/>
</controls>
</pages>
</system.web>
接下來,你需要把一個Atlas指令碼管理器控制項添加到你的.aspx頁面並且使用AtlasUIDragDrop庫來配置:
<atlas:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<atlas:ScriptReference ScriptName="AtlasUIDragDrop" />
</Scripts>
</atlas: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標籤。該樣本展示了結合Atlas使用聲明性方式的簡單性和容易性。在Atlas所引入的術語中,你僅使用了聲明性標記來把漂浮行為添加到一個HTML元素。
三. 強制性拖放
為了使用編程方式來實現相同的功能,我們需要進行一些編程,但是不需要較多的編碼。你必須明白,當你把一個Atlas指令碼管理器組件添加到你的頁面上時,你實際上是在下命令把Atlas JavaScript庫載入到你的頁面。這個Atlas庫提供了擴充DOM的用戶端類,並且提供允許你在一個瀏覽器中進行編碼的工具(儘管現在在Safari相容性方面還存在一些問題)。這些用戶端類還允許你把你的HTML元素添加到行為。
為了切換到一個強制性模型,你需要用兩個JavaScript函數來代替XML標記。第一個函數是一個普通指令碼用於把漂浮行為添加到一個HTML元素上。它利用了Atlas用戶端類來完成此功能:
<script type="text/javascript">
function addFloatingBehavior(ctrl, ctrlHandle){
//建立新的漂浮行為對象
var floatingBehavior = new Sys.UI.FloatingBehavior();
//漂浮行為類具有一個Handle屬性
floatingBehavior.set_handle(ctrlHandle);
//把對象參考值的為Atlas用戶端控制項
var dragItem = new Sys.UI.Control(ctrl);
//從Atlas控制項中取得行為集合
//添加我們自己的漂浮行為
dragItem.get_behaviors().add(floatingBehavior);
//運行該漂浮行為的內部javascript
floatingBehavior.initialize();
}
</script>
這個函數使用兩個參數值:你想要拖放的HTML元素和實現該拖允許存取為的拖放控制代碼HTML元素。然後,你執行個體化一個新的Atlas用戶端行為對象。該漂浮行為具有一個handle屬性-你把HTML元素的控制代碼傳遞給它。然後,你需要基於你想使之成為可拖放的控制項以建立一個新的用戶端控制項對象。把你的div標籤轉換成一個Atlas用戶端控制項能夠使你把Atlas行為添加到它上面。你可以使用get_behaviors()方法來返回一個行為集合,並且使用add方法來把一個新行為添加到你的HTML對象。最後,你調用行為對象的initialize()方法以允許在內部配置行為自身。我們將在本文剩下的部分中一直使用這個工具函數。
現在,當頁面裝載時,你需要調用addFloatingBehavior函數。說實話,這是編寫這個樣本中最有難度的編碼部分。指令碼管理器並不是簡單地建立一個到Atlas JavaScript庫的引用,我推想它實際把該庫指令碼裝載到DOM。在任何情況下,這意味著,只有頁面中的其它一切都裝載後該庫才得到載入。這樣以來,我們所面臨的問題在於,裝載該庫後,沒有標準的方法來使我們的添加漂浮行為的代碼運行;並且如果我們在載入該庫前運行它,那麼我們可以簡單地產生JavaScript錯誤-因為我們調用的所有的Atlas方法都不能被發現。
其實,存在好幾種方法可以來解決這個問題,但是最容易的方法是使用一個定製的Atlas事件pageLoad()-這個事件實際只在裝載這些庫後才調用它。為了把漂浮行為添加到你的div標籤中,當第一次載入頁面時(但是在庫指令碼裝載後),你僅需要編寫如下代碼:
<script type="text/javascript">
function pageLoad(){
addFloatingBehavior(document.getElementById('draggableDiv'),document.getElementById('handleBar'));
}
</script>
這可以使用一種Atlas指令碼速記方式來書寫-用"$()"代替"document.getElementById()":
<script type="text/javascript">
function pageLoad(){
addFloatingBehavior($('draggableDiv'),$('handleBar'));
}
</script>
在此,可以看到,你有一個可拖動的div,其行為與你使用聲明性模型編寫的可拖動的div完全一致。
四. 動態拖放
既然聲明性模型比強制性模型更為清晰,那麼為什麼你還要編寫自己的JavaScript來處理Atlas行為呢?其實,這種聲明性模型的一個限制是,你只能使用一開始就位於該頁面上的對象。如果你開始動態地把對象添加到該頁面,那麼你無法使用聲明性模型來把漂浮行為添加到其上。不過,藉助於強制性模型,你能夠實現。
基於前面的例子,你要用一個據要求建立漂浮div的函數來代替pageLoad()函數。下列JavaScript函數會建立一個嵌有另一個div標籤(用作一個handlebar)的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 = $('containerDiv').appendChild(panel);
addFloatingBehavior(panel, panelHandle);
}
然後,你只需要把一個按鈕添加到該調用createDraggableDiv()函數的頁面。現在,新的HTML體看上去具有如下形式:
<input type="button" value="Add Floating Div" />
<div id="containerDiv" style="background-color:Purple;height:800px;width:600px;"/>
這將允許你把很多的可拖放元素添加到你的頁面上,這說明了,一旦你理解了在以聲明方式使用Atlas和以編程方式使用它之間的關係,那麼Atlas將表現出強大的威力和靈活性。作為參考,下面是動態拖放樣本的完整實現代碼:
<%@ 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 = $('containerDiv').appendChild(panel);
addFloatingBehavior(panel, panelHandle);
}
function addFloatingBehavior(ctrl, ctrlHandle){
var floatingBehavior = new Sys.UI.FloatingBehavior();
floatingBehavior.set_handle(ctrlHandle);
var dragItem = new Sys.UI.Control(ctrl);
dragItem.get_behaviors().add(floatingBehavior);
floatingBehavior.initialize();
}
</script>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<atlas:ScriptReference ScriptName="AtlasUIDragDrop" />
</Scripts>
</atlas:ScriptManager>
<h2>Imperative Drag and Drop Code with javascript:
demonstrate dynamic loading of behaviors</h2>
<input type="button" value="Add Floating Div" />
<div id="containerDiv" style="background-color:Purple;height:800px;width:600px;"/>
</form>
</body>
</html>
五. 聲明性Dropzone
我們可以在一個頁面上拖動HTML元素,然後讓它們位於其投放位置。然而,為了使該行為真正有用,當投放發生時,應該拋出一個事件。而且,所拋出的事件應該依賴於在何處發生投放。換句話說,需要把行為添加到一個給定HTML元素-由它來把這一行為轉換成一個"dropzone"或"投放目標",可以使用相同的方法把漂浮行為添加到一個HTML div標籤以便把它轉換成一個可拖放的元素。
在下列例子中,我將向你展示Atlas是如何支援dropzone概念的。在它的目前狀態中,Atlas並不支援與其支援漂浮元素一樣的方式來提供一種現成的行為以支援建立dropzone元素。然而,它確實實現了一個DragDropList元素和一個DraggableListItem元素的行為(這兩個元素聯用,允許你建立能夠通過拖放重新排序的列表)。如果你想進一步探討這一功能,你可以在網上找到若干使用DragDropList行為的好例子,例如,《Introduction to Drag And Drop with Atlas》。
dragdropzone行為的主要不利條件是,它僅與具有DragDropList行為的項一起工作。為了確定我上面描述的開放端點的dropzone功能的類型(它將與預定義的漂浮行為一同使用),你需要用JavaScript編寫你自己的dropzone行為類。幸好,這並不困難。
Atlas把若干OOP擴充添加到JavaScript中以使加強其擴充能力,例如命名空間,抽象類別和介面。在編寫你自己的dropzone行為時你還要利用這些工具。如果你分析一下AtlasUIDragDrop.js檔案的源碼(可以使用Visual Studio調試器),那麼你會發現在那裡定義了若干介面,這包括一個相應於Sys.UI.DragSource而另一個相應於Sys.UI.DropTarget。事實上,FloatingBehavior類和DraggableListItem類都實現了Sys.UI.DragSource介面,而Sys.UI.DropTarget被DragDropList類所實現。這兩個介面的代碼如下所示:
Sys.UI.IDragSource = function() {
this.get_dataType = Function.abstractMethod;
this.get_data = Function.abstractMethod;
this.get_dragMode = Function.abstractMethod;
this.onDragStart = Function.abstractMethod;
this.onDrag = Function.abstractMethod;
this.onDragEnd = Function.abstractMethod;
}
Sys.UI.IDragSource.registerInterface('Sys.UI.IDragSource');
Sys.UI.IDropTarget = function() {
this.get_dropTargetElement = Function.abstractMethod;
this.canDrop = Function.abstractMethod;
this.drop = Function.abstractMethod;
this.onDragEnterTarget = Function.abstractMethod;
this.onDragLeaveTarget = Function.abstractMethod;
this.onDragInTarget = Function.abstractMethod;
}
Sys.UI.IDropTarget.registerInterface('Sys.UI.IDropTarget');
為什麼你需要實現這些介面而不是簡單地編寫一些新類來支援拖放和dropzone呢?秘密是,在後台,還有一個類DragDropManager,負責實際協調可拖放元素與dropzone元素之間的互動,並且它僅僅知道如何與實現IDragSource或IDropTarget介面的類一起工作。這個DragDropManager類註冊對於每一個可拖放的元素來說哪些dropzone是合法的目標,並處理MouseOver事件以決定何時一個dropzone上面具有一個可拖放的元素,以及其它你不需要自己做的大量事情。事實上,它處理得如此完美,以至後面你要編寫的dropzone行為需要極少的代碼。首先,建立一新的JavaScript檔案DropZoneBehavior.js。我把我的JavaScript檔案放到了一個子目錄scriptLibrary下,但是,這對於實現dropzone行為是不必要的。然後,把下列代碼複製到你的檔案中:
Type.registerNamespace('Custom.UI');
Custom.UI.DropZoneBehavior = function() {
Custom.UI.DropZoneBehavior.initializeBase(this);
this.initialize = function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this, 'initialize');
//把我們自己註冊為一個拖放目標.
Sys.UI.DragDropManager.registerDropTarget(this);
}
this.dispose = function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this, 'dispose');
}
this.getDescriptor = function() {
var td = Custom.UI.DropZoneBehavior.callBaseMethod(this, 'getDescriptor');
return td;
}
//IDropTarget成員.
this.get_dropTargetElement = function() {
return this.control.element;
}
this.drop = function(dragMode, type, data) {
alert('dropped');
}
this.canDrop = function(dragMode, dataType) {
return true;
}
this.onDragEnterTarget = function(dragMode, type, data) {}
this.onDragLeaveTarget = function(dragMode, type, data) {}
this.onDragInTarget = function(dragMode, type, data) {}
}
Custom.UI.DropZoneBehavior.registerClass('Custom.UI.DropZoneBehavior',
Sys.UI.Behavior, Sys.UI.IDragSource,
Sys.UI.IDropTarget, Sys.IDisposable);
Sys.TypeDescriptor.addType('script', 'DropZoneBehavior',
Custom.UI.DropZoneBehavior);
我需要解釋一下這個類。首先要注意從第二(以"Custom.UI.DropZoneBehavior.registerClass"開始)到最後一行代碼。這是上面定義的dropZoneBehaviorClass註冊到Atlas的位置。registerClass方法的第一個參數相應於類的名字,第二個參數則相應於基類的名字。第三個參數相應於實現新類的介面。接下來的一行代碼使你的類可用於聲明性標記指令碼。現在,我們回到開始,"Type.registerNamespace"方法允許你註冊你的定製命名空間。下一行使用一個匿名方法文法聲明我們的新類。這裡使用了JavaScript物件導向的設計思想,這對於設計Atlas行為來說是必要的。在該匿名方法中,類方法Initialize,Dispose和getDescriptor都是一些簡單的標準方法,用於所有的行為類,而且在這個簡單實現中,你僅需要調用基方法(也就是,從這個例子的第二到最後一行代碼中所指定的基類的方法)即可。你要做的唯一特別的一點是,使用在Initialize方法中的Sys.UI.DragDropManager來註冊拖放目標。這裡是大部分的拖放"魔術"所在。
然後,你實現IDropTarget方法。在這個例子中,你僅實現了兩個方法:this.canDrop與this.drop。對於canDrop,你只是簡單地返回true。其實,更有趣的邏輯可以放到其中,譬如實現有哪些div標籤被實際拖放到一個給定的目標上,或者決定相應於不同類型的漂浮div,在拖放它們時各自的不同行為;但是,在此情況下,你僅想簡單地實現IDropTarget-它允許任何漂浮div拖動到其上。你的"drop"方法的實現只是個架構而已。當一個漂浮元素被拖放到你的拖放目標之一時,將顯示一條警告訊息指示已經發生了一些事情。現在,你已經有了一個拖允許存取為,它能夠與我們在上一個例子中所用的漂浮行為一同工作。
現在你應該建立一個頁面來展示你的新定製的dropzone行為。為此,你可以在前面樣本的基礎上來實現。在Atlas指令碼管理器中,除註冊AtlasUIDragDrop指令碼以外,你還要註冊你的新的DropZoneBehavior指令碼:
<atlas:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<atlas:ScriptReference ScriptName="AtlasUIDragDrop" />
<atlas:ScriptReference Path="scriptLibrary/DropZoneBehavior.js" />
</Scripts>
</atlas:ScriptManager>
然後,你要把一個新的div標籤添加到HTML體,這可以被用作一個拖放的目標:
<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行為到你計劃用作一個dropzone元素的div。該XML標記應該有如下所示形式:
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<control id="dropZone">
<behaviors>
<DropZoneBehavior/>
</behaviors>
</control>
<control id="draggableDiv">
<behaviors>
<floatingBehavior handle="handleBar"/>
</behaviors>
</control>
</components>
</page>
</script>
剛才的代碼把一個dropzone添加到最初聲明的拖放樣本中。當你在dropzone上拖動元素時,將出現一個警告訊息框。你可以擴充這些代碼以便使你的定製dropzone行為的drop方法實現一些更為有趣的事情,例如啟用當前的頁面中的其它JavaScript事件,甚至使用Atlas調用一個web服務-由它來為你處理伺服器端代碼。
六. 強制性Dropzone
為了使用JavaScript代替聲明性指令碼建立dropzone,僅需要使用定製的dropzone行為添加如下的JavaScript函數來初始化你的dropzone元素:
function addDropZoneBehavior(ctrl){
var dropZone = new Sys.UI.Control(ctrl);
var dropZoneBehavior = new Custom.UI.DropZoneBehavior();
dropZone.get_behaviors().add(dropZoneBehavior);
dropZoneBehavior.initialize();
}
為了"鉤住"一切,你可以調用這個來自Atlas pageLoad()方法的addDropZoneBehavior函數(就象你在前面的樣本中操作addFloatingBehavior函數一樣)。這樣可以把正確的行為依附到它們各自的HTML元素並且複製上面你使用聲明性標記所建立的拖放和dropzone功能。如果你想使此能夠動態工作,那麼你只要添加你為上一個樣本編寫的createDraggableDiv()函數即可。作為一種參考,下面是建立可程式化dropzone的完整代碼:
<%@ 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){
var floatingBehavior = new Sys.UI.FloatingBehavior();
floatingBehavior.set_handle(ctrlHandle);
var dragItem = new Sys.UI.Control(ctrl);
dragItem.get_behaviors().add(floatingBehavior);
floatingBehavior.initialize();
}
function addDropZoneBehavior(ctrl){
var dropZone = new Sys.UI.Control(ctrl);
var dropZoneBehavior = new Custom.UI.DropZoneBehavior();
dropZone.get_behaviors().add(dropZoneBehavior);
dropZoneBehavior.initialize();
}
function pageLoad(){
addDropZoneBehavior($('dropZone'));
addFloatingBehavior($('draggableDiv'),$('handleBar'));
}
</script>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<atlas:ScriptReference ScriptName="AtlasUIDragDrop" />
<atlas:ScriptReference Path="scriptLibrary/DropZoneBehavior.js" />
</Scripts>
</atlas: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"
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>
</form>
</body>
</html>
除了dropzone行為以外,你可能還想寫你自己的漂浮行為。例如,預設地,帶有漂浮行為的元素只是簡單地停留在你放下它們的位置。然而,你可能想擴充這一特徵以便你的漂浮div會"退回"到它原來的位置-當你把它放到一個投放區外的時候。另外,當你拖動它時,你可能想改變被拖放的元素看上去的樣子,或使它透明,或改變它的顏色,或全部替換原來的拖動映像。所有這些都可以通過建立實現IDragSource介面的一種行為來實現,這與你建立一個實現IDropTarget介面的定製類是思路一樣。
七. 總結
本文應該為你擴充Atlas提供的基本拖放功能來建立你自己的行為和功能提供了一個起點。而且,你可以基於此建立控制項;還可以在此基礎上繼續建立使用聲明性標記實現你的行為的Atlas擴充控制項,或建立使用Atlas行為自動建立HTML元素的伺服器端控制項。這樣以來,你就可以進一步建立進階伺服器端控制項-或者是靜態聲明性的,或者是強制性的,卻更複雜些但也更靈活。當然,這是一個超出本文題目的問題。不過,我希望,此後有人會嘗試伺服器端Atlas編程,正象本文所作的用戶端Atlas指令碼編程嘗試一樣。