<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react/dist/react-with-addons.min.js"></script>
<script src="https://unpkg.com/react-dom/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/react-virtualized@8.0.11/dist/umd/react-virtualized.js"></script>
<script src="https://unpkg.com/react-draggable@2.2.2/dist/react-draggable.min.js"></script>
<script src="https://unpkg.com/react-sortable-hoc@0.0.9/dist/umd/react-sortable-hoc.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/react-virtualized/styles.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="example"></div>
<script src="create-list.js"></script>
<script src="demo-app.js"></script>
<script src="script.js"></script>
</body>
</html>
// Render your list
ReactDOM.render(
<DemoApp />,
document.getElementById('example')
);
html, body, #example {
height: 100%;
}
* {
box-sizing: border-box;
}
.Row {
width: 100%;
height: 100%;
border-bottom: 1px solid black;
display: flex;
align-items: center;
padding: 0 0.5rem;
user-select: none;
cursor: move;
/* Draggable rows should be above non-draggable List */
z-index: 2;
}
.DraggableHeader {
position: relative;
padding-right: 1rem;
}
.DragHandle {
position: absolute;
right: 0;
top: 0;
z-index: 3;
cursor: col-resize;
color: #CCC;
padding: 0 0.5rem;
}
.DragHandle:hover,
.DragHandleActive {
color: #F6A;
}
const FIRST_NAMES = ['Peter', 'Tera', 'Kandy', 'Lonna', 'Kristie', 'Raul', 'Yukiko', 'Velvet', 'Donette', 'Loraine', 'Shyla', 'Marhta', 'Alene', 'Holley', 'Randell', 'Wilfred', 'Naida', 'Marine', 'Glinda', 'Lupe', 'Cordelia', 'Samara', 'Era', 'Malka', 'Edward', 'Clemencia', 'Loretta', 'Dana', 'Sanda', 'Soo', 'Apolonia', 'Liliana', 'Angel', 'Yvonne', 'Jonas', 'Tran', 'Buddy', 'Rosita', 'Rosalind', 'Renae', 'Deandrea', 'Kelvin', 'Guadalupe', 'Zelma', 'Laurel', 'Edyth', 'Marylin', 'Hsiu', 'Mark', 'Winford', 'Shizuko', 'Roslyn', 'Nilsa', 'Agustin', 'Earlie', 'Libby', 'Shanna', 'Brendan', 'Windy', 'Alice', 'Eden', 'Klara', 'Sherryl', 'Gala', 'Stephani', 'Twana', 'Mauro', 'Claudie', 'Adrienne', 'Pearlene', 'Rachelle', 'Louis', 'Susann', 'Mandi', 'Ola', 'Leif', 'Tisha', 'Hector', 'Benita', 'Kaley', 'Jim', 'Freda', 'Rafaela', 'Stasia', 'Evia', 'Rocky', 'Sonja', 'Dee', 'Damon', 'Alma', 'Linsey', 'Stefan', 'Giovanna', 'Myrtis', 'Marguerita', 'Junior', 'Allene', 'Margery', 'Nelly', 'Felix']
const LAST_NAMES = ['Brimer', 'Gaona', 'Liston', 'Wrede', 'Yard', 'Host', 'Binger', 'Natera', 'Ponton', 'Grim', 'Mable', 'Sing', 'Munden', 'Pagel', 'Tolman', 'Juneau', 'Madson', 'Amison', 'Palazzo', 'Island', 'Trotta', 'Berrier', 'Stepp', 'Spradlin', 'Haner', 'Feather', 'Rasnake', 'Hasbrouck', 'Nery', 'Reiling', 'Volk', 'Cacho', 'Couchman', 'Adam', 'Curci', 'Cesar', 'Panos', 'Ells', 'Tavares', 'Keehn', 'Bester', 'Lemmon', 'Mccullar', 'Mayers', 'Stcyr', 'Everette', 'Shevlin', 'Blackwelder', 'Ferguson', 'Noggle', 'Gilchrist', 'Cress', 'Lesniak', 'Grant', 'Jester', 'Daigle', 'Maloy', 'Wilken', 'Knittel', 'Curren', 'Lumsden', 'Morfin', 'Noack', 'Munsey', 'Frew', 'Anthony', 'Matlock', 'Meisner', 'Petrarca', 'Shurtleff', 'Piro', 'Cocco', 'Mcsweeney', 'Kempker', 'Moller', 'Mcgahan', 'Wurster', 'Pinkett', 'Jemison', 'Findley', 'Torkelson', 'Okafor', 'Markert', 'Carwile', 'Kahler', 'Almon', 'Beals', 'Fomby', 'Eatman', 'Grieve', 'Bollig', 'Cloninger', 'Blind', 'Remy', 'Dostal', 'Baranowski', 'Seto', 'Caves', 'Moudy', 'Sailer']
const list = []
for (var i = 0; i < 1000; i++) {
let firstName = FIRST_NAMES[Math.floor(Math.random() * FIRST_NAMES.length)]
let lastName = LAST_NAMES[Math.floor(Math.random() * LAST_NAMES.length)]
list.push({
age: 20 + Math.round(Math.random() * 40),
index: i,
name: `${firstName} ${lastName}`
})
}
// Read deps from UMD exports
const { Component } = React
const { findDOMNode } = ReactDOM
const Draggable = ReactDraggable
const { AutoSizer, defaultTableHeaderRenderer, defaultTableRowRenderer, Column, Table } = ReactVirtualized
const { SortableContainer, SortableElement, SortableHandle } = SortableHOC
// Connect react-virtualized and react-sortable-hoc
const SortableTable = SortableContainer(Table, {
withRef: true
})
const SortableRow = SortableElement(defaultTableRowRenderer)
const DragHandle = SortableHandle(({ children }) => (
<div>{children}</div>
))
const MIN_COLUMN_WIDTH = 50
class DemoApp extends Component {
constructor (props, context) {
super(props, context)
this._draggableHeaderRenderer = this._draggableHeaderRenderer.bind(this)
this._draggableColumn = this._draggableColumn.bind(this)
this._rowRenderer = this._rowRenderer.bind(this)
this._sortRow = this._sortRow.bind(this)
this.state = {
flexColums: [
'index',
'age',
'name'
],
flexColumProps: {
age: {
cellRenderer: this._draggableColumn,
dataKey: 'age',
label: 'Age',
width: 100
},
index: {
cellRenderer: this._draggableColumn,
dataKey: 'index',
label: '#',
width: 50
},
name: {
cellRenderer: this._draggableColumn,
dataKey: 'name',
label: 'Name',
width: 200
}
}
}
}
render () {
const { flexColums, flexColumProps } = this.state
return (
<AutoSizer>
{({ width, height }) => (
<SortableTable
getContainer={(wrappedInstance) => findDOMNode(wrappedInstance.Grid)}
headerHeight={40}
height={height}
onSortEnd={this._sortRow}
rowClassName='Row'
rowHeight={50}
rowGetter={({ index }) => list[index]}
rowCount={list.length}
rowRenderer={this._rowRenderer}
useDragHandle
width={width}
>
{flexColums.map((key) => (
<Column
className='DragHandleColumn'
key={key}
headerRenderer={this._draggableHeaderRenderer}
{...flexColumProps[key]}
/>
))}
</SortableTable>
)}
</AutoSizer>
)
}
_draggableHeaderRenderer (props) {
return (
<div className='DraggableHeader'>
{defaultTableHeaderRenderer(props)}
<Draggable
axis='x'
defaultClassName='DragHandle'
defaultClassNameDragging='DragHandleActive'
onStop={(event, data) => this._resizeColumn({
dataKey: props.dataKey,
deltaX: data.x
})}
position={{
x: 0,
y: 0
}}
zIndex={999}
>
<div>||</div>
</Draggable>
</div>
)
}
_draggableColumn ({ cellData, rowData, rowIndex }) {
if (this._isRowSortable(rowIndex)) {
return (
<DragHandle>{cellData}</DragHandle>
)
} else {
return cellData + 1
}
}
_isRowSortable (index) {
return index >= 0 // Header row should not be draggable
}
_resizeColumn ({ dataKey, deltaX }) {
const { flexColumProps } = this.state
// Once a column has been resized, lock its size
// This prevents an awkward user experience where their resized width is overridden by flex
const thisColumn = flexColumProps[dataKey]
thisColumn.flexGrow = 0
thisColumn.flexShrink = 0
thisColumn.width = Math.max(MIN_COLUMN_WIDTH, thisColumn.width + deltaX)
this.setState({
flexColumProps
})
}
_rowRenderer (props) {
const { index } = props
return this._isRowSortable(index)
? <SortableRow {...props} />
: defaultTableRowRenderer(props)
}
_sortRow ({ newIndex, oldIndex }) {
if (newIndex === oldIndex) {
return
}
const row = list[oldIndex]
list.splice(oldIndex, 1)
list.splice(newIndex, 0, row)
this.forceUpdate() // Re-render
}
}