Drag and Drop subir archivos con bootstrap y php
DropzoneJS subir archivos usando Dropzone y PHP
Drag and Drop subir archivos con bootstrap y php. En este artículo vamos a realizar un ejemplo completo de cómo subir ficheros al servidor haciendo uso de varias librerías y herramientas.
Este ejemplo se podrá subir masivamente archivos al servidor. Sin embargo, es bueno encontrar el plugin ideal para incorporar la subida masiva de archivos y de esta forma optimizar el trabajo de subida.
Cómo subir archivos Drag and Drop mediante DropzoneJS
Solo necesitaremos dos archivos de la librería de Dropzone: dropzone.js y dropzone.css. Por lo tanto, si deseas descargarte estos dos ficheros de la página oficial de DropzoneJS y obtener el paquete actualizado.
- Descargar la librería DropzoneJS y colocar dentro de una carpeta js e insertar el fichero dropzone.js
- Una carpeta css para el fichero dropzone.css
- Crear una carpeta «images» para almacenar los archivos subidos por PHP
- Un fichero index.php para mostrar el formulario
- Fichero upload.php para validar la subida del fichero al servidor
- Images.php se encargará de visualizar los archivos subidos.
¿Qué es Dropzonejs?
Es una biblioteca de código abierto que proporciona la carga de archivos arrastrando y soltando (drag and drop). Sin embargo, una de las ventajas es que es muy liviano ya que no depende de ninguna otra librería (como jQuery), etc.
Ventajas de usar Dropzonejs
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.
La librería nos va a ahorrar mucho trabajo a la hora de subir archivos al servidor.
Descarga de la Biblioteca
Esta bibliotrca puede ser descargado desde su página oficial.
¿Qué herramientas necesito?
Lo detallaremos a continuación:
- Formulario con la posibilidad de subir múltiples archivos al servidor
- La biblioteca Dropzonejs.
- Framework Bootstrap 4.5.
- Lenguaje PHP
- jQuery Sortable.
Declaración de librerías
En la cabecera de la página principal o dentro de la etiqueta <head> declaramos los estilos y scripts necesarios para hacer funcionar el ejemplo completo.
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/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://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.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> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" />
DropzoneJS subir archivos: Estructura
A continuación, mostraremos los archivos completos del ejemplo
A) Formulario: index.php
Este fichero es la parte principal porque mostrara el formulario de subida de los ficheros al servidor
<!DOCTYPE html> <html lang="es" class="h-100"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="stylesheet" href="css/styles.css"> <script src="https://code.jquery.com/jquery-3.2.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.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> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" /> <title>Dropzone múltiple subida de archivos</title> </head> <body class="d-flex flex-column h-100"> <header> <!-- Fixed navbar --> <nav class="navbar navbar-expand-md navbar-dark bg-dark"> <div class="container"> <a class="navbar-brand" href="index.php">BaulPHP</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="images.php">Subidas <span class="sr-only">(current)</span></a> </li> </ul> <form class="form-inline mt-2 mt-md-0"> <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Busqueda</button> </form> </div> </div> </nav> </header> <!-- Begin page content --> <hr> <br> <main> <div class="container"> <div class="row"> <div class="col-md-12"> <h3>Dropzone múltiple subida de archivos</h3> <hr> </div> </div> <div class="row"> <div id="content" class="col-lg-12"> <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>Añadir imágenes...</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"><span><i class="fas fa-cloud-upload-alt fa-3x"></i></span><br>Arrastra los archivos aquí para subirlos.</div> </form> </div> </div> <div class="footer-content row"> <div class="col-lg-12"> <div class="card-body text-center mt-4 mb-4"> <h5 class="card-title">Ver imagénes guardadas</h5> <a href="images.php" class="btn btn-primary" style="text-align: center;"> <i class="fa fa-eye"></i> Ver Imágenes </a> </div> </div> </div> <footer> <hr> <div class="copyright"> © 2013 - <?=date("Y")?> <a href="https://baulcode.com" target="_blank">baulcode</a>. All rights reserved </div> <div class="footerlogo"><a href="https://baulcode.com" target="_blank"></a> </div> </footer> </div> </main> <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', 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> </body> </html>
B) Fichero: Styles.css
Aquí se almacenará nuestros estilos styles.css. Sin embargo, solo agregamos algunas clases para el presente ejemplo:
#previews { padding: 15px; padding-top: 0px; padding-bottom: 0px; margin-top: 15px; min-height: 220px; background-color: #fbfbfb; border-style: dashed; } .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)); }
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>.
Configuracion de la libreria
La clase Dropzone posee algunos parámetros que podemos configurar a nuestro antojo y lo explicamos a continuación:
- url
- paramName
- aceptedFiles
- maxFilesize
- maxFiles
- thumbnailWidth
- thumbnailHeight
- previewTemplate
- autoQueue
- previewsContainter
- clickable
Si desea más opciones de configuración puedes ver todas las opciones en la página oficial.
C) Fichero: upload.php
El archivo upload.php que se encarga de avlidar la extension del archivo y subir las imágenes al servidor, veamos su contenido.
<?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.
D) Archivo images.php
Muestra los ficheros subidos al servidor y nos da la posibilidad de poder eliminarlos.
<?php function showFiles($path) { $dir = opendir($path); $files = array(); while ($current = readdir($dir)){ if( $current != "." && $current != "..") { if(is_dir($path.$current)) { showFiles($path.$current.'/'); } else { $files[] = $current; } } } echo '<h6>'.$path.'</h6><hr>'; echo '<div class="row">'; for ($i=0; $i<count( $files ); $i++) { echo '<div class="col-xs-12 col-lg-2 text-center" style="margin-bottom:10px;">'; echo '<span class="preview" style="width:160px;height: 160px;margin-bottom:7px;"> '; echo '<img src="images/'.$files[$i].'" style="max-width:160px;max-height: 160px;" />'; echo '</span>'; echo '<a class="btn btn-danger" href="images.php?remove='.$files[$i].'"><i class="fas fa-trash-alt"></i> Eliminar</a>'; echo '</div>'; } echo '</div>'; } if (isset($_GET['remove'])) { if (file_exists('images/'.$_GET['remove'])) { unlink('images/'.$_GET['remove']); } } ?> <!DOCTYPE html> <html lang="es" class="h-100"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="stylesheet" href="css/styles.css"> <script src="https://code.jquery.com/jquery-3.2.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.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> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" /> <title>Dropzone múltiple subida de archivos</title> </head> <body class="d-flex flex-column h-100"> <header> <!-- Fixed navbar --> <nav class="navbar navbar-expand-md navbar-dark bg-dark"> <div class="container"> <a class="navbar-brand" href="index.php">BaulPHP</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="index.php">Portada <span class="sr-only">(current)</span></a> </li> </ul> <form class="form-inline mt-2 mt-md-0"> <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Busqueda</button> </form> </div> </div> </nav> </header> <!-- Begin page content --> <hr> <br> <main> <div class="container"> <div class="row"> <div class="col-md-12"> <hr> </div> </div> <div class="row"> <div class="col-md-12"> <div class="card"> <h5 class="card-header">Imagenes subidas</h5> <div class="card-body"> <div id="content" class="col-lg-12"> <?php showFiles('images/'); ?> </div> </div> </div> </div> </div> <footer> <hr> <div class="copyright"> © 2013 - <?=date("Y")?> <a href="https://baulcode.com" target="_blank">baulcode</a>. All rights reserved </div> <div class="footerlogo"><a href="https://baulcode.com" target="_blank"></a> </div> </footer> </div> </main> </body> </html>
Conclusión
Hemos aprendido a validar los archivos que se están intentando subir al servidor sean imágenes tipo:
- pjpeg
- jpeg
- png
- gif
Para poder subir los archivos nos apoyaremos de la función move_uploaded_file()
y movera a la carpeta images declarado.
Es muy liviano viendo de un aspexcto neutral ya que solo es necesario incorporar 2 archivos para implementar la subida de archivos al servidor.
Además tiene muchas opciones y eventos que lo hace muy personalizable.