J'ai implémenté react-dnd , un mixin HTML5 flexible par glisser-déposer pour React avec un contrôle complet du DOM.
Les bibliothèques de glisser-déposer existantes ne correspondaient pas à mon cas d'utilisation, j'ai donc écrit la mienne. C'est similaire au code que nous utilisons depuis environ un an sur Stampsy.com, mais réécrit pour tirer parti de React et Flux.
Principales exigences que j'avais:
- Émettre zéro DOM ou CSS propre, en le laissant aux composants consommateurs;
- Imposer le moins de structure possible aux composants consommateurs;
- Utilisez le glisser-déposer HTML5 comme backend principal, mais cela permet d'ajouter différents backends à l'avenir;
- Comme l'API HTML5 d'origine, mettez l'accent sur le glissement des données et pas seulement sur les «vues déplaçables»;
- Masquer les bizarreries de l'API HTML5 du code consommateur;
- Différents composants peuvent être des «sources de glissement» ou des «cibles de dépôt» pour différents types de données;
- Permettre à un composant de contenir plusieurs sources de glissement et de déposer des cibles si nécessaire;
- Facilitez le changement d'apparence des cibles de dépôt si des données compatibles sont déplacées ou survolées;
- Facilitez l'utilisation d'images pour faire glisser des miniatures au lieu de captures d'écran d'éléments, évitant ainsi les bizarreries du navigateur.
Si cela vous semble familier, lisez la suite.
Usage
Source de glissement simple
Tout d'abord, déclarez les types de données qui peuvent être glissés.
Ceux-ci sont utilisés pour vérifier la «compatibilité» des sources de glissement et des cibles de dépôt:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Si vous ne disposez pas de plusieurs types de données, cette bibliothèque n'est peut-être pas faite pour vous.)
Ensuite, créons un composant déplaçable très simple qui, lorsqu'il est déplacé, représente 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)} />
);
}
);
En spécifiant configureDragDrop
, nous indiquons DragDropMixin
le comportement glisser-déposer de ce composant. Les composants traînables et déposables utilisent le même mixin.
À l'intérieur configureDragDrop
, nous devons appeler registerType
pour chacun de nos produits personnalisés pris en charge par ItemTypes
ce composant. Par exemple, il pourrait y avoir plusieurs représentations d'images dans votre application, et chacune fournirait un dragSource
pour ItemTypes.IMAGE
.
A dragSource
est juste un objet spécifiant le fonctionnement de la source de glissement. Vous devez implémenter beginDrag
pour renvoyer l'élément qui représente les données que vous faites glisser et, éventuellement, quelques options qui ajustent l'interface utilisateur de glissement. Vous pouvez éventuellement implémenter canDrag
pour interdire le glissement, ou endDrag(didDrop)
pour exécuter une logique lorsque le dépôt s'est (ou n'a pas) eu lieu. Et vous pouvez partager cette logique entre les composants en laissant un mixin partagé générer dragSource
pour eux.
Enfin, vous devez utiliser {...this.dragSourceFor(itemType)}
sur certains (un ou plusieurs) éléments dans render
pour attacher des gestionnaires de glissement. Cela signifie que vous pouvez avoir plusieurs «poignées de glissement» dans un élément, et elles peuvent même correspondre à différents types d'éléments. (Si vous n'êtes pas familier avec la syntaxe des attributs de propagation JSX , vérifiez-la).
Cible de dépôt simple
Disons que nous voulons ImageBlock
être une cible de chute pour IMAGE
s. C'est à peu près la même chose, sauf que nous devons donner registerType
une dropTarget
implémentation:
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>
);
}
);
Faites glisser la source + déposez la cible dans un composant
Disons que nous voulons maintenant que l'utilisateur puisse faire glisser une image hors de ImageBlock
. Nous devons juste y ajouter les éléments appropriés dragSource
et quelques gestionnaires:
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>
);
}
);
Quoi d'autre est possible?
Je n'ai pas tout couvert mais il est possible d'utiliser cette API de plusieurs manières supplémentaires:
- Utilisez
getDragState(type)
et getDropState(type)
pour savoir si le glissement est actif et utilisez-le pour basculer entre les classes CSS ou les attributs;
- Spécifiez
dragPreview
d' Image
utiliser les images comme espaces réservés de glissement (utilisez ImagePreloaderMixin
pour les charger);
- Dites, nous voulons rendre
ImageBlocks
réorganisable. Nous avons seulement besoin d'eux pour mettre en œuvre dropTarget
et dragSource
pour ItemTypes.BLOCK
.
- Supposons que nous ajoutions d'autres types de blocs. Nous pouvons réutiliser leur logique de réorganisation en la plaçant dans un mixin.
dropTargetFor(...types)
permet de spécifier plusieurs types à la fois, de sorte qu'une zone de dépôt peut capturer de nombreux types différents.
- Lorsque vous avez besoin d'un contrôle plus fin, la plupart des méthodes reçoivent l'événement de glissement qui les a provoquées comme dernier paramètre.
Pour obtenir une documentation et des instructions d'installation à jour, rendez -vous sur react-dnd repo sur Github .