Created
July 30, 2016 04:12
-
-
Save WuTheFWasThat/a42d9fa321b8c412b927d5fbd03a1b6a to your computer and use it in GitHub Desktop.
Sortable: Drag-to-reorder list
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I wrote this for work today after being unable to find a sortable list API for react to my liking | |
// Beware: It's only been tested for my use case | |
// Based largely off of https://github.com/danielstocks/react-sortable/blob/master/src/SortableComposition.js | |
export default class Sortable extends React.Component { | |
static get propTypes() { | |
return React.PropTypes.shape({ | |
items: React.PropTypes.array.isRequired, | |
updateState: React.PropTypes.func.isRequired, | |
// template should respect *inherits* | |
template: React.PropTypes.func.isRequired, | |
// sortId: React.PropTypes.number, | |
outline: React.PropTypes.string.isRequired, // row | column | |
}).isRequired; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
items: this.props.items.slice(), | |
draggingIndex : null, | |
elementEdge: 0, | |
updateEdge: true | |
}; | |
} | |
componentWillReceiveProps(props) { | |
this.setState({ | |
items: props.items.slice(), | |
}); | |
} | |
sortEnd() { | |
this.props.updateState({ | |
items: this.state.items | |
}); | |
this.setState({ | |
draggingIndex: null | |
}); | |
} | |
sortStart(e) { | |
const draggingIndex = e.currentTarget.dataset.id; | |
this.setState({ | |
draggingIndex: draggingIndex, | |
updateEdge: true | |
}); | |
if (e.dataTransfer !== undefined) { | |
e.dataTransfer.setData('text', e.target); | |
} | |
} | |
dragOver(e) { | |
e.preventDefault(); | |
let mouseBeyond; | |
let positionX, positionY; | |
let topOffset; | |
let items = this.state.items; | |
//underlying element //TODO: not working for touch | |
const overEl = e.currentTarget; | |
//index of underlying element in the set DOM elements | |
const indexDragged = Number(overEl.dataset.id); | |
const indexFrom = Number(this.state.draggingIndex); | |
const height = overEl.getBoundingClientRect().height; | |
if (e.type === 'dragover') { | |
positionX = e.clientX; | |
positionY = e.clientY; | |
topOffset = overEl.offsetTop - overEl.scrollTop + overEl.clientTop; | |
} else if (e.type === 'touchmove') { | |
positionX = e.touches[0].pageX; | |
positionY = e.touches[0].pageY; | |
let elementEdge = this.state.elementEdge; | |
if (this.state.updateEdge) { | |
elementEdge = e.currentTarget.getBoundingClientRect().top; | |
this.setState({ | |
updateEdge: false, | |
elementEdge: elementEdge | |
}); | |
} | |
e.currentTarget.style.top = (positionY - elementEdge) + 'px'; | |
topOffset = elementEdge; | |
} | |
function isMouseBeyond(mousePos, elementPos, elementSize) { | |
// break point is set to the middle line of element | |
const breakPoint = elementSize / 2; | |
const mouseOverlap = mousePos - elementPos; | |
return mouseOverlap > breakPoint; | |
} | |
if (this.props.outline === 'list') { | |
mouseBeyond = isMouseBeyond(positionY, topOffset, height); | |
} else if (this.props.outline === 'column') { | |
mouseBeyond = isMouseBeyond( | |
positionX, | |
overEl.getBoundingClientRect().left, | |
overEl.getBoundingClientRect().width | |
); | |
} | |
if (indexDragged !== indexFrom && mouseBeyond) { | |
const newItems = items.slice(); | |
const item = newItems.splice(indexFrom, 1)[0]; | |
newItems.splice(indexDragged, 0, item); | |
this.setState({ | |
items: newItems, | |
draggingIndex: indexDragged | |
}); | |
} | |
} | |
render() { | |
return ( | |
<ul> | |
{ | |
this.state.items.map((item, i) => { | |
return this.props.template({ | |
item: item, | |
dragging: (i === this.props.draggingIndex), | |
dragProps: { | |
'draggable' : true, | |
'onDragOver' : this.dragOver.bind(this), | |
'onDragStart' : this.sortStart.bind(this), | |
'onDragEnd' : this.sortEnd.bind(this), | |
'onTouchStart' : this.sortStart.bind(this), | |
'onTouchMove' : this.dragOver.bind(this), | |
'onTouchEnd' : this.sortEnd.bind(this), | |
'data-id' : i, | |
} | |
}); | |
}) | |
} | |
</ul> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
example usage (untested):