Skip to content

Instantly share code, notes, and snippets.

@jjjjjenkins
Last active September 27, 2016 08:24
Show Gist options
  • Save jjjjjenkins/c5149fc33a80c00ff281 to your computer and use it in GitHub Desktop.
Save jjjjjenkins/c5149fc33a80c00ff281 to your computer and use it in GitHub Desktop.
/************************************************************************
1) Have the Container which has the DragDropContext with HTML5Backend
*/
...
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd/modules/backends/HTML5';
...
class Container extends Component {
render() { ... }
}
export default DragDropContext(HTML5Backend)(Container);
/************************************************************************
2) Specify the DragSource
See: http://gaearon.github.io/react-dnd/docs-drag-source.html
- DragSource accepts 3 params: (type, spec, and collect).
- Details of (2a) type, (2b) spec, and (2c) collect below
*/
...
import { DragSource } from 'react-dnd';
...
class Card { ... }
...
// i.e. export default DragSource(type, spec, collect)(Knight);
export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
// ------------------------------------------------
// 2a) DragSource - type
// "Either a string, an ES6 symbol, or a function that
// returns either given the component's props".
// ItemTypes.js file:
export default {
CARD: 'card'
};
// Card.jsx file:
...
import ItemTypes from './ItemTypes';
...
// NOTE: ItemTypes.CARD
export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
// ------------------------------------------------
// 2b) DragSource - spec
// "A plain JavaScript object with a few allowed methods on it.
// It describes how the drag source reacts to the drag and drop events."
//
// - Required: beginDrag(props, monitor, component)
// - Optional: endDrag(props, monitor, component)
// - Optional: canDrag(props, monitor)
// - Optional: isDragging(props, monitor)
//
// -- Method params:
// - props: Your component's current props.
// - monitor: An instance of DragSourceMonitor.
// - component: When specified, it is the instance of your component.
// Card.js - defining some 'spec' drag & drop
...
const cardSource = {
beginDrag(props) {
return {
name: props.name
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
const dropResult = monitor.getDropResult();
if (dropResult) {
//window.alert(`You dropped ${item.name} into ${dropResult.name}!`);
window.alert('You dropped '+ item.name + ' into ' + dropResult.name);
}
}
};
...
// NOTE: cardSource
export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
// ------------------------------------------------
// 2c) DragSource - collect
// "The collecting function.
// It should return a plain object of the props to inject into your component.
// It receives two parameters: monitor and connect."
// - monitors, the connectors, and the collecting function explained more here:
// http://gaearon.github.io/react-dnd/docs-overview.html
//
// - connect: An instance of DragSourceConnector. (http://gaearon.github.io/react-dnd/docs-drag-source-connector.html)
// - DragSourceConnector methods:
// - dragSource() => (elementOrNode, options?): Returns a function that must be used inside the component to assign the drag source role to a node. By returning { connectDragSource: connect.dragSource() } from your collecting function, you can mark any React element as the draggable node. To do that, replace any element with this.props.connectDragSource(element) inside the render function.
// - dragPreview() => (elementOrNode, options?): (Optional) Returns a function that may be used inside the component to assign the drag preview role to a node. By returning { connectDragPreview: connect.dragPreview() } from your collecting function, you can mark any React element as the drag preview node. To do that, replace any element with this.props.connectDragPreview(element) inside the render function.
// - monitor: An instance of DragSourceMonitor. (http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html)
// - DragSourceMonitor is an object passed to a collecting function of the DragSource.
// - DragSourceMonitor methods: http://gaearon.github.io/react-dnd/docs-drag-source-monitor.html
// - canDrag(): Returns true if no drag operation is in progress, and the owner's canDrag() returns true or is not defined.
// - isDragging(): Returns true if there is a drag operation is in progress, and either the owner initiated the drag, or its isDragging() is defined and returns true.
// - getItemType(): Returns a string or an ES6 symbol identifying the type of the current dragged item. Returns null if no item is being dragged.
// - getItem(): Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its beginDrag() method. Returns null if no item is being dragged.
// - getDropResult(): Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their drop() methods. When a chain of drop() is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from drop() overrides the child drop result previously set by the child. Returns null if called outside endDrag().
// - didDrop() Returns true if some drop target has handled the drop event, false otherwise. Even if a target did not return a drop result, didDrop() returns true. Use it inside endDrag() to test whether any drop target has handled the drop. Returns false if called outside endDrag().
// - getInitialClientOffset(): Returns the { x, y } client offset of the pointer at the time when the current drag operation has started. Returns null if no item is being dragged.
// - getInitialSourceClientOffset(): Returns the { x, y } client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns null if no item is being dragged.
// - getClientOffset: Returns the last recorded { x, y } client offset of the pointer while a drag operation is in progress. Returns null if no item is being dragged.
// - getDifferenceFromInitialOffset(): Returns the { x, y } difference between the last recorded client offset of the pointer and the client offset when current the drag operation has started. Returns null if no item is being dragged.
// - getSourceClientOffset(): Returns the projected { x, y } client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns null if no item is being dragged.
// Card.js - collection funtion defined
// NOTE: in below example: 'isDragging' property is just a name that
// happens to have same name as the monitor method function isDragging().
// It could have been anything (e.g isMovingAbout : monitor.isDraggin())
...
// NOTE: { PropTypes }
import React, { Component, PropTypes } from 'react';
...
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
}
...
// NOTE: props are defined below in 'Card.propType = { ... }'
// connectDragSource & isDragging props are used within 'collect' function above.
// 'name' is and exmaple of an inate props of the component, it is unrelated directly to drag-n-drop.
class Card extends Component {
render() {
const { isDragging, connectDragSource } = this.props;
const { name } = this.props;
const opacity = isDragging ? 0.4 : 1;
const style = {
height: '4rem',
'min-width': '4rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
'background-color': '#26D',
'margin-right': '5px',
cursor: 'pointer',
opacity: opacity
};
// NOTE: be sure to use a dragSource fn (e.g. connectDragSource(...)) to encapsulate the render.
return (
connectDragSource(
<div style={style}>
{name}
</div>
)
);
}
}
...
Card.propType = {
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired
}
...
extend default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
// If you were to run the code as is with all the code above
// you should be able to drag the Card. It has no where to be dropped yet
// but clicking and dragging it should appear as if you were draggin it.
/************************************************************************
3) Specify the DropTarget
See: http://gaearon.github.io/react-dnd/docs-drop-target.html
- DropTarget accepts 3 params: types, spec, and collect.
- Details of (3a) types, (3b) spec, and (3c) collect below
*/
// E.g. CardBin.jsx
...
import { DropTarget } from 'react-dnd';
...
class CardBin {
/* ... */
}
...
//export default DropTarget(types, spec, collect)(CardBin);
export default DropTarget(ItemTypes.CARD, cardTarget, collect)(CardBin);
// ------------------------------------------------
// 3a) DropTarget - type
// "Either a string, an ES6 symbol, or a function that
// returns either given the component's props".
// ItemTypes.js file:
export default {
CARD: 'card'
};
// CardBin.jsx file:
...
import ItemTypes from './ItemTypes';
...
// NOTE: ItemTypes.CARD
export default DropTarget(ItemTypes.CARD, cardTarget, collect)(CardBin);
// ------------------------------------------------
// 3b) DropTarget - spec
// "A plain object implementing the drop target specification"
//
// - DropTarget Methods
// - drop(props, monitor, component): Optional. Called when a compatible item is dropped on the target. You may either return undefined, or a plain object. If you return an object, it is going to become the drop result and will be available to the drag source in its endDrag method as monitor.getDropResult().
// - hover(props, monitor, component): Optional. Called when an item is hovered over the component. You can check monitor.isOver({ shallow: true }) to test whether the hover happens over just the current target, or over a nested one. Unlike drop(), this method will be called even if canDrop() is defined and returns false. You can check monitor.canDrop() to test whether this is the case.
// - canDrop(props, monitor): Optional. Use it to specify whether the drop target is able to accept the item. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dropping based on some predicate over props or monitor.getItem(). Note: You may not call monitor.canDrop() inside this method.
//
// NOTE: The spec offers no methods to handle enter or leave events by purpose. Instead, return the result of monitor.isOver() call from your collecting function, so that you can use componentWillReceiveProps or componentDidUpdate React hooks to process the entering and the leaving events in your component. You may also check monitor.isOver({ shallow: true }) if don't want the nested drop targets to count.
//
// - Specification Method Parameters:
// - props: Your component's current props.
// - monitor: An instance of DropTargetMonitor. Use it to query the information about the current drag state, such as the currently dragged item and its type, the current and initial coordinates and offsets, whether it is over the current target, and whether it can be dropped.
// - component: When specified, it is the instance of your component. Use it to access the underlying DOM node for position or size measurements, or to call setState, and other component methods.
//
// CardBin.js - defining some 'spec' drag & drop
...
const cardTarget = {
// For fun always make this true, but it is here as an example.
canDrop(props) {
return true;
},
drop() {
return { name: 'Dustbin' };
}
};
...
// NOTE: cardSource
export default DropTarget(ItemTypes.CARD, cardTarget, collect)(Card);
// ------------------------------------------------
// 3c) DropTarget - collect
//
// "[We need to now] connect the React DnD event handlers to some node in the component;
// [and] pass some knowledge about the dragging state to our component."
//
// "The collecting function.
// It should return a plain object of the props to inject into your component.
// It receives two parameters: monitor and connect."
// - monitors, the connectors, and the collecting function explained more here:
// http://gaearon.github.io/react-dnd/docs-overview.html
//
// - connect: An instance of DropTargetConnector. (http://gaearon.github.io/react-dnd/docs-drop-target-connector.html)
// - DropTargetConnector's method:
// - dropTarget() => (elementOrNode): Returns a function that must be used inside the component to assign the drop target role to a node. By returning { connectDropTarget: connect.dropTarget() } from your collecting function, you can mark any React element as the droppable node. To do that, replace any element with this.props.connectDropTarget(element) inside the render function.
//
// - monitor: An instance of DropTargetMonitor . (http://gaearon.github.io/react-dnd/docs-drop-target-monitor.html)
// - DropTargetMonitor is an object passed to a collecting function of the DropTarget. Its methods let you get information about the drag state of a specific drop target. The specific drop target bound to that monitor is called the monitor's owner below.
// - DropTargetMonitor methods:
// - canDrop(): Returns true if no drag operation is in progress, and the owner's canDrop() returns true or is not defined.
// - isOver(options): Returns true if there is a drag operation is in progress, and the pointer is currently hovering over the owner. You may optionally pass { shallow: true } to strictly check whether only the owner is being hovered, as opposed to a nested target.
// - getItemType(): Returns a string or an ES6 symbol identifying the type of the current dragged item. Returns null if no item is being dragged.
// - getItem(): Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its beginDrag() method. Returns null if no item is being dragged.
// - getDropResult(): Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their drop() methods. When a chain of drop() is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from drop() overrides the drop result previously set by the child. Returns null if called outside drop().
// - didDrop() Returns true if some drop target has handled the drop event, false otherwise. Even if a target did not return a drop result, didDrop() returns true. Use it inside drop() to test whether any nested drop target has already handled the drop. Returns false if called outside drop().
// - getInitialClientOffset(): Returns the { x, y } client offset of the pointer at the time when the current drag operation has started. Returns null if no item is being dragged.
// - getInitialSourceClientOffset(): Returns the { x, y } client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns null if no item is being dragged.
// - getClientOffset(): Returns the last recorded { x, y } client offset of the pointer while a drag operation is in progress. Returns null if no item is being dragged.
// - getDifferenceFromInitialOffset(): Returns the { x, y } difference between the last recorded client offset of the pointer and the client offset when current the drag operation has started. Returns null if no item is being dragged.
// - getSourceClientOffset(): Returns the projected { x, y } client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns null if no item is being dragged.
//
// CardBin.js - collection funtion defined
...
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
...
class CardBin extends Component {
render() {
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
let backgroundColor = '#222';
if (isActive) {
backgroundColor = 'darkgreen';
} else if (canDrop) {
backgroundColor = 'darkkhaki';
}
const style = {
height: '12rem',
width: '12rem',
marginRight: '1.5rem',
marginBottom: '1.5rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
'background-color': backgroundColor
};
return connectDropTarget(
<div style={style}>
{isActive ?
'Release the card' :
'Drag card here'
}
</div>
);
}
}
CardBin.propTypes = {
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired,
canDrop: PropTypes.bool.isRequired
}
..
export default DropTarget(ItemTypes.CARD, cardTarget, collect)(Card);
import React, { Component, PropTypes } from 'react';
import ItemTypes from './ItemTypes';
import { DragSource } from 'react-dnd';
const cardSource = {
beginDrag(props) {
return {
name: props.name
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
const dropResult = monitor.getDropResult();
if (dropResult) {
var msg = 'You dropped ' + item.name + ' into ' + dropResult.name;
// window.alert(`You dropped ${item.name} into ${dropResult.name}!`);
window.alert(msg);
}
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
}
class Card extends Component {
render() {
const { isDragging, connectDragSource } = this.props;
const { name } = this.props;
const opacity = isDragging ? 0.4 : 1;
const style = {
height: '4rem',
'min-width': '4rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
'background-color': '#26D',
'margin-right': '5px',
cursor: 'pointer',
opacity: opacity
};
// NOTE: be sure to use a dragSource fn (e.g. connectDragSource(...)) to encapsulate the render.
return (
connectDragSource(
<div style={style}>
{name}
</div>
)
);
}
}
Card.propType = {
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired
};
export default DragSource(ItemTypes.CARD, cardSource, collect)(Card);
import React, { PropTypes, Component } from 'react';
import { DropTarget } from 'react-dnd';
import ItemTypes from './ItemTypes';
const cardTarget = {
// For fun always make this true, but it is here as an example.
canDrop(props) {
return true;
},
drop() {
return { name: 'Dustbin' };
}
};
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
};
}
class CardBin extends Component {
render() {
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
let backgroundColor = '#222';
if (isActive) {
backgroundColor = 'darkgreen';
} else if (canDrop) {
backgroundColor = 'darkkhaki';
}
const style = {
height: '12rem',
width: '12rem',
marginRight: '1.5rem',
marginBottom: '1.5rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
'background-color': backgroundColor
};
return connectDropTarget(
<div style={style}>
{isActive ?
'Release the card' :
'Drag card here'
}
</div>
);
}
}
export default DropTarget(ItemTypes.CARD, cardTarget, collect)(CardBin);
import React, { Component } from 'react';
import update from 'react/lib/update';
import CardBin from './CardBin.jsx';
import Card from './Card.jsx';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd/modules/backends/HTML5';
const style = {
width: 700
};
class Container extends Component {
render() {
return (
<div style={style}>
<CardBin/>
<Card name='Glass'/>
<Card name='Banana'/>
<Card name='Paper'/>
</div>
);
}
}
export default DragDropContext(HTML5Backend)(Container);
export default {
CARD: 'card'
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment