Carga de archivos HTML5 con jQuery

Hoy desarrollaremos una pequeña aplicación web llamada Upload Center , que permitirá a las personas cargar fotos desde sus computadoras arrastrándolas y soltándolas en la ventana del navegador, posible con las nuevas API HTML5 expuestas por los navegadores modernos.

Las fotos tendrán una vista previa y una barra de progreso, todo lo cual se controla en el lado del cliente. Actualmente, las fotos solo se almacenan en una carpeta en el servidor, pero puedes mejorarlas como quieras.

¿Qué son las cargas de archivos HTML5?

La carga de archivos usando HTML5 es en realidad una combinación de tres tecnologías:la nueva API File Reader, la también nueva API Drag &Drop y el buen AJAX (con la adición de transferencia de datos binarios). Aquí hay una descripción de un proceso de carga de archivos HTML5:

  1. El usuario suelta uno o más archivos de su sistema de archivos en la ventana del navegador arrastrándolos. Navegadores compatibles con la API de arrastrar y soltar activará un evento, que junto con otra información útil, contiene una lista de archivos que se eliminaron;
  2. Uso de la API del lector de archivos , leemos los archivos de la lista como datos binarios y los almacenamos en la memoria;
  3. Usamos el nuevo sendAsBinary método de XMLHttpRequest objeto y envíe los datos del archivo al servidor.

¿Suena complicado? Sí, podría usar alguna optimización. Afortunadamente, existen complementos de jQuery que pueden hacer esto por nosotros. Uno de ellos es Filedrop, que es un contenedor de esta funcionalidad y proporciona funciones para limitar el tamaño máximo de archivo y especificar funciones de devolución de llamada, lo que es realmente útil para integrarlo en sus aplicaciones web.

Actualmente, la carga de archivos funciona solo en Firefox y Chrome , pero las próximas versiones principales de los otros navegadores también incluyen soporte para él. Una solución alternativa simple para los navegadores más antiguos sería mostrar un cuadro de diálogo de entrada de archivos regular, pero no lo haremos hoy, ya que centraremos nuestra atención en el uso de HTML5.

¡Así que empecemos!

El HTML

El marcado de nuestro Centro de carga no podría ser más simple. Tenemos un documento HTML5 normal, que incluye nuestra hoja de estilo y script.js archivo, el complemento Filedrop y la biblioteca jQuery .

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>HTML5 File Drag and Drop Upload with jQuery and PHP | Tutorialzine Demo</title>

        <!-- Our CSS stylesheet file -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

        <header>
            <h1>HTML5 File Upload with jQuery and PHP</h1>
        </header>

        <div id="dropbox">
            <span class="message">Drop images here to upload. <br /><i>(they will only be visible to you)</i></span>
        </div>

        <!-- Including The jQuery Library -->
        <script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>

        <!-- Including the HTML5 Uploader plugin -->
        <script src="assets/js/jquery.filedrop.js"></script>

        <!-- The main script file -->
        <script src="assets/js/script.js"></script>

    </body>
</html>

El único div con el que interactúa Filedrop es #dropbox . Pasaremos este elemento al complemento, que detectará cuando se coloque un archivo encima. El intervalo de mensajes se actualiza si hay una condición de error (por ejemplo, si su navegador no es compatible con una de las API de HTML5 en las que se basa este ejemplo).

Más tarde, cuando sueltemos un archivo, nuestro código jQuery mostrará una vista previa agregando el siguiente marcado a la página:

<div class="preview done">

    <span class="imageHolder">
        <img src="" />
        <span class="uploaded"></span>
    </span>

    <div class="progressHolder">
        <div class="progress"></div>
    </div>

</div>

Este fragmento contiene una vista previa de la imagen (el atributo de origen se completará con una URL de datos de la imagen) y una barra de progreso. Toda la vista previa puede tener el ".done" class, lo que provoca el ".uploaded" span para que aparezca (está oculto de forma predeterminada). Este lapso tiene la marca de verificación verde como fondo e indica que la carga se completó.

Genial, pasemos a nuestro script.js archivo!

El código jQuery

Como toda la funcionalidad real de transferencia de archivos es manejada por el complemento Filedrop, solo necesitamos llamarlo y pasar algunas devoluciones de llamada, para que podamos conectarlo a nuestro Centro de carga . Escribiremos un pequeño script PHP que maneje las cargas en el servidor en la siguiente sección.

El primer paso es escribir una función de ayuda que tome un objeto de archivo (un objeto especial que crea el navegador web al soltar el archivo y tiene propiedades como nombre de archivo, ruta y tamaño) y crea el marcado para obtener una vista previa de la carga.

activos/js/script.js

var template = '<div class="preview">'+
                        '<span class="imageHolder">'+
                            '<img />'+
                            '<span class="uploaded"></span>'+
                        '</span>'+
                        '<div class="progressHolder">'+
                            '<div class="progress"></div>'+
                        '</div>'+
                    '</div>'; 

    function createImage(file){

        var preview = $(template),
            image = $('img', preview);

        var reader = new FileReader();

        image.width = 100;
        image.height = 100;

        reader.onload = function(e){

            // e.target.result holds the DataURL which
            // can be used as a source of the image:

            image.attr('src',e.target.result);
        };

        // Reading the file as a DataURL. When finished,
        // this will trigger the onload function above:
        reader.readAsDataURL(file);

        message.hide();
        preview.appendTo(dropbox);

        // Associating a preview container
        // with the file, using jQuery's $.data():

        $.data(file,preview);
    }

La plantilla La variable contiene el marcado HTML5 de la vista previa. Obtenemos la DataURL de la imagen (una representación codificada en base64 de los bytes de la imagen) y la agregamos como fuente de la imagen. Luego, todo se adjunta al contenedor de Dropbox. Ahora nos queda llamar al complemento filedrop:

activos/js/script.js

$(function(){

    var dropbox = $('#dropbox'),
        message = $('.message', dropbox);

    dropbox.filedrop({
        // The name of the $_FILES entry:
        paramname:'pic',

        maxfiles: 5,
        maxfilesize: 2, // in mb
        url: 'post_file.php',

        uploadFinished:function(i,file,response){
            $.data(file).addClass('done');
            // response is the JSON object that post_file.php returns
        },

        error: function(err, file) {
            switch(err) {
                case 'BrowserNotSupported':
                    showMessage('Your browser does not support HTML5 file uploads!');
                    break;
                case 'TooManyFiles':
                    alert('Too many files! Please select 5 at most!');
                    break;
                case 'FileTooLarge':
                    alert(file.name+' is too large! Please upload files up to 2mb.');
                    break;
                default:
                    break;
            }
        },

        // Called before each upload is started
        beforeEach: function(file){
            if(!file.type.match(/^image\//)){
                alert('Only images are allowed!');

                // Returning false will cause the
                // file to be rejected
                return false;
            }
        },

        uploadStarted:function(i, file, len){
            createImage(file);
        },

        progressUpdated: function(i, file, progress) {
            $.data(file).find('.progress').width(progress);
        }

    });

    var template = '...'; 

    function createImage(file){
        // ... see above ...
    }

    function showMessage(msg){
        message.html(msg);
    }

});

Con esto, cada archivo de imagen válido que se coloca en el #dropbox div se carga en post_file.php , que puedes ver en la siguiente sección.

El código PHP

En el lado PHP de las cosas, no hay diferencia entre una carga de archivo de formulario normal y una de arrastrar y soltar. Esto significa que puede proporcionar fácilmente una solución alternativa a su aplicación y reutilizar el mismo backend.

post_archivo.php

// If you want to ignore the uploaded files,
// set $demo_mode to true;

$demo_mode = false;
$upload_dir = 'uploads/';
$allowed_ext = array('jpg','jpeg','png','gif');

if(strtolower($_SERVER['REQUEST_METHOD']) != 'post'){
    exit_status('Error! Wrong HTTP method!');
}

if(array_key_exists('pic',$_FILES) && $_FILES['pic']['error'] == 0 ){

    $pic = $_FILES['pic'];

    if(!in_array(get_extension($pic['name']),$allowed_ext)){
        exit_status('Only '.implode(',',$allowed_ext).' files are allowed!');
    }   

    if($demo_mode){

        // File uploads are ignored. We only log them.

        $line = implode('       ', array( date('r'), $_SERVER['REMOTE_ADDR'], $pic['size'], $pic['name']));
        file_put_contents('log.txt', $line.PHP_EOL, FILE_APPEND);

        exit_status('Uploads are ignored in demo mode.');
    }

    // Move the uploaded file from the temporary
    // directory to the uploads folder:

    if(move_uploaded_file($pic['tmp_name'], $upload_dir.$pic['name'])){
        exit_status('File was uploaded successfuly!');
    }

}

exit_status('Something went wrong with your upload!');

// Helper functions

function exit_status($str){
    echo json_encode(array('status'=>$str));
    exit;
}

function get_extension($file_name){
    $ext = explode('.', $file_name);
    $ext = array_pop($ext);
    return strtolower($ext);
}

El script ejecuta algunas comprobaciones en el método HTTP que se utilizó para solicitar la página y la validez de la extensión del archivo. El modo de demostración es principalmente para demo.tutorialzine.com , donde no quiero almacenar ninguna carga de archivos (si no llama a move_uploaded_file en su secuencia de comandos, el archivo se elimina automáticamente al final de la solicitud).

¡Ahora hagámoslo bonito!

Los estilos CSS

Omití las partes de la hoja de estilo que no están directamente relacionadas con las cargas. Puedes ver todo en styles.css .

activos/css/estilos.css

/*-------------------------
    Dropbox Element
--------------------------*/

#dropbox{
    background:url('../img/background_tile_3.jpg');

    border-radius:3px;
    position: relative;
    margin:80px auto 90px;
    min-height: 290px;
    overflow: hidden;
    padding-bottom: 40px;
    width: 990px;

    box-shadow:0 0 4px rgba(0,0,0,0.3) inset,0 -3px 2px rgba(0,0,0,0.1);
}

#dropbox .message{
    font-size: 11px;
    text-align: center;
    padding-top:160px;
    display: block;
}

#dropbox .message i{
    color:#ccc;
    font-size:10px;
}

#dropbox:before{
    border-radius:3px 3px 0 0;
}

/*-------------------------
    Image Previews
--------------------------*/

#dropbox .preview{
    width:245px;
    height: 215px;
    float:left;
    margin: 55px 0 0 60px;
    position: relative;
    text-align: center;
}

#dropbox .preview img{
    max-width: 240px;
    max-height:180px;
    border:3px solid #fff;
    display: block;

    box-shadow:0 0 2px #000;
}

#dropbox .imageHolder{
    display: inline-block;
    position:relative;
}

#dropbox .uploaded{
    position: absolute;
    top:0;
    left:0;
    height:100%;
    width:100%;
    background: url('../img/done.png') no-repeat center center rgba(255,255,255,0.5);
    display: none;
}

#dropbox .preview.done .uploaded{
    display: block;
}

/*-------------------------
    Progress Bars
--------------------------*/

#dropbox .progressHolder{
    position: absolute;
    background-color:#252f38;
    height:12px;
    width:100%;
    left:0;
    bottom: 0;

    box-shadow:0 0 2px #000;
}

#dropbox .progress{
    background-color:#2586d0;
    position: absolute;
    height:100%;
    left:0;
    width:0;

    box-shadow: 0 0 1px rgba(255, 255, 255, 0.4) inset;

    -moz-transition:0.25s;
    -webkit-transition:0.25s;
    -o-transition:0.25s;
    transition:0.25s;
}

#dropbox .preview.done .progress{
    width:100% !important;
}

El .progreso div está posicionado absolutamente. Cambiar su ancho (en porcentaje) lo convierte en un indicador de progreso natural. Agregue una transición de 0.25 y tendrá incrementos animados que serían un poco complicados de hacer solo con jQuery.

¡Con esto, nuestro Centro de carga de HTML5 está completo!

¡Hemos terminado!

Puede usar esto como punto de partida para un servicio de carga de archivos, una galería HTML5, un administrador de archivos o el nuevo panel de administración de su aplicación. Agrega tus pensamientos o sugerencias en los comentarios.