Dropzone múltiple subida de archivos con Bootstrap y PHP

En este tutorial vamos explicar en detalle cómo agregar a un formulario la posibilidad de subir múltiples archivos al servidor utilizando la biblioteca Dropzonejs, Bootstrap 4, PHP y jQuery Sortable.

La subida masiva de archivos al servidor es una tarea pendiente que hace mucho tiempo queríamos  incluir en este blog pero, es difícil encontrar el plugin perfecto para incorporar la subida masiva de archivos y de esta forma ahorrarte mucho trabajo.

La múltiple subida de archivos puede resultar muy útil en los casos específicos cuando necesitas que el usuario pueda subir varios archivos a la vez al servidor sin tener que seleccionar los archivos de uno en uno.

Dropzonejs es una biblioteca de código abierto que proporciona la carga de archivos arrastrando y soltando con vista previa de imágenes. Es muy liviano ya que no depende de ninguna otra librería (como jQuery) y es altamente personalizable.

Dropzonejs quizá es uno de las librerías más interesantes y gratuitas que hay para implementar la múltiple subida de archivos del lado del cliente debido a que de cara al usuario es muy práctico y rápido. Esto indica que la usabilidad del mismo es muy alta y por tanto atractiva para incluir en nuestras páginas Webs.

Bien es cierto que la librería Dropzonejs nos va a ahorrar mucho trabajo pero, esto no deja de lado que tengas un trabajo para conseguir que esta biblioteca funcione con PHP y de esta forma permita la subida de archivos al servidor.

En este artículo vamos a explicar un ejemplo práctico que permite al usuario subir imágenes al servidor utilizando dropzonejs, Bootstrap 4, PHP y jQuery sortable.

En el ejemplo en funcionamiento las imágenes se suben directamente al servidor una vez arrestres y sueltes o las selecciones desde el ordenador pulsando en el botón verde “Añadir imágenes…”.

Dropzonejs puede ser descargado desde la página oficial.

Existe un tutorial de esta implementación utilizando Bootstrap en la que nos hemos basado para crear nuestro ejemplo. Ver Dropzone Bootstrap tutorial.

En la cabecera de la página o dentro de la etiqueta <head> agregamos los estilos y scripts necesarios para hacer funcionar nuestro ejemplo.

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="css/styles.css">
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="js/dropzone.js"></script>
<script src="js/jquery-ui.min.js"></script>

Estamos incluyendo los archivos necesarios para tener disponibles las posibilidades que ofrece Bootstrap, nuestra hoja de estilos personalizada, la librería jQuery y jQuery Ui para poder ordenar los elementos y finalmente la biblioteca dropzonejs.

En nuestra hoja de estilos styles.css tan solo agregamos algunos estilos que usamos para el ejemplo:

#previews {
  padding: 15px;
  padding-top: 0px;
  padding-bottom: 0px;
  margin-top: 15px;
  min-height: 220px;
  background-color: #fbfbfb;
}
 
.dropzone-here {
    text-align: center;
    padding-top: 60px;
    width: 100%;
    position: absolute;
    font-size: 18px;
    font-weight: bold;
    top: 50px;
}
 
#previews .file-row .delete {
    display: none;
}
 
#previews .file-row.dz-success .start,
#previews .file-row.dz-success .cancel {
    display: none;
}
 
#previews .file-row.dz-success .delete {
    display: block;
}
 
.dz-image-preview {
    border: 1px solid #d6d4d4;
    padding-top: 15px;
    padding-bottom: 15px;
    margin-bottom: 15px;
}
 
.preview {
    position: relative;
    background: #fff;
    border: 1px solid #dadada;
    text-align: center;
    display: table-cell;
    vertical-align: middle;
}
 
.preview img {
    cursor: pointer;
}
 
.progress {
    border: 1px solid #ccc;
    position: relative;
    display: block;
    height: 22px;
    padding: 0;
    min-width: 200px;
    margin:4px 0;
    background: #DEDEDE;
    background: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#e9e9e9));
    background: -moz-linear-gradient(top, #ccc, #e9e9e9);
    filter:  progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#e9e9e9');
    -moz-box-shadow:0 1px 0 #fff;
    -webkit-box-shadow:0 1px 0 #fff;
    box-shadow:0 1px 0 #fff;
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
}
 
.progress-bar {
    color: #ffffff;
    display: block;
    height: 20px;
    margin: 0;
    padding: 0;
    text-align:center;
    -moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);
    -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);
    box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);
    -moz-border-radius: 3px;
    -webkit-border-radius: 3px;
    border-radius: 3px;
    border: 1px solid #0078a5;
    background-color: #5C9ADE;
    background: -moz-linear-gradient(top, #00adee 10%, #0078a5 90%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0.1, #00adee), color-stop(0.9, #0078a5));
}

Dentro del <body> o cuerpo de la página, en el formulario puedes incluir el siguiente código:

<form action="index.php" method="post" enctype="multipart/form-data">
    <div class="fallback">
        <input name="file" type="file" multiple />
    </div>
    <div id="actions" class="row">
        <div class="col-lg-7">
            <!-- The fileinput-button span is used to style the file input field as button -->
            <span class="btn btn-success fileinput-button">
                <i class="glyphicon glyphicon-plus"></i>
                <span>Add files...</span>
            </span>
            <button type="submit" class="btn btn-primary start" style="display: none;">
                <i class="glyphicon glyphicon-upload"></i>
                <span>Start upload</span>
            </button>
            <button type="reset" class="btn btn-warning cancel" style="display: none;">
                <i class="glyphicon glyphicon-ban-circle"></i>
                <span>Cancel upload</span>
            </button>
        </div>
 
        <div class="col-lg-5">
            <!-- The global file processing state -->
            <span class="fileupload-process">
                <div id="total-progress" class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
                    <div class="progress-bar progress-bar-success" style="width:0%;" data-dz-uploadprogress></div>
                </div>
            </span>
        </div>
    </div>
 
    <div class="table table-striped files" id="previews">
        <div id="template" class="file-row row">
            <!-- This is used as the file preview template -->
            <div class="col-xs-12 col-lg-3">
                <span class="preview" style="width:160px;height:160px;">
                    <img data-dz-thumbnail />
                </span>
                <br/>
                <button class="btn btn-primary start" style="display:none;">
                    <i class="glyphicon glyphicon-upload"></i>
                    <span>Empezar</span>
                </button>
                <button data-dz-remove class="btn btn-warning cancel">
                    <i class="icon-ban-circle fa fa-ban-circle"></i> 
                    <span>Cancelar</span>
                </button>
                <button data-dz-remove class="btn btn-danger delete">
                    <i class="icon-trash fa fa-trash"></i> 
                    <span>Eliminar</span>
                </button>
            </div>
            <div class="col-xs-12 col-lg-9">
                <p class="name" data-dz-name></p>
                <p class="size" data-dz-size></p>
                <div>
                    <strong class="error text-danger" data-dz-errormessage></strong>
                </div>
                <div>
                    <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
                      <div class="progress-bar progress-bar-success" style="width:0%;" data-dz-uploadprogress></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
 
    <div class="dropzone-here">Drop files here to upload.</div>
</form>

Este código es el formulario que en el ejemplo tan solo permite subir imágenes directamente al servidor. Como puedes observar hay 3 partes bien diferenciadas. Tenemos el bloque de acciones donde en nuestro ejemplo tan solo nos interesa mostrar el botón para agregar imágenes. La plantilla dentro del contenedor con id=”previews” que se usa para mostrar la imagen previa de cada elemento. Y finalmente, justo debajo, la zona para arrastrar y soltar.

El código más interesante y que ejecuta la acción se puede situar en la cabecera justo después de la llamada a las librerías o justo antes de la etiqueta de cierre </body>.

<script>
// Get the template HTML and remove it from the doument
var previewNode = document.querySelector("#template");
previewNode.id = "";
var previewTemplate = previewNode.parentNode.innerHTML;
previewNode.parentNode.removeChild(previewNode);
 
var myDropzone = new Dropzone(document.body, {
    url: "upload.php",
    paramName: "file",
    acceptedFiles: 'image/*',
    maxFilesize: 2,
    maxFiles: 3,
    thumbnailWidth: 160,
    thumbnailHeight: 160,
    thumbnailMethod: 'contain',
    parallelUploads: 20,
    previewTemplate: previewTemplate,
    autoQueue: true,
    previewsContainer: "#previews",
    clickable: ".fileinput-button"
});
 
myDropzone.on("addedfile", function(file) {
    $('.dropzone-here').hide();
    // Hookup the start button
    file.previewElement.querySelector(".start").onclick = function() { myDropzone.enqueueFile(file); };
});
 
// Update the total progress bar
myDropzone.on("totaluploadprogress", function(progress) {
    document.querySelector("#total-progress .progress-bar").style.width = progress + "%";
});
 
myDropzone.on("sending", function(file) {
    // Show the total progress bar when upload starts
    document.querySelector("#total-progress").style.opacity = "1";
    // And disable the start button
    file.previewElement.querySelector(".start").setAttribute("disabled", "disabled");
});
 
// Hide the total progress bar when nothing's uploading anymore
myDropzone.on("queuecomplete", function(progress) {
    //document.querySelector("#total-progress").style.opacity = "0";
});
 
// Setup the buttons for all transfers
// The "add files" button doesn't need to be setup because the config
// `clickable` has already been specified.
document.querySelector("#actions .start").onclick = function() {
    myDropzone.enqueueFiles(myDropzone.getFilesWithStatus(Dropzone.ADDED));
};
 
$('#previews').sortable({
    items:'.file-row',
    cursor: 'move',
    opacity: 0.5,
    containment: "parent",
    distance: 20,
    tolerance: 'pointer',
    update: function(e, ui){
        //actions when sorting
    }
});
</script>

En el código anterior estamos estableciendo el contenedor que hará de vista previa y hacemos una llamada a la clase Dropzone con algunos parámetros que explicamos a continuación:

  • url: especificamos la url o el archivo que subirá las imágenes al servidor con PHP.
  • paramName: especificamos el nombre del campo del formulario que contiene los archivos a subir.
  • aceptedFiles: indicamos que solo queremos que se puedan subir imágenes.
  • maxFilesize: Indicamos que solo queremos imágenes que tengan un tamaño inferior a 2 MB.
  • maxFiles: Indicamos el número máximo de imágenes que deseamos que se puedan subir. En el caso del ejemplo solo se pueden subir 3 imágenes de golpe.
  • thumbnailWidth: para especificar el ancho de la imagen previa. En el caso del ejemplo 160px.
  • thumbnailHeight: para especificar el alto de la imagen previa. En el caso del ejemplo 160px.
  • previewTemplate: indicamos la plantilla a usar para las imágenes previas.
  • autoQueue: establecido en true para procesar la cola de archivos automáticamente.
  • previewsContainter: para establecer el identificador del contenedor donde se agregarán las imágenes previas.
  • clickable: para indicar el elemento que abre la ventana para seleccionar archivos.

Este plugin tiene muchísimas más opciones. Puedes ver todas las opciones en la página oficial.

Adicionalmente también puedes manejar prácticamente todos los eventos.  En el caso del ejemplo estamos usando:

  • addedfile: Se ejecuta cuando un archivo ha sido añadido a la lista.
  • totaluploadprogress: Se invoca con el total de uploadProgress (0-100), totalBytes y totalBytesSent. Este evento se puede usar para mostrar el progreso general de carga de todos los archivos.
  • sending: Se llama justo antes de que se envíe cada archivo. Obtiene el objeto xhr y los objetos formData como segundo y tercer parámetro, por lo que puede modificarlos (por ejemplo, para agregar un token CSRF) o agregar datos adicionales.
  • queuecomplete: Se ejecuta cuando todos los archivos en la cola terminan de cargarse.

Puedes ver todos los eventos que se pueden manejar en Event list.

El archivo upload.php que se encarga de subir las imágenes al servidor contiene el siguiente código:

<?php
if (($_FILES["file"]["type"] == "image/pjpeg")
    || ($_FILES["file"]["type"] == "image/jpeg")
    || ($_FILES["file"]["type"] == "image/png")
    || ($_FILES["file"]["type"] == "image/gif")) {
    if (move_uploaded_file($_FILES["file"]["tmp_name"], "images/".$_FILES['file']['name'])) {
        echo 'si';
    } else {
        echo 'no';
    }
}

Donde tan solo comprobamos que los archivos que se están intentando subir sean imágenes tipo pjpeg, jpeg, png o gif y con la función move_uploaded_file() movemos el archivo temporal subido a la carpeta deseada.

Además de la subida de archivos, en el ejemplo en funcionamiento estamos ofreciendo la posibilidad de poder ordenar los elementos utilizando jQuery Sortable. Lo estamos consiguiendo usando la función sortable que trae la librería jQuery UI.

Conclusión

En los más de 10 años que llevo trabajando en esto de la creación de páginas Webs siempre he estado buscando y usando plugins para la subida múltiple de archivos pero, o son muy simples y no acaban de funcionar o añaden demasiados archivos que acaban confundiéndote. Dropzone es muy liviano en este sentido ya que tan solo es necesario incorporar 2 archivos para implementar la subida de archivos que te facilita la página oficial ahorrándote mucho trabajo. Además tiene muchas opciones y eventos que lo hace muy personalizable. Aún así, no te vas a librar para dedicar unas horas a comprender el funcionamiento y sobretodo para conseguir una implementación utilizando PHP.

Para una comprensión completa de cómo funciona Dropzone.js, lee detenidamente este tutorial de Dropzone y visita el sitio web www.dropzonejs.com para ver la documentación completa.

Básicamente, Dropzone hace todo el trabajo pesado por ti y expone una API de muy alto nivel que puedes usar para construir tu interfaz de usuario.

La característica principal de Dropzone son sus muchas opciones y sus eventos que puedes escuchar, para que puedas reaccionar a cada cambio y, de esta forma, poder personalizarlo al máximo.

Ver demo Descargar

Autor
Escrito por Jose Aguilar - Experto programador Prestashop y Wordpress.
Te ha servido? Valora esta entrada!
1 estrella2 estrellas3 estrellas4 estrellas5 estrellas (No hay votos)
Cargando…
Comparte en las redes sociales

Una respuesta a “Dropzone múltiple subida de archivos con Bootstrap y PHP”

  1. Antonio Aranzana dice:

    Buenos días Jose.

    Estoy intentando incluir al formulario de tu ejemplo dos campos más de tipo text, y luego enviar mediante “action” del formulario a un fichero php, por post, el resultado de todos los campos: los nombres de los ficheros de imagen y los de tipo text, pero me dan error precisamente los ficheros de imágenes. Sin embargo el proceso de subir imágenes lo hace correctamente.

    ¿Me puedes echar una mano e indicarme como podría hacerlo?

    Utilizo algo similar a esto como recepción de los campos:

    <?php

    echo $_POST['campo1'] . "”;
    echo $_POST[‘campo2’] . “”;

    foreach ($archivo as $_FILES[‘file’][‘tmp_name’]) {
    echo $archivo;
    }

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.