Drag and Drop con HTML 5

Seguimos con las APIs de HTML 5 :D, intentando abarcar más terreno y disfrutando de todas las posibilidades que cada vez están más cerca de nosotros. Otra de las funcionalidades que aportan una experiencia de usuario diferencial es la posibilidad de arrastrar elementos de nuestra interfaz, con el fin de soltarlos en otra posición. JQuery UI hace tiempo que nos facilitaba esta opción a través de JQuery UI Draggable pero ¿Y si pudieramos conseguirlo sin necesidad de librerías JavaScript externas? Vamos a verlo:

Drag and Drop, también conocido como DnD, comienza en el momento en el que el usuario hace clic sobre un elemento y, sin liberar el botón del ratón, es capaz de moverlo a través de nuestra página hasta una zona preparada para su reposicionamiento. Dependiendo de nuestras necesidades existen diferentes expectativas de lo que queremos conseguir con este arrastrar y soltar. En este post os mostraré la posibilidad de intercambiar la posición de elementos HTML:

Si arrastramos cualquier de los conjuntos textbox + label podremos cambiarlo de posición por otro.

Eventos

Lo primero que debemos conocer son los distintos eventos a los cuales nos podemos suscribir para añadir el comportamiento que esperamos:

  • dragstart: Se trata del primer evento dentro del ciclo de vida de una operación DnD. El mismo comienza en el momento que hacemos clic en un elemento draggable.
  • drag: Podemos controlar a través de este evento todo el tiempo en el que el elemento esté capturado a través del ratón.
  • dragenter: Nos permite conocer el primer momento en el que un elemento se posiciona, aunque sea parcialmente, sobre otro.
  • dragover: Gracias a él podemos conocer cuándo estamos sobre un elemento draggable.
  • dragleave: Se lanza en el momento en el que un elemento que está siendo arrastrado deja de estar posicionado sobre otro elemento draggable.
  • drop: Ocurre en el momento que soltamos el elemento, liberando el botón del ratón, sobre una zona permitida.
  • dragend: Se ejecuta cuando la operación ha sido completada.

Para que los elementos tengan la capacidad de poder ser arrastrados, es necesario hacer uso del atributo draggable=»true», tal y como se muestra en el siguiente código:

<li draggable="true">
                <label for="txtA">Label A</label><input type="text" placeholder="Texbox A" />
            </li>

Por último, suscribo a todos los elementos con el atributo draggable a true a los eventos nombrados, a través de la función init:

HTML5.DnD = function() {
    //private members
    var dragSrcEl = null,
        draggables = null,
        init = function(selector) {
            draggables = document.querySelectorAll(selector);
            //Set listeners
            [].forEach.call(draggables, function(elem) {
                elem.addEventListener("dragstart", dragStart, false);
                elem.addEventListener("drag", drag, false);
                elem.addEventListener("dragenter", dragEnter, false);
                elem.addEventListener("dragover", dragOver, false);
                elem.addEventListener("dragleave", dragLeave, false);
                elem.addEventListener("drop", drop, false);
                elem.addEventListener("dragend", dragEnd, false);
            });
        },

El resto de funciones dentro de mi objeto me permite cambiar los estilos de los elementos en cada una de sus etapas y, además, hacer uso de dataTransfer.

dataTransfer

dataTransfer se trata de un objeto utilizado para almacenar y recuperar la información que está siendo arrastrada durante la operación de Drag and Drop. El mismo puede almacenar diferentes elementos y la forma de componerlos es indicando el MIME type seguido de la información. Este objeto tiene diferentes funciones y propiedades que pueden ser realmente útiles, como por ejemplo la propiedad effectAllowed, para determinar qué tipos de efectos están permitidos (como copy, move, link, all, etcétera) y por otro lado dropEffect el cual inicializará el efecto seleccionado siempre y cuando esté permitido por la propiedad anteriormente nombrada. Más información
Desde el punto de vista de los métodos, se utilizan principalmente setData, el cual recibe como primer parámetro el mime type y como segundo el valor. En este caso el mime type es la clave del elemento y podrá ser reemplazado por otro en el caso de ser necesario. En este ejemplo se utiliza para almacenar el contenido HTML del elemento sobre el cual comenzamos la operación de Drag, con el objetivo de poder volcar ese contenido en otra parte del DOM:

        dragStart = function(e) {
            e.dataTransfer.effectAllowed = 'move';
            //e.dataTransfer.setData('text/html', this.innerHTML);
            e.dataTransfer.setData('text', this.innerHTML);
            dragSrcEl = this;
            this.className = this.className.replace("target", "");
        },

Si nos fijamos en el contenido anterior, lo primero que se especifica es el tipo de efecto que se permite para el elemento que estamos arrastrando. Acto seguido almacenamos su contenido, recuperándolo de innerHTML, en el objeto dataTransfer. Si bien en la mayoría de los navegadores el tipo text/html está totalmente aceptado, para obtener la mayor compatibilidad con todos los navegadores hacemos uso de text. Por último se almacena el elemento en una variable y se le asigna una clase para indicar que el mismo está en movimiento.
Tanto drag, dragover, como dragEnter y dragLeave solamente se utilizan para añadir ciertos estilos sobre elementos terceros que afecten el drag del principal:

        drag = function(e) {
            this.className += ' moving';
        },
        dragOver = function(e) {
            if (e.preventDefault) {
                e.preventDefault();
            }
            e.dataTransfer.dropEffect = 'move';
            this.className += " over";
        },
        dragEnter = function() {
            this.className += " over";
        },
        dragLeave = function() {
            this.className = "";
        },

Por último, en el momento que hacemos drop sobre un tercero capturamos dicho evento para recuperar su código HTML e inyectarle el contenido del elemento autor con el que iniciamos el drag:

        drop = function(e) {
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            if (dragSrcEl != this) {
                dragSrcEl.innerHTML = this.innerHTML;
                //this.innerHTML = e.dataTransfer.getData('text/html');
                this.innerHTML = e.dataTransfer.getData('text');
            }
            return false;
        },

Una vez completada la tarea, utilizamos el evento final, dragEnd, para limpiar los estilos residuales que pudieran tener los elementos sobre los que hemos pasado y sobre el que se estaba moviendo.

        dragEnd = function() {[].forEach.call(draggables, function(elem) {
                elem.className = "";
            });
        };

Si nos damos cuenta, gracias a esta API de HTML 5 seremos capaces de controlar todo el ciclo de vida de esta funcionalidad. En este enlace puedes ver el ejemplo completo.

Espero que haya sido de utilidad.

¡Saludos!