拖拽:從Dojo到HTML5
Dojo 及 HTML 5 簡介
Dojo 是目前最流行的開源 JavaScript 工具庫之一,很多開發者以及企業用戶都把Dojo 作為首選的 JavaScript 工具。Dojo 為 Web 應用的開發提供了大量的客戶端組件,能夠讓你可以方便的進行 HTML DOM 操作、拖拽、AJAX 調用、定制可視化控件等來使得你的 Web 應用變成富網絡應用 (RIA)。而且 Dojo 在性能、可訪問性、多語言支持以及文檔方面都做的非常出色,這也是企業選擇 Dojo 的原因之一。
HTML 5 是最新一代的 HTML,它將成為 HTML、XHTML 以及 HTML DOM 的新標準, HTML 5 是 W3C 與 WHATWG 合作的結果,目前仍外于開發中 ; 自從上一代 HTML4,Web 世界已經發生了巨大的變化,HTML 5 的到來將更大的促進 Web 的發展,HTML 5 提供了很多新的功能,主要有:
◆新的 HTML 元素,例如 section, nav, header, footer, article 等
◆用于繪畫的 Canvas 元素
◆用于多媒體播放的 video 和 audio 元素
◆用于定位的 Geolocation API
◆本地存儲以及離線應用
◆Web Workers
◆拖拽 API
◆文件 API
我們主要對 HTML 5 的拖拽功能進行講解,并結合文件 API 與桌面進行交互。來與 Dojo 的 dnd 拖拽組件進行比較。
使用 Dojo 創建及定制拖拽應用
類似 Dojo 其他組件,拖拽的實現有兩種方式:聲明式和編程式。在這里我們使用聲明式的方式做簡要的介紹。
在 Dojo 拖拽實現中,有兩個重要的元素 dojo.dnd.Source 和 dojo.dnd.Target。這兩個元素分別標示了拖拽中的源容器 Source 和目標容器 Target。值得注意的是源容器 Source 默認也是目標容器 Target,而不需要作目標容器 Target 的聲明。我們在源容器 Source 中創建一些可以拖動的元素,要讓這些元素可拖動,我們要為這些元素添加 class 屬性值 DojoDndItem。下面的示例代碼定義了一個源容器 Source 以及一些可以拖動的元素。在這些可以拖動的元素中我們可以定義它們的拖拽類型 dndType。dndType 的值開發者可以自己定義,而目標容器 Target 元素的 accept 屬性定義了該目標容器接受的拖拽類型。
清單 1. 創建拖拽的源容器和可拖拽的元素
2. div id="item1" class="dojoDndItem item" dndType="divItem"item1/div
3. img src="w3c.jpg" class="dojoDndItem item" dndType="imageItem"/img
4. a href="http://www.w3.org/TR/html5/" class="dojoDndItem item" dndType="linkItem"
5. HTML5 specification/a
6. /div
對于 Target 我們可以創建一個 div,然后加上屬性 dojoType=dojo.dnd.Target和屬性 accept。不在 accept 中的類型的 dojoDndItem 元素將不被這個容器接受。例如清單 2 中的目標容器只接受 divItem 和 imageItem 這兩種類型,那么清單 1 中的 linkItem 將不能被拖到這個目標容器中。
清單 2. 創建拖拽的目標容器和可接受的類型
2. accept="divItem, imageItem"
真正 Web 應用的拖拽沒有這么簡單,開發者往往需要在拖拽的過程中更多的介入。這時候可以通過對 Dojo 提供的 dojo.dnd.Source 和 dojo.dnd.Target 進行繼承擴展,開發滿足業務需要的功能和特性。這里將不贅述。
使用 HTML 5 創建拖拽應用
在這一章中,我們將要使用 HTML 5 創建一個簡單的拖拽應用,如圖 1 所示,用戶可以把網頁上內容從左邊的區域拖放到右邊的區域。這個應用程序的代碼可到附件中可以下載。
圖 1. HTML 5 拖拽應用效果圖
創建可以拖動的節點
使用 HTML 5 創建拖拽只需要對可拖拽的節點進行聲明給可以拖拽的節點添加 draggable 屬性并設值為 true。如清單 3 中的 div 節點,通過添加 draggable 屬性就可以拖拽了。在 HTML 5 中img和a 默認情況下是可以拖拽的,所以不需要設置 draggable 屬性。
清單 3. 通過添加 draggable 屬性來創建源容器中可以拖動的節點
2. div id="item1" class="item" draggable="true"item1/div
3. img id="item2" src="w3c.jpg" class="item"/img
4. a id="item3" href="http://www.w3.org/TR/html5/" class="item"HTML5 specification/a
5. /div
創建 HTML 5 拖拽的源容器和目標容器
在 HTML 5 中,我們需要給指定的節點來綁定一些事件來使之成為具有源容器或目標容器的功能。在 HTML 5 的拖拽過程中的事件有 7 個,分別是應用在目標容器或拖動節點上的 dragstart, drag, dragend 等 3 個事件,以及應用在目標容器節點上的dragenter, dragover, dragleave 和 drop 等 4 個事件。表 1 中對這些事件的觸發機制和常見的操作進行了描述。
表 1. HTML 5 拖拽過程中可以綁定的事件
備注 : 在 Dojo 中所有 dnd 源容器或目標容器在拖拽開始時都會調用 onDndStart 事件方法,而在 HTML 5 中只有拖動的節點及源容器可以觸發 dragstart 事件,其他容器包括目標容器在拖動開始時不會感知源容器及拖動節點的 dragstart 事件。
清單 4 展示給目標容器綁定 dragenter, dragover, dragleave, drop 事件的示例代碼。在 dragenter 和 dragleave 事件中,我們對目標容器的背景樣式進行修改使得用戶感知目標容器的狀態(如圖 2 所示)。在 dragover 事件中我們對清單 3 中的鏈接元素(id 屬性值為 item3)的節點進行了限制。drop 事件中我們要把拖動的節點插入到目標節點的 DOM 結構中。
清單 4. 創建目標容器的事件
2. dojo.connect(target, 'dragover', function(e){
3. // doesn't allows link item (id = item3) to drop
4. if (e.dataTransfer.getData('id') != "item3"){
5. e.preventDefault();
6. }
7. });
8.
9. dojo.connect(target, 'dragenter', function(e){
10. //add style
11. dojo.addClass(target, "over");
12. });
13.
14. dojo.connect(target, 'dragleave', function(){
15. //remove style
16. dojo.removeClass(target, "over");
17. });
18.
19. dojo.connect(target, 'drop', function(e){
20. //remove style if drop is successful
21. dojo.removeClass(target, "over");
22.
23. // stops the browser from redirecting
24. if (e.stopPropagation) e.stopPropagation();
25.
26.
27. var itemId = e.dataTransfer.getData('id');
28. var dragItem = dojo.byId(itemId);
29. e.target.appendChild(dragItem);
30.
31. }
圖 2. 當拖動節點到目標容器是時對目標容器進行高亮顯示
從清單 4 中我們在目標容器上對事件對象的 dataTransfer 屬性進行了 getData 操作取出了關鍵字 id 對應的數據。在 HTML 5 拖拽過程中,用戶可以在表 1 定義的事件里通過 event.dataTransfer 得到 DataTransfer 對象 ( 詳見 W3C 網站上的接口定義)并對其進行定制傳輸數據、定制拖拽影像等操作。例如我們可以在 dragstart 事件中通過 setData 方法初始化數據(代碼詳見附件)。表 2 中列出了這各數據對象的方法及常用的用途。
表 2. DataTransfer 的常用方法
與桌面進行交互
除了在網頁中對一些頁面上的元素進行拖拽以外,HTML 5 擴充的 API 還允許網頁與文件系統進行交互,比如從文件系統拖一個或幾個文件到網頁中,或是從網頁拖到文件系統中。以前者為例,當我們從桌面或其它文件夾拖動文件到網頁上某個目標結點時,我們可以通過 DataTransfer 的 files 屬性得到這些文件數量以及文件的屬性及內容。DataTransfer.files.length 的大小即為拖動文件的數量,當沒有拖動文件時,files.length 的大小即為 0,可用來判斷是否有文件拖動。
清單 5. 通過 dataTransfer.files 拿到文件對象
2. var msg = "";
3. for (var i = 0; i files.length; i++) {
4. console.log ("Name: " + files[i].name + ", fileSize: " + files[i].size);
5. var dataReader = new FileReader();
6. dataReader.onload = function(){
7. msg += ("content: " + dataReader.result);
8. }
9. dataReader.readAsText(files[i]);
10. }
從清單 5 中的代碼中我們可以看到 files 中存儲了若干 file 對象,通過這個對象可以獲取文件名,文件大小等。然后我們可以通過 FileReader 獲取文件的內容。獲取內容的 FileReader 并不是 HTML 5 拖拽的功能,而是借助了 File API。它可以以文本,二進制,以及 dataURL 的形式讀取,實現讀取文件內容實現文件上傳等,在我們的示例代碼 HTML 5dndfile.html 中我們演示了通過 readAsText 方法讀取文本文件和通過 readAsDataURL 方法讀取圖像文件的使用。
與桌時行交互時,我們只需要對將清單 5 中給出的代碼稍加修改加到目標容器的 drop 事件中,其它事件不用修改。例如清單 6 所示。
清單 6. 在目標容器的 drop 事件讀取文本文件的內容
2. if (e.stopPropagation){
3. e.stopPropagation(); // stops the browser from redirecting
4. }
5. var files = e.dataTransfer.files;
6. var msg = "";
7. for (var i = 0; i files.length; i++) {
8. msg += ("Name: " + files[i].fileName + ", fileSize: " + files[i].fileSize);
9. var dataReader = new FileReader();
10. dataReader.onload = function(){
11. msg += ("content: " + dataReader.result);
12. textdiv.textContent = msg;
13. }
14. dataReader.readAsText(files[i]);
15. }
16. });
這樣當我們拖動一個文本文件到指定的目標區域時,我們就可以看到文件內容。
Dojo 和 HTML 5 拖拽功能的比較和選擇
Dojo 實現了一套完整的拖拽框架和事件機制,并提供了默認的實現,用戶可以通過聲明的方式快速實現拖拽,而且還可以通過繼承默認的 Source、Target 以及 Avatar 實現拖拽定制化。從使用經驗上來看,Dojo 更傾向于完整的 DOM 節點操作,而數據的傳輸往往是通過綁在 DOM 節點上的屬性實現的。
HTML 5 的拖拽現在還在規范的定制和完善中,各個主流瀏覽器對該規范的支持也是各有千秋,基本上還處于發展的階段。本文中提供的示例僅在 Firefox 3.6 以上版本測試通過。HTML 5 作為新時代的 HTML 協議,拖拽事件中的 DataTransfer 接口體現了拖拽過程中以數據傳輸為中心的發展前景。與此同時,跟 File API 的結合可以使得 Web 應用的數據交互通過拖拽操作延伸到最終用戶的桌面及文件系統上。另外 HTML 5 還可以實現在不同瀏覽器窗口之間的拖拽操作,也是拖拽過程傳輸數據的一種應用。
小結
對比 Dojo 和 HTML 5,我們不難發現在使用 Dojo 比 HTML 5 可以更容易地開發出體驗效果非常好的拖拽應用;而 HTML 5 作為 HTML 的新規范,注重了拖拽過程中數據傳輸的重要性。
兩者如果可以融合則可以互補其短。通過實驗,筆者發現 Dojo dnd 與 HTML 5 拖拽暫時不能在同一結點上同時使用,由于 Dojo 是在 HTML4 規范基礎上的工具包,它們之間可能因在事件上的沖突會導致 HTML 5 拖拽不可用。但是我們有理由相信,隨著 HTML 5 規范的發展,將會有支持 HTML 5 拖拽的工具包出現,屆時開發者可以更為便捷的開發出更為豐富的 Web 應用。