Is it possible to capture the grid item's click and cancel the drag event? I want to open a modal window when a grid item is clicked, but I can't figure out how to implement this. I'm capturing the click with onClick, but stopPropagation and preventDefault don't prevent the mousedown event that starts the dragging process.
Having the same issue!
I opened a question on stackoverflow as well: http://stackoverflow.com/q/38543470/173630
Make the grid item static:
https://strml.github.io/react-grid-layout/examples/5-static-elements.html
If it's static then it can't be dragged. I want to keep the dragging behavior intact.
Did you tried onMouseDown instead on onClick?
This may help
<RGL ... >
<div> // If you add onMouseDown here react-draggable will override it. You will have to use nested element to capture clicks
<div onMouseDown={ e => e.stopPropagation() }>
...
</div>
<div>
</RGL>
But you can achieve same effect using state and static elements or draggableCancel selectors.
mouseDown is not the event I want to capture: I need to respond when the user completes the click. I'm trying to implement this with static elements now - it looks like I have to re-implement the dragging behavior, in mouseDown/mouseUp events, is that correct?
Hmm.. Can you describe the flow? I mean, when the dragging should be canceled?
React Grid passes onMouseDown, onMouseUp, onTouchStart, onTouchEnd, className and style props to each child of <ReactGridLayout>. My solution was to pass custom GridElement components as children of <ReactGridLayout> and just execute those events whenever I see fit, so onDragStart and onDragOver events.
Here's a snippet of how my GridElement looks:
onDragStart = (e) => {
e.preventDefault();
e.stopPropagation();
this.props.onMouseDown(e);
}
onDragEnd = (e) => {
e.preventDefault();
e.stopPropagation();
this.props.onMouseUp(e);
}
// ===== Render Methods ======
render() {
// React grid defines some important style, so it needs to be passed
const { children, style, className } = this.props;
const child = children[0];
const dragHandle = children[1]; // always a second element. First element is an array of real children;
return (
<div
draggable
className={className}
onDragStart={this.onDragStart} // this fixes rgl's onClick and onMouseDown
onMouseUp={this.onDragEnd} // use onMouseUp because onDragStart prevents default
style={style}
>
{child}
{dragHandle}
</div>
);
}
}
GridElement.propTypes = {
// React-grid-layout injected props
className: PropTypes.string,
onMouseDown: PropTypes.func,
onMouseUp: PropTypes.func,
onTouchStart: PropTypes.func,
onTouchEnd: PropTypes.func,
'data-grid': PropTypes.object,
};
GridElement.defaultProps = {
// When element is static, rgl doesn't pass these props
onMouseDown: () => {},
onMouseUp: () => {},
onTouchStart: () => {},
onTouchEnd: () => {},
};
Any update on that? No better solution since August?
If you would like a better solution, you are free to contribute one - remember that this is a collaborative project and work in progress.
Thanks for your quick reply, I've used @martynasj solution and it works nicely but have done a bit of hacking for the resize to not trigger a click. I was quite surprise that such a great component haven't that feature in. I'll give a try to backed this feature in.
I've just tried this solution on Firefox, unfortunately it does work properly...
@DigitalMarc I've worked around this issue by using a <button> inside the grid item and responding to the onClick event there. My code is here: https://github.com/ccnmtl/juxtapose/blob/master/src/TrackElement.jsx#L83
Cheers @nikolas. I went for the idiomatic hacky way but with the lowest friction to sort this out on both firefox and chrome as event handling behave differently.
I listen for onDrag (was not working with onDragStart) and on onDragStop, setting a flag. Once dragging is done I reset that flag with a bit of delay, otherwise onClick is triggered.
Simply put the flag is used to secure onClick.
Same logic is used for resize. Hope that can help:
import React from 'react';
import { Responsive, WidthProvider } from 'react-grid-layout';
import _ from 'lodash';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
class Exemple extends React.Component {
constructor(props) {
super(props)
this.isDragging = false;
this.isResizing = false;
}
static propTypes = {
onItemClick: React.PropTypes.func.isRequired
};
onItemClick = (e) => {
// idiomatic way to prevent a click when resizing
if (!this.isDragging && !this.isResizing)
this.props.onItemClick(e);
}
onDrag = (e) => {
this.isDragging = true;
}
onDragStop = (e) => {
// HACK: add some delay otherwise a click event is sent
setTimeout((obj) => { obj.isDragging = false }, 200, this)
}
onResizeStart = (e) => {
this.isResizing = true;
}
onResizeStop = (e) => {
// HACK: add some delay otherwise a click event is sent
setTimeout((obj) => { obj.isResizing = false }, 200, this)
}
createElement = (el) => {
return (
<div key={el.datagrid.i} data-grid={el.datagrid} onClick={this.onItemClick.bind(this, el.datagrid.i)} >
<div>....</div>
</div>
);
}
render() {
return (
<ResponsiveReactGridLayout
... // other props
onDrag={this.onDrag}
onDragStop={this.onDragStop}
onResizeStart={this.onResizeStart}
onResizeStop={this.onResizeStop}
>
{_.map(this.props.items, this.createElement)}
</ResponsiveReactGridLayout>
);
}
}
About what @RiiD wrote:
<RGL ... >
<div> // If you add onMouseDown here react-draggable will override it. You will have to use nested element to capture clicks
<div onMouseDown={ e => e.stopPropagation() }>
...
</div>
<div>
</RGL>
This doesn't work, because the e.stopPropagation() prevents react-draggable from working.
It happens in this EventPluginUtils.js:
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
My case is nested RGL with dragging, and when I drag a GridItem of the nested RGL , both RGL start dragging.
I would like to have only the inner RGL drag, so I need to stopPropagation. My GridItems are Custom Components which add a <button> child. As @RiiD wrote, if I put my onMouseDown event listener on the GridItem, react-draggable will override it, and it will not be called. I experienced that.
So How do I prevent propagation and still have react-draggable work?
I open issue https://github.com/STRML/react-grid-layout/issues/502 for this.
after trying out different things, the thing that worked the best for me was to create a draggable handle (draggable cancel also worked, but this suits my needs better).
I did manage to do it through handle drag start event and event handlers on all nested components that needed it, but that feel quite right.
@minjaitk if you define a drag handle, does this problem go away? you're able to handle onClick as usual? i tried that but didnt work
import GridLayout, { Layout } from "react-grid-layout";
export default () => {
const layout = [
{ i: "a", x: 0, y: 0, w: 1, h: 2, static: true },
{ i: "b", x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: "c", x: 4, y: 0, w: 1, h: 2 },
];
return (
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={30}
width={1200}
draggableHandle=".drag"
>
{layout.map((l) => (
<div key={l.i}>
<Elem />
</div>
))}
</GridLayout>
);
};
const Elem = () => {
return (
<div onMouseDown={() => console.log("this works")}>
<div className="drag">Header</div>
<div>{/* onClick on this doesnt work */}
<div>Body</div>
<div>Body</div>
<div>Body</div>
<div>Body</div>
<div>Body</div>
<div>Body</div>
</div>
</div>
);
};
Most helpful comment
This may help
But you can achieve same effect using state and static elements or draggableCancel selectors.