Studi Kasus - Download Tarik lalu Lepas di Chrome

Pengantar

Tarik lalu lepas (DnD) adalah salah satu dari banyak fitur hebat 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 hingga Ryan Seddon memposting artikel tentang penemuan dari reverse engineering-nya pada fitur baru ini.

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

Saya ingin membagikan 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 lalu 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-tama, saya mencoba pendekatan yang ditemukan Seddon di Gmail. Saya menambahkan atribut baru yang disebut 'data-downloadurl' untuk anchor link file. Proses ini menggunakan atribut data kustom HTML5. Di data-downloadurl, Anda harus menyertakan jenis MIME file, nama file tujuan (nama file yang diinginkan dari file yang didownload), dan URL download file. Dengan demikian, 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 oleh von Schorsch, yang didasarkan pada artikel Seddon, saya menambahkan plugin jQuery yang melakukan sedikit deteksi fitur browser. Baris yang saya tambahkan ke versi von Schorsch ditandai:

(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 membuat error JavaScript karena IE menggunakan metode attachEvent()-nya sendiri. e.dataTransfer tidak ditentukan di IE (sekarang), e.dataTransfer.constructor menampilkan DataTransfer di Firefox (Mozilla), sedangkan browser Webkit (Chrome dan Safari) mengimplementasikan konstruktor Clipboard. Di Safari, e.dataTransfer.setData('DownloadURL','http://www.box.net') menampilkan nilai salah (false), dan Chrome menampilkan nilai benar (true) untuk pernyataan ini. Melakukan semua pengujian yang disebutkan di atas akan membuat fitur ini hanya tersedia untuk Chrome. Anda mungkin berpendapat bahwa saya dapat melakukan hal berikut:

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

Namun, saya lebih memilih deteksi fitur daripada deteksi browser, meskipun secara teknis hal ini tidak mendeteksi bahwa download DnD akan berfungsi.

Masalah iterasi 1

1) Karena saat ini kita mengaktifkan DnD di halaman untuk memindahkan/menyalin file antarfolder, kita memerlukan cara untuk membedakan DnD Download dan DnD di halaman. Secara teknis, kita tidak dapat menggabungkan kedua tindakan ini. Kami tidak dapat memprediksi apakah pengguna ingin memindahkan file ke folder lain dalam akun Box.net atau menariknya ke desktop. Kedua tindakan ini sama sekali 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, hal ini cukup sulit karena bubbling peristiwa. 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. Editor folder Box berpotensi dapat mengupload file yang dapat dieksekusi yang melakukan sesuatu yang tidak diinginkan di komputer siapa pun yang mendownloadnya. Kita ingin pengguna mengetahui tepatnya kapan file akan didownload ke desktop.

Iterasi 2

Kami memutuskan untuk bereksperimen dengan kontrol + tarik (menarik file saat tombol Ctrl Windows ditekan). Tindakan ini konsisten dengan tindakan yang dapat dilakukan orang di desktop Windows untuk menduplikasi file. Hal ini juga memerlukan pekerjaan tambahan (tetapi bukan langkah tambahan) dari pengguna untuk mencegah file didownload secara tidak sengaja.

Plugin jQuery dalam iterasi 1 sekarang dihentikan karena kita perlu mengintegrasikan DnD Download dengan DnD di halaman secara ketat. 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 tooltip toaster kecil, yang muncul saat pengguna melakukan tarikan biasa di halaman. Ikon ini memberi tahu pengguna bahwa file dapat didownload jika ikon file ditarik ke desktop saat tombol Ctrl ditahan.

Masalah iterasi 2

Karena masalah keamanan, Box.net tidak mengekspos URL permanen untuk mengakses file statis secara langsung. Hal ini tidak hanya berlaku untuk Box.net. Setiap layanan penyimpanan online tidak boleh mengekspos URL permanen tanpa lapisan keamanan tambahan untuk memeriksa apakah file bersifat publik dan apakah download yang diinginkan diminta oleh pengguna dengan izin yang sesuai.

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

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

Untuk mengilustrasikan perbedaan antara "URL sebenarnya" dan "URL alihan" dengan lebih baik, lihat screenshot berikut:

URL alihan 302
URL pengalihan 302
URL sebenarnya
URL sebenarnya

Iterasi 3

Mari kita coba Ajax.

Kita sedikit mengubah kode dalam iterasi sebelumnya dan mendapatkan 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");
    $.ajax({
        complete: function(data) {
        e.dataTransfer.setData("DownloadURL", data.responseText);
        },
        type:'GET',
        url: url
    });
    }
}, false);
return;
}
}

Hal ini masuk akal. Setelah dragstart, tindakan ini akan segera membuat panggilan Ajax ke server untuk mengambil URL download terbaru file. Namun, tindakan ini tidak berhasil.

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

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

Dan berfungsi dengan baik hingga 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 mencair sama sekali seolah-olah mengalami error.

Sebaiknya lakukan hal 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 melihat demo fitur ini, jangan ragu untuk mengupload file statis ke akun Box.net. Tarik ikon file ke desktop sambil menahan tombol Ctrl. Jika Anda tidak memiliki akun, Anda hanya perlu waktu kurang dari 30 detik untuk membuatnya.

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

Mengirim file ke printer
Menarik file ke printer.
Menarik file ke klien IM
Menarik file ke klien IM.

Pemikiran, dan peningkatan di masa mendatang

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

Pada kenyataannya, performanya cukup dapat diterima. Panggilan Ajax sinkron (Sjax) hanya mengambil string URL, yang seharusnya cukup cepat. WebSocket memiliki overhead yang besar di header HTTP, yang mungkin dapat diatasi oleh WebSocket. Namun, hingga kita melihat lebih banyak penggunaan jenis teknologi ini, tidak ada gunanya menggunakan WebSocket untuk mengirim setiap update kecil ke klien.

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

  • Tarik lalu lepas kolom
  • Mengatur ulang daftar
  • Membuat galeri gambar
  • Mengekspor gambar kanvas

Referensi