Implementé react-dnd , un mixin flexible de arrastrar y soltar HTML5 para React con control DOM completo.
Las bibliotecas de arrastrar y soltar existentes no se ajustaban a mi caso de uso, así que escribí la mía propia. Es similar al código que hemos estado ejecutando durante aproximadamente un año en Stampsy.com, pero reescrito para aprovechar React y Flux.
Requisitos clave que tenía:
- Emite cero DOM o CSS propio, dejándolo en manos de los componentes consumidores;
- Imponer la menor estructura posible a los componentes consumidores;
- Use HTML5 arrastrar y soltar como backend principal pero haga posible agregar diferentes backends en el futuro;
- Al igual que la API HTML5 original, enfatice el arrastre de datos y no solo las "vistas que se pueden arrastrar";
- Ocultar las peculiaridades de la API HTML5 del código consumidor;
- Los diferentes componentes pueden ser "fuentes de arrastre" o "destinos de colocación" para diferentes tipos de datos;
- Permitir que un componente contenga varias fuentes de arrastre y soltar destinos cuando sea necesario;
- Facilite que los destinos para soltar cambien su apariencia si se arrastran o se desplazan datos compatibles
- Facilite el uso de imágenes para arrastrar miniaturas en lugar de capturas de pantalla de elementos, evitando las peculiaridades del navegador.
Si te suenan familiares, sigue leyendo.
Uso
Fuente de arrastre simple
Primero, declare los tipos de datos que se pueden arrastrar.
Se utilizan para comprobar la "compatibilidad" de las fuentes de arrastre y los destinos de colocación:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Si no tiene varios tipos de datos, es posible que esta biblioteca no sea para usted).
Luego, hagamos un componente arrastrable muy simple que, cuando se arrastra, representa IMAGE:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var Image = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
// Specify all supported types by calling registerType(type, { dragSource?, dropTarget? })
registerType(ItemTypes.IMAGE, {
// dragSource, when specified, is { beginDrag(), canDrag()?, endDrag(didDrop)? }
dragSource: {
// beginDrag should return { item, dragOrigin?, dragPreview?, dragEffect? }
beginDrag() {
return {
item: this.props.image
};
}
}
});
},
render() {
// {...this.dragSourceFor(ItemTypes.IMAGE)} will expand into
// { draggable: true, onDragStart: (handled by mixin), onDragEnd: (handled by mixin) }.
return (
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
);
}
);
Al especificar configureDragDrop, contamos DragDropMixinel comportamiento de arrastrar y soltar de este componente. Tanto los componentes que se pueden arrastrar como los que se pueden soltar utilizan la misma mezcla.
En el interior configureDragDrop, necesitamos llamar registerTypepara cada uno de nuestros ItemTypescomponentes personalizados que admite. Por ejemplo, puede haber varias representaciones de imágenes en su aplicación, y cada proporcionaría una dragSourcedeItemTypes.IMAGE .
A dragSourcees solo un objeto que especifica cómo funciona la fuente de arrastre. Debe implementar beginDragpara devolver el elemento que representa los datos que está arrastrando y, opcionalmente, algunas opciones que ajustan la IU de arrastre. Opcionalmente, puede implementar canDragpara prohibir el arrastre, o endDrag(didDrop)ejecutar alguna lógica cuando la caída ha ocurrido (o no). Y puede compartir esta lógica entre componentes dejando que se genere un mixin compartido dragSourcepara ellos.
Finalmente, debe usar {...this.dragSourceFor(itemType)}en algunos (uno o más) elementos renderpara adjuntar controladores de arrastre. Esto significa que puede tener varios "controles de arrastre" en un elemento, e incluso pueden corresponder a diferentes tipos de elementos. (Si no está familiarizado con los atributos de difusión de JSX sintaxis de , compruébelo).
Destino de caída simple
Digamos que queremos ImageBlockser un objetivo de caída para IMAGEs. Es más o menos lo mismo, excepto que necesitamos dar registerTypeuna dropTargetimplementación:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// dropTarget, when specified, is { acceptDrop(item)?, enter(item)?, over(item)?, leave(item)? }
dropTarget: {
acceptDrop(image) {
// Do something with image! for example,
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
// {...this.dropTargetFor(ItemTypes.IMAGE)} will expand into
// { onDragEnter: (handled by mixin), onDragOver: (handled by mixin), onDragLeave: (handled by mixin), onDrop: (handled by mixin) }.
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{this.props.image &&
<img src={this.props.image.url} />
}
</div>
);
}
);
Arrastrar origen + soltar destino en un componente
Supongamos que ahora queremos que el usuario pueda arrastrar una imagen fuera de ImageBlock. Solo necesitamos agregarle lo apropiado dragSourcey algunos controladores:
var { DragDropMixin } = require('react-dnd'),
ItemTypes = require('./ItemTypes');
var ImageBlock = React.createClass({
mixins: [DragDropMixin],
configureDragDrop(registerType) {
registerType(ItemTypes.IMAGE, {
// Add a drag source that only works when ImageBlock has an image:
dragSource: {
canDrag() {
return !!this.props.image;
},
beginDrag() {
return {
item: this.props.image
};
}
}
dropTarget: {
acceptDrop(image) {
DocumentActionCreators.setImage(this.props.blockId, image);
}
}
});
},
render() {
return (
<div {...this.dropTargetFor(ItemTypes.IMAGE)}>
{/* Add {...this.dragSourceFor} handlers to a nested node */}
{this.props.image &&
<img src={this.props.image.url}
{...this.dragSourceFor(ItemTypes.IMAGE)} />
}
</div>
);
}
);
¿Qué más es posible?
No he cubierto todo, pero es posible usar esta API de varias maneras más:
- Utilice
getDragState(type)ygetDropState(type) para saber si el arrastre está activo y úselo para alternar clases o atributos de CSS;
- Especifique si
dragPreviewdesea Imageusar imágenes como marcadores de posición de arrastre (use ImagePreloaderMixinpara cargarlas);
- Digamos, queremos hacer
ImageBlocksreordenables. Solo los necesitamos para implementar dropTargety dragSourcepara ItemTypes.BLOCK.
- Supongamos que agregamos otros tipos de bloques. Podemos reutilizar su lógica de reordenamiento colocándola en un mixin.
dropTargetFor(...types) permite especificar varios tipos a la vez, por lo que una zona de caída puede capturar muchos tipos diferentes.
- Cuando necesita un control más detallado, a la mayoría de los métodos se les pasa el evento de arrastre que los causó como último parámetro.
Para obtener documentación e instrucciones de instalación actualizadas, diríjase a react-dnd repo en Github .