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

David Tong
David Tong

Giriş

Sürükle ve Bırak (DnD), HTML 5'in birçok harika özelliğinden biridir ve Firefox 3.5, Safari, Chrome ve IE'de desteklenir. Google kısa süre önce, Google Chrome kullanıcılarının dosyaları tarayıcıdan masaüstüne sürükleyip bırakmasına olanak tanıyan yeni bir özelliği kullanıma sundu. Bu son derece kullanışlı bir özellik, ancak Ryan Seddon bu yeni özellikle ilgili tersine mühendislik keşifleri hakkında bir makale yayınlayana kadar pek fazla kullanıcı tarafından bilinmiyor.

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ı bizi çok heyecanlandırıyor. DnD Download'un ürünümüze entegre edildiğini bildirmekten memnuniyet duyuyorum. Artık Box kullanıcıları, dosyaları indirmek ve kaydetmek için doğrudan Chrome tarayıcıdan masaüstüne sürükleyebilir.

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

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

İlk olarak, tarayıcınızın HTML5 sürükle ve bırak özelliğini tam olarak desteklediğinden emin olun. Bunu yapmanın kolay bir yolu, belirli bir özellik olup olmadığını kontrol etmek için Modernizr adlı kitaplıktan yararlanmaktır:

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

Yineleme 1

Önce Seddon'un Gmail'de bulduğu yaklaşımı denedim. Dosyaların bağlantılarını sabitlemek için "data-downloadurl" adlı yeni bir özellik ekledim. Bu işlem, HTML5'in özel veri özelliklerini kullanır. data-downloadurl özelliğine dosyanın MIME türünü, hedef dosya adını (indirilen dosyanın istenen dosya adı) ve dosyanın indirme URL'sini eklemeniz gerekir. Böylece, bu değer HTML şablonuna eklenir:

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

Bu da aşağıdaki gibi bir çıktı oluşturur:

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

von Schorsch'un oluşturduğu ve Seddon'ın makalesine dayanarak oluşturduğu bir jQuery plugin dayanarak, tarayıcı özelliklerini algılamaya yönelik bir jQuery eklentisi ekledim. von Schorsch'ün sürümüne eklediğim satırlar vurgulanıyor:

(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ılaması olmadan, IE'deki bir HTML öğesine addEventListener() yapmanın, IE'nin kendi ekini Event() yöntemini kullanması nedeniyle bir JavaScript hatası oluşturması. e.dataTransfer, IE'de (şimdilik) tanımlanmamıştır. Safari'de, e.dataTransfer.setData('DownloadURL','http://www.box.net') false (yanlış) değerini ve Chrome bu ifade için true değerini döndürür. Yukarıda belirtilen tüm testler yapıldığında bu özellik yalnızca Chrome'da kullanılabilir durumda kalır. Aşağıdakileri yapabileceğimi iddia edebilirsiniz:

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

Özellik algılamayı tarayıcı algılamaya tercih ediyorum, ancak bu yöntem DnD indirmenin çalışacağını teknik olarak algılamaz.

Yineleme sorunları 1

1) Şu anda sayfa üzerinde bulunan ve klasörler arasında dosya taşıma/kopyalama işlemi için DnD etkin olduğundan, DnD İndirme ile sayfa üzerindeki DnD'yi ayırt etmek için bir yönteme ihtiyacımız vardır. Teknik olarak, bu iki işlemi birleştiremeyiz. Kullanıcının bir dosyayı Box.net hesabındaki başka bir klasöre mi taşımak yoksa masaüstüne sürüklemek mi istediğini tahmin edemeyiz. Bu iki işlem birbirinden tamamen farklıdır. Dahası, imlecin tarayıcı penceresinin dışında olup olmadığını saptamanın kolay bir yolu yoktur. Dokümana fare çekme etkinliği eklemek için window.onmouseout (IE) ve document.onmouseout (diğer tarayıcılar) özelliklerini kullanabilir ve e.relatedTarget.nodeName == "HTML" (e fareyle üzerine gelme etkinliği veya window.event (hangisi geçerliyse) olup olmadığını kontrol edebilirsiniz. Ancak kaynaşmalar nedeniyle bu oldukça zor. Özellikle Box.net gibi karmaşık bir web uygulamasında bir resmin veya katmanın üzerinde olduğunuzda etkinlik rastgele tetiklenebilir.

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

Yineleme 2

Control + sürükleme (Windows Ctrl tuşuna basıldığında bir dosyayı sürükleme) ile denemeler yapmaya karar verdik. Bu işlem, kullanıcıların bir dosyayı kopyalamak için Windows masaüstünde yaptıklarıyla tutarlıdır. Dosyaların yanlışlıkla indirilmesini önlemek için kullanıcının ek bir işlem yapmasını da gerektirir (ancak fazladan bir adım gerekmez).

DnD İndirme'yi sayfadaki DnD ile sıkı bir şekilde entegre etmemiz gerektiğinden 1. yinelemedeki jQuery eklentisi şimdi kullanılmamaktadır. İlgilenenler için jQuery kullanıcı arayüzünün Draggable eklentisinin değiştirilmiş bir sürümünü kullanıyoruz. Bir hedef öğenin fare imlecini aşağı çekme etkinliğinin iç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ı sayfada normal bir sürükleyiş gerçekleştirdiğinde görünen küçük bir ekmek kızartma makinesi ipucu da ekledik. Kullanıcıya, Ctrl tuşu basılı tutulurken dosya simgesi masaüstüne sürüklenirse dosyaların indirilebileceğini bildirir.

Yineleme sorunları 2

Güvenlik nedeniyle, Box.net kalıcı URL'lerin doğrudan statik dosyalara erişmesini sağlamaz. Bu, Box.net'e özgü değildir. Çevrimiçi depolama hizmetleri, dosyanın herkese açık olup olmadığını ve istenen indirmenin uygun izinlere sahip bir kullanıcı tarafından istenip istenmediğini kontrol etmek için ek bir güvenlik katmanı olmadan kalıcı URL'ler 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 kodunu döndürü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önlendirme yapar. Zorluk, kodun birkaç dakikada bir dolmasıdır ve bu nedenle, HTML çıkışına yerleştirilmesi pratik değildir. Kullanıcı birkaç dakika önce oluşturulan HTML çıkışındaki bağlantıda bulunan dosyayı indirmeye çalıştığında "404" sonucunu döndürebilir.

DnD İndirme yalnızca doğrudan bir kaynağa yönlendiren gerçek URL'lerde çalışır. Yönlendirme söz konusuysa, şu anda zinciri takip edecek kadar akıllı değildir (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ı, dosyayı tarayıcı konum çubuğuna girdiğinizde indirmenize izin veriyor olsa da DnD ile çalışmaz.

"Gerçek URL" ile "yönlendirme URL'si" arasındaki farkları daha iyi göstermek 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

İterasyon 3

Ajax'ı deneyelim.

Önceki iterasyonda kodda küçük bir değişiklik yaptık 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;
}
}

Gayet makul. Dragstart sonrasında dosyanın en son indirme URL'sini almak için hemen sunucuya bir Ajax çağrısı yapar. Ancak bu yöntem işe yaramaz.

Bunun eşzamanlı bir arama (ya da ben buna Sjax diyorum) olması gerekiyor. Görünüşe göre setData'nın etkinlik işleyici eklendiği zamanda yapılması gerekiyor. jQuery API'sine göre, vurgulanan satırlar şu şekildedir:

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

Ağ bağlantısını kesene kadar sorunsuz çalışıyor. Eşzamanlı 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ı çökmüş gibi çözmez.

Aşağıdakileri yapmak ç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 demosu için, bir Box.net hesabına statik dosya yükleyebilirsiniz. Ctrl tuşunu basılı tutarken dosya simgesini masaüstünüze sürükleyin. Hesabınız yoksa oluşturmak gerçekten 30 saniyeden kısa sürüyor.

Bu özelliği kullanarak yaratıcı olabilir ve birçok şeyi mümkün kılabilirsiniz. Bir Windows yazıcı iletişim kutusuna sürüklendiğinde görüntü hemen yazdırılır. Box'tan cep telefonunuzun sürücüsüne bir şarkı kopyalayabilir, bir dosyayı Box'tan IM istemcinize sürükleyerek doğrudan arkadaşınıza aktarabilirsiniz... Bu, üretkenliğinizi artırmak için sonsuz olasılıkların kapısını açar.

bir dosyayı yazıcıya gönderme
Bir dosyayı yazıcıya sürükleme.
IM istemcisine dosya sürükleme
IM istemcisine dosya sürükleme.

Düşünceler ve gelecekteki iyileştirmeler

Eşzamanlı bir çağrı, tarayıcıyı kısa bir süreliğine kilitleyebileceğinden bu hâlâ ideal değildir. Web İşçisinin eşzamansız olması gerektiğinden HTML 5 Web İşçisi de yardımcı olmaz. Etkinlik işleyici eklendiği zamanda setData'nın yapılması gerekiyormuş gibi görünüyor.

Aslında, performans oldukça kabul edilebilir düzeydedir. Eşzamanlı Ajax (Sjax) çağrısı yalnızca bir URL dizesi alır. Bu işlem oldukça hızlı olmalıdır. HTTP başlığında büyük olasılıkla WebSockets tarafından ele alınabilen büyük bir yük bulunur. Ancak, bu tür bir teknolojinin daha fazla kullanıldığını görene kadar, her küçük güncellemeyi istemciye göndermek için WebSockets'i kullanmaya değmez.

Gelecekte API'ye çoklu dosya indirme özelliğinin de ekleneceğini umuyorum. Kullanıcı arayüzünde birden fazla dosya seçmeyi sağlayan özel onay kutularıyla birleştirildiğinde bu olağanüstü sonuçlar sağlar. Ayrıca, gönderilen bir formun sonucunda oluşturulan metin dosyaları gibi istemci tarafından oluşturulan dosyaların bu şekilde indirilmesi daha da iyi olur.

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

Referanslar