jQuery PostFile

jQuery PostFile

Pewien czas temu natknelem się na problem, probując wysłać plik na serwer bez przeładowywania strony. Dodatkowym wymaganiem było aby odpowiedź była w formacie JSON. Niestety nie udało mi się znaleźć rozwiązania spełniającego moje potrzeby, dlatego postanowiłem napisać plugin jQuery. Jest to mój pierwszy plugin jaki udostępniam publicznie.

 

Opis

Tworząc plugin musiałem pokonać dwa problemy. Pierwszy z nich to wysłanie pliku bez przeładowania strony. Zostało to osiągnięte poprzez zastosowanie ramki. Ramka jest tworzona w tle dynamicznie, formularz jest wysyłany do ramki, odpowiedź jest odczytywana i ramka jest usuwana ze strony. Kolejnym problemem było odczytanie odpowiedzi w formacie JSON. Wysyłając poprawnie walidujący się JSON do ramki przeglądarka próbuje to odczytać jako HTML wprowadzając pewne modyfikacje burzące strukturę JSON. Rozwiązaniem problemu okazało się owinięcie odpowiedzi w następujący kod HTML:

 

<html><body><script id="fetchme">
 
</script></body></html>

Odpowiedź właściwa jest zamieszczona i odczytywana z wewnątrz znacznika script(na podstawie ID które jest konfigurowalne w pluginie). Plugin działa na wszystkich współcześnie używanych przeglądarkach(IE6.0 włącznie). jQuery postFile jest podwójnie licencjonowany na zasadach licensji MIT i GPL.

 

Sposób użycia

Chcąc maksymalnie uprościć plugin, wywołujemy go na elemencie <form> w następujący sposób:

 

$('form#my_form').postFile();

Jako argumenty plugin przyjmuje funkcje wywoływaną po otrzymaniu odpowiedzi na zapytanie(funkcja przyjmuje jeden argument zawierający odpowiedź). Jako drugi argument podajemy obiekt z ustawieniami:

responseType: przyjmuje obecnie wartości ‚json'(domyślnie) lub ‚text’. Odpowiedź json jest parsowana z elementu o id jsonId, natomiast odpowiedź text jest zwracana bezpośrednio z ramki. Wyjątkiem jest jeśli odpowiedź jest odpowiedzią HTML. Korzystając z metody text otrzymamy wtedy zawartość elementu body odpowiedzi zamiast całej odpowiedzi. W przyszłości planuję dorobić nowe wartości.
jsonId: id elementu script w którym zawarta jest odpowiedź json. W przypadku korzystania z odpowiedzi typu text, parametr ten jest nieistotny. Domyślnie przyjmuje wartość ‚fetchme’
iframeDelay: opóźnienie w ms licząc od chwili otrzymania odpowiedzi na zapytania, po którym usuwamy ramkę wraz z zawartością.

Praktyczny sposób wykorzystania pluginu może wyglądać następująco:

$('#my_form').postFile(function(response) {
   if(typeof(response)=='object') {
      if(object.success==true) {
         alert('Plik został pomyślnie wgrany na serwer');
      else
         alert('Nie udało się wgrać pliku na serwer');
   } else {
      alert('Odpowiedź nie jest obiektem json');
   }
});

Źródło

/*
 * jQuery postFile
 *
 * http://www.tomaszmazur.eu
 * Copyright (c) 2011 Tomasz Mazur (http://tomaszmazur.eu)
 * Dual licensed under the MIT and GPL licenses
 *
 */
(function($){
    /*
    * @param <function> callback function with one parameter(response)
    * @param <object> settings
    */
    $.fn.postFile = function(callback,settings) {
        settings = $.extend({
            responseType: 'json'//allowed: json,text
            ,jsonId: 'fetchme'//id, in which the json response will be contained, json response must be contained in <script></script> tags
            ,iframeDelay: 100//delay before iframe is removed
        },settings);
        return this.each(function() {
            var iframeName = ("jqpf" + (new Date()).getTime());//name for our iframe
            var iframe = $( "<iframe name=\"" + iframeName + "\" src=\"about:blank\" />" ).css( "display", "none" );
            iframe.load(function() {
                switch(settings.responseType) {
                    case 'text':
                        var resp = window.frames[ iframeName ].document.body.innerHTML;//get response
                        break;
                    case 'json':
                        var resp = window.frames[ iframeName ].document.getElementById(settings.jsonId);//get response text
                        resp = $.parseJSON(resp.innerHTML);//read the response as json
                        break;
                    default:
                        var resp = 'unknown responseType';
                        break;//unknown responseType
                }
                setTimeout(function(){iframe.remove();},settings.iframeDelay);//remove iframe from dom with delay
                if(typeof(callback)=='function')
                    callback(resp);
            });
            $("body:first").append(iframe);//add the iframe to dom
            $(this)
                .attr( "method", "post" )//just in case
                .attr( "enctype", "multipart/form-data" )//just in case
                .attr( "encoding", "multipart/form-data" )//for IE
                .attr( "target", iframeName )//the form now sends to iframe instead of browser
                .submit();//submit the form
            ;
        });
    }
})(jQuery);//closure