在拖动时React DnD显示整个列表

时间:2017-02-14 19:26:47

标签: reactjs meteor material-ui react-dnd

我正在尝试使用React DnDListListItem来整合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,这是我在拖动时看到的

enter image description here

我做错了什么?

修改

例如,这是一个示例用法

<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>

它不会改变一件事。

3 个答案:

答案 0 :(得分:1)

问题可能在于如何将idkey传递到您的子组件中。使用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具有行的宽度和隐藏控件的高度。

我希望你会发现这很有帮助。