我正在尝试使用React DnD的List
和ListItem
来整合Material UI,并且在拖动时,整个列表会显示为拖动元素。我试图尽我所能地遵循这些例子,这就是我所拥有的
import React, { Component, PropTypes } from 'react';
import { Random } from 'meteor/random';
import LocalizedComponent from '/client/components/LocalizedComponent';
// MUI
import { List, ListItem } from 'material-ui/List';
// ---
import { DragDropContext, DragSource, DropTarget } from 'react-dnd';
import { findDOMNode } from 'react-dom';
import HTML5Backend from 'react-dnd-html5-backend';
const itemSource = {
beginDrag(props) {
return {
id: props.id,
index: props.index
};
},
};
const itemTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
const clientOffset = monitor.getClientOffset();
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
// Time to actually perform the action
props.onMoveItem(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex;
},
};
class SortableListComponent extends Component {
render() {
const { children, onMoveItem } = this.props;
let index = 0;
return (
<List>
{ React.Children.map(children, child => React.cloneElement(child, {
id: Random.id(),
index: index++,
onMoveItem: onMoveItem
})) }
</List>
);
}
}
SortableListComponent.propTypes = {
onMoveItem: PropTypes.func.isRequired
};
class SortableListItemComponent extends Component {
render() {
const {
id,
index,
isDragging,
connectDragSource,
connectDropTarget,
onMoveItem,
...other
} = this.props;
const opacity = 1; // isDragging ? 0 : 1;
return connectDragSource(connectDropTarget(
<div style={{ opacity }}>
<ListItem { ...other } disabled={ isDragging } />
</div>
));
}
}
SortableListItemComponent.propTypes = {
connectDragSource: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired,
id: PropTypes.any.isRequired,
index: PropTypes.number.isRequired,
isDragging: PropTypes.bool.isRequired,
onMoveItem: PropTypes.func.isRequired,
};
export const SortableList = DragDropContext(HTML5Backend)(SortableListComponent);
export const SortableListItem = DropTarget('SortableListItem', itemTarget, connect => ({
connectDropTarget: connect.dropTarget(),
}))(DragSource('SortableListItem', itemSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))(SortableListItemComponent));
基本上,我将List
替换为SortableList
而将ListItem
替换为SortableListItem
,这是我在拖动时看到的
我做错了什么?
例如,这是一个示例用法
<SortableList>
{ actions.map((action, index) => (
<SortableListItem id={ action.name } key={ index }
primaryText={ (index + 1) + '. ' + action.name }
onTouchTap={ this.handleActionEdit.bind(this, index) }
/>
)) }
</SortableList>
或
<SortableList>
{ actions.map((action, index) => (
<SortableListItem id={ action.name } key={ action.name }
primaryText={ (index + 1) + '. ' + action.name }
onTouchTap={ this.handleActionEdit.bind(this, index) }
/>
)) }
</SortableList>
等
它不会改变一件事。
答案 0 :(得分:1)
问题可能在于如何将id
和key
传递到您的子组件中。使用React dnd生成随机id
会导致错误。您应该做的是,在您的案例children
中,您应该将每个项目的唯一ID包含在您的数据中。这里有一个例子:https://github.com/react-dnd/react-dnd/blob/master/examples/04%20Sortable/Simple/Container.js#L17
{ React.Children.map(children, child => React.cloneElement(child, {
id: child.id,
key: child.id,
index: index++,
onMoveItem: onMoveItem
})) }
答案 1 :(得分:1)
我还尝试使用React-DND制作一个可排序的Material-UI <List>
并遇到同样的问题,但快照是我的整个页面,而不仅仅是列表!
仍在努力解决问题,但我确实注意到如果您使用常规<div>
代替<ListItem />
,问题就会消失。所以我担心这可能是一个Material-UI怪癖。最简单的解决方案可能只是为了避免<ListItem>
。
我确实想出了一个解决方法,如果找到更好的答案,我会编辑我的答案:
<强>用法强>
import SortableList from './SortableList';
<SortableList
items={[
{id: 1, text: 'one'},
{id: 2, text: 'two'},
{id: 3, text: 'three'},
{id: 4, text: 'four'},
]}
onChange={items => {
console.log(JSON.stringify(items));
// flux action, etc. goes here
}}
listItemProps={(item, index) => {
return {
primaryText: item.name,
hoverColor: 'green',
// pass desired props to <ListItem> here
};
}}
/>
<强> SortableList.jsx 强>
import React, { Component, PropTypes } from 'react';
import update from 'react-addons-update';
import List, { ListItem } from 'material-ui/List';
import { findDOMNode } from 'react-dom';
import { DragDropContext, DragSource, DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
const SortableListTypes = {
ITEM: 'ITEM',
};
class SortableListItem extends Component {
render() {
const { connectDragSource, connectDragPreview, connectDropTarget, ...rest } = this.props;
return (
<ListItem
id={this.props.id + '/' + this.props.index}
key={this.props.id + '/' + this.props.index}
ref={instance => {
if (!instance) {
return;
}
const node = findDOMNode(instance);
connectDragSource(node);
connectDropTarget(node);
const greatGrandChild = node.childNodes[0].childNodes[0].childNodes[0];
connectDragPreview(greatGrandChild);
}}
{...rest}
/>
);
}
}
SortableListItem = DropTarget(
SortableListTypes.ITEM,
{
hover: (props, monitor, component) => {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
props.moveItem(dragIndex, hoverIndex);
monitor.getItem().index = hoverIndex;
},
drop: props => props.dropHandler()
},
(connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
}
}
)(DragSource(
SortableListTypes.ITEM,
{
beginDrag: (props, monitor, component) => {
return {
id: props.id,
index: props.index,
}
}
},
(connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
}
}
)(SortableListItem));
class SortableList extends Component {
constructor(props) {
super(props);
this.state = {
items: _.clone(props.items),
};
this.moveItem = this.moveItem.bind(this);
this.dropHandler = this.dropHandler.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({
items: _.clone(nextProps.items),
});
}
moveItem(fromIndex, toIndex) {
const draggedItem = this.state.items[fromIndex];
this.setState(update(this.state, {
items: {
$splice: [
[fromIndex, 1],
[toIndex, 0, draggedItem],
],
},
}));
}
dropHandler() {
this.props.onChange(_.clone(this.state.items));
}
render() {
return (
<List>
{this.state.items.map((item, index) => (
<SortableListItem
key={item.id}
index={index}
moveItem={this.moveItem}
dropHandler={this.dropHandler}
{...this.props.listItemProps(item, index)}
/>
))}
</List>
)
}
}
export default DragDropContext(HTML5Backend)(SortableList);
<强> SortableListItem.jsx 强>
import React, { Component, PropTypes } from 'react';
import { ListItem } from 'material-ui/List';
import { findDOMNode } from 'react-dom';
import { DragSource, DropTarget } from 'react-dnd';
const SortableListTypes = {
ITEM: 'ITEM',
};
class SortableListItem extends Component {
render() {
const { connectDragSource, connectDragPreview, connectDropTarget, ...rest } = this.props;
return (
<ListItem
id={this.props.id + '/' + this.props.index}
key={this.props.id + '/' + this.props.index}
ref={instance => {
if (!instance) {
return;
}
const node = findDOMNode(instance);
connectDragSource(node);
connectDropTarget(node);
const greatGrandChild = node.childNodes[0].childNodes[0].childNodes[0];
connectDragPreview(greatGrandChild);
}}
{...rest}
/>
);
}
}
export default DropTarget(
SortableListTypes.ITEM,
{
hover: (props, monitor, component) => {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
props.moveItem(dragIndex, hoverIndex);
monitor.getItem().index = hoverIndex;
},
drop: props => props.dropHandler()
},
(connect, monitor) => {
return {
connectDropTarget: connect.dropTarget(),
}
}
)(DragSource(
SortableListTypes.ITEM,
{
beginDrag: (props, monitor, component) => {
return {
id: props.id,
index: props.index,
}
}
},
(connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
}
}
)(SortableListItem));
绝不是一个完美的解决方案,但希望它有所帮助。如果您的商品没有id
属性,则需要在key
内修改SortableListItem
的{{1}}道具。
答案 2 :(得分:0)
我也遇到了类似的问题,发现这在Chrome上无效。有关类似问题,请参阅https://github.com/react-dnd/react-dnd/issues/832。用户的以下评论帮助我找出了问题。
@kaiomagalhaes在我的情况下我得到了这个问题,因为行(单元格内容)的子元素之一实际上具有高于行高的高度,但是被可见性隐藏:隐藏在css中。因此dragSource具有行的宽度和隐藏控件的高度。
我希望你会发现这很有帮助。