Ho implementato React-dnd , un flessibile mixin HTML5 drag-and-drop per React con pieno controllo DOM.
Le librerie drag-and-drop esistenti non si adattavano al mio caso d'uso, quindi ho scritto le mie. È simile al codice che abbiamo eseguito per circa un anno su Stampsy.com, ma riscritto per sfruttare React e Flux.
Requisiti chiave che avevo:
- Emette zero DOM o CSS propri, lasciandolo ai componenti che consumano;
- Imporre la minor struttura possibile al consumo di componenti;
- Usa il drag and drop HTML5 come backend principale ma rendi possibile aggiungere backend diversi in futuro;
- Come l'API HTML5 originale, enfatizza il trascinamento dei dati e non solo le "visualizzazioni trascinabili";
- Nascondi le stranezze dell'API HTML5 dal codice che consuma;
- Componenti diversi possono essere "sorgenti di trascinamento" o "destinazioni di rilascio" per diversi tipi di dati;
- Consentire a un componente di contenere diverse sorgenti di trascinamento e obiettivi di rilascio quando necessario;
- Semplifica la modifica dell'aspetto delle destinazioni di rilascio se i dati compatibili vengono trascinati o spostati con il mouse;
- Semplifica l'uso delle immagini per trascinare le miniature invece degli screenshot degli elementi, aggirando le stranezze del browser.
Se ti sembrano familiari, continua a leggere.
Utilizzo
Fonte di trascinamento semplice
Innanzitutto, dichiara i tipi di dati che possono essere trascinati.
Questi vengono utilizzati per verificare la "compatibilità" delle sorgenti di trascinamento e delle destinazioni di rilascio:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Se non disponi di più tipi di dati, questa libreria potrebbe non essere adatta a te.)
Quindi, creiamo un componente trascinabile molto semplice che, quando trascinato, rappresenta 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)} />
);
}
);
Specificando configureDragDrop
, diciamoDragDropMixin
il comportamento del drag-drop di questo componente. Entrambi i componenti trascinabili e droppabili utilizzano lo stesso mixin.
All'interno configureDragDrop
, dobbiamo richiedere registerType
ciascuna delle nostre abitudini ItemTypes
supportate dal componente. Ad esempio, potrebbero esserci diverse rappresentazioni di immagini nella tua app e ognuna fornirebbe un dragSource
perItemTypes.IMAGE
.
A dragSource
è solo un oggetto che specifica come funziona la sorgente di trascinamento. È necessario implementare beginDrag
per restituire l'elemento che rappresenta i dati che stai trascinando e, facoltativamente, alcune opzioni che regolano l'interfaccia utente di trascinamento. È possibile opzionalmente implementare canDrag
per vietare il trascinamento o endDrag(didDrop)
per eseguire una logica quando il rilascio si è verificato (o meno). E puoi condividere questa logica tra i componenti lasciando che un mixin condiviso generi dragSource
per loro.
Infine, è necessario utilizzare {...this.dragSourceFor(itemType)}
su alcuni (uno o più) elementi render
per collegare i gestori di trascinamento. Ciò significa che puoi avere più "maniglie di trascinamento" in un elemento e possono anche corrispondere a diversi tipi di elemento. (Se non hai familiarità con JSX Spread Attributes sintassi degli , dai un'occhiata).
Target di rilascio semplice
Diciamo che vogliamo ImageBlock
essere un obiettivo di rilascio per IMAGE
s. È più o meno lo stesso, tranne per il fatto che dobbiamo dareregisterType
dropTarget
un'implementazione:
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>
);
}
);
Trascina sorgente + rilascio destinazione in un componente
Supponiamo che ora vogliamo che l'utente sia in grado di trascinare un'immagine fuori da ImageBlock
. Dobbiamo solo aggiungere appropriatodragSource
ad esso e alcuni gestori:
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>
);
}
);
Cos'altro è possibile?
Non ho coperto tutto ma è possibile utilizzare questa API in alcuni altri modi:
- Uso
getDragState(type)
egetDropState(type)
per sapere se il trascinamento è attivo e usalo per attivare / disattivare classi o attributi CSS;
- Specificare
dragPreview
per Image
utilizzare le immagini come segnaposto di trascinamento (utilizzareImagePreloaderMixin
per caricarle);
- Diciamo, vogliamo renderlo
ImageBlocks
riordinabile. Abbiamo solo bisogno di loro per implementare dropTarget
e dragSource
perItemTypes.BLOCK
.
- Supponiamo di aggiungere altri tipi di blocchi. Possiamo riutilizzare la loro logica di riordino inserendola in un mixin.
dropTargetFor(...types)
consente di specificare diversi tipi contemporaneamente, quindi una zona di rilascio può catturare molti tipi diversi.
- Quando è necessario un controllo più dettagliato, alla maggior parte dei metodi viene passato l'evento di trascinamento che li ha causati come ultimo parametro.
Per la documentazione e le istruzioni di installazione aggiornate, vai al repository react-dnd su Github .