Implementei o react-dnd , um mixin de arrastar e soltar HTML5 flexível para o React com controle DOM completo.
As bibliotecas existentes de arrastar e soltar não se adequavam ao meu caso de uso, então escrevi o meu próprio. É semelhante ao código que executamos há cerca de um ano no Stampsy.com, mas reescrito para aproveitar as vantagens do React e do Flux.
Principais requisitos que eu tinha:
- Emite zero DOM ou CSS próprio, deixando para os componentes consumidores;
- Imponha o mínimo de estrutura possível aos componentes de consumo;
- Use o recurso arrastar e soltar do HTML5 como back-end primário, mas torne possível adicionar back-ends diferentes no futuro;
- Como a API HTML5 original, enfatize o arrastar de dados e não apenas “visualizações arrastáveis”;
- Ocultar peculiaridades da API HTML5 do código de consumo;
- Diferentes componentes podem ser “fontes de arrastar” ou “destinos de soltar” para diferentes tipos de dados;
- Permita que um componente contenha várias fontes de arrastar e destinos de soltar quando necessário;
- Facilite a alteração da aparência dos alvos de soltar se dados compatíveis estiverem sendo arrastados ou pairados;
- Facilite o uso de imagens para arrastar miniaturas em vez de capturas de tela de elementos, evitando peculiaridades do navegador.
Se isso soa familiar para você, continue lendo.
Uso
Fonte de arrasto simples
Primeiro, declare os tipos de dados que podem ser arrastados.
Eles são usados para verificar a “compatibilidade” de fontes de arrastar e destinos de soltar:
// ItemTypes.js
module.exports = {
BLOCK: 'block',
IMAGE: 'image'
};
(Se você não tiver vários tipos de dados, esta biblioteca pode não ser para você.)
Então, vamos fazer um componente arrastável muito simples que, quando arrastado, 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)} />
);
}
);
Ao especificar configureDragDrop, informamos DragDropMixino comportamento de arrastar e soltar desse componente. Os componentes arrastáveis e soltáveis usam o mesmo mixin.
Por dentro configureDragDrop, precisamos chamar registerTypecada um de nossos custom ItemTypesque esse componente suporta. Por exemplo, pode haver várias representações de imagens em seu aplicativo e cada uma forneceria um dragSourcefor ItemTypes.IMAGE.
A dragSourceé apenas um objeto que especifica como a fonte de arrastar funciona. Você deve implementar beginDragpara retornar o item que representa os dados que você está arrastando e, opcionalmente, algumas opções que ajustam a IU de arrastar. Você pode, opcionalmente, implementar canDragpara proibir arrastar ou endDrag(didDrop)para executar alguma lógica quando o soltar ocorreu (ou não). E você pode compartilhar essa lógica entre os componentes, permitindo que um mixin compartilhado seja gerado dragSourcepara eles.
Finalmente, você deve usar {...this.dragSourceFor(itemType)}em alguns (um ou mais) elementos renderpara anexar manipuladores de arrasto. Isso significa que você pode ter várias “alças de arrastar” em um elemento e elas podem até corresponder a diferentes tipos de itens. (Se você não estiver familiarizado com os atributos de propagação JSX sintaxe de , verifique).
Alvo de queda simples
Digamos que queremos ImageBlockser um alvo de soltar para IMAGEs. É praticamente o mesmo, exceto que precisamos fornecer registerTypeuma dropTargetimplementação:
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>
);
}
);
Arraste a fonte + solte o destino em um componente
Digamos que agora desejamos que o usuário seja capaz de arrastar uma imagem para fora de ImageBlock. Precisamos apenas adicionar apropriado dragSourcea ele e alguns manipuladores:
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>
);
}
);
O que mais é possível?
Não abordei tudo, mas é possível usar esta API de mais algumas maneiras:
- Use
getDragState(type)egetDropState(type) para saber se arrastar está ativo e use-o para alternar classes ou atributos CSS;
- Especifique
dragPreviewpara Imageusar imagens como espaços reservados para arrastar (use ImagePreloaderMixinpara carregá-las);
- Digamos, queremos torná-lo
ImageBlocksreordenável. Só precisamos deles para implementar dropTargete dragSourcepara ItemTypes.BLOCK.
- Suponha que adicionemos outros tipos de blocos. Podemos reutilizar sua lógica de reordenamento, colocando-a em um mixin.
dropTargetFor(...types) permite especificar vários tipos de uma vez, de modo que uma zona de lançamento pode capturar muitos tipos diferentes.
- Quando você precisa de um controle mais refinado, a maioria dos métodos recebe o evento de arrastar que os causou como o último parâmetro.
Para obter a documentação atualizada e as instruções de instalação, acesse o reac-dnd repo no Github .