Реагировать на 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
})) }