拖放(DnD)是 HTML 5 的众多强大功能之一,Firefox 3.5,Safari,Chrome 和 IE 都支持它。谷歌最近推出了一项 新功能 (link:http://gmailblog.blogspot.com/2010/08/drag-and-drop-attachments-to-save-them.html),允许谷歌浏览器用户将文件从浏览器拖放到桌面。这是一个非常方便的功能,但直到 Ryan Seddon 发布了一篇关于 发现 (link:http://www.thecssninja.com/javascript/gmail-dragout)他对这个新功能的逆向工程
在 Box.net,我们对这些新功能如何使我们能够改进云内容管理解决方案以及为开发人员社区做出更多贡献感到非常兴奋。我很高兴地宣布,DnD 下载已集成到我们的产品中。现在,Box 用户可以将文件直接从 Chrome 浏览器拖到桌面上,以下载和保存文件。
我想分享一下我在这个新功能的开发过程中是如何经历几次迭代的。
首先要做的是检查您的浏览器是否完全支持 HTML5 拖放。一个简单的方法是使用一个名为 Modernizr (link:http://www.modernizr.com/)的库来检查某个功能:
if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}
我首先尝试了Seddon在Gmail中找到的方法。我添加了一个名为“data-downloadurl”的新属性来锚定文件链接。此过程使用 HTML5 的 自定义数据属性 (link:http://ejohn.org/blog/html-5-data-attributes/)。在数据下载网址中,您需要包括文件的 MIME 类型、目标文件名(下载文件的所需文件名)和文件的下载 URL。因此,这被添加到 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插件,该插件 (link:http://dev.blog.salesking.eu/coding/jquery-plugin-to-drag-files-from-browser-onto-desktop/)基于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()将创建JavaScript错误,因为IE使用自己的attachEvent()方法。 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)因为我们目前启用了页面DnD,以便在文件夹之间移动/复制文件,我们需要一种方法来区分DnD下载和页面DnD.从技术上讲,我们不能将这两个操作结合起来。我们无法预测用户是要将文件移动到 Box.net 帐户中的另一个文件夹还是将其拖到桌面。这两个动作是完全不同的。此外,没有简单的方法来检测光标是否在浏览器窗口之外。您可以使用window.onmouseout(IE)和document.onmouseout(其他浏览器)将mouseout事件附加到文档,并检查e.relatedTarget.nodeName == “HTML”(e是mouseout事件或window.event,以可用者为准)。但由于事件冒泡,这是相当困难的。当您位于图像或图层上时,该事件可能会随机触发,尤其是在像 Box.net 这样的复杂 Web 应用程序中。
2)我们希望用户明确执行某些操作,以防止他们错误地将某些内容拖到桌面上。Box文件夹的编辑者可能会上传一个可执行文件,该文件在下载它的人的计算机上执行不希望的操作。我们希望用户确切知道何时将文件下载到桌面。
我们决定尝试控制+拖动(在按下Windows Ctrl键时拖动文件)。此操作与用户可以在 Windows 桌面上复制文件的操作一致。它还需要用户进行额外的工作(但不是额外的步骤),以防止错误地下载文件。
迭代 1 中的 jQuery 插件现在被放弃了,因为我们需要将 DnD 下载与页面上的 DnD 紧密集成。对于那些感兴趣的人,我们使用jQuery UI 的Draggable插件 (link:http://jqueryui.com/demos/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 键外,我们还添加了一个小的烤面包机工具提示,当用户执行常规页面拖动时会显示。它告诉用户,如果在按住 Ctrl 键时将文件图标拖动到桌面,则可以下载文件。
出于安全考虑,Box.net 不会公开永久 URL 以直接访问静态文件。这并非 Box.net 所独有。任何在线存储服务都不应在没有额外安全层的情况下公开永久 URL,以检查文件是否为公共文件以及具有适当权限的用户是否请求预期的下载。
当跟踪项目的“下载 URL”(例如 https://www.box.net/box_download_file?file_id=f_60466690)时,它会返回“302 Found”状态代码,并重定向到随机 URL(例如 https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b),这是文件的临时“实际 URL”。挑战在于它每隔几分钟就会过期,因此将其放在 HTML 输出中是不切实际的。当用户尝试在几分钟前生成的 HTML 输出中的链接下载文件时,它可能会返回“404”。
DnD 下载仅适用于直接指向资源的实际 URL。如果涉及重定向,它目前不够智能,无法遵循链(并且由于安全性,它不应该遵循链)。因此,尽管 https://www.box.net/box_download_file?file_id=f_60466690 上面 https://www.box.net/box_download_file?file_id=f_60466690 的链接可以让您在浏览器地址栏中输入文件时下载文件,但它不适用于 DnD。
为了更好地说明 实际 URL 和 重定向 URL 之间的区别,请参阅屏幕截图:

302 重定向网址

实际网址
让我们试试阿贾克斯。
我们在上一次迭代中稍微修改了代码,并得出了以下内容:
// 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;
}
}
这是有道理的。在 dragstart 启动时,它会立即对服务器进行 Ajax 调用,以检索文件的最新下载 URL。但是,它不起作用。
事实证明,它需要是一个同步调用(或者我喜欢称之为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拖到IM客户端,以便将其直接传输给您的朋友...这为提高您的生产力开辟了无限的可能性。

将文件拖到打印机。

将文件拖动到 IM 客户端。
这仍然不太理想,因为同步调用可能会在短时间内锁定浏览器。HTML 5 Web Worker 也无济于事,因为 Web Worker 必须是异步的。似乎 setData 必须在附加事件侦听器时完成。
实际上,性能是可以接受的。同步 Ajax (Sjax) 调用仅检索一个 URL 字符串,这应该非常快。它确实在HTTP标头中带来了很大的开销,这可以通过WebSockets解决。但是,在我们看到这种技术的更多使用之前,不值得使用 WebSockets 将每个小更新发送到客户端。
我也希望将来将多文件下载的功能添加到 API 中。结合自定义复选框以在用户界面上选择多个文件,这将令人难以置信。此外,如果客户端生成的文件(例如根据提交的表单结果生成的文本文件)可以以这种方式下载,那就更好了。

