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.
Buenas tardes Jose.
Lo primero agradecerte este post ya que me ha servido de mucho. Lo único es que si intento hacerlo para subir otro tipo de archivos como por ejemplo videos, cambio el valor en ‘acceptedFiles’, tamaño máximo y demás, pero no me llega a subir el archivo.
Habría que añadir algo más?
Muchas gracias y un saludo.
Hola,
Si el video pesa mucho quizá tienes que verificar el tamaño máximo de subida de tu servidor. Debes revisar que sea permitido.
Saludos
Cómo se implementa más de 1 dropzone en una misma pagina para que tengan funcionalidad independiente?
Buena pregunta que dejo abierta por si alguien lo sabe. Quizá tan solo tienes que trabajar con clases en vez de con identificadores para los contenedores.
Obrigado pela ajuda, estava há uns 3 dias tentando enviar as imagens e os demais campos do formulário com DropZone do AdminLTE3…
Hola,
Me gustaría hacerte saber que nosotros podemos ayudarte si envías todos los detalles de tu problema a nuestro centro de soporte técnico ubicado en:
https://www.jamodules.com/es/soporte
Donde podemos estudiar el caso sin compromiso.
Saludos
Muy buenas Jose,
Gracias por este aporte…
Estoy siguiendo tu guía, exactamente igual, pero cuando lo ejecuto en lugar de cargar la imagen, me abre una hoja del navegador nueva y me muestra la imagen…en lugar de cargar el archivo
Tenes idea de que está sucediendo?
Muchas gracias, un saludo
Hola,
Si todavía necesitas ayuda, puedes enviar un mensaje al centro de soporte técnico situado en:
https://www.jamodules.com/prestashop-addons/es/soporte
indicando todos los detalles de tu petición para que lo podamos estudiar.
Saludos
Buenos días, estoy buscando un formulario que me permita subir datos de obituarios y fotografías por parte del usuario. ¿podrías ayudarme?
Hola,
Este post es una buena base para hacer tu solicitud. Si necesitas ayuda técnica, envía un ticket al departamento técnico y lo podemos estudiar.
https://www.jamodules.com/prestashop-addons/es/soporte
Saludos
Gracias por este aporte.
Tengo un formulario tipo Wizard y en uno de los pasos, tengo que subir uno o varios archivos. Por lo que he investigado y probado, Dropzone se puede instanciar en un ‘div’ y es así tal como lo tengo ahora mismo. Sin embargo, tras establecer la ruta en donde debe subir el archivo (jQuery), en pantalla no me muestra ningún error e incluso me da la vista previa del archivo que subí, pero se revisar la ruta que he establecido, el archivo nunca sube a esa ni a ninguna carpeta. ¿Podrías hacer este mismo ejemplo usando ‘div’ ? Muchas gracias
Hola,
Si nos puedes facilitar un enlace, seguramente podremos ayudarte mejor.
Saludos
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;
}