Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background events feature #1851

Merged
merged 15 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import './prism.scss'
import Card from './Card'
import ExampleControlSlot from './ExampleControlSlot'
import Basic from './demos/basic'
import BackgroundEvents from './demos/backgroundEvents'
import Selectable from './demos/selectable'
import CreateEventWithNoOverlap from './demos/createEventWithNoOverlap'
import Cultures from './demos/cultures'
Expand Down Expand Up @@ -44,6 +45,7 @@ const EXAMPLES = {
timeslots: 'Custom Time Grids',
rendering: 'Customized Component Rendering',
customView: 'Custom Calendar Views',
backgroundEvents: 'Background Events',
resource: 'Resource Scheduling',
dnd: 'Addon: Drag and drop',
dndresource: 'Resource Drag and drop',
Expand Down Expand Up @@ -74,6 +76,7 @@ class Example extends React.Component {
let selected = this.state.selected
let Current = {
basic: Basic,
backgroundEvents: BackgroundEvents,
selectable: Selectable,
cultures: Cultures,
popup: Popup,
Expand Down
8 changes: 8 additions & 0 deletions examples/backgroundEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default [
{
id: 0,
title: 'Available for Clients',
start: new Date(2015, 3, 13, 6),
end: new Date(2015, 3, 13, 18),
},
]
24 changes: 24 additions & 0 deletions examples/demos/backgroundEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import { Calendar, Views } from 'react-big-calendar'
import events from '../events'
import backgroundEvents from '../backgroundEvents'
import * as dates from '../../src/utils/dates'

let allViews = Object.keys(Views).map(k => Views[k])

let Basic = ({ localizer }) => (
<Calendar
events={events}
defaultView={Views.DAY}
views={allViews}
step={60}
showMultiDayTimes
max={dates.add(dates.endOf(new Date(2015, 17, 1), 'day'), -1, 'hours')}
defaultDate={new Date(2015, 3, 13)}
localizer={localizer}
backgroundEvents={backgroundEvents}
dayLayoutAlgorithm={'no-overlap'}
/>
)

export default Basic
22 changes: 20 additions & 2 deletions examples/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,28 @@ export default [
},
{
id: 11,
title: 'Birthday Party',
start: new Date(2015, 3, 13, 7, 0, 0),
title: 'Planning Meeting with Paige',
start: new Date(2015, 3, 13, 8, 0, 0),
end: new Date(2015, 3, 13, 10, 30, 0),
},
{
id: 11.1,
title: 'Inconvenient Conference Call',
start: new Date(2015, 3, 13, 9, 30, 0),
end: new Date(2015, 3, 13, 12, 0, 0),
},
{
id: 11.2,
title: "Project Kickoff - Lou's Shoes",
start: new Date(2015, 3, 13, 11, 30, 0),
end: new Date(2015, 3, 13, 14, 0, 0),
},
{
id: 11.3,
title: 'Quote Follow-up - Tea by Tina',
start: new Date(2015, 3, 13, 15, 30, 0),
end: new Date(2015, 3, 13, 16, 0, 0),
},
{
id: 12,
title: 'Late Night Event',
Expand Down
31 changes: 31 additions & 0 deletions src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,29 @@ class Calendar extends React.Component {
*/
events: PropTypes.arrayOf(PropTypes.object),

/**
* An array of background event objects to display on the calendar. Background
* Events behave similarly to Events but are not factored into Event overlap logic,
* allowing them to sit behind any Events that may occur during the same period.
* Background Events objects can be any shape, as long as the Calendar knows how to
* retrieve the following details of the event:
*
* - start time
* - end time
*
* Each of these properties can be customized or generated dynamically by
* setting the various "accessor" props. Without any configuration the default
* event should look like:
*
* ```js
* BackgroundEvent {
* start: Date,
* end: Date,
* }
* ```
*/
backgroundEvents: PropTypes.arrayOf(PropTypes.object),

/**
* Accessor for the event title, used to display event information. Should
* resolve to a `renderable` value.
Expand Down Expand Up @@ -783,6 +806,7 @@ class Calendar extends React.Component {

constructor(...args) {
super(...args)

this.state = {
context: this.getContext(this.props),
}
Expand All @@ -801,6 +825,7 @@ class Calendar extends React.Component {
resourceIdAccessor,
resourceTitleAccessor,
eventPropGetter,
backgroundEventPropGetter,
slotPropGetter,
slotGroupPropGetter,
dayPropGetter,
Expand All @@ -820,6 +845,9 @@ class Calendar extends React.Component {
getters: {
eventProp: (...args) =>
(eventPropGetter && eventPropGetter(...args)) || {},
backgroundEventProp: (...args) =>
(backgroundEventPropGetter && backgroundEventPropGetter(...args)) ||
{},
slotProp: (...args) =>
(slotPropGetter && slotPropGetter(...args)) || {},
slotGroupProp: (...args) =>
Expand All @@ -828,6 +856,7 @@ class Calendar extends React.Component {
},
components: defaults(components[view] || {}, omit(components, names), {
eventWrapper: NoopWrapper,
backgroundEventWrapper: NoopWrapper,
eventContainerWrapper: NoopWrapper,
dateCellWrapper: NoopWrapper,
weekWrapper: NoopWrapper,
Expand Down Expand Up @@ -885,6 +914,7 @@ class Calendar extends React.Component {
view,
toolbar,
events,
backgroundEvents = [],
style,
className,
elementProps,
Expand Down Expand Up @@ -934,6 +964,7 @@ class Calendar extends React.Component {
<View
{...props}
events={events}
backgroundEvents={backgroundEvents}
date={current}
getNow={getNow}
length={length}
Expand Down
11 changes: 8 additions & 3 deletions src/DayColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ class DayColumn extends React.Component {
slotMetrics={slotMetrics}
>
<div className={clsx('rbc-events-container', rtl && 'rtl')}>
{this.renderEvents()}
{this.renderEvents({
events: this.props.backgroundEvents,
isBackgroundEvent: true,
})}
{this.renderEvents({ events: this.props.events })}
</div>
</EventContainer>

Expand All @@ -174,9 +178,8 @@ class DayColumn extends React.Component {
)
}

renderEvents = () => {
renderEvents = ({ events, isBackgroundEvent }) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation/Suggestion: It feels like the isBackgroundEvent property here may be confusing since the name is singular, but it applies to all events passed in.

How do we feel about keeping the current state where renderEvents doesn't accept arguments, and instead we get both events and backgroundEvents from the props within renderEvents and merge them into a single collection of events such that background events have a property isBackgroundEvent set to true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging events and backgroundEvents will cause no-overlap algorithm to treat them as colliding events, and place them next to each other instead of one on top of each other.

let {
events,
rtl,
selected,
accessors,
Expand Down Expand Up @@ -233,6 +236,7 @@ class DayColumn extends React.Component {
selected={isSelected(event, selected)}
onClick={e => this._select(event, e)}
onDoubleClick={e => this._doubleClick(event, e)}
isBackgroundEvent={isBackgroundEvent}
onKeyPress={e => this._keyPress(event, e)}
resizable={resizable}
/>
Expand Down Expand Up @@ -382,6 +386,7 @@ class DayColumn extends React.Component {

DayColumn.propTypes = {
events: PropTypes.array.isRequired,
backgroundEvents: PropTypes.array.isRequired,
step: PropTypes.number.isRequired,
date: PropTypes.instanceOf(Date).isRequired,
min: PropTypes.instanceOf(Date).isRequired,
Expand Down
33 changes: 30 additions & 3 deletions src/TimeGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default class TimeGrid extends Component {
})
}

renderEvents(range, events, now) {
renderEvents(range, events, backgroundEvents, now) {
let {
min,
max,
Expand All @@ -117,6 +117,7 @@ export default class TimeGrid extends Component {

const resources = this.memoizedResources(this.props.resources, accessors)
const groupedEvents = resources.groupEvents(events)
const groupedBackgroundEvents = resources.groupEvents(backgroundEvents)

return resources.map(([id, resource], i) =>
range.map((date, jj) => {
Expand All @@ -129,6 +130,17 @@ export default class TimeGrid extends Component {
)
)

let daysBackgroundEvents = (
groupedBackgroundEvents.get(id) || []
).filter(event =>
dates.inRange(
date,
accessors.start(event),
accessors.end(event),
'day'
)
)

return (
<DayColumn
{...this.props}
Expand All @@ -141,6 +153,7 @@ export default class TimeGrid extends Component {
key={i + '-' + jj}
date={date}
events={daysEvents}
backgroundEvents={daysBackgroundEvents}
dayLayoutAlgorithm={dayLayoutAlgorithm}
/>
)
Expand All @@ -151,6 +164,7 @@ export default class TimeGrid extends Component {
render() {
let {
events,
backgroundEvents,
range,
width,
rtl,
Expand All @@ -176,7 +190,8 @@ export default class TimeGrid extends Component {
this.slots = range.length

let allDayEvents = [],
rangeEvents = []
rangeEvents = [],
rangeBackgroundEvents = []

events.forEach(event => {
if (inRange(event, start, end, accessors)) {
Expand All @@ -195,6 +210,12 @@ export default class TimeGrid extends Component {
}
})

backgroundEvents.forEach(event => {
if (inRange(event, start, end, accessors)) {
rangeBackgroundEvents.push(event)
}
})

allDayEvents.sort((a, b) => sortEvents(a, b, accessors))

return (
Expand Down Expand Up @@ -246,7 +267,12 @@ export default class TimeGrid extends Component {
className="rbc-time-gutter"
getters={getters}
/>
{this.renderEvents(range, rangeEvents, getNow())}
{this.renderEvents(
range,
rangeEvents,
rangeBackgroundEvents,
getNow()
)}
</div>
</div>
)
Expand Down Expand Up @@ -311,6 +337,7 @@ export default class TimeGrid extends Component {

TimeGrid.propTypes = {
events: PropTypes.array.isRequired,
backgroundEvents: PropTypes.array.isRequired,
resources: PropTypes.array,

step: PropTypes.number,
Expand Down
40 changes: 28 additions & 12 deletions src/TimeGridEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function TimeGridEvent(props) {
getters,
onClick,
onDoubleClick,
isBackgroundEvent,
onKeyPress,
components: { event: Event, eventWrapper: EventWrapper },
} = props
Expand All @@ -40,29 +41,44 @@ function TimeGridEvent(props) {
</div>,
]

const eventStyle = isBackgroundEvent
? {
...userProps.style,
top: stringifyPercent(top),
height: stringifyPercent(height),
width: `calc(${width}% + 10px)`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the additional 10px here to compensate for the right margin on events? It might be worth adding a comment to explain why we need this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Also, there was a small inconsistency causing these events to not render at 100% width of the column. I'll add a comment and fix this behavior.

Also, I'll update the examples screenshots.

[rtl ? 'right' : 'left']: stringifyPercent(Math.max(0, xOffset)),
}
: {
...userProps.style,
top: stringifyPercent(top),
width: stringifyPercent(width),
height: stringifyPercent(height),
[rtl ? 'right' : 'left']: stringifyPercent(xOffset),
}

return (
<EventWrapper type="time" {...props}>
<div
onClick={onClick}
onDoubleClick={onDoubleClick}
style={eventStyle}
onKeyPress={onKeyPress}
style={{
...userProps.style,
top: stringifyPercent(top),
[rtl ? 'right' : 'left']: stringifyPercent(xOffset),
width: stringifyPercent(width),
height: stringifyPercent(height),
}}
title={
tooltip
? (typeof label === 'string' ? label + ': ' : '') + tooltip
: undefined
}
className={clsx('rbc-event', className, userProps.className, {
'rbc-selected': selected,
'rbc-event-continues-earlier': continuesEarlier,
'rbc-event-continues-later': continuesLater,
})}
className={clsx(
isBackgroundEvent ? 'rbc-background-event' : 'rbc-event',
className,
userProps.className,
{
'rbc-selected': selected,
'rbc-event-continues-earlier': continuesEarlier,
'rbc-event-continues-later': continuesLater,
}
)}
>
{inner}
</div>
Expand Down
Loading