Реагировать на DnD, показывая весь список при перетаскивании

Я пытаюсь интегрировать React DnD с помощью List а также ListItem пользовательского интерфейса материала и при перетаскивании весь список отображается как перетаскиваемый элемент. Я старался в меру своего понимания следовать примерам, и вот что у меня есть

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>

и т.п.

Это ничего не меняет.

3 ответа

Я также столкнулся с подобной проблемой и обнаружил, что это не работает должным образом на Chrome. Пожалуйста, обратитесь к https://github.com/react-dnd/react-dnd/issues/832 для аналогичной проблемы. Следующий комментарий пользователя помог мне разобраться в проблеме.

@kaiomagalhaes В моем случае я получил эту проблему из-за того, что один из дочерних элементов строки (содержимое ячейки) на самом деле имел высоту, превышающую высоту строки, но был скрыт видимостью: скрыто в css. Таким образом, dragSource имеет ширину строк и высоту скрытого элемента управления.

Я надеюсь, что вы найдете это полезным.

Я также пытался сделать сортируемый Material-UI <List> с React-DND и имел ту же проблему, но снимок был всей моей страницы, а не только список!

Все еще работаю над ее решением, но я заметил, что проблема исчезнет, ​​если вы используете обычный <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 в SortableList.render,

Проблема может быть в том, как вы передаете id а также key в ваш дочерний компонент. Генерация случайного id с React dnd заканчивается ошибкой. Что вы должны сделать, это то, что вы должны иметь уникальные идентификаторы каждого отдельного элемента в ваших данных, в вашем случае children, Пример здесь: 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
})) }
Другие вопросы по тегам