Sortable list example ===================== This example demonstrates how to implement a custom component on top of ``RepeatingFieldset`` which allows to sort items via drag'n'drop. Add a few items and try to drag them with the handle to sort items in a list. .. raw:: html
Implementation -------------- First we need to bring some names into scope: .. jsx:: var React = require('react') var ReactForms = require('react-forms') var Form = ReactForms.Form var Schema = ReactForms.schema.Schema var List = ReactForms.schema.List var Property = ReactForms.schema.Property var RepeatingFieldset = ReactForms.RepeatingFieldset var cloneWithProps = React.addons.cloneWithProps var classSet = React.addons.classSet var Item = RepeatingFieldset.Item A little utility which will be used in implementation: .. jsx:: function merge(a, b) { var k var r = {} for (k in a) { r[k] = a[k] } for (k in b) { r[k] = b[k] } return r } function setImagePositionFromEvent(node, e) { node.style.left = '' + (e.pageX + 10) + 'px' node.style.top = '' + (e.pageY + 10) + 'px' } /** * Swap values in an array */ function swap(array, a, b) { array = array.slice(0) var aVal = array[a] var bVal = array[b] array[a] = bVal array[b] = aVal return array } Now we implement a ``DraggableMixin`` which provides some basic drag'n'drop functionality: .. jsx:: var DraggableMixin = { componentWillMount: function() { this.dragging = null }, onMouseDown: function(e) { if (!((!this.onDragStart || this.onDragStart(e) !== false) && e.button === 0)) { return } window.addEventListener('mouseup', this.onMouseUp) window.addEventListener('mousemove', this.onMouseMove) this.dragging = this.getDraggingInfo ? this.getDraggingInfo.apply(null, arguments) : true }, onMouseMove: function(e) { if (this.dragging === null) { return } if (e.stopPropagation) { e.stopPropagation() } if (e.preventDefault) { e.preventDefault() } if (this.onDrag) { this.onDrag(e) } }, onMouseUp: function(e) { this.dragging = null window.removeEventListener('mousemove', this.onMouseMove) window.removeEventListener('mouseup', this.onMouseUp) if (this.onDragEnd) { this.onDragEnd(e) } } } .. jsx:: /** * Custom RepeatingFieldset item component which adds sortable handle and * callbacks onSortStart and onSortOver */ var SortableItem = React.createClass({ render: function() { return this.transferPropsTo(
drag to sort
{this.props.children}
) }, onSortStart: function(e) { var box = this.getDOMNode().getBoundingClientRect() this.props.onSortStart(e, { name: this.props.name, size: {height: box.height, width: box.width} }) }, onSortOver: function(e) { if (!this.props.sorting) { return } this.props.onSortOver(e, this.props.name) } }) .. jsx:: var SortableRepeatingFieldset = React.createClass({ mixins: [ ReactForms.FormElementMixin, // we need ReactForms.FormElementMixin cause we want to update the form value ReactForms.FormContextMixin, DraggableMixin // DraggableMixin provides basic dragging functionality ], getInitialState: function() { return {sorting: null} }, render: function() { var className = classSet({ SortableRepeatingFieldset: true, SortableActive: this.state.sorting !== null }) return this.transferPropsTo( ) }, /** * Render a single item in a fieldset * * It returns a placeholder for the currently sorted item if repeating * fieldset is in sortable state. */ renderItem: function(props, child) { var sorting = this.state.sorting if (sorting && sorting.name === props.name) { return
} else { props = merge(props, { sorting: sorting, onSortStart: this.onSortStart, onSortOver: this.onSortOver, }) return SortableItem(props, child) } }, /** * Called by DraggableMixin on drag end */ onDragEnd: function() { this.setState({sorting: null}) if (this._image) { document.body.removeChild(this._image) this._image = undefined } }, onDrag: function(e) { if (this._image) { setImagePositionFromEvent(this._image, e) } }, onSortStart: function(e, info) { // call into DraggableMixin to start dragging this.onMouseDown(e) var node = this._image = document.createElement('div') var val = this.value() var schema = val.schema.children var value = val.value[info.name] React.renderComponent(Form({schema: schema, value: value}), node) node.classList.add('SortableImage') node.style.position = 'absolute' node.style.width = '' + info.size.width + 'px' node.style.height = '' + info.size.height + 'px' setImagePositionFromEvent(node, e) document.body.appendChild(node) this.setState({sorting: info}) }, onSortOver: function(e, name) { if (!this.state.sorting) { return } // update sorting state and swap values this.setState({sorting: merge(this.state.sorting, {name: name})}) this.onValueUpdate(this.value().swap(name, this.state.sorting.name)) } }) .. jsx:: var Persons = ( ) React.renderComponent(
, document.getElementById('example') )