Studi Kasus - Download Tarik lalu Lepas di Chrome

Pengantar

{i>Drag and Drop<i} (DnD) adalah salah satu dari banyak fitur hebat dari HTML 5, dan didukung di Firefox 3.5, Safari, Chrome, dan IE. Google baru-baru ini meluncurkan fitur baru yang memungkinkan pengguna Google Chrome menarik lalu melepas file dari browser ke desktop. Fitur ini sangat praktis, tetapi tidak banyak diketahui sampai Ryan Seddon memposting sebuah artikel tentang temuan rekayasa baliknya mengenai fitur baru ini.

Di Box.net, kami sangat senang karena kemampuan baru ini memungkinkan kami untuk meningkatkan solusi pengelolaan konten cloud, serta berkontribusi lebih banyak bagi komunitas developer. Dengan senang hati kami umumkan bahwa Download DnD telah diintegrasikan ke produk kami. Sekarang, pengguna Box dapat menarik file langsung dari browser Chrome ke desktop mereka untuk mendownload dan menyimpan file.

Saya ingin berbagi bagaimana saya melakukan beberapa iterasi selama pengembangan fitur baru ini.

Memeriksa Dukungan API Tarik lalu Lepas

Hal pertama yang harus dilakukan adalah memeriksa apakah browser Anda sepenuhnya mendukung tarik dan lepas HTML5. Cara mudah untuk melakukannya adalah menggunakan library yang disebut Modernizr untuk memeriksa fitur tertentu:

if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}

Iterasi 1

Pertama kali saya mencoba pendekatan yang ditemukan Seddon di Gmail. Saya menambahkan atribut baru yang disebut {i>'data-downloadurl'<i} untuk menambatkan tautan file. Proses ini menggunakan atribut data kustom HTML5. Dalam data-downloadurl, Anda harus menyertakan jenis MIME file, nama file tujuan (nama file yang diinginkan dari file yang didownload), dan URL download file tersebut. Oleh karena itu, kode ini ditambahkan ke template HTML:

<a href="#" class="dnd"
data-downloadurl="{$item.mime}:{$item.filename}:{$item.url}"></a>

yang akan menghasilkan output seperti berikut:

<a href="#" class="dnd" data-downloadurl=
"image/jpeg:Penguins.jpg:https://www.box.net/box_download_file?file_id=f66690"></a>

Berdasarkan plugin jQuery yang dibuat von Schorsch, yang didasarkan pada artikel Seddon, saya menambahkan plugin jQuery yang melakukan sedikit deteksi fitur browser. Yang disorot adalah baris-baris yang saya tambahkan ke versi 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);

Alasan saya melakukan ini adalah karena tanpa deteksi browser sebelumnya, melakukan addEventListener() ke elemen HTML di IE akan menimbulkan error JavaScript karena IE menggunakan metode AttachEvent() sendiri. e.dataTransfer tidak ditentukan di IE (seperti saat ini), e.dataTransfer.Konstruktor menampilkan DataTransfer di Firefox (Mozilla), sementara browser Webkit (Chrome dan Safari) menerapkan konstruktor Clipboard. Di Safari, e.dataTransfer.setData('DownloadURL','http://www.box.net') menampilkan nilai salah, dan Chrome menampilkan benar untuk pernyataan ini. Melakukan semua pengujian yang disebutkan di atas membuat fitur hanya tersedia untuk Chrome. Anda mungkin berpendapat bahwa saya dapat melakukan hal berikut:

/chrome/.test( navigator.userAgent.toLowerCase() )

Tetapi saya lebih suka deteksi fitur daripada deteksi browser, meskipun secara teknis ini tidak mendeteksi bahwa download DnD akan berfungsi.

Masalah-masalah iterasi 1

1) Karena saat ini kami mengaktifkan DnD di halaman untuk memindahkan/menyalin file antarfolder, kami memerlukan cara untuk membedakan Download DnD dan DnD di halaman. Secara teknis, kita tidak dapat menggabungkan kedua tindakan ini. Kita tidak dapat memprediksi apakah pengguna ingin memindahkan file ke folder lain dalam akun Box.net atau menariknya ke desktop. Kedua tindakan ini sangat berbeda. Selain itu, tidak ada cara mudah untuk mendeteksi apakah kursor berada di luar jendela browser. Anda dapat menggunakan window.onmouseout (IE) dan document.onmouseout (browser lain) untuk melampirkan peristiwa mouseout ke dokumen, dan memeriksa apakah e.relatedTarget.nodeName == "HTML" (e adalah peristiwa mouseout atau window.event, mana saja yang tersedia). Namun, ini cukup sulit karena event menggelembung. Peristiwa ini dapat dipicu secara acak saat Anda berada di atas gambar atau lapisan, terutama di aplikasi web yang kompleks seperti Box.net.

2) Kita ingin pengguna melakukan sesuatu secara eksplisit untuk mencegah mereka menarik sesuatu ke desktop secara tidak sengaja. Kemungkinan, editor folder Box dapat mengupload file yang dapat dieksekusi dan melakukan sesuatu yang tidak diinginkan pada komputer siapa pun yang mendownloadnya. Kita ingin pengguna tahu persis kapan file akan diunduh ke desktop.

Iterasi 2

Kami memutuskan untuk bereksperimen dengan control + drag (menarik file saat tombol Windows Ctrl ditekan). Tindakan ini konsisten dengan apa yang dapat dilakukan orang di desktop Windows untuk menduplikasi file. Tindakan ini juga mengharuskan pengguna melakukan upaya ekstra (tetapi bukan langkah tambahan) untuk mencegah file didownload secara tidak sengaja.

Plugin jQuery dalam iterasi 1 ditinggalkan sekarang karena kita perlu mengintegrasikan DnD Download secara erat dengan DnD di halaman. Bagi yang tertarik, kami menggunakan versi modifikasi plugin Draggable jQuery UI. Di dalam peristiwa mousedown elemen target, kita menempatkan kode berikut:

// 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;
}
}

Selain mengaktifkan tombol Ctrl, kami juga menambahkan sedikit tooltip pemanggang roti, yang muncul saat pengguna melakukan penarikan pada halaman secara rutin. Fungsi ini memberi tahu pengguna bahwa file dapat didownload jika ikon file ditarik ke desktop saat tombol Ctrl ditahan.

Masalah-masalah iterasi 2

Karena masalah keamanan, Box.net tidak mengekspos URL permanen untuk langsung mengakses file statis. Ini tidak unik untuk Box.net. Layanan penyimpanan online apa pun tidak boleh mengekspos URL permanen tanpa lapisan keamanan ekstra untuk memeriksa apakah file tersebut bersifat publik dan apakah download yang dimaksud diminta oleh pengguna dengan izin yang sesuai.

Saat mengikuti "URL download" (misalnya, https://www.box.net/box_download_file?file_id=f_60466690) item, item akan menampilkan kode status "302 Ditemukan", dan mengalihkan ke URL acak (misalnya https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b) yang merupakan "URL sebenarnya" sementara dari file tersebut. Tantangannya adalah masa berlaku kode ini akan habis setiap beberapa menit, sehingga menempatkannya dalam output HTML tidak praktis. Kode ini dapat menampilkan "404" saat pengguna mencoba mendownload file pada link dalam output HTML yang dihasilkan beberapa menit yang lalu.

Download DnD hanya berfungsi pada URL sebenarnya yang mengarah langsung ke sumber daya. Jika pengalihan terlibat, saat ini perangkat tidak cukup cerdas untuk mengikuti rantai (dan seharusnya tidak pernah mengikuti rantai karena keamanan). Oleh karena itu, meskipun link https://www.box.net/box_download_file?file_id=f_60466690 dari atas akan memungkinkan Anda mendownload file saat memasukkannya di kolom lokasi browser, link tidak akan berfungsi dengan DnD.

Untuk menggambarkan perbedaan antara "URL sebenarnya" dan "URL pengalihan" dengan lebih baik, lihat screenshot:

URL alihan 302
URL alihan 302
URL sebenarnya
URL sebenarnya

Iterasi 3

Mari kita coba Ajax.

Kami sedikit memodifikasi kode dalam iterasi sebelumnya dan menghasilkan hal berikut:

// 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;
}
}

Hal ini masuk akal. Setelah mulai, fungsi ini akan segera melakukan panggilan Ajax ke server untuk mengambil URL download terbaru file. Namun, hal tersebut tidak berlaku.

Ternyata itu harus berupa panggilan sinkron (atau seperti yang saya suka menyebutnya, Sjax). Sepertinya setData harus dilakukan pada saat pemroses peristiwa terpasang. Menurut API jQuery, baris yang ditandai menjadi:

$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
type: 'GET',
url: url
});

Dan itu berfungsi dengan baik sampai saya mencabut koneksi jaringan. Karena melakukan panggilan sinkron, browser akan berhenti berfungsi hingga panggilan berhasil. Jika panggilan Ajax gagal (404, atau jika tidak merespons sama sekali), browser tidak akan defrost sama sekali seolah-olah telah error.

Jauh lebih aman untuk melakukan hal seperti berikut:

$.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
});

Untuk demo fitur ini, jangan ragu untuk mengupload file statis ke akun Box.net. Seret ikon {i>file<i} ke {i>desktop<i} sambil menahan tombol Ctrl. Jika Anda tidak memiliki akun, pembuatannya biasanya kurang dari 30 detik.

Dengan fitur ini, Anda dapat menjadi kreatif dan mewujudkan banyak hal. Menarik gambar ke dialog printer Windows akan langsung mencetak gambar. Anda dapat menyalin lagu dari Box ke drive ponsel Anda, tarik file dari Box ke klien IM Anda untuk mentransfernya langsung ke teman Anda... Ini membuka kemungkinan tak terbatas untuk meningkatkan produktivitas Anda.

meng-raging file ke printer
Menarik file ke printer.
Menarik file ke klien IM
Menarik file ke klien IM.

Masukan, dan perbaikan di masa mendatang

Hal ini masih kurang ideal, karena panggilan sinkron dapat mengunci browser untuk beberapa saat. HTML 5 Web Worker juga tidak membantu, karena Web Worker harus asinkron. Sepertinya setData harus dilakukan pada saat pemroses peristiwa terpasang.

Kenyataannya, kinerja tersebut cukup dapat diterima. Panggilan Ajax (Sjax) sinkron hanya mengambil string URL, yang seharusnya cukup cepat. Itu memang datang dengan overhead besar di {i> header<i} HTTP, yang mungkin bisa ditangani oleh WebSockets. Namun, sampai kita melihat banyak penggunaan teknologi semacam ini, tidak ada gunanya menggunakan WebSockets untuk mengirimkan setiap pembaruan kecil ke klien.

Saya juga berharap kemampuan download multi-file akan ditambahkan ke API di masa mendatang. Ini akan luar biasa jika digabungkan dengan kotak centang kustom untuk memilih beberapa file pada antarmuka pengguna. Selain itu, akan lebih baik lagi jika file buatan klien, seperti file teks yang dihasilkan dari hasil formulir yang dikirim, dapat didownload dengan cara ini.

  • Kolom ke-d
  • Susun ulang daftar
  • Membuat galeri gambar
  • Mengekspor gambar kanvas

Referensi