Arrastrar y Soltar en la Web en HTML5

Arrastrar y soltar en la web en HTML5 un elemento desde un lugar y luego soltarlo en otro es algo que hacemos todo el tiempo en aplicaciones de escritorio, pero ni siquiera contemplamos el hacerlo en la web. Esto no es debido a que las aplicaciones web son diferentes sino porque desarrolladores nunca contaron con una tecnología estándar disponible para ofrecer esta herramienta.

Ahora, gracias a la API Drag and Drop, introducida por la especificación HTML5, finalmente tenemos la oportunidad de crear software para la web que se comportará exactamente como las aplicaciones de escritorio que usamos desde siempre.

Nuevos eventos

Uno de los más importantes aspectos de esta API es un conjunto de siete nuevos eventos que se introdujeron para informar sobre cada una de las situaciones involucradas en el proceso.

Algunos de estos eventos se disparan por la fuente (el elemento que se arrastra) y otros se disparan por el destino (el elemento en el cual el elemento arrastrado será soltado). Por ejemplo, cuando el usuario realiza una operación de arrastrar y soltar, el elemento origen (el que es arrastrado) dispara estos tres eventos:

  • dragstart. Este evento se dispara en el momento en el que el arrastre comienza. Los datos asociados con el elemento origen se definen en este momento en el sistema
  • drag. Este evento es similar al evento mousemove, excepto que se disparará durante una operación de arrastre por el elemento origen
  • dragend. Cuando la operación de arrastrar y soltar finaliza (sea la operación exitosa o no) este evento se dispara por el elemento origen.

Y estos son los eventos que se disparan por el elemento destino (donde el origen se suelta) durante la operación:

  • dragenter. Cuando el puntero del ratón entra dentro del área ocupada por los posibles elementos destino durante una operación de arrastrar y soltar, este evento se dispara
  • dragover. Este evento es similar al evento mousemove, excepto que se dispara durante una operación de arrastre por posibles elementos destino
  • drop. Cuando el elemento origen es soltado durante una operación de arrastrar y soltar, este evento se dispara por el elemento destino
  • dragleave. Este evento es disparado cuando el ratón sale del área ocupada por un elemento durante una operación de arrastrar y soltar. Este evento es generalmente usado junto con dragenter para mostrar una ayuda visual al usuario que le permita identificar el elemento destino (donde soltar).

Uso de herramientas

Antes de trabajar con esta nueva herramienta, existe un aspecto importante que debemos considerar. Los navegadores realizan acciones por defecto durante una operación de arrastrar y soltar.

Para obtener el resultado que queremos, necesitamos prevenir en algunas ocasiones este comportamiento por defecto y personalizar las reacciones del navegador. Para algunos eventos, como dragenter, dragover y drop, la prevención es necesaria, incluso cuando una acción personalizada ya se especificó.

Veamos cómo debemos proceder usando un ejemplo simple:

<!DOCTYPE html>

<html lang="es">

<head>

<title>Drag and Drop</title>

<link rel="stylesheet" href="dragdrop.css">

<script src="dragdrop.js"></script>

</head>

<body>

<section id=”cajasoltar”>

Arrastre y suelte la imagen aquí:

</section>

<section id="cajaimagenes">

<img id="imagen" src="http://www.minkbooks.com/content/

monster1.gif">

</section>

</body>

</html>

El documento HTML incluye un elemento < section > que se identifica como cajasoltar y una imagen. El elemento < section > se usará como elemento destino y la imagen será el elemento a arrastrar. También incluimos dos archivos para estilos CSS y el código javascript que se encargará de la operación.

#cajasoltar{

float: left;

width: 500px;

height: 300px;

margin: 10px;

border: 1px solid #999999;

}

#cajaimagenes{

float: left;

width: 320px;

margin: 10px;

border: 1px solid #999999;

}

#cajaimagenes > img{

float: left;

padding: 5px;

}

function iniciar(){

origen1=document.getElementById('imagen');

origen1.addEventListener('dragstart', arrastrado, false);

destino=document.getElementById('cajasoltar');

destino.addEventListener('dragenter', function(e){

e.preventDefault(); }, false);

destino.addEventListener('dragover', function(e){

e.preventDefault(); }, false);

destino.addEventListener('drop', soltado, false);

}

function arrastrado(e){

var codigo='<img src="'+origen1.getAttribute('src')+'">';

e.dataTransfer.setData('Text', codigo);

}

function soltado(e){

e.preventDefault();

destino.innerHTML=e.dataTransfer.getData('Text');

}

window.addEventListener('load', iniciar, false);

Atributos de JavaScript

Existen algunos atributos que podemos usar en los elementos HTML para configurar el proceso de una operación arrastrar y soltar, pero básicamente todo puede hacerse desde código JavaScript. En el Listado 8-3 presentamos tres funciones: la función iniciar() agrega las escuchas para los eventos necesarios en esta operación, y las funciones arrastrado() y soltado() generan y reciben la información que se transmite por este proceso.

Para que una operación arrastrar y soltar se realice normalmente, debemos preparar la información que se compartirá entre el elemento origen y el elemento destino. Para lograr esto, una escucha para el evento dragstart se agregó. La escucha llama a la función arrastrado() cuando el evento se dispara y la información a ser compartise se prepara en esta función usando setData().

La operación soltar no es normalmente permitida en la mayoría de los elementos de un documento por defecto. Por este motivo, para hacer esta operación disponible en nuestro elemento destino, debemos prevenir el comportamiento por defecto del navegador.

Esto se hizo agregando una escucha para los eventos dragenter y dragover y ejecutando el método preventDefault() cuando se disparan. Finalmente, una escucha para el evento drop se agrega para llamar a la función soltado() que recibirá y procesará los datos enviados por el elemento origen.

Cuando el elemento origen comienza a arrastrarse, el evento dragstart se dispara y la función arrastrado() consigue llamarse. En esta función obtenemos el valor del atributo src del elemento que está arrastrándose y declaramos los datos que se transferirán usando el método setData() del objeto dataTransfer.

Desde el otro lado, cuando un elemento es soltado dentro del elemento destino, el evento drop es disparado y la función soltado() es llamada. Esta función modifica el contenido del elemento destino con la información que se obtiene por el método getData().

Objetos, funciones y métodos

Los navegadores también realizan acciones por defecto cuando estos eventos son disparados (por ejemplo, abrir un enlace o actualizar la ventana para mostrar la imagen que fue soltada) por lo que debemos prevenir este comportamiento usando el método preventDefault(), como ya hicimos para otros eventos anteriormente.

  • dataTransfer

Este es el objeto que contendrá la información en una operación arrastrar y soltar. El objeto dataTransfer tiene varios métodos y propiedades asociados.

Ya utilizamos los métodos setData() y getData(). Junto con clearData(), estos son los métodos a cargo de la información que es transferida:

  • setData(tipo, dato)

Este método es usado para declarar los datos a ser enviados y su tipo. El método puede recibir tipos de datos regulares (como text/plain, text/html o text/uri-list), tipos de datos especiales (como URL o Text) o incluso tipos de datos personalizados. Un método setData() debe llamarse por cada tipo de datos que queremos enviar en la misma operación.

  • getData(tipo)

Dicho método retorna los datos enviados por el origen, pero solo del tipo especificado.

  • clearData()

Este método remueve los datos del tipo especificado.

En la función arrastrado() del Listado 8-3, creamos un pequeño código HTML que incluye el valor del atributo src del elemento que comenzó a ser arrastrado, grabamos este código en la variable codigo y luego enviamos esta variable como el dato a ser transferido usando el método setData(). Debido a que estamos enviando texto, declaramos el tipo de dato como Text.

El objeto dataTransfer tiene algunos métodos y propiedades más que a veces podrían resultar útil para nuestras aplicaciones:

  • setDragImage(elemento, x, y)

Algunos navegadores muestran una imagen en miniatura junto al puntero del ratón que representa al elemento que está siendo arrastrado. Este método es usado para personalizar esa imagen y seleccionar la posición la posición en la que será mostrada relativa al puntero del ratón. Esta posición es determinada por los atributos x e y.

  • types

Esta propiedad retorna un array conteniendo los tipos de datos que fueron declarados durante el evento dragstart (por el código o el navegador). Podemos grabar este array en una variable (lista=dataTransfer.types) y luego leerlo con un bucle for.

  • files

Esta propiedad retorna un array conteniendo información acerca de los archivos que están siendo arrastrados.

  • dropEffect

Dicha propiedad retorna el tipo de operación actualmente seleccionada. Los posibles valores son none, copy, link y move.

  • effectAllowed

Esta propiedad retorna los tipos de operaciones que están permitidas. Puede ser usada para cambiar las operaciones permitidas. Los posibles valores son: none, copy, copyLink, copyMove, link, linkMove, move, all y uninitialized.

  • dragenter, dragleave y dragend

Nada fue hecho aún con el evento dragenter. Solo cancelamos el comportamiento por defecto de los navegadores cuando este evento es disparado para prevenir efectos no deseados. Y tampoco aprovechamos los eventos dragleave y dragend. Estos son eventos importantes que nos permitirán ayudar al usuario cuando se encuentra arrastrando objetos por la pantalla.

function iniciar(){

origen1=document.getElementById('imagen');

origen1.addEventListener('dragstart', arrastrado, false);

origen1.addEventListener('dragend', finalizado, false);

soltar=document.getElementById('cajasoltar');

soltar.addEventListener('dragenter', entrando, false);

soltar.addEventListener('dragleave', saliendo, false);

soltar.addEventListener('dragover', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('drop', soltado, false);

}

function entrando(e){

e.preventDefault();

soltar.style.background='rgba(0,150,0,.2)';

}

function saliendo(e){

e.preventDefault();

soltar.style.background='#FFFFFF';

}

function finalizado(e){

elemento=e.target;

elemento.style.visibility='hidden';

}

function arrastrado(e){

var codigo='<img src="'+origen1.getAttribute('src')+'">';

e.dataTransfer.setData('Text', codigo);

}

function soltado(e){

e.preventDefault();

soltar.style.background='#FFFFFF';

soltar.innerHTML=e.dataTransfer.getData('Text');

}

window.addEventListener('load', iniciar, false);

Ejemplo del código de JavaScript

El código JavaScript del Listado 8-4 reemplaza al código del Listado 8-3. En este nuevo ejemplo, agregamos dos funciones para el elemento destino y una para el elemento origen. Las funciones entrando() y saliendo() cambiarán el color de fondo del elemento destino cada vez que el puntero del ratón esté arrastrando un objeto y entre o salga del área ocupada por este elemento. (Estas acciones disparan los eventos dragenter y dragleave).

Además, la función finalizado() se llamará por la escucha del evento dragend cuando el objeto a arrastrar se suelta. Note que este evento o la función misma no controlan si el proceso fue exitoso o no. Este control lo deberemos hacer nosotros en el código.

Gracias a los eventos y funciones agregadas, cada vez que el ratón arrastra un objeto y entra en el área del elemento destino, este elemento se volverá verde. A su vez, cuando el objeto se suelta, la imagen original se borra de la pantalla. Estos cambios visibles no están afectando el proceso de arrastrar y soltar, pero sí están ofreciendo una guía clara para el usuario durante la operación.

Para prevenir acciones por defecto del navegador, tenemos que usar el método preventDefault() en cada función, incluso cuando acciones personalizadas se declararon.

Seleccionando un origen válido

No existe ningún método específico para detectar si el elemento origen es válido o no. No podemos confiar en la información que se retorna por el método getData(). Esto porque, incluso cuando podemos recuperar solo los datos del tipo especificado, otras fuentes podrían originar el mismo tipo y proveer datos que no esperábamos.

Hay una propiedad del objeto dataTransfer llamada types que retorna un array con la lista de tipos configurados durante el evento dragstart, pero también es inútil para propósitos de validación.

Por esta razón, las técnicas para seleccionar y validar los datos transferidos en una operación arrastrar y soltar son variados. Por ende, estas pueden ser tan simples o complejos como necesitemos.

<!DOCTYPE html>

<html lang="es">

<head>

<title>Drag and Drop</title>

<link rel="stylesheet" href="dragdrop.css">

<script src="dragdrop.js"></script>

</head>

<body>

<section id="cajasoltar">

Arrastre y suelte las imágenes aquí:

</section>

<section id="cajaimagenes">

<img id="imagen1"

src="http://www.minkbooks.com/content/monster1.gif">

<img id="imagen2"

src="http://www.minkbooks.com/content/monster2.gif">

<img id="imagen3"

src="http://www.minkbooks.com/content/monster3.gif">

<img id="imagen4"

src="http://www.minkbooks.com/content/monster4.gif">

</section>

</body>

</html>

Usando la nueva plantilla HTML del Listado 8-5 vamos a filtrar los elementos a soltarse dentro del elemento destino controlando el atributo id de la imagen. El siguiente código JavaScript indicará cuál imagen puede soltarse y cuál no:

function iniciar(){

var imagenes=document.querySelectorAll('#cajaimagenes > img');

for(var i=0; i<imagenes.length; i++){

imagenes[i].addEventListener('dragstart', arrastrado, false);

}

soltar=document.getElementById('cajasoltar');

soltar.addEventListener('dragenter', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('dragover', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('drop', soltado, false);

}

Uso de métodos en las imágenes

No han cambiado muchas cosas en el Listado 8-6 de anteriores listados. En este código estamos usando el método querySelectorAll() para agregar una escucha para el evento dragstart a cada imagen dentro del elemento cajaimagenes, enviando el valor del atributo id con setData() cada vez que una imagen se arrastra.

Esto al mismo tiempo que se está controlando el valor de id en la función soltado() para evitar que el usuario arrastre y suelte la imagen con el atributo igual a ”imagen4”. (El mensaje “la imagen no es admitida” se muestra dentro del elemento destino cuando el usuario intenta arrastrar y soltar esta imagen en particular).

Este es, por supuesto, un filtro extremadamente sencillo. Puede usar el método querySelectorAll() en la función soltado() para controlar que la imagen recibida es una de las que se encuentran dentro del elemento cajaimagenes (por ejemplo). O, también, para usar propiedades del objeto dataTransfer (como types o files). Sin embargo, es siempre un proceso personalizado. En otras palabras, deberemos hacernos cargo nosotros mismos de realizar este control.

setDragImage()

Cambiar la imagen en miniatura que se muestra junto al puntero del ratón en una operación arrastrar y soltar puede parecer inútil, pero en ocasiones nos evitará dolores de cabeza. El método setDragImage() no solo nos permite cambiar la imagen sino también recibe dos atributos, x e y, para especificar la posición de esta imagen relativa al puntero.

Algunos navegadores generan una imagen en miniatura por defecto a partir del objeto original que se arrastra, pero su posición relativa al puntero del ratón se determina por la posición del puntero cuando el proceso comienza. El método setDragImage() nos permite declarar una posición específica que será la misma para cada operación arrastrar y soltar.

<!DOCTYPE html>

<html lang="es">

<head>

<title>Drag and Drop</title>

<link rel="stylesheet" href="dragdrop.css">

<script src="dragdrop.js"></script>

</head>

<body>

<section id="cajasoltar">

<canvas id="lienzo" width="500" height="300"></canvas>

</section>

<section id="cajaimagenes">

<img id="imagen1"

src="http://www.minkbooks.com/content/monster1.gif">

<img id="imagen2"

src="http://www.minkbooks.com/content/monster2.gif">

<img id="imagen3"

src="http://www.minkbooks.com/content/monster3.gif">

<img id="imagen4"

src="http://www.minkbooks.com/content/monster4.gif">

</section>

</body>

</html>

Con el nuevo documento HTML del Listado 8-7 vamos a estudiar la importancia del método setDragImage() usando un elemento < canvas > como el elemento destino.

function iniciar(){

var imagenes=document.querySelectorAll('#cajaimagenes > img');

for(var i=0; i<imagenes.length; i++){

imagenes[i].addEventListener('dragstart', arrastrado, false);

imagenes[i].addEventListener('dragend', finalizado, false);

}

soltar=document.getElementById('lienzo');

lienzo=soltar.getContext('2d');

soltar.addEventListener('dragenter', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('dragover', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('drop', soltado, false);

}

function finalizado(e){

elemento=e.target;

elemento.style.visibility='hidden';

}

function arrastrado(e){

elemento=e.target;

e.dataTransfer.setData('Text', elemento.getAttribute('id'));

e.dataTransfer.setDragImage(e.target, 0, 0);

}

function soltado(e){

e.preventDefault();

var id=e.dataTransfer.getData('Text');

var elemento=document.getElementById(id);

var posx=e.pageX-soltar.offsetLeft;

var posy=e.pageY-soltar.offsetTop;

lienzo.drawImage(elemento,posx,posy);

}

window.addEventListener('load', iniciar, false);

Funciones en el arrastrado de imagen

Probablemente, con este ejemplo, nos estemos aproximando a lo que sería una aplicación de la vida real. El código del Listado 8-8 controlará tres diferentes aspectos del proceso. Cuando la imagen se arrastra, la función arrastrado() se llama y en su interior una imagen miniatura se generan con el método setDragImage(). El código también crea el contexto para trabajar con el lienzo y dibuja la imagen soltada usando el método drawImage() estudiado en el capítulo anterior. Al final de todo, el proceso la imagen original se tapa usando la función finalizado().

Para la imagen miniatura personalizada usamos el mismo elemento que está arrastrándose, pero declaramos la posición relativa al puntero del ratón como 0,0. Gracias a esto ahora sabremos siempre cual es exactamente la ubicación de la imagen miniatura.

Aprovechamos este dato importante dentro de la función soltado(). Usando la misma técnica que se introdujo en el capítulo anterior, calculamos dónde el objeto se suelta dentro del lienzo y dibujamos la imagen en ese lugar preciso. Si prueba este ejemplo en navegadores que ya aceptan el método setDragImage() (por ejemplo, Firefox 4+), verá que la imagen se dibuja en el lienzo exactamente en la posición de la imagen miniatura que acompaña al puntero del ratón. Esto hace más sencillo para el usuario el elegir el lugar adecuado para soltarla.

Archivos

Posiblemente la característica más interesante de la API Drag and Drop es la habilidad de trabajar con archivos. La API no está solo disponible dentro del documento, sino también integrada con el sistema, permitiendo a los usuarios arrastrar elementos desde el navegador hacia otras aplicaciones y así recíprocamente. Y normalmente los elementos más requeridos desde aplicaciones externas son archivos.

Como vimos anteriormente, existe una propiedad especial en el objeto dataTransfer que retornará un array conteniendo la lista de archivos que están arrastrándose.

Podemos usar esta información para construir complejos códigos que trabajan con archivos o subirlos a un servidor.

<!DOCTYPE html>

<html lang="es">

<head>

<title>Drag and Drop</title>

<link rel="stylesheet" href="dragdrop.css">

<script src="dragdrop.js"></script>

</head>

<body>

<section id="cajasoltar">

Arrastre y suelte archivos en este espacio:

</section>

</body>

</html>

El documento HTML del Listado 8-9 genera simplemente una caja para soltar los archivos arrastrados. Los archivos se arrastrarán desde una aplicación externa (por ejemplo, el Explorador de Archivos de Windows). Los datos provenientes de los archivos se procesarán por el siguiente código:

function iniciar(){

soltar=document.getElementById('cajasoltar');

soltar.addEventListener('dragenter', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('dragover', function(e){

e.preventDefault(); }, false);

soltar.addEventListener('drop', soltado, false);

}

function soltado(e){

e.preventDefault();

var archivos=e.dataTransfer.files;

var lista='';

for(var f=0;f<archivos.length;f++){

lista+='Archivo: '+archivos[f].name+' '+archivos[f].size+'<br>';

}

soltar.innerHTML=lista;

}

window.addEventListener('load', iniciar, false);

La información que se retorna por la propiedad files del objeto dataTransfer puede grabarse en una variable y luego leerse por un bucle for. En el código del Listado 8-10, solo presentamos el nombre y el tamaño del archivo en el elemento destino usando las propiedades name y size. Para tomar ventaja de esta información y construir aplicaciones más elaboradas, necesitaremos acudir a otras APIs y técnicas de programación, como veremos más adelante en este libro.

https://aprendeinformaticas.com/

https://www.facebook.com/aprendeinformaticas/