Skip to content

Commit

Permalink
DnD support for custom event components (#716)
Browse files Browse the repository at this point in the history
* mostly new DnD implementation

* comment/docs

* use native storybook actions

* Preserve dates on non-allDay DnD, improve docs

* added alert() in resize example
  • Loading branch information
Eric Hahn committed Mar 19, 2018
1 parent b343b3d commit e2fd1b3
Show file tree
Hide file tree
Showing 12 changed files with 647 additions and 559 deletions.
2 changes: 2 additions & 0 deletions examples/demos/dnd.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class Dnd extends React.Component {
this.setState({
events: nextEvents,
})

alert(`${event.title} was resized to ${start}-${end}`)
}

render() {
Expand Down
8 changes: 7 additions & 1 deletion src/DayColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,14 @@ class DayColumn extends React.Component {

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

let wrapperProps = {
event,
continuesPrior: _continuesPrior,
continuesAfter: _continuesAfter,
}

return (
<EventWrapper event={event} key={'evt_' + idx}>
<EventWrapper {...wrapperProps} key={'evt_' + idx}>
<div
style={{
...xStyle,
Expand Down
21 changes: 16 additions & 5 deletions src/EventCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class EventCell extends React.Component {
endAccessor,
titleAccessor,
tooltipAccessor,
allDayAccessor,
slotStart,
slotEnd,
onSelect,
Expand All @@ -50,9 +51,10 @@ class EventCell extends React.Component {
tooltip = get(event, tooltipAccessor),
end = get(event, endAccessor),
start = get(event, startAccessor),
isAllDayEvent =
allDay = get(event, allDayAccessor),
showAsAllDay =
isAllDay ||
get(event, props.allDayAccessor) ||
allDay ||
dates.diff(start, dates.ceil(end, 'day'), 'day') > 1,
continuesPrior = dates.lt(start, slotStart, 'day'),
continuesAfter = dates.gte(end, slotEnd, 'day')
Expand All @@ -65,13 +67,22 @@ class EventCell extends React.Component {
selected
)

let wrapperProps = {
event,
allDay,
continuesPrior,
continuesAfter,
}

return (
<EventWrapper event={event}>
// give EventWrapper some extra info to help it determine whether it
// it's in a row, etc. Useful for dnd, etc.
<EventWrapper {...wrapperProps} isRow={true}>
<div
style={{ ...props.style, ...style }}
className={cn('rbc-event', className, xClassName, {
'rbc-selected': selected,
'rbc-event-allday': isAllDayEvent,
'rbc-event-allday': showAsAllDay,
'rbc-event-continues-prior': continuesPrior,
'rbc-event-continues-after': continuesAfter,
})}
Expand All @@ -80,7 +91,7 @@ class EventCell extends React.Component {
>
<div className="rbc-event-content" title={tooltip || undefined}>
{Event ? (
<Event event={event} title={title} isAllDay={isAllDayEvent} />
<Event event={event} title={title} isAllDay={allDay} />
) : (
title
)}
Expand Down
165 changes: 140 additions & 25 deletions src/addons/dragAndDrop/DraggableEventWrapper.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,166 @@
import PropTypes from 'prop-types'
import React from 'react'
import { DragSource } from 'react-dnd'
import { getEmptyImage } from 'react-dnd-html5-backend'
import cn from 'classnames'
import compose from './compose'

import BigCalendar from '../../index'
const EventWrapper = BigCalendar.components.eventWrapper

/* drag sources */
class DraggableEventWrapper extends React.Component {
static propTypes = {
event: PropTypes.object.isRequired,

let eventSource = {
beginDrag(props) {
return props.event
},
}
connectDragSource: PropTypes.func.isRequired,
connectTopDragPreview: PropTypes.func.isRequired,
connectTopDragSource: PropTypes.func.isRequired,
connectBottomDragPreview: PropTypes.func.isRequired,
connectBottomDragSource: PropTypes.func.isRequired,
connectLeftDragPreview: PropTypes.func.isRequired,
connectLeftDragSource: PropTypes.func.isRequired,
connectRightDragPreview: PropTypes.func.isRequired,
connectRightDragSource: PropTypes.func.isRequired,

function collectSource(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
allDay: PropTypes.bool,
isRow: PropTypes.bool,
continuesPrior: PropTypes.bool,
continuesAfter: PropTypes.bool,
isDragging: PropTypes.bool,
isResizing: PropTypes.bool,
}
}

const propTypes = {
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
event: PropTypes.object.isRequired,
}
componentDidMount() {
// this is needed to prevent the backend from
// screenshot'ing the event during a resize which
// would be very confusing visually
const emptyImage = getEmptyImage()
const previewOptions = { captureDraggingState: true }
this.props.connectTopDragPreview(emptyImage, previewOptions)
this.props.connectBottomDragPreview(emptyImage, previewOptions)
this.props.connectLeftDragPreview(emptyImage, previewOptions)
this.props.connectRightDragPreview(emptyImage, previewOptions)
}

class DraggableEventWrapper extends React.Component {
render() {
let { connectDragSource, isDragging, children, event } = this.props
let EventWrapper = BigCalendar.components.eventWrapper
let {
connectDragSource,
connectTopDragSource,
connectBottomDragSource,
connectLeftDragSource,
connectRightDragSource,
isDragging,
isResizing,
children,
event,
allDay,
isRow,
continuesPrior,
continuesAfter,
} = this.props

let StartAnchor = null,
EndAnchor = null

/*
* The resizability of events depends on whether they are
* allDay events and how they are displayed.
*
* 1. If the event is being shown in an event row (because
* it is an allDay event shown in the header row or because as
* in month view the view is showing all events as rows) then we
* allow east-west resizing.
*
* 2. Otherwise the event is being displayed
* normally, we can drag it north-south to resize the times.
*
* See `DropWrappers` for handling of the drop of such events.
*
* Notwithstanding the above, we never show drag anchors for
* events which continue beyond current component. This happens
* in the middle of events when showMultiDay is true, and to
* events at the edges of the calendar's min/max location.
*/
if (isRow || allDay) {
const anchor = (
<div className="rbc-addons-dnd-resize-ew-anchor">
<div className="rbc-addons-dnd-resize-ew-icon" />
</div>
)
StartAnchor = !continuesPrior && connectLeftDragSource(anchor)
EndAnchor = !continuesAfter && connectRightDragSource(anchor)
} else {
const anchor = (
<div className="rbc-addons-dnd-resize-ns-anchor">
<div className="rbc-addons-dnd-resize-ns-icon" />
</div>
)
StartAnchor = !continuesPrior && connectTopDragSource(anchor)
EndAnchor = !continuesAfter && connectBottomDragSource(anchor)
}

/*
* props.children is the singular <Event> component.
* BigCalendar positions the Event abolutely and we
* need the anchors to be part of that positioning.
* So we insert the anchors inside the Event's children
* rather than wrap the Event here as the latter approach
* would lose the positioning.
*/
const childrenWithAnchors = (
<div className="rbc-addons-dnd-resizable">
{StartAnchor}
{children.props.children}
{EndAnchor}
</div>
)

children = React.cloneElement(children, {
className: cn(
children.props.className,
isDragging && 'rbc-addons-dnd-dragging'
isDragging && 'rbc-addons-dnd-dragging',
isResizing && 'rbc-addons-dnd-resizing'
),
children: childrenWithAnchors, // replace original event child with anchor-embellished child
})

return (
<EventWrapper event={event}>{connectDragSource(children)}</EventWrapper>
<EventWrapper event={event} allDay={allDay}>
{connectDragSource(children)}
</EventWrapper>
)
}
}

DraggableEventWrapper.propTypes = propTypes
/* drag sources */
const makeEventSource = anchor => ({
beginDrag: ({ event }) => ({ event, anchor }),
// canDrag: ({ event }) => event.draggable === undefined || event.draggable - e.g. support per-event dragability/sizability
})

export default DragSource('event', eventSource, collectSource)(
DraggableEventWrapper
)
export default compose(
DragSource('event', makeEventSource('drop'), (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
})),
DragSource('event', makeEventSource('resizeTop'), (connect, monitor) => ({
connectTopDragSource: connect.dragSource(),
connectTopDragPreview: connect.dragPreview(),
isResizing: monitor.isDragging(),
})),
DragSource('event', makeEventSource('resizeBottom'), (connect, monitor) => ({
connectBottomDragSource: connect.dragSource(),
connectBottomDragPreview: connect.dragPreview(),
isResizing: monitor.isDragging(),
})),
DragSource('event', makeEventSource('resizeLeft'), (connect, monitor) => ({
connectLeftDragSource: connect.dragSource(),
connectLeftDragPreview: connect.dragPreview(),
isResizing: monitor.isDragging(),
})),
DragSource('event', makeEventSource('resizeRight'), (connect, monitor) => ({
connectRightDragSource: connect.dragSource(),
connectRightDragPreview: connect.dragPreview(),
isResizing: monitor.isDragging(),
}))
)(DraggableEventWrapper)
Loading

0 comments on commit e2fd1b3

Please sign in to comment.