Original article: React.js: implement the drag and drop feature without using external libraries

Entremos en detalle acerca de la implementación de la funcionalidad de arrastrar y soltar en React desde cero.

Veamos primero el resultado de lo que vamos a construir. Te lo muestro por medio de un .gif; espero que este se visualice correctamente, para ello he utilizado Camtasia con una licencia de uso personal.

1*Y11YSJEJ9A4JFGllOQSroQ
Ignora por favor la interfaz y el estilo actual!

Los puntos clave para aprender son:

  1. Haz que el elemento arrastrable añadiendo el atributo "draggable"
  2. Haz que un área sea desplegable implementando el evento “dragover”
  3. Captura los datos arrastrados al implementar  el evento “dragstart”.
  4. Captura la caída implementando el evento "drop".
  5. Implementar el evento "drag" que se dispara al arrastrar el elemento.
  6. Almacenar los datos intermedios en el objeto dataTransfer (transferenciaDeDatos)

Para los que aprenden visualmente, pueden ver el vídeo a continuación.

Paso 1 – crear la aplicación raíz para el demo

Todo el código para arrastrar y soltar irá en el componente AppDragDropDemo.js.

import React from 'react';
import ReactDOM from 'react-dom';import '.index.css';
import AppDragDropDemo from './AppDragDropDemo';
Importando las dependencias en el archivo raíz, para renderizar nuestro componente.
ReactDOM.render(
	<AppDragDropDemo />,     
    document.getElementById("root")
);
Renderizando el componente usando ReactDOM.render()

El punto de entrada para la aplicación AppDragDropDemo se verá como en el código de abajo.

import React, { Component } from 'react';
export default class AppDragDropDemo extends Component {  
    render () {    
    	return (
            <div className="container-drag">        
            	DRAG & DROP DEMO      
            </div>);  
     }
 }

Si corres la aplicación, se te mostrará esta increíble pantalla (broma intencional):

1*16qtjJ6Bh53hsY2z4oi2gw

Paso 2 – crear el objeto estado (state) para almacenar algunas tareas.

Vamos a crear algunas tareas para simular una simple aplicación. Lo que queremos hacer es "arrastrar y soltar" estas tareas en diferentes categorías como wip, complete, y demás.

export default class AppDragDropDemo extends Component {      
	state = {            
    	tasks: [
        	{
            	name:"Learn Angular",             
        		category:"wip",              
        		bgcolor: "yellow"
     		},
     		{
            	name:"React",              
     			category:"wip",              
                bgcolor:"pink"
             },                        
             {
             	name:"Vue",              
                category:"complete",              
                bgcolor:"skyblue"
              }                
         ]
    }
Código del componente AppDragDropDemo
  render () {    
  	return (      
    	<div className="container-drag">        
        	DRAG & DROP DEMO      
        </div>);  
    }
 }

Paso 3 — Organizar nuestros datos en categorías.

Vamos a implementar el siguiente código en el método render, para agrupar las tareas en sus respectivas categorías wip y complete. Siéntete libre de añadir más categorías y jugar con el código.

1*u7edSd4vxCBW_JMnA1qbYA

También puedes copiar y pegar este código desde el fragmento de abajo.

render() {          
	var tasks = { 
    	wip: [], 
        complete: []
    }
    
    this.state.tasks.forEach ((t) => {     
    	tasks[t.category].push(
        	<div    key={t.name}                           
            		onDragStart={(e)=>this.onDragStart(e, t.name)}                          		 draggable                          
                    className="draggable"                          
                    style={{backgroundColor: t.bgcolor}}>
                     
                     {t.name}                    
             </div>);          
     });
}

En el código de arriba, estamos recorriendo las tareas, creando un div para cada una y guardándolo en las respectivas categorías.

Así que  wip[] contiene todas las tareas de la categoría  wip y complete[] contiene todas las tareas completadas.

Paso 4 — Haz que el elemento de "tarea" se pueda arrastrar.

Agrega el atributo "draggable" al <div> o a cualquier elemento que desees hacer arrastrable. Usa el bloque de código anterior o el de abajo como referencia para el formato del texto.

Hacer-un-elemento-arrastrable-2

Paso 5 — crear un contenedor desplegable

Para crear un contenedor desplegable, o "droppable" implementa el evento dragover. Ahora, como queremos deshabilitar el comportamiento por defecto del evento dragover, debemos simplemente llamar event.preventDefault() desde el objeto event que recibimos del evento dragover.

También renderizaremos {tasks.wip} y {tasks.complete} en sus elementos div correspondientes.

Hacer-un-elemento-arrastrable-2-1
return (
	<div className="container-drag">     
    	<h2 className="header">DRAG & DROP DEMO</h2>                       
        <div className="wip"        
        	 onDragOver={(e)=>this.onDragOver(e)} 
             onDrop={(e)=>{this.onDrop(e, "wip")}}> 
             	<span className="task-header">WIP</span>    
                	{tasks.wip}                     
         </div>                     
         <div className="droppable"      
         	  onDragOver={(e)=>this.onDragOver(e)}  
              onDrop={(e)=>this.onDrop(e, "complete")}>
              	<span className="task-header">COMPLETED</span>  
                	{tasks.complete}                     
         </div>               
    </div>
);
Implementemos ahora para el evento onDragOver() su correspondiente función o event handler.
1*hNDl0tztfkDNddbIN4cVew

El resultado hasta ahora se verá como en la captura de pantalla que se muestra abajo.

1*fHaKQZ_1Iw0J1bFlIufbTw

Paso 6 — captura el estado del elemento arrastrado.

Vamos a modificar el código dónde creamos la categoría para cada tarea. Agregaremos una función para responder al evento (event handler) ondragstart y pasa el identificador, nombre (id/name) o cualquier información que necesites guardar mientras sucede el arrastrado/soltado.

Estoy usando name (nombre) cómo valor único para identificar una tarea. Siéntate libre de usar ID o cualquier clave única que gustes.

Hacer-un-elemento-arrastrable-3-2

Ahora implementemos la función para responder al evento  onDragStart.

Hacer-un-elemento-arrastrable-4

En el manejador onDragStart, tomamos el parámetro y lo almacenamos dentro del objeto dataTransfer. (*Nota: no te confundas con el nombre del parámetro).

*Nota para IE (Internet Explorer): este código podría no funcionar en IE, ya que la mejor práctica es darle formato como te muestro a continuación:

En lugar de:
ev.dataTransfer.setData("id", id)
USAR:
ev.dataTransfer.setData(“text/plain”,id)

En manejador anterior asegurará de que el elemento que se arrastra sea almacenado en el objeto "event" y está disponible para su uso cuando sea requerido. Este podría requerirse mientras soltamos en el contendor que será el objetivo.

Ahora si corres la aplicación y arrastras los elementos, podrás ver los siguientes logs en consola.

1*T9eejIeJ6gZJGWFoLxXxgg

Paso 7 — Manejar el evento drop (soltar).

Vamos al método render y añadamos el evento onDrop al div con el atributo className con valor droppable.

Hacer-un-elemento-arrastrable-5

En el código de arriba, agregamos la función para responder al evento drop y pasar la categoría requerida ( complete ) como argumento. Esto indica que estamos soltando el elemento desde el estado wip hacia el estado complete que representa una categoría. Siéntete bienvenido a cambiar los nombres cómo veas conveniente.

Ahora implementemos la función o handler para responder al evento onDrop.

Hacer-un-elemento-arrastrable-6

Acá puedes copiar para luego pegar el código.

onDrop = (ev, cat) => {         
	let id = ev.dataTransfer.getData("id");  
    let tasks = this.state.tasks.filter((task) => {      
    	if (task.name == id) {               
            task.category = cat;                 }                     
            return task;          
     });           
     this.setState({                 
     	...this.state,                 
        tasks          
     });    
}

En el handler para el evento onDrop , tomamos la tarea que se está arrastrando mediante el uso del método getData en el objeto dataTransfer del evento.

Luego creamos el arreglo con las nuevas tareas mediante el uso del método filter y cambiamos la categoría de la tarea que se está arrastrando.

setState() desatará el renderizado y las tareas serán desplegadas en las áreas correctas.

*Note de IE: para lograr que esto funcione en IE usa el método getData de abajo.

En lugar de

var id = ev.dataTransfer.getData(“id”)

usar

var id = ev.dataTransfer.getData(“text”)

Paso 8 — para implementar el soltado desde "complete" hasta "wip" agrega el handler para el evento onDrop

El handler o función de respuesta para el evento onDrop() permanece igual que antes.

Hacer-un-elemento-arrastrable-7

Finalmente corre el código y maravíllate de tu creación :) además claro diviértete programando.

Puedes tomar el código fuente de aquí.

Nota: para que este código funcione en los navegadores principales, cambia el tipo de datos de setData a string. Por ejemplo, para cambiar los datos usa ev.dataTransfer.setData(“text/plain”,id). Para leer datos usa var id = ev.dataTransfer.getData(“text”).

Ya que mi objetivo era demostrar la funcionalidad esencial de la característica de "arrastrar y soltar" el código no ha sido optimizado para contemplar factores cómo el diseño y las convenciones de nombres.