簡介
拖曳放置 (DnD) 是 HTML 5 的眾多優異功能之一,並且受到 Firefox 3.5、Safari、Chrome 和 IE 的支援。Google 最近推出了新功能,讓 Google Chrome 使用者可以將檔案從瀏覽器拖曳至電腦桌面。這項功能非常方便,但直到 Ryan Seddon 發布一篇文章,說明他對這項新功能的發現,這項功能才廣為人知。
在 Box.net,我們很高興這些新功能可協助我們改善雲端內容管理解決方案,並為開發人員社群做出更多貢獻。很高興在此宣布,我們已將 DnD Download 整合至產品中。 Box 使用者現在可以將檔案直接從 Chrome 瀏覽器拖曳到電腦,以便下載及儲存檔案。
我想分享我在開發這項新功能時,如何經歷了幾次迭代。
檢查是否支援拖曳 API
首先,請確認您的瀏覽器是否完全支援 HTML5 拖曳功能。最簡單的方法就是使用名為 Modernizr 的程式庫,檢查特定功能:
if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}
疊代作業 1
我首先嘗試 Seddon 在 Gmail 中發現的方法。我已在檔案的錨點連結中新增「data-downloadurl」屬性。這個程序會使用 HTML5 的自訂資料屬性。在 data-downloadurl 中,您需要加入檔案的 MIME 類型、目的地檔案名稱 (下載檔案的所需檔案名稱) 和檔案的下載網址。因此,這會加入到 HTML 範本中:
<a href="#" class="dnd"
data-downloadurl="{$item.mime}:{$item.filename}:{$item.url}"></a>
這會產生類似以下的輸出內容:
<a href="#" class="dnd" data-downloadurl=
"image/jpeg:Penguins.jpg:https://www.box.net/box_download_file?file_id=f66690"></a>
根據 von Schorsch 建立的 jQuery plugin (以 Seddon 的文章為基礎),我新增了可執行部分瀏覽器功能偵測的 jQuery 外掛程式。我已在 von Schorsch 的版本中新增了以下程式碼行,並以黃色標示:
(function($) {
$.fn.extend({
dragout: function() {
var files = this;
if (files.length > 0) {
$(files).each(function() {
var url = (this.dataset && this.dataset.downloadurl) ||
this.getAttribute("data-downloadurl");
if (this.addEventListener) {
this.addEventListener("dragstart", function(e) {
if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
e.dataTransfer.setData("DownloadURL", url);
}
},false);
}
});
}
}
});
})(jQuery);
我這麼做的原因是,如果未先偵測瀏覽器,在 IE 中對 HTML 元素執行 addEventListener() 時,會因為 IE 使用自己的 attachEvent() 方法而產生 JavaScript 錯誤。e.dataTransfer 在 IE 中未定義 (截至目前為止),e.dataTransfer.constructor 會在 Firefox (Mozilla) 中傳回 DataTransfer,而 Webkit 瀏覽器 (Chrome 和 Safari) 則會實作 Clipboard 建構函式。在 Safari 中,e.dataTransfer.setData('DownloadURL','http://www.box.net')
會傳回 false,而 Chrome 會針對此陳述式傳回 true。完成上述所有測試後,這項功能就只會在 Chrome 中提供。您可能會說,我可以直接執行以下操作:
/chrome/.test( navigator.userAgent.toLowerCase() )
不過,我更偏好使用功能偵測功能,而非瀏覽器偵測功能,雖然從技術層面來說,這不會偵測 DnD 下載功能是否可用。
第 1 個疊代作業的問題
1) 由於我們目前已啟用網頁式 DnD,可在資料夾之間移動/複製檔案,因此需要一種方法來區分 DnD 下載和網頁式 DnD。從技術層面來說,我們無法將這兩項操作合併。我們無法預測使用者是否想將檔案移至 Box.net 帳戶中的其他資料夾,或是將檔案拖曳到電腦桌面。這兩項操作完全不同。此外,沒有簡單的方法可以偵測游標是否位於瀏覽器視窗之外。您可以使用 window.onmouseout (IE) 和 document.onmouseout (其他瀏覽器) 將 mouseout 事件附加至文件,並檢查是否為 e.relatedTarget.nodeName == "HTML"
(e 為 mouseout 事件或 window.event,視哪個可用而定)。但由於事件冒泡,這麼做相當困難。當您滑過圖片或圖層時,事件可能會隨機觸發,尤其是在 Box.net 等複雜的網路應用程式中。
2) 我們希望使用者明確執行某些動作,避免他們誤將某些項目拖曳到桌面。Box 資料夾的編輯者可能會上傳執行檔,在下載檔案的電腦上執行不當動作。我們希望使用者能確切知道檔案何時會下載到電腦。
疊代作業 2
我們決定嘗試使用控制鍵 + 拖曳 (在按下 Windows 的 Ctrl 鍵時拖曳檔案)。這項操作與使用者在 Windows 電腦上複製檔案的操作方式相同。使用者也必須額外操作 (但不是額外步驟),才能避免誤下載檔案。
我們現在已放棄第 1 個版本中的 jQuery 外掛程式,因為我們需要將 DnD 下載與網頁上的 DnD 緊密整合。有興趣的讀者可以參考我們使用 jQuery UI Draggable 外掛程式的修改版本。我們在目標元素的 mousedown 事件中放入以下程式碼:
// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
that[0].addEventListener("dragstart",function(e) {
// e.dataTransfer in Firefox uses the DataTransfer constructor
// instead of Clipboard
// make sure it's Chrome and not Safari (both webkit-based).
// setData on DownloadURL returns true on Chrome, and false on Safari
if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
e.dataTransfer.setData('DownloadURL','http://www.box.net')) {
var url = (this.dataset && this.dataset.downloadurl) ||
this.getAttribute("data-downloadurl");
e.dataTransfer.setData("DownloadURL", url);
}
}, false);
return;
}
}
除了啟用 Ctrl 鍵,我們也新增了一個小型 Toaster 工具提示,當使用者執行一般網頁拖曳動作時,就會顯示這個提示。它會告知使用者,如果在按住 Ctrl 鍵的情況下將檔案圖示拖曳到桌面,即可下載檔案。
第 2 版疊代作業的問題
基於安全考量,Box.net 不會公開永久網址,以便直接存取靜態檔案。這並非 Box.net 獨有的做法。任何線上儲存空間服務都應在沒有額外安全防護機制下,避免公開永久網址,以便檢查檔案是否為公開檔案,以及是否有使用者具備適當權限,並要求下載檔案。
當系統追蹤項目的「下載網址」(例如 https://www.box.net/box_download_file?file_id=f_60466690
) 時,會傳回「302 Found」狀態碼,並重新導向至隨機網址 (例如 https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b
),該網址是檔案的臨時「實際網址」。但問題是,這類資訊每隔幾分鐘就會過期,因此將其放在 HTML 輸出內容中並不實際。當使用者嘗試透過幾分鐘前產生的 HTML 輸出連結下載檔案時,可能會傳回「404」。
DnD 下載功能僅適用於直接指向資源的實際網址。如果涉及重新導向,目前的做法不夠聰明,無法追蹤鏈結 (而且基於安全性考量,不應追蹤鏈結)。因此,雖然上述連結 https://www.box.net/box_download_file?file_id=f_60466690 可讓您在瀏覽器位置列中輸入檔案名稱來下載檔案,但無法與 DnD 搭配使用。
如要進一步瞭解「實際網址」和「重新導向網址」的差異,請參閱下列螢幕截圖:
迭代 3
我們來試試 Ajax。
我們稍微修改了上一個疊代中的程式碼,並產生以下程式碼:
// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
that[0].addEventListener("dragstart", function(e) {
// e.dataTransfer in Firefox uses the DataTransfer constructor
// instead of Clipboard
// make sure it's Chrome and not Safari (both webkit-based).
// setData on DownloadURL returns true on Chrome, and false on Safari
if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
var url = (this.dataset && this.dataset.downloadurl) ||
this.getAttribute("data-downloadurl");
$.ajax({
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
type:'GET',
url: url
});
}
}, false);
return;
}
}
這很合理。在拖曳開始時,它會立即對伺服器發出 Ajax 呼叫,擷取檔案的最新下載網址。但這麼做並無法運作。
結果發現必須是同步呼叫 (或我喜歡稱呼的 Sjax)。似乎必須在附加事件監聽器時執行 setData。根據 jQuery 的 API,醒目顯示的程式碼行會變成:
$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
type: 'GET',
url: url
});
在拔除網路連線之前,一切都運作正常。由於這是同步呼叫,因此瀏覽器會在呼叫成功前保持凍結狀態。如果 Ajax 呼叫失敗 (404 或完全沒有回應),瀏覽器就不會解凍,就像瀏覽器已當機一樣。
因此,建議您採取以下做法:
$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
error: function(xhr) {
if (xhr.status == 404) {
xhr.abort();
}
},
type: 'GET',
timeout: 3000,
url: url
});
如要試用這項功能,請將靜態檔案上傳至 Box.net 帳戶。按住 Ctrl 鍵,將檔案圖示拖曳到桌面。如果您沒有帳戶,建立帳戶只需不到 30 秒鐘的時間。
有了這項功能,您就能發揮創意,實現許多事物。將圖片拖曳至 Windows 印表機對話方塊,即可立即列印圖片。您可以將歌曲從 Box 複製到手機的雲端硬碟,將檔案從 Box 拖曳到即時通訊應用程式,直接傳送給朋友... 這麼一來,您就能無限提升工作效率。
想法和未來改善項目
這仍不理想,因為同步呼叫可能會暫時鎖定瀏覽器。HTML 5 Web Worker 也無法解決這個問題,因為 Web Worker 必須為非同步。似乎必須在附加事件監聽器時執行 setData。
實際上,效能表現相當不錯。同步 Ajax (Sjax) 呼叫只會擷取網址字串,因此速度應該會相當快。這會在 HTTP 標頭中產生大量額外負擔,而 WebSocket 可能會解決這個問題。不過,在我們看到更多這類技術的使用情況之前,不建議使用 WebSocket 傳送每項小更新至用戶端。
我也希望日後 API 能支援多檔案下載功能。搭配自訂核取方塊,在使用者介面上選取多個檔案,效果會非常驚人。此外,如果用戶端產生的檔案 (例如從提交表單的結果產生的文字檔) 也能以這種方式下載,那就更棒了。
- 欄 dnd
- 重新排列清單
- 建立圖片庫
- 匯出無框畫圖片
參考資料
- 拖曳規格