Örnek Olay - Chrome'da Sürükleyip Bırakarak İndirme

David Tong
David Tong

Giriş

HTML 5'in birçok harika özelliğinden biri olan sürükle ve bırak (DnD), Firefox 3.5, Safari, Chrome ve IE'de desteklenir. Google kısa süre önce Google Chrome kullanıcılarının tarayıcıdan masaüstüne dosya sürükleyip bırakmasına olanak tanıyan yeni bir özellik kullanıma sundu. Son derece kullanışlı bir özellik olsa da Ryan Seddon bu yeni özellikte yaptığı tersine mühendislik çalışmalarıyla ilgili keşiflerini yayınlayana kadar pek bilinmiyordu.

Box.net olarak bu yeni özelliklerin bulut içerik yönetimi çözümümüzü iyileştirmemize ve geliştirici topluluğuna daha fazla katkıda bulunmamıza olanak tanımasından dolayı çok heyecanlıyız. DnD Download'ın ürünümüze entegre edildiğini duyurmaktan memnuniyet duyuyoruz. Box kullanıcıları artık dosyaları indirip kaydetmek için doğrudan Chrome tarayıcısından masaüstüne sürükleyebilir.

Bu yeni özelliğin geliştirilmesi sırasında nasıl birkaç iterasyondan geçtiğimi paylaşmak isterim.

Sürükle ve Bırak API Desteğini Kontrol Etme

Yapmanız gereken ilk şey, tarayıcınızın HTML5 sürükle ve bırak özelliğini tam olarak destekleyip desteklemediğini kontrol etmektir. Bunu yapmanın kolay bir yolu, belirli bir özelliği kontrol etmek için Modernizr adlı bir kitaplığı kullanmaktır:

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

1. iterasyon

Öncelikle Seddon'un Gmail'de bulduğu yaklaşımı denedim. Dosyaların ana bağlantılarına "data-downloadurl" adlı yeni bir özellik ekledim. Bu işlemde HTML5'in özel veri özellikleri kullanılır. data-downloadurl parametresine dosyanın MIME türünü, hedef dosya adını (indirilen dosyanın istenen dosya adı) ve dosyanın indirme URL'sini eklemeniz gerekir. Bu nedenle, HTML şablonuna aşağıdakiler eklenir:

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

Bu komut, aşağıdaki gibi bir çıkış oluşturur:

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

Seddon'un makalesine dayalı olarak von Schorsch tarafından oluşturulan bir jQuery plugin temel alarak, tarayıcı özelliklerini algılayan bir jQuery eklentisi ekledim. Von Schorsch'un sürümüne eklediğim satırlar vurgulanmıştır:

(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);

Bunu yapmamın nedeni, önceden tarayıcı algılamadan IE'de bir HTML öğesine addEventListener() yapılmasının JavaScript hatası oluşturmasıdır. Bunun nedeni, IE'nin kendi attachEvent() yöntemini kullanmasıdır. e.dataTransfer, IE'de (şu anda) tanımlanmamıştır. e.dataTransfer.constructor, Firefox'ta (Mozilla) DataTransfer döndürür. Webkit tarayıcıları (Chrome ve Safari) ise Clipboard yapıcısını uygular. Safari'de e.dataTransfer.setData('DownloadURL','http://www.box.net') yanlış değerini, Chrome ise bu ifade için doğru değerini döndürür. Yukarıda belirtilen testlerin tümünü yaptığınızda özellik yalnızca Chrome'da kullanılabilir. Aşağıdakileri yapabileceğimi iddia edebilirsiniz:

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

Ancak tarayıcı algılamasına kıyasla özellik algılamayı tercih ediyorum. Bu yöntem, teknik olarak DnD indirme işleminin işe yarayıp yaramayacağını algılamaz.

1. iterasyonun sorunları

1) Şu anda klasörler arasında dosya taşımak/kopyalamak için sayfa üzerinde DnD etkinleştirdiğimizden, DnD indirme ve sayfa üzerinde DnD'yi ayırt etmenin bir yoluna ihtiyacımız var. Teknik olarak bu iki işlemi birleştiremiyoruz. Kullanıcının bir dosyayı Box.net hesabındaki başka bir klasöre taşımak mı yoksa masaüstüne sürüklemek mi istediğini tahmin edemeyiz. Bu iki işlem tamamen farklıdır. Ayrıca, imlecin tarayıcı penceresinin dışında olup olmadığını algılamanın kolay bir yolu yoktur. mouseout etkinliğini dokümana eklemek için window.onmouseout (IE) ve document.onmouseout (diğer tarayıcılar) kullanabilir ve e.relatedTarget.nodeName == "HTML" değerini kontrol edebilirsiniz (e, mevcut olan mouseout etkinliği veya window.event'tir). Ancak bu, etkinlik kabarcıklaşması nedeniyle oldukça zordur. Etkinlik, özellikle Box.net gibi karmaşık bir web uygulamasında bir resmin veya katmanın üzerindeyken rastgele tetiklenebilir.

2) Kullanıcının, yanlışlıkla masaüstüne sürüklemesini önlemek için açıkça bir işlem yapmasını istiyoruz. Bir Box klasörünün düzenleyicisi, indiren kişinin bilgisayarında istenmeyen bir işlem yapan yürütülebilir bir dosya yükleyebilir. Kullanıcının, dosyanın masaüstüne tam olarak ne zaman indirileceğini bilmesini istiyoruz.

2. iterasyon

Ctrl + sürükle (Windows Ctrl tuşuna basılıyken dosyayı sürükleme) seçeneğini denemeye karar verdik. Bu işlem, kullanıcıların Windows masaüstünde bir dosyayı kopyalamak için yapabileceği işlemle tutarlıdır. Ayrıca, dosyaların yanlışlıkla indirilmesini önlemek için kullanıcının ek bir işlem (ancak ek bir adım) yapması gerekir.

DnD İndirme'yi sayfadaki DnD ile sıkı bir şekilde entegre etmemiz gerektiğinden 1. iterasyondaki jQuery eklentisi artık kullanılmıyor. İlgilenenler için jQuery UI'nin Draggable eklentisinin değiştirilmiş bir sürümünü kullanıyoruz. Hedef öğenin mousedown etkinliğine aşağıdaki kodu yerleştiririz:

// 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 tuşunu etkinleştirmenin yanı sıra, kullanıcı sayfa üzerinde normal bir sürükleme işlemi gerçekleştirdiğinde görünen küçük bir bilgi çubuğu da ekledik. Kullanıcıya, Ctrl tuşu basılıyken dosya simgesinin masaüstüne sürüklenmesiyle dosyaların indirilebileceğini bildirir.

2. iterasyonun sorunları

Box.net, güvenlik endişeleri nedeniyle statik dosyalara doğrudan erişmek için kalıcı URL'ler göstermez. Bu durum Box.net'e özgü değildir. Hiçbir online depolama hizmeti, dosyanın herkese açık olup olmadığını ve istenen indirme işleminin uygun izinlere sahip bir kullanıcı tarafından istenip istenmediğini kontrol etmek için ekstra bir güvenlik katmanı olmadan kalıcı URL'leri göstermemelidir.

Bir öğenin "indirme URL'si" (ör. https://www.box.net/box_download_file?file_id=f_60466690) takip edildiğinde "302 Bulundu" durum kodu döndürülür ve dosyanın geçici "gerçek URL'si" olan rastgele bir URL'ye (ör. https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b) yönlendirilir. Sorun, birkaç dakikada bir geçerlilik süresinin sona ermesi ve bu nedenle HTML çıkışına yerleştirilmesinin pratik olmamasıdır. Kullanıcı, birkaç dakika önce oluşturulan HTML çıkışındaki bağlantıdan dosyayı indirmeye çalıştığında "404" döndürülebilir.

Dosya indirme özelliği yalnızca doğrudan bir kaynağa işaret eden gerçek URL'lerde çalışır. Yönlendirme söz konusuysa şu anda zinciri takip edecek kadar akıllı değil (ve güvenlik nedeniyle zinciri hiçbir zaman takip etmemelidir). Bu nedenle, yukarıdaki https://www.box.net/box_download_file?file_id=f_60466690 bağlantısını tarayıcı konum çubuğuna girdiğinizde dosyayı indirmenize olanak tanısa da bu bağlantı, DnD ile çalışmaz.

"Gerçek URL" ile "yönlendirme URL'si" arasındaki farkları daha iyi anlamak için ekran görüntülerine bakın:

302 yönlendirme URL&#39;si
302 yönlendirme URL'si
Gerçek URL
Gerçek URL

3. iterasyon

Ajax'ı deneyelim.

Önceki iterasyondaki kodu biraz değiştirdik ve aşağıdaki sonucu elde ettik:

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

Bu mantıklı. Dosya sürüklenmeye başlandığında, dosyanın en son indirme URL'sini almak için sunucuya hemen bir Ajax çağrısı yapılır. Ancak bu yöntem işe yaramıyor.

Bunun senkronize bir çağrı (veya benim deyimimle Sjax) olması gerektiği ortaya çıktı. setData'nın, etkinlik işleyicinin eklendiği sırada yapılması gerekiyor. jQuery'nin API'sine göre, vurgulanan satırlar şu şekilde olur:

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

Ağ bağlantısını çıkarıncaya kadar sorunsuz çalışıyor. Senkronize bir çağrı yaptığından, çağrı başarılı olana kadar tarayıcı donar. Ajax çağrısı başarısız olursa (404 veya hiç yanıt vermezse) tarayıcı, kilitlenmiş gibi hiç açılmaz.

Aşağıdaki gibi bir yöntem kullanmak çok daha güvenlidir:

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

Bu özelliğin bir demosunu görmek için Box.net hesabına statik bir dosya yükleyebilirsiniz. Ctrl tuşunu basılı tutarken dosya simgesini masaüstünüze sürükleyin. Hesabınız yoksa hesap oluşturmak 30 saniyeden kısa sürer.

Bu özellik sayesinde yaratıcılığınızı konuşturabilir ve birçok şeyi mümkün kılabilirsiniz. Bir resmi Windows yazıcı iletişim kutusuna sürüklediğinizde resim hemen yazdırılır. Box'tan bir şarkıyı cep telefonunuzun sürücüsüne kopyalayabilir, bir dosyayı Box'tan doğrudan arkadaşınıza aktarmak için IM istemcinize sürükleyebilirsiniz. Bu sayede verimliliğinizi artıracak sayısız olasılık elde edersiniz.

Dosyayı yazıcıya sürükleme
Bir dosyayı yazıcıya sürükleme.
Bir dosyayı IM istemcisine sürükleme
Bir dosyayı IM istemcisine sürükleme.

Düşünceler ve gelecekteki iyileştirmeler

Ancak senkronize çağrılar tarayıcıyı kısa bir süreliğine kilitleyebileceğinden bu yöntem ideal değildir. Web İşleyicinin eşzamansız olması gerektiğinden HTML 5 Web İşleyici de bu konuda yardımcı olmaz. setData'nın, etkinlik dinleyicisinin eklendiği sırada yapılması gerektiği anlaşılıyor.

Gerçekte performans oldukça kabul edilebilir. Senkronize Ajax (Sjax) çağrısı yalnızca bir URL dizesi alır ve bu işlem oldukça hızlıdır. HTTP üstbilgisinde büyük bir yükü beraberinde getirir. Bu yük, WebSocket'ler tarafından ele alınabilir. Ancak bu tür bir teknolojinin daha fazla kullanıldığını görmediğimiz sürece, her küçük güncellemeyi istemciye göndermek için WebSocket'leri kullanmakta fayda yoktur.

Ayrıca, gelecekte API'ye birden fazla dosyayı indirme özelliğinin eklenmesini umuyoruz. Kullanıcı arayüzünde birden fazla dosya seçmek için özel onay kutularıyla birlikte bu özellik harika olur. Ayrıca, gönderilen bir formun sonucunda oluşturulan metin dosyaları gibi müşteri tarafından oluşturulan dosyaların bu şekilde indirilebilmesi daha da iyi olur.

  • Sütun dnd
  • Listeyi yeniden düzenleme
  • Resim galerisi oluşturma
  • Kanvas resmini dışa aktarma

Referanslar