Skip to content

Commit e2fd1b3

Browse files
author
Eric Hahn
authored
DnD support for custom event components (#716)
* mostly new DnD implementation * comment/docs * use native storybook actions * Preserve dates on non-allDay DnD, improve docs * added alert() in resize example
1 parent b343b3d commit e2fd1b3

File tree

12 files changed

+647
-559
lines changed

12 files changed

+647
-559
lines changed

examples/demos/dnd.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class Dnd extends React.Component {
4747
this.setState({
4848
events: nextEvents,
4949
})
50+
51+
alert(`${event.title} was resized to ${start}-${end}`)
5052
}
5153

5254
render() {

src/DayColumn.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,14 @@ class DayColumn extends React.Component {
224224

225225
let { height, top, width, xOffset } = style
226226

227+
let wrapperProps = {
228+
event,
229+
continuesPrior: _continuesPrior,
230+
continuesAfter: _continuesAfter,
231+
}
232+
227233
return (
228-
<EventWrapper event={event} key={'evt_' + idx}>
234+
<EventWrapper {...wrapperProps} key={'evt_' + idx}>
229235
<div
230236
style={{
231237
...xStyle,

src/EventCell.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class EventCell extends React.Component {
3737
endAccessor,
3838
titleAccessor,
3939
tooltipAccessor,
40+
allDayAccessor,
4041
slotStart,
4142
slotEnd,
4243
onSelect,
@@ -50,9 +51,10 @@ class EventCell extends React.Component {
5051
tooltip = get(event, tooltipAccessor),
5152
end = get(event, endAccessor),
5253
start = get(event, startAccessor),
53-
isAllDayEvent =
54+
allDay = get(event, allDayAccessor),
55+
showAsAllDay =
5456
isAllDay ||
55-
get(event, props.allDayAccessor) ||
57+
allDay ||
5658
dates.diff(start, dates.ceil(end, 'day'), 'day') > 1,
5759
continuesPrior = dates.lt(start, slotStart, 'day'),
5860
continuesAfter = dates.gte(end, slotEnd, 'day')
@@ -65,13 +67,22 @@ class EventCell extends React.Component {
6567
selected
6668
)
6769

70+
let wrapperProps = {
71+
event,
72+
allDay,
73+
continuesPrior,
74+
continuesAfter,
75+
}
76+
6877
return (
69-
<EventWrapper event={event}>
78+
// give EventWrapper some extra info to help it determine whether it
79+
// it's in a row, etc. Useful for dnd, etc.
80+
<EventWrapper {...wrapperProps} isRow={true}>
7081
<div
7182
style={{ ...props.style, ...style }}
7283
className={cn('rbc-event', className, xClassName, {
7384
'rbc-selected': selected,
74-
'rbc-event-allday': isAllDayEvent,
85+
'rbc-event-allday': showAsAllDay,
7586
'rbc-event-continues-prior': continuesPrior,
7687
'rbc-event-continues-after': continuesAfter,
7788
})}
@@ -80,7 +91,7 @@ class EventCell extends React.Component {
8091
>
8192
<div className="rbc-event-content" title={tooltip || undefined}>
8293
{Event ? (
83-
<Event event={event} title={title} isAllDay={isAllDayEvent} />
94+
<Event event={event} title={title} isAllDay={allDay} />
8495
) : (
8596
title
8697
)}
Lines changed: 140 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,166 @@
11
import PropTypes from 'prop-types'
22
import React from 'react'
33
import { DragSource } from 'react-dnd'
4+
import { getEmptyImage } from 'react-dnd-html5-backend'
45
import cn from 'classnames'
6+
import compose from './compose'
57

68
import BigCalendar from '../../index'
9+
const EventWrapper = BigCalendar.components.eventWrapper
710

8-
/* drag sources */
11+
class DraggableEventWrapper extends React.Component {
12+
static propTypes = {
13+
event: PropTypes.object.isRequired,
914

10-
let eventSource = {
11-
beginDrag(props) {
12-
return props.event
13-
},
14-
}
15+
connectDragSource: PropTypes.func.isRequired,
16+
connectTopDragPreview: PropTypes.func.isRequired,
17+
connectTopDragSource: PropTypes.func.isRequired,
18+
connectBottomDragPreview: PropTypes.func.isRequired,
19+
connectBottomDragSource: PropTypes.func.isRequired,
20+
connectLeftDragPreview: PropTypes.func.isRequired,
21+
connectLeftDragSource: PropTypes.func.isRequired,
22+
connectRightDragPreview: PropTypes.func.isRequired,
23+
connectRightDragSource: PropTypes.func.isRequired,
1524

16-
function collectSource(connect, monitor) {
17-
return {
18-
connectDragSource: connect.dragSource(),
19-
isDragging: monitor.isDragging(),
25+
allDay: PropTypes.bool,
26+
isRow: PropTypes.bool,
27+
continuesPrior: PropTypes.bool,
28+
continuesAfter: PropTypes.bool,
29+
isDragging: PropTypes.bool,
30+
isResizing: PropTypes.bool,
2031
}
21-
}
2232

23-
const propTypes = {
24-
connectDragSource: PropTypes.func.isRequired,
25-
isDragging: PropTypes.bool.isRequired,
26-
event: PropTypes.object.isRequired,
27-
}
33+
componentDidMount() {
34+
// this is needed to prevent the backend from
35+
// screenshot'ing the event during a resize which
36+
// would be very confusing visually
37+
const emptyImage = getEmptyImage()
38+
const previewOptions = { captureDraggingState: true }
39+
this.props.connectTopDragPreview(emptyImage, previewOptions)
40+
this.props.connectBottomDragPreview(emptyImage, previewOptions)
41+
this.props.connectLeftDragPreview(emptyImage, previewOptions)
42+
this.props.connectRightDragPreview(emptyImage, previewOptions)
43+
}
2844

29-
class DraggableEventWrapper extends React.Component {
3045
render() {
31-
let { connectDragSource, isDragging, children, event } = this.props
32-
let EventWrapper = BigCalendar.components.eventWrapper
46+
let {
47+
connectDragSource,
48+
connectTopDragSource,
49+
connectBottomDragSource,
50+
connectLeftDragSource,
51+
connectRightDragSource,
52+
isDragging,
53+
isResizing,
54+
children,
55+
event,
56+
allDay,
57+
isRow,
58+
continuesPrior,
59+
continuesAfter,
60+
} = this.props
61+
62+
let StartAnchor = null,
63+
EndAnchor = null
64+
65+
/*
66+
* The resizability of events depends on whether they are
67+
* allDay events and how they are displayed.
68+
*
69+
* 1. If the event is being shown in an event row (because
70+
* it is an allDay event shown in the header row or because as
71+
* in month view the view is showing all events as rows) then we
72+
* allow east-west resizing.
73+
*
74+
* 2. Otherwise the event is being displayed
75+
* normally, we can drag it north-south to resize the times.
76+
*
77+
* See `DropWrappers` for handling of the drop of such events.
78+
*
79+
* Notwithstanding the above, we never show drag anchors for
80+
* events which continue beyond current component. This happens
81+
* in the middle of events when showMultiDay is true, and to
82+
* events at the edges of the calendar's min/max location.
83+
*/
84+
if (isRow || allDay) {
85+
const anchor = (
86+
<div className="rbc-addons-dnd-resize-ew-anchor">
87+
<div className="rbc-addons-dnd-resize-ew-icon" />
88+
</div>
89+
)
90+
StartAnchor = !continuesPrior && connectLeftDragSource(anchor)
91+
EndAnchor = !continuesAfter && connectRightDragSource(anchor)
92+
} else {
93+
const anchor = (
94+
<div className="rbc-addons-dnd-resize-ns-anchor">
95+
<div className="rbc-addons-dnd-resize-ns-icon" />
96+
</div>
97+
)
98+
StartAnchor = !continuesPrior && connectTopDragSource(anchor)
99+
EndAnchor = !continuesAfter && connectBottomDragSource(anchor)
100+
}
101+
102+
/*
103+
* props.children is the singular <Event> component.
104+
* BigCalendar positions the Event abolutely and we
105+
* need the anchors to be part of that positioning.
106+
* So we insert the anchors inside the Event's children
107+
* rather than wrap the Event here as the latter approach
108+
* would lose the positioning.
109+
*/
110+
const childrenWithAnchors = (
111+
<div className="rbc-addons-dnd-resizable">
112+
{StartAnchor}
113+
{children.props.children}
114+
{EndAnchor}
115+
</div>
116+
)
33117

34118
children = React.cloneElement(children, {
35119
className: cn(
36120
children.props.className,
37-
isDragging && 'rbc-addons-dnd-dragging'
121+
isDragging && 'rbc-addons-dnd-dragging',
122+
isResizing && 'rbc-addons-dnd-resizing'
38123
),
124+
children: childrenWithAnchors, // replace original event child with anchor-embellished child
39125
})
40126

41127
return (
42-
<EventWrapper event={event}>{connectDragSource(children)}</EventWrapper>
128+
<EventWrapper event={event} allDay={allDay}>
129+
{connectDragSource(children)}
130+
</EventWrapper>
43131
)
44132
}
45133
}
46134

47-
DraggableEventWrapper.propTypes = propTypes
135+
/* drag sources */
136+
const makeEventSource = anchor => ({
137+
beginDrag: ({ event }) => ({ event, anchor }),
138+
// canDrag: ({ event }) => event.draggable === undefined || event.draggable - e.g. support per-event dragability/sizability
139+
})
48140

49-
export default DragSource('event', eventSource, collectSource)(
50-
DraggableEventWrapper
51-
)
141+
export default compose(
142+
DragSource('event', makeEventSource('drop'), (connect, monitor) => ({
143+
connectDragSource: connect.dragSource(),
144+
isDragging: monitor.isDragging(),
145+
})),
146+
DragSource('event', makeEventSource('resizeTop'), (connect, monitor) => ({
147+
connectTopDragSource: connect.dragSource(),
148+
connectTopDragPreview: connect.dragPreview(),
149+
isResizing: monitor.isDragging(),
150+
})),
151+
DragSource('event', makeEventSource('resizeBottom'), (connect, monitor) => ({
152+
connectBottomDragSource: connect.dragSource(),
153+
connectBottomDragPreview: connect.dragPreview(),
154+
isResizing: monitor.isDragging(),
155+
})),
156+
DragSource('event', makeEventSource('resizeLeft'), (connect, monitor) => ({
157+
connectLeftDragSource: connect.dragSource(),
158+
connectLeftDragPreview: connect.dragPreview(),
159+
isResizing: monitor.isDragging(),
160+
})),
161+
DragSource('event', makeEventSource('resizeRight'), (connect, monitor) => ({
162+
connectRightDragSource: connect.dragSource(),
163+
connectRightDragPreview: connect.dragPreview(),
164+
isResizing: monitor.isDragging(),
165+
}))
166+
)(DraggableEventWrapper)

0 commit comments

Comments
 (0)