Skip to content

Commit bbc02f3

Browse files
authored
feat: add drag and drop support (#100)
BREAKING CHANGE: A dependency on the `angular-draggable-droppable` library has been added. System.js users will need to add this to their config: ``` 'angular-draggable-droppable': 'npm:angular-draggable-droppable/dist/umd/angular-draggable-droppable.js' ``` Closes #10 Closes #102
1 parent 36458ab commit bbc02f3

20 files changed

+428
-42
lines changed

demo/demo.component.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ const colors: any = {
8080
[events]="events"
8181
[refresh]="refresh"
8282
[activeDayIsOpen]="activeDayIsOpen"
83-
(dayClicked)="dayClicked($event.day)">
83+
(dayClicked)="dayClicked($event.day)"
84+
(eventTimesChanged)="eventTimesChanged($event)">
8485
</mwl-calendar-month-view>
8586
<mwl-calendar-week-view
8687
*ngSwitchCase="'week'"
@@ -139,13 +140,14 @@ export class DemoComponent {
139140
}, {
140141
start: addHours(startOfDay(new Date()), 2),
141142
end: new Date(),
142-
title: 'A resizable event',
143+
title: 'A draggable and resizable event',
143144
color: colors.yellow,
144145
actions: this.actions,
145146
resizable: {
146147
beforeStart: true,
147148
afterEnd: true
148-
}
149+
},
150+
draggable: true
149151
}];
150152

151153
activeDayIsOpen: boolean = true;

karma.conf.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ module.exports = function(config) {
8080
}
8181
}),
8282
new FixDefaultImportPlugin()
83-
]
83+
],
84+
performance: {
85+
hints: false
86+
}
8487
},
8588

8689
remapIstanbulReporter: {

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@
123123
},
124124
"dependencies": {
125125
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.0",
126-
"angular-resizable-element": "~0.5.0",
127-
"calendar-utils": "0.0.38",
126+
"angular-draggable-droppable": "^0.4.0",
127+
"angular-resizable-element": "^0.5.4",
128+
"calendar-utils": "0.0.39",
128129
"date-fns": "^1.15.1"
129130
}
130131
}

scss/day-view.scss

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
white-space: nowrap;
4141
}
4242

43+
.cal-draggable {
44+
cursor: move;
45+
}
46+
4347
.cal-event.cal-starts-within-day {
4448
border-top-left-radius: 5px;
4549
border-top-right-radius: 5px;

scss/month-view.scss

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@
119119
font-size: 1.9em;
120120
}
121121

122+
.cal-day-cell.cal-drag-over {
123+
background-color: darken(#ededed, 5%) !important;
124+
}
125+
122126
.cal-open-day-events {
123127
padding: 15px;
124128
color: white;

scss/week-view.scss

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
line-height: 30px;
4040
}
4141

42+
.cal-draggable {
43+
cursor: move;
44+
}
45+
4246
.cal-event.cal-starts-within-week {
4347
border-top-left-radius: 5px;
4448
border-bottom-left-radius: 5px;

src/calendar.module.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NgModule, ModuleWithProviders } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { ResizableModule } from 'angular-resizable-element';
4+
import { DragAndDropModule } from 'angular-draggable-droppable';
45
import { CalendarDayViewComponent } from './components/day/calendarDayView.component';
56
import { CalendarWeekViewComponent } from './components/week/calendarWeekView.component';
67
import { CalendarMonthViewComponent } from './components/month/calendarMonthView.component';
@@ -38,8 +39,17 @@ import { CalendarDateFormatter } from './providers/calendarDateFormatter.provide
3839
CalendarDate,
3940
CalendarEventTitlePipe
4041
],
41-
imports: [CommonModule, ResizableModule],
42-
exports: [CalendarDayViewComponent, CalendarWeekViewComponent, CalendarMonthViewComponent, CalendarDate],
42+
imports: [
43+
CommonModule,
44+
ResizableModule,
45+
DragAndDropModule
46+
],
47+
exports: [
48+
CalendarDayViewComponent,
49+
CalendarWeekViewComponent,
50+
CalendarMonthViewComponent,
51+
CalendarDate
52+
],
4353
entryComponents: [CalendarTooltipWindowComponent]
4454
})
4555
export class CalendarModule {

src/components/day/calendarDayView.component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import {
1313
import { getDayView, getDayViewHourGrid, CalendarEvent, DayView, DayViewHour } from 'calendar-utils';
1414
import { Subject } from 'rxjs/Subject';
1515
import { Subscription } from 'rxjs/Subscription';
16-
import { CalendarEventTimesChangedEvent } from './../../interfaces/calendarEventTimesChangedEvent.interface';
16+
import { CalendarEventTimesChangedEvent } from '../../interfaces/calendarEventTimesChangedEvent.interface';
1717

1818
const SEGMENT_HEIGHT: number = 30;
1919

2020
@Component({
2121
selector: 'mwl-calendar-day-view',
2222
template: `
23-
<div class="cal-day-view">
23+
<div class="cal-day-view" #dayViewContainer>
2424
<mwl-calendar-all-day-event
2525
*ngFor="let event of view.allDayEvents"
2626
[event]="event"
@@ -34,6 +34,7 @@ const SEGMENT_HEIGHT: number = 30;
3434
[hourSegments]="hourSegments"
3535
[tooltipPlacement]="tooltipPlacement"
3636
[eventSnapSize]="eventSnapSize"
37+
[dayViewContainer]="dayViewContainer"
3738
(eventClicked)="eventClicked.emit({event: dayEvent.event})"
3839
(eventResized)="eventTimesChanged.emit($event)">
3940
</mwl-calendar-day-view-event>

src/components/day/calendarDayViewEvent.component.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { Component, Input, Output, EventEmitter } from '@angular/core';
1+
import { Component, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
22
import { DayViewEvent } from 'calendar-utils';
33
import { ResizeEvent } from 'angular-resizable-element';
44
import addMinutes from 'date-fns/add_minutes';
5+
import { CalendarDragHelper } from '../../providers/calendarDragHelper.provider';
56

67
@Component({
78
selector: 'mwl-calendar-day-view-event',
89
template: `
910
<div
1011
class="cal-event"
12+
#event
1113
[style.marginTop.px]="dayEvent.top"
1214
[style.marginLeft.px]="dayEvent.left + 70"
1315
[style.height.px]="dayEvent.height"
@@ -16,6 +18,7 @@ import addMinutes from 'date-fns/add_minutes';
1618
[style.borderColor]="dayEvent.event.color.primary"
1719
[class.cal-starts-within-day]="!dayEvent.startsBeforeDay"
1820
[class.cal-ends-within-day]="!dayEvent.endsAfterDay"
21+
[class.cal-draggable]="dayEvent.event.draggable"
1922
[ngClass]="dayEvent.event.cssClass"
2023
[mwlCalendarTooltip]="dayEvent.event | calendarEventTitle:'dayTooltip'"
2124
[tooltipPlacement]="tooltipPlacement"
@@ -24,7 +27,13 @@ import addMinutes from 'date-fns/add_minutes';
2427
[resizeSnapGrid]="{top: eventSnapSize, bottom: eventSnapSize}"
2528
(resizeStart)="resizeStarted(dayEvent, $event)"
2629
(resizing)="resizing(dayEvent, $event)"
27-
(resizeEnd)="resizeEnded(dayEvent)">
30+
(resizeEnd)="resizeEnded(dayEvent)"
31+
mwlDraggable
32+
[dragAxis]="{x: false, y: dayEvent.event.draggable && !currentResize}"
33+
[dragSnapGrid]="{y: eventSnapSize}"
34+
[validateDrag]="validateDrag"
35+
(dragStart)="dragStart(event)"
36+
(dragEnd)="eventDragged(dayEvent, $event.y)">
2837
<mwl-calendar-event-title
2938
[event]="dayEvent.event"
3039
view="day"
@@ -44,6 +53,8 @@ export class CalendarDayViewEventComponent {
4453

4554
@Input() tooltipPlacement: string;
4655

56+
@Input() dayViewContainer: HTMLElement;
57+
4758
@Output() eventClicked: EventEmitter<any> = new EventEmitter();
4859

4960
@Output() eventResized: EventEmitter<any> = new EventEmitter();
@@ -54,6 +65,10 @@ export class CalendarDayViewEventComponent {
5465
edge: string
5566
};
5667

68+
validateDrag: Function;
69+
70+
constructor(private cdr: ChangeDetectorRef) {}
71+
5772
resizeStarted(event: DayViewEvent, resizeEvent: ResizeEvent): void {
5873
this.currentResize = {
5974
originalTop: event.top,
@@ -93,7 +108,26 @@ export class CalendarDayViewEventComponent {
93108
}
94109

95110
this.eventResized.emit({newStart, newEnd, event: dayEvent.event});
111+
this.currentResize = null;
96112

97113
}
98114

115+
dragStart(event: HTMLElement): void {
116+
const dragHelper: CalendarDragHelper = new CalendarDragHelper(this.dayViewContainer, event);
117+
this.validateDrag = ({x, y}) => dragHelper.validateDrag({x, y});
118+
this.cdr.markForCheck();
119+
}
120+
121+
eventDragged(dayEvent: DayViewEvent, draggedInPixels: number): void {
122+
const segments: number = draggedInPixels / this.eventSnapSize;
123+
const segmentAmountInMinutes: number = 60 / this.hourSegments;
124+
const minutesMoved: number = segments * segmentAmountInMinutes;
125+
const newStart: Date = addMinutes(dayEvent.event.start, minutesMoved);
126+
let newEnd: Date;
127+
if (dayEvent.event.end) {
128+
newEnd = addMinutes(dayEvent.event.end, minutesMoved);
129+
}
130+
this.eventResized.emit({newStart, newEnd, event: dayEvent.event});
131+
}
132+
99133
}

src/components/month/calendarMonthCell.component.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ import { MonthViewDay } from 'calendar-utils';
99
<span class="cal-day-number">{{ day.date | calendarDate:'monthViewDayNumber':locale }}</span>
1010
</div>
1111
<div class="cal-events">
12-
<span
12+
<div
1313
class="cal-event"
1414
*ngFor="let event of day.events"
1515
[style.backgroundColor]="event.color.primary"
1616
[ngClass]="event?.cssClass"
1717
(mouseenter)="highlightDay.emit({event: event})"
1818
(mouseleave)="unhighlightDay.emit({event: event})"
1919
[mwlCalendarTooltip]="event | calendarEventTitle:'monthTooltip'"
20-
[tooltipPlacement]="tooltipPlacement">
21-
</span>
20+
[tooltipPlacement]="tooltipPlacement"
21+
mwlDraggable
22+
[dropData]="{event: event}"
23+
[dragAxis]="{x: event.draggable, y: event.draggable}">
24+
</div>
2225
</div>
2326
`,
2427
host: {

src/components/month/calendarMonthView.component.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ import {
2121
import { Subject } from 'rxjs/Subject';
2222
import { Subscription } from 'rxjs/Subscription';
2323
import isSameDay from 'date-fns/is_same_day';
24+
import setDate from 'date-fns/set_date';
25+
import setMonth from 'date-fns/set_month';
26+
import setYear from 'date-fns/set_year';
27+
import getDate from 'date-fns/get_date';
28+
import getMonth from 'date-fns/get_month';
29+
import getYear from 'date-fns/get_year';
30+
import differenceInSeconds from 'date-fns/difference_in_seconds';
31+
import addSeconds from 'date-fns/add_seconds';
32+
import { CalendarEventTimesChangedEvent } from '../../interfaces/calendarEventTimesChangedEvent.interface';
2433

2534
@Component({
2635
selector: 'mwl-calendar-month-view',
@@ -36,13 +45,18 @@ import isSameDay from 'date-fns/is_same_day';
3645
<div class="cal-cell-row">
3746
<mwl-calendar-month-cell
3847
*ngFor="let day of view.days | slice : rowIndex : rowIndex + 7"
48+
[class.cal-drag-over]="day.dragOver"
3949
[day]="day"
4050
[openDay]="openDay"
4151
[locale]="locale"
4252
[tooltipPlacement]="tooltipPlacement"
4353
(click)="dayClicked.emit({day: day})"
4454
(highlightDay)="toggleDayHighlight($event.event, true)"
45-
(unhighlightDay)="toggleDayHighlight($event.event, false)">
55+
(unhighlightDay)="toggleDayHighlight($event.event, false)"
56+
mwlDroppable
57+
(dragEnter)="day.dragOver = true"
58+
(dragLeave)="day.dragOver = false"
59+
(drop)="day.dragOver = false; eventDropped(day, $event.dropData.event)">
4660
</mwl-calendar-month-cell>
4761
</div>
4862
<mwl-calendar-open-day-events
@@ -108,6 +122,11 @@ export class CalendarMonthViewComponent implements OnChanges, OnInit, OnDestroy
108122
*/
109123
@Output() eventClicked: EventEmitter<{event: CalendarEvent}> = new EventEmitter<{event: CalendarEvent}>();
110124

125+
/**
126+
* Called when an event is dragged and dropped
127+
*/
128+
@Output() eventTimesChanged: EventEmitter<CalendarEventTimesChangedEvent> = new EventEmitter<CalendarEventTimesChangedEvent>();
129+
111130
/**
112131
* @private
113132
*/
@@ -193,6 +212,22 @@ export class CalendarMonthViewComponent implements OnChanges, OnInit, OnDestroy
193212
});
194213
}
195214

215+
/**
216+
* @private
217+
*/
218+
eventDropped(day: MonthViewDay, event: CalendarEvent): void {
219+
const year: number = getYear(day.date);
220+
const month: number = getMonth(day.date);
221+
const date: number = getDate(day.date);
222+
const newStart: Date = setYear(setMonth(setDate(event.start, date), month), year);
223+
let newEnd: Date;
224+
if (event.end) {
225+
const secondsDiff: number = differenceInSeconds(newStart, event.start);
226+
newEnd = addSeconds(event.end, secondsDiff);
227+
}
228+
this.eventTimesChanged.emit({event, newStart, newEnd});
229+
}
230+
196231
private refreshHeader(): void {
197232
this.columnHeaders = getWeekViewHeader({
198233
viewDate: this.viewDate,

src/components/month/calendarOpenDayEvents.component.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@ import { CalendarEvent } from 'calendar-utils';
1616
<div class="cal-open-day-events" [@collapse] *ngIf="isOpen">
1717
<div
1818
*ngFor="let event of events"
19-
[ngClass]="event?.cssClass">
20-
<span class="cal-event" [style.backgroundColor]="event.color.primary"></span>
19+
[ngClass]="event?.cssClass"
20+
mwlDraggable
21+
[dropData]="{event: event}"
22+
[dragAxis]="{x: event.draggable, y: event.draggable}">
23+
<span
24+
class="cal-event"
25+
[style.backgroundColor]="event.color.primary">
26+
</span>
2127
<mwl-calendar-event-title
2228
[event]="event"
2329
view="month"

0 commit comments

Comments
 (0)