HTML5 ve AJAX kullanılarak nasıl dosya upload edilir?

Selamlar,

HTML ile kullanıcıların kendi bilgisayarlarından seçtikleri dosyaları sunucuya gönderebilmelerini sağlamanın klasik yolunu, sanıyorum bu işle uğraşan arkadaşların birçoğu biliyordur. Bir HTML form içerisinde “file” tipinde bir “<input>” elementi oluşturup, bu elementin içerisinde yer aldığı formu sunucuya göndererek -elbette kullanıcının dosya seçtiğini varsayıyoruz- yapıyoruz bu işi. En azından HTML5 çıkmadan önce bu şekilde yapıyorduk, en basit anlamda.

Ancak AJAX‘ın(Asynchronous JavaScript and XML) ortaya çıkışı ile birlikte, formlar üzerinde yer alan dosya elementlerini, tüm sayfa bilgilerini sunucuya göndermeden (en kaba tabiri ile sayfayı tarayıcı içerisinde yeninden yüklemeden) yapma ihtiyacı da ortaya çıktı. 2004’lü yıllardan itibaren de ZİU(Zengin İnternet Uygulamaları) -RIA(Rich Internet Applications) uygulamalarının geliştirilmeye başlanması ile birlikte bu ihtiyaç iyiden iyiye belirgin bir hal almaya başladı. HTML5 ortaya çıkmadan önce bu işi yapmanın iki yolu vardı ancak bu yöntemler de aşağıda belirttiğim durumlardan ötürü güvenilir ve tutarlı değildiler (hala da öyleler).

  1. Flash tabanlı çözümler kullanmak (SWFUpload gibi)

    Flash, IE tabanlı olmayan (NPAPI desteği olan) tarayıcılarda sorunlara neden olmaktadır[1] ve bu nedenle bu tarayıcılarda Flash tabanlı bu çözümler güvenilir değildir. Bunun da ötesinde iOS işletim sistemi Flash desteği sunmadığı için, iOS tabanlı iPhone, iPad gibi aygıtlarda Flash tabanlı çözümler de çalışmamaktadır[2]. iOS işletim sistemlerinde Flash tabanlı çözümlerin mutlaka Adobe Packager ile native iOS uygulaması olarak derlenmesi gerekmektedir.

  2. IFrame içerisinde FORM kullanmak

    Bu yöntem öncelikle tarayıcıların “geri” ve “ileri” düğmelerinin çalışma yapısını bozmaktadır. Aynı zamanda bu durum, kullanılan web uygulamasının akışını da olumsuz yönde etkileyen bir durum yaratır. Bu durumların da “extra” bir takım önlemlerle kontrol edilmesi ve önlenmesi gerekebilir. Ayrıca, bu çözümde upload işlemine ait gösterge durumu (progress) Chrome dışındaki tarayıcılarda çalışmaz. (Konu dışı, artı bir parantez: Iframe, frameset ve noframe elementleri güvenlik sebeplerinden dolayı HTML5 içerisinde YOKTUR[3])


HTML5 içerisinde yer alan iki API sayesinde, dosyalarımızı AJAX ile(XMLHttpRequest kullanarak), sayfamızı refresh etmeden sunucuya göndermemiz ve upload işlemine ilişkin bilgileri kolaylıkla elde etmemiz mümkün hale geliyor. Peki bu iki API hangileridir? Şunlardır efendim:

  • File API (Dosya API)

  • DnD API (Sürükle ve Bırak API)

Bu iki API’nin(Application Programming Interface – Uygulama Geliştirme Arayüzü) detaylarına girmeden önce, AJAX ile dosya upload işleminin adımlarını belirleyelim.

  1. Kullanıcıdan dosya alınır.

    Bu işlemi yapmanın iki yolu var. Birincisi HTML5 ile gelen File API’yi kullanarak dosya bilgilerine  ulaşmak, diğer yol ise yine HTML5’in bize sunduğu DnD, yani sürükle-bırak özelliği ile dosya bilgilerine zahmetsizce -bir önceki yola göre- erişmek.

  2. Dosya okunur.

  3. RFC 1867 gözetilerek “multipart/form-data” isteği oluşturmak.

    Bu işlem için de iki yöntemimiz var. Birinci yol, RFC’yi gözeterek gerekli form verisini biz manuel oluşturabiliriz ya  da en kolayı, yine HTML5’in bize sunduğu “FormData” nesnesini kullanabiliriz[4].

  4. Dosya bilgileri XMLHttpRequest ile gönderilir.

 

Şimdi adım adım bu işlemlerin detaylarından bahsedelim.

1. Kullanıcıdan Dosyaları Almak

Yukarıda sıraladığımız işlemler içerisinde, HTML5’in bize sunmuş olduğu yeni özellikler sayesinde, en kolay adımın bu olduğunu söyleyebiliriz arkadaşlar. Yukarıda da bahsettiğim gibi bu işlemi iki şekilde yapmamız mümkün. Birinci yol HTML5 File API’yi kullanmak. Konu gayet basit, şöyle ki: File API, <FORM> öğeleri içerisinde yer alan <input type=”file”> (dosya tipinde form elemanı) elementleri ile seçtiğimiz dosya bilgilerine DOM üzerinden erişebilmemize olanak sağlıyor[5]. Hemen bir örnek verelim JavaScript ile:

// uploadDataForm bir formdur
// fileChooserInput tipi 'dosya' olan bir &lt;input&gt; elemanıdır
var file = document.forms['uploadDataForm']['fileChooserInput'].files[0];
 
// alternatif olarak şu da kullanılabilir
// var file = document.forms['uploadDataForm']['fileChooserInput'].files.item(0);
 
if(file)
{
   // Dosya işlemlerini istediğiniz gibi burada gerçekleştirebilirsiniz
}

Örnekte gördüğümüz gibi arkdaşlar, kullanıcıdan dosya bilgilerini almak HTML5 File API sayesinde inanılmaz kolay. Tabi burada hemen güvenlik aklımıza geliyor. Öncelikle şunu söylemeliyim; bir form içerisindeki dosya tipindeki input elementlerinin sağladığı dosya nesneleri sadece okunabilir (read-only) niteliklere sahip. Diğer güvenlik önlemleri için ise bu referansı kullanabilirsiniz[6].

Kullanıcıdan dosyayı almanın bir diğer yolu da HTML5’in yeni özelliklerinden DnD (Sürükle Bırak) API’sini kullanmak[7]. Bir HTML elementine sürükle-bırak özelliği eklemek çok kolaydır. Bunun için, sürükle-bırak özelliği vermek istediğiniz HTML elemanına “draggable” niteliğini (attribute) eklemeniz ve sürükle-bırak ile verilerin saklandığı “dragstart” olayı için bir olay dinleyici (event listener) belirlemeniz yeterli olacaktır. W3’ün sitesindeki bir örnekle somut hale getirelim olayı (sevdiğimiz meyveleri bir listeden seçip, başka bir alana bırakıyoruz):

<p>Hangi meyveleri seviyorsunuz?</p>
<ol ondragstart="dragStartHandler(event)">
 <li draggable="true" data-value="fruit-apple">Elma</li>
 <li draggable="true" data-value="fruit-orange">Portakal</li>
 <li draggable="true" data-value="fruit-pear">Armut</li>
</ol>
<script>
  var internalDNDType = 'text/x-example'; // bu değişkene sitenize özgü bir değer verebilirsiniz
  function dragStartHandler(event) {
    if (event.target instanceof HTMLLIElement) {
      // HTML elementlerinin "data-value" niteliğini kullanabilirsiniz
      event.dataTransfer.setData(internalDNDType, event.target.dataset.value);
      event.dataTransfer.effectAllowed = 'move'; // yalnızca taşımaya izin ver
    } else {
      event.preventDefault(); // don't allow selection to be dragged
    }
  }
</script>

Elbette sürükleyebildiğiniz bir öğeyi bir yerlere bırakmanız gerekecektir. Bir HTML elemanına “bırak-drop” özelliği verebilmeniz de çok basit HTML5 ile: bu özelliği vermek istediğiniz HTML elemanına “dropzone” isimli bir nitelik ekliyoruz ve aynı zamanda “drop” olayı (event) için bir dinleyici belirliyoruz. Bu kadar basit, yine W3’ün sitesindeki aynı örnekle açıklayalım durumu:

<p>Favori meyvelerinizi aşağıya bırakın:</p>
<ol dropzone="move s:text/x-example" ondrop="dropHandler(event)">
 <--text/x-example" değerini sitenize özgü bir değerle değiştirmeyi unutmayın -->
</ol>
<script>
  var internalDNDType = 'text/x-example'; // bu değişkeni sitenize ögü bir değerle değiştirin
  function dropHandler(event) {
    var li = document.createElement('li');
    var data = event.dataTransfer.getData(internalDNDType);
    if (data == 'fruit-apple') {
      li.textContent = 'Elma';
    } else if (data == 'fruit-orange') {
      li.textContent = 'Portakal';
    } else if (data == 'fruit-pear') {
      li.textContent = 'Armut';
    } else {
      li.textContent = 'Bilinmeyen Meyve';
    }
    event.target.appendChild(li);
  }
</script>

Dikkat ettiyseniz <ol> HTML öğesine ait “dropzone” niteliğinin (attribute) değeri “move s:text/x-example“. Bu konu çok önemli, bu niteliğin değeri bize, sürükle-bırak ile bırakılan verinin tipini ve ne şekilde bir geri besleme olacağını, verinin taşınma biçimini anlatmaktadır. Örneklerde tamamen kendi oluşturduğumuz “text/x-example” tipinde bir veri kullandık (siz de kendinize göre veri tipleri belirleyebilirsiniz yukarıdaki örneklerdeki gibi). Mesela, “s:text/plain” veri tipi, bu elemanın sürükle-bırak metodu ile her türlü String tipindeki veriyi kabul edeceğini, “f:image/png” ise bu öğenin yalnızca PNG formatındaki görselleri kabul edeceğini belirtir. Bunların dışında, sürükle-bırak operasyonlarını[8] da belirtebiliriz bu niteliğin değerini kullanarak. En basit anlamda “move” operasyonu (örnekte de kullandığımız) sürükle-bırak olayında, kaynak öğenin hedefe “taşınacağını” belirtir. Yani kaynak işlem sonunda kaybolacaktır. Buna karşılık “copy” operasyonu ile de kaynağın bir kopyasını hedefe taşımış oluruz. Operasyonlarla ilgili daha detaylı bilgiye verdiğim referanstan ulaşmanız mümkün arkadaşlar (8 no’lu referans).

Hemen bu noktada bir not eklemek istiyorum. “dropzone” niteliğini kullanmak yerine, “dragenter” ve “dragover” olaylarını kontrol ederek de sürükle-bırak işlemini gerçekleştirmemiz mümkün. Bu notumu belki başka bir yazımda ele alırım. Şimdilik böylece bırakalım…

DataTransfer Ara Yüzü

Peki herşey buraya kadar iyi, güzel ama sürükle-bırak ile dosya verilerine (sürükleyip bıraktığımız) nasıl erişeceğiz? İşte tam da bu noktada DataTransfer ara yüzünü anlatmanın/anlamanın vakti geliyor.

DataTransfer nesneleri sürükle-bırak olayı sırasında, drag-data-store (tam olarak Türkçe’ye nasıl çevirmem gerektiğini bilemedim) verisi içerisindeki dosyalara erişebilmemize olanak tanır. Aşağıdaki örnek, bir sürükle-bırak durumu için, bırak (drop) olayı içerisinde, bırakılan dosyalara nasıl erişebildiğimizi göstermektedir arkadaşlar:

<html>
<head>
<script>
 
function dodrop(event)
{
  var dt = event.dataTransfer;
  var files = dt.files;
 
  var count = files.length;
  output("File Count: " + count + "\n");
 
	for (var i = 0; i < files.length; i++) {
	  output(" Dosya " + i + ":\n(" + (typeof files[i]) + ") : <" + files[i] + " > " +
			 files[i].name + " " + files[i].size + "\n");
	}
}
 
function output(text)
{
  document.getElementById("output").textContent += text;
  dump(text);
}
 
</script>
</head>
<body>
 
<div id="output" style="min-height: 100px; white-space: pre; border: 1px solid black;"
     ondragenter="document.getElementById('output').textContent = '';
     event.stopPropagation(); event.preventDefault();"
     ondragover="event.stopPropagation(); event.preventDefault();"
     ondrop="event.stopPropagation(); event.preventDefault(); dodrop(event);">
 
</body>
</html>

Örnekte sürükleyip bıraktığımız dosyaların boyutları ve isimleri ekranımızda listelenecektir. Çok fazla detayına girmeden, sürükle-bırak ile dosya bilgilerine nasıl eriştiğimizi burada noktalamak istiyorum arkadaşlar, daha anlatacak çok şeyimiz var.

 

2. Dosya Okunur

Bu adımı da hızlıca geçeceğim. Asıl konumuz dosyaları nasıl okuyacağımızdan ziyade, HTML5 API’lerini kullanarak dosya upload işlemini yapmak çünkü. Belki de, bu konuyu da başka bir yazıda ele alabiliriz 🙂 HTML5 File API kullanarak dosyaların nasıl okunacağı konusunda yazımın sonunda verdiğim referanslarımı inceleyebillirsiniz.

 

3. RFC 1867 gözetilerek “multipart/form-data” isteği oluşturmak

Bu konuyu, eski tarayıcılar ve yeni nesil tarayıcılar olarak iki aşamada incelememiz ve ele almamız gerekiyor.  Bunun nedeni de yazımın ilerleyen bölümlerinde bulacaksınız arkdaşlar.

Yeni nesil tarayıcılar, HTML5’in de gücü ile bu işlemi kolayca gerçekleştirebiliyorlar zaten. Hepsinden bahsedeceğim.

Eski Nesil Tarayıcılar

Firefox 3.0 ve üstü tarayıcılar için dosya verisine aşağıdaki şekilde ulaşmanız mümkün:

var bin = file.getAsBinary();

Firefox 3.6 için ise aşağıdaki şekilde, istediğimiz dosya bilgilerine ulaşmamız mümküm oluyor:

var reader = new FileReader();
reader.onloadend = function (ev) {
    var bin = ev.target.result;
;{
reader.onerror = function (ev) {
    switch (ev.target.error) { ... }
;{
reader.readAsBinaryString(file)

Yeni Nesil Tarayıcılar

Yeni nesil tarayıcılarda HTML5 ile birlikte çok güzel bir özellik de geliyor arkadaşlar. Bu yeni nesil tarayıcılarda (Firefox 4, Safari 5, Chrome 5) işimiz o kadar kolay ki… HTML5 bize HTML <form> verilerini manipule edebileceğimiz bir yapı sağlıyor: FormData arayüzü[4]. Bu ara yüzü kullanarak sıfırdan bir form verisini, HTML form oluşturmadan, sunucuya göndermeniz mümkün. FormData ara yüzü ile ilgili detayları anlatmaya başlayalım…

FormData Ara Yüzü

FormData nesnesini kullanarak JavaScript ile HTML form verileri oluşturabilir ya da mevcut bir HTML form verisini (verilerini) manipüle edebilirsiniz arkadaşlar. HTML5 ile birlikte gelen en önemli ve en kullanışlı özelliklerden birisi bu sınıf bence. FormData nesnesi aslında, sıralı girdilerden oluşan bir kolleksiyondur ve bu kolleksiyonun herbir elemanının name (isim), value (değer), type (tip) ve filename (dosya adı) özellikleri bulunmaktadır[9]. Eğer FormData kolleksiyonu içerisine “String” tipinde bir değer eklemek istiyorsak, kolleksiyon elemanının tip özelliğini “text” olarak, dosya (file) tipinde bir değer/veri eklemek istiyorsak, tip özelliğini “file” olarak işaretleyebilirsiniz.

FormData nesnemizi nasıl kullanmamız gerektiğine gelince:

var formData = new FormData();
 
formData.append("kullaniciadi", "abutun");
formData.append("sifre", 123456);
formData.append("birdosya", fileInputElement.files[0]);

yukarıdaki örnekte sıfırdan oluşturulan bir FormData sınıfı görüyorsunuz. Örnekte, FormData nesnesinin append() metodunu kullanarak, yeni bir form verisi oluşturuyoruz. Bu form verisi kümemize (kolleksiyonumuza) iki tane “String” tipinde değer ekliyoruz (kullaniciadi ve sifre) ve bir tane de “file” tipinde bir dosya ekliyoruz (birdosya). İşte bir sonraki adımımızda oluşturduğumuz bu form verilerini XMLHttpRequest ile sunucumuza göndereceğiz. Oldukça basit dimi 🙂

var formElement = document.getElementById("myFormElement");
formData = new FormData(formElement);
formData.append("serinumarasi", 999);

Yukarıda dilersek mevcut bir form verisini FormData nesnesini kullanarak manipüle edebiliriz demiştik. Bu örneğimizde (bir üstteki) de bunu göstermeye çalıştım. “myFormElement” adlı bir HTML formuna ait form bilgilerine, FormData nesnesinin FormData(FormElement) constructor’ını kullanarak erişiyoruz ve bu form bilgilerine, “serinumarasi” adlı yeni bir değer ekliyoruz. Bu da çok kolay, dimi 🙂

Karşılaştırma Tablosu

Şu ana kadar yazdıklarımızdan bir özet çıkartmak gerekirse, kullanıcıdan dosya bilgilerini HTML5’in yeni API’lerini kullanarak alıyoruz. Daha sonra bu bilgilere “eski nesil tarayıcılar” ve “yeni nesil tarayıcılar” için farklı şekillerde ulaşarak, XMLHttpRequest ile sunucumuza göndermek için hazırlıyoruz (bir sonraki başlıkta göndereceğiz verilerimizi:) ) Ancak burada bir şeye dikkat etmemiz lazım:

Dikkat ettiyseniz Opera ve IE (Internet Explorer) tarayıcılarını yazımıza dahil etmedik ve bahsetmedik hiç. Nedenini aşağıdaki tablodan anlayabilirsiniz.

TarayıcıOkuma(read)FormGönderme(send)
FireFox 3.0/3.5readAsBinary()RFC 1867 ref.sendAsBinary()
FireFox 3.6FileReaderRFC 1867 ref.sendAsBinary()
FireFox 4.0FormDataFormDatasend(FormData)
Chrome 5FormDataFormDatasend(FormData)
Safari 5FormDataFormDatasend(FormData)
Opera 10.6Henü Desteklemiyor!
IE (Internet Explorer)Oooooppppssssss!!!

Dolayısıyla IE (Internet Explorer) yine kendisinden bekleneni başarı ile yapıyor ve bu konularda tıraşa bağlıyor 🙂 Güldüğüme bakmayın IE deli ediyor beni 🙂 bkz. http://www.ahmetbutun.net/bNI

 

4. Dosya bilgileri XMLHttpRequest ile gönderilir

İlk defa sanıyorum bu kadar uzun bir yazı yazıyorum güncemde. Bir oturuşta da yazmadığım için yazıyı (ara ara oturup bir kaç paragraf ekleyerek, malumunuz iş-güç yoruyor, vakit ayıramıyorum) umarım bölümler arasında kopukluklar oluşmamıştır. Gerçi her yazımı son bir defa daha okuyup yayına almaya özen gösteriyorum her zaman.

Buraya kadar dosyaları kullanıcıdan aldık, dosya verilerine ulaştık. Tek yapmamız gereken şey, elde ettiğimiz bu verileri, XMLHttpRequest nesnesi ile sunucuya göndermek. Bu işlemi de FormData nesnesi kullandığımız durumlarda aşağıdaki gibi gerçekleştiriyoruz arkadaşlar, sanıyorum bu işleme bir çoğunuz aşinadır zaten (daha doğrusu XMLHttRequest nesnesinin kullanımına).

Bir HTML form oluşturup formumuza bir dosya elemanı ekleyelim:

<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo">
  <label>Eposta adresiniz:</label>
  <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64"><br />
  <label>Dosya ID:</label>
  <input type="text" name="fileid" size="12" maxlength="32"><br />
  <label>Saklanacak dosya:</label>
  <input type="file" name="file" required>
</form>
<div id="output"></div>
<a href="javascript:sendForm()">Dosyayı Kaydet!</a>

“Dosyayı Kaydet” bağlantısına tıkladığımızda JavaScript ile FormData nesnesini kullanarak  kullanıcının seçtiği dosyayı XMLHttpRequest ile sunumuza gönderiyoruz. Bu işlemi ilgili bağlantının href özelliği içerisindeki sendForm() fonksiyonumuz yapıyor:

function sendForm() {
  var output = document.getElementById("output");
  var data = new FormData(document.getElementById("fileinfo"));
 
  data.append("CustomField", "Ekstra Veri");
 
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "upload.php", false)
  xhr.send(data);
  if (xhr.status == 200) {
    output.innerHTML += "Yüklendi!&lt;br /&gt;";
  } else {
    output.innerHTML += "Hata oluştu (" + xhr.status + ").&lt;br /&gt;";
  }
}

dilersek bu işlemi JQuery kullanarak da aşağıdaki şekilde yapabiliriz:

var fd = new FormData(document.getElementById("fileinfo"));
fd.append("CustomField", "Ekstra Veri");
$.ajax({
  url: "upload.php",
  type: "POST",
  data: fd,
  processData: false,  // JQuery'ye veriyi process etmemesini söyle
  contentType: false   // JQuery'ye içerik tipini set etmemesini söyle
});

hepinize iyi kodlamalar arkadaşlar. Yarın yine malum iş güç, erken kalkmam lazım. Keşke bir sponsorum olabilse de durmadan güncemde böyle yazılar yazabilsem…

 

Referanslar

[1] – http://demo.swfupload.org/Documentation/#knownissues
[2] – http://en.wikipedia.org/wiki/Comparison_of_HTML5_and_Flash
[3] – http://www.w3.org/TR/html5-diff/#absent-elements
[4] – http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-formdata-interface
[5] – http://dev.w3.org/2006/webapi/FileAPI/#filelist-section
[6] – http://dev.w3.org/2006/webapi/FileAPI/#security-discussion
[7] – http://www.w3.org/TR/html5/dnd.html
[8] – https://developer.mozilla.org/En/DragDrop/Drag_Operations
[9] – https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest

, , , , , , , , , , , , , , , ,

2 Responses to HTML5 ve AJAX kullanılarak nasıl dosya upload edilir?

  1. Tuncay AKAY 19 Mayıs 2011 at 10:10 AM #

    Merhaba Hocam,
    Bu tür konuları paylaştığınız için gerçekten teşekkür ederim. Bigi kirliliği yaratmadan açık ve net şekilde anlatmışsınız. Başarılarınızın devamını diliyorum.

    enetmar Internet ve Bilişim Hizmetleri
    wwww.enetmar.net – Tuncay AKAY

    • @bütün 19 Mayıs 2011 at 10:53 AM #

      Çok teşekkürler Tuncay. Vakit buldukça yazıyorum, yazmaya da devam edeceğim. Faydalı olduğuna sevindim.

      Ahmet BÜTÜN

Bir Cevap Yazın

Font Resize