Hace un par de años, Dropbox introdujo una nueva característica, el Dropbox Chooser. Insertando esto en tu página web, proporcionas un botón a tus usuarios con el cual pueden adjuntar archivos desde su propio almacenamiento online.
En este tutorial vamos a utilizar esta característica para crear una aplicación simple que permita a los visitantes adjuntar fotografías desde de su cuenta de Dropbox, recortarlas con el plugin jCrop y bajarse el resultado. Adicionalmente, haremos uso de Twitter Bootstrap para mostrar las ventanas de diálogo y también de PHP en el backend para implementar el recorte de las fotos.
El HTML
Para empezar, aquí esta el documento HTML sobre el que trabajaremos. En el head, incluyo las hojas de estilo para bootstrap, el plugin jCrop y nuestro archivo CSS personalizado. En el footer, tenemos la librería Javascript de Dropbox, jQuery, Bootstrap, jCrop y el script.js, del cual hablaremos en la siguiente sección.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Dropbox File Uploader With Twitter Bootstrap | Tutorialzine </title>
<!-- The stylesheets -->
<link rel="stylesheet" href="assets/css/bootstrap.min.css" />
<link rel="stylesheet" href="assets/Jcrop/jquery.Jcrop.min.css" />
<link rel="stylesheet" href="assets/css/styles.css" />
</head>
<body>
<div id="main">
<input type="dropbox-chooser" name="selected-file" id="db-chooser"
data-link-type="direct" class="hide" />
<div id="content"></div>
<button class="btn btn-inverse hide" type="button"
id="cropButton">Crop Image</button>
<!-- Bootstrap Modal Dialogs -->
<div id="cropModal" class="modal hide fade" role="dialog"
aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">×</button>
<h4>Your cropped image</h4>
</div>
<div class="modal-body center"></div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div id="errorModal" class="modal hide fade" role="dialog" aria-hidden="true">
<div class="modal-header">
<h4></h4>
</div>
<div class="modal-footer">
<button class="btn btn-danger" data-dismiss="modal"
aria-hidden="true">OK</button>
</div>
</div>
<div id="progressModal" class="modal hide fade" role="dialog" aria-hidden="true">
<div class="progress progress-striped active">
<div class="bar" style="width: 100%;"></div>
</div>
</div>
</div>
<!-- JavaScript Includes -->
<script src="https://www.dropbox.com/static/api/1/dropbox.js"
id="dropboxjs" data-app-key="z4ylr6z1qlivll4"></script>
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/Jcrop/jquery.Jcrop.min.js"></script>
<script src="assets/js/script.js"></script>
</body>
</html>
En el body del documento, tenemos el markup que Twitter Bootstrap utilizará para mostrar las ventanas de diálogo. Contmos con tres modales con IDs únicos: #cropModal, #errorModal y #progressModal. Estos modales se mostrar con una llamada a una función de jQuery.
Familiarízate con el markup ya que es el estándar de Bootstrap. Los modales pueden tener headers opcionales, bodys y footers que pueden ser personalizados como queramos. Puedes poner el HTML que quieras en ellos. Especificando las clases, puedes modificar el comportamiento de las ventanas de diálogo. Los atributos aria-hidden están ahí para esconder el contenido de los screenreaders. Los botones tienes el atributo data-dismiss="modal", que le dice a bootstrap que deberían tener un detector de eventos sobre ellos y cerrar la ventana modal cuando se hace clic.
El jQuery
El trabajo del jQuery es escuchar los eventos del botón de Dropbox, inicializar jCrop con la imagen seleccionada y enviar mediante AJAX una respuesta a crop.php. El código luce tal que así:
assets/js/script.js
$(document).ready(function() { var cropButton = $('#cropButton'), dbChooser = $("#db-chooser"), errorModal = $('#errorModal'), errorMessage = errorModal.find('h4'), progressBar = $('#progressModal'), cropModal = $('#cropModal'), content = $('#content'); var coordinates, src, name, type, imgWidth, imgHeight, newWidth, newHeight, ratio, jcrop; dbChooser.on("DbxChooserSuccess", function(e) { // Here we will listen when a file is // chosen from dropbox, insert it into the page // and initialize the Jcrop plugin }); function showCropButton(c) { // This function will called when we want to show // the crop button. This is executed when we have // made a selection with Jcrop. } function showError(err){ // This function will display an error dialog } cropButton.click(function() { // This will send an AJAX requst to crop.php // with the dimensions of the crop area and // the URL of the image. }); });
El primer paso es crear una key para tu Dropbox Chooser. Cuando la tengas no olvides sustituirla por la mia del fichero index.html. Ahora que tenemos el botón funcionando, tenemos que crear función de escucha para el evento de éxito. El objeto del evento contendrá la URL de la imagen de Dropbox, junto a atributos como el tamaño del archivo, el nombre y las diferentes miniaturas:
dbChooser.on("DbxChooserSuccess", function(e) { // Assigning the original event object, so we have access //to the files property passed by Dropbox: e = e.originalEvent; name = e.files[0].name; src = e.files[0].link; type = name.split('.'); type = type[1] || ''; if (type.toLowerCase() != 'jpg') { showError('This file type is not supported! Choose a jpg.'); return false; } if (e.files[0].bytes > 1024*1024) { showError('Please choose an image smaller than 1MB!'); return false; } // If we have previously initialized jCrop: if(jcrop){ jcrop.destroy(); cropButton.hide(); } progressBar.modal('show'); var img = $(''); img.load(function() { imgWidth = img.width(); imgHeight = img.height(); if (imgWidth >= 575 || imgHeight >= 575) { // The image is too large, resize it to fit a 575x575 square! if (imgWidth > imgHeight) { // Wide ratio = imgWidth / 575; newWidth = 575; newHeight = imgHeight / ratio; } else { // Tall or square ratio = imgHeight / 575; newHeight = 575; newWidth = imgWidth / ratio; } } else { ratio = 1; newHeight = imgHeight; newWidth = imgWidth; } // Remove the old styles img.removeAttr('style'); // Set the new width and height img.width(newWidth).height(newHeight); // Initialize jCrop img.Jcrop({ onChange : showCropButton, onSelect : showCropButton }, function(){ // Save the jCrop instance locally jcrop = this; }); // Hide the progress bar progressBar.modal('hide'); }); // Show the image off screen, so we can // calculate the width and height properly img.css({ 'position' : 'absolute', 'top' : -100000, 'left' : -100000, 'visibility' : 'hidden', 'display' : 'block' }); // Set the SRC attribute and trigger the load // function when the image is downloaded content.html(img.attr('src', src)); });
Cuando el usuario haga la selección con jCrop, se llamará a showCropButton con un objeto que contiene las coordenadas, el ancho y el alto de la imagen. Dentro de esa función, podemos mostrar o esconder el elemento #cropButton dependiendo del tamaño del área seleccionada.
function showCropButton(c) { if (c.w == 0 || c.h == 0) { cropButton.hide(); } else { cropButton.show(); coordinates = c; } }
Por último, solo tenemos que escribir las funciones que muestran los errores y enviar la petición AJAX.
function showError(err){ errorMessage.text(err); errorModal.modal('show'); } cropButton.click(function() { coordinates.x = Math.round(coordinates.x * ratio); coordinates.y = Math.round(coordinates.y * ratio); coordinates.w = Math.round(coordinates.w * ratio); coordinates.h = Math.round(coordinates.h * ratio); progressBar.modal('show'); $.post('crop.php', { 'coordinates' : coordinates, 'src' : src }, function(r) { // Notice the "one" method - this // executes the callback only once progressBar.modal('hide').one('hidden', function() { cropModal.find('.modal-body').html(''); setTimeout(function() { cropModal.modal('show'); }, 500); }); }); });
Genial, ya tenemos un ejemplo funcional. Todo lo que tienes que hacer ahora es recortar la imagen. Para ello, contamos con un script de PHP muy cortito.
El PHP
Este script recibirá la petición POST de AJAX con la URL de la imagen original de Dropbox y las coordenadas del área seleccionada. A continuación, utiliza las funciones de la librería GD para cambiar el tamaño y almacenarlo en el alojamiento. Antes de salir, hago un echo del nombre temporal del recorte que será mostrado por jQuery.
crop.php
$filename_length = 10; $dir = 'tmp/'; // where to store the cropped images if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['src'])) { $src = $_POST['src']; $coordinates = $_POST['coordinates']; $url = parse_url($src); $info = get_headers($src, 1); // Only allow photos from dropbox if ($url['host'] == 'dl.dropbox.com') { if ($info['Content-Type'] == 'image/jpeg' && $info['Content-Length'] < 1024*1024) { // Cache the remote file locally $cache = $dir . md5($src); if(!file_exists($cache)){ file_put_contents($cache, file_get_contents($src)); } // Original image $img = imagecreatefromjpeg($cache); // New image with the width and height of the crop $dst = imagecreatetruecolor($coordinates['w'], $coordinates['h']); // Copy and resize it depending on the crop area imagecopyresampled($dst, $img, 0, 0, $coordinates['x'], $coordinates['y'], $coordinates['w'], $coordinates['h'], $coordinates['w'], $coordinates['h']); // Generate a temporary name and write the file to disk $name = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz". "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $filename_length); imagejpeg($dst, $dir . $name . '.jpg'); // Print it for jQuery echo $dir . $name . '.jpg'; } else { echo 1; } } else { echo 2; } }
Y con esto ya estaría completo nuestra aplicación para subir seleccionar imágenes de Dropbox, y recortarlas, con Twitter Bootstrap.