Skip to content

Commit 6641319

Browse files
committed
feat(day-view): allow events to be dragged outside of the view
BREAKING CHANGE: if you were extending the day view component then the internal API has changed slightly and you may need to adjust your app Closes #532
1 parent 1e414cf commit 6641319

File tree

7 files changed

+248
-51
lines changed

7 files changed

+248
-51
lines changed

demos/demo-modules/draggable-external-events/component.ts

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11
import { Component, ChangeDetectionStrategy } from '@angular/core';
22
import {
33
CalendarEvent,
4-
CalendarEventTimesChangedEvent
4+
CalendarEventTimesChangedEvent,
5+
CalendarView
56
} from 'angular-calendar';
67
import { Subject } from 'rxjs';
78
import { colors } from '../demo-utils/colors';
89

910
@Component({
1011
selector: 'mwl-demo-component',
1112
changeDetection: ChangeDetectionStrategy.OnPush,
12-
templateUrl: 'template.html'
13+
templateUrl: 'template.html',
14+
styles: [
15+
`
16+
.drag-active {
17+
position: relative;
18+
z-index: 1;
19+
}
20+
.drag-over {
21+
background-color: #eee;
22+
}
23+
`
24+
]
1325
})
1426
export class DemoComponent {
15-
view: string = 'month';
27+
CalendarView = CalendarView;
1628

17-
viewDate: Date = new Date();
29+
view = CalendarView.Month;
30+
31+
viewDate = new Date();
1832

1933
externalEvents: CalendarEvent[] = [
2034
{
@@ -33,16 +47,16 @@ export class DemoComponent {
3347

3448
events: CalendarEvent[] = [];
3549

36-
activeDayIsOpen: boolean = false;
50+
activeDayIsOpen = false;
3751

38-
refresh: Subject<any> = new Subject();
52+
refresh = new Subject<void>();
3953

4054
eventDropped({
4155
event,
4256
newStart,
4357
newEnd
4458
}: CalendarEventTimesChangedEvent): void {
45-
const externalIndex: number = this.externalEvents.indexOf(event);
59+
const externalIndex = this.externalEvents.indexOf(event);
4660
if (externalIndex > -1) {
4761
this.externalEvents.splice(externalIndex, 1);
4862
this.events.push(event);
@@ -51,7 +65,17 @@ export class DemoComponent {
5165
if (newEnd) {
5266
event.end = newEnd;
5367
}
54-
this.viewDate = newStart;
55-
this.activeDayIsOpen = true;
68+
if (this.view === 'month') {
69+
this.viewDate = newStart;
70+
this.activeDayIsOpen = true;
71+
}
72+
this.events = [...this.events];
73+
}
74+
75+
externalDrop(event: CalendarEvent) {
76+
if (this.externalEvents.indexOf(event) === -1) {
77+
this.events = this.events.filter(iEvent => iEvent !== event);
78+
this.externalEvents.push(event);
79+
}
5680
}
5781
}

demos/demo-modules/draggable-external-events/template.html

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
<div class="row">
22

33
<div class="col-md-3">
4-
<div class="card">
4+
<div
5+
class="card"
6+
mwlDroppable
7+
(drop)="externalDrop($event.dropData.event)"
8+
dragOverClass="drag-over">
59
<div class="card-body">
10+
<p *ngIf="externalEvents.length === 0"><em>No events added</em></p>
611
<ul>
712
<li
813
*ngFor="let event of externalEvents"
914
mwlDraggable
1015
[dropData]="{event: event}"
11-
style="position:relative; z-index: 10">
16+
dragActiveClass="drag-active">
1217
<a
1318
href="javascript:;"
1419
[style.color]="event.color.primary">
@@ -28,28 +33,29 @@
2833

2934
<div [ngSwitch]="view">
3035
<mwl-calendar-month-view
31-
*ngSwitchCase="'month'"
36+
*ngSwitchCase="CalendarView.Month"
3237
[viewDate]="viewDate"
3338
[events]="events"
3439
[activeDayIsOpen]="activeDayIsOpen"
3540
[refresh]="refresh"
3641
(eventTimesChanged)="eventDropped($event)">
3742
</mwl-calendar-month-view>
3843
<mwl-calendar-week-view
39-
*ngSwitchCase="'week'"
44+
*ngSwitchCase="CalendarView.Week"
4045
[viewDate]="viewDate"
4146
[events]="events"
4247
[refresh]="refresh"
4348
(eventTimesChanged)="eventDropped($event)">
4449
</mwl-calendar-week-view>
4550
<mwl-calendar-day-view
46-
*ngSwitchCase="'day'"
51+
*ngSwitchCase="CalendarView.Day"
4752
[viewDate]="viewDate"
4853
[events]="events"
4954
[refresh]="refresh"
55+
[snapDraggedEvents]="false"
5056
(eventTimesChanged)="eventDropped($event)">
5157
</mwl-calendar-day-view>
5258
</div>
5359
</div>
5460

55-
</div>
61+
</div>

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
"@angular/core": ">=6.0.0 <8.0.0"
164164
},
165165
"dependencies": {
166-
"angular-draggable-droppable": "^4.0.0-beta.3",
166+
"angular-draggable-droppable": "^4.0.0-beta.4",
167167
"angular-resizable-element": "^3.0.0",
168168
"calendar-utils": "^0.2.0-alpha.5",
169169
"positioning": "^1.3.1"

src/modules/common/util.ts

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export function isInside(outer: ClientRect, inner: ClientRect): boolean {
2222
);
2323
}
2424

25+
export function roundToNearest(amount: number, precision: number) {
26+
return Math.round(amount / precision) * precision;
27+
}
28+
2529
export const trackByEventId = (index: number, event: CalendarEvent) =>
2630
event.id ? event.id : event;
2731

src/modules/day/calendar-day-view.component.ts

+55-27
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import { CalendarDragHelper } from '../common/calendar-drag-helper.provider';
2525
import { CalendarResizeHelper } from '../common/calendar-resize-helper.provider';
2626
import { CalendarEventTimesChangedEvent } from '../common/calendar-event-times-changed-event.interface';
2727
import { CalendarUtils } from '../common/calendar-utils.provider';
28-
import { validateEvents, trackByEventId } from '../common/util';
28+
import { validateEvents, trackByEventId, roundToNearest } from '../common/util';
2929
import { DateAdapter } from '../../date-adapters/date-adapter';
30+
import { DragEnd } from 'angular-draggable-droppable/draggable.directive';
3031

3132
export interface CalendarDayViewBeforeRenderEvent {
3233
body: {
@@ -64,15 +65,19 @@ export interface DayViewEventResize {
6465
@Component({
6566
selector: 'mwl-calendar-day-view',
6667
template: `
67-
<div class="cal-day-view" #dayViewContainer>
68+
<div class="cal-day-view">
6869
<mwl-calendar-all-day-event
6970
*ngFor="let event of view.allDayEvents; trackBy:trackByEventId"
7071
[event]="event"
7172
[customTemplate]="allDayEventTemplate"
7273
[eventTitleTemplate]="eventTitleTemplate"
7374
(eventClicked)="eventClicked.emit({event: event})">
7475
</mwl-calendar-all-day-event>
75-
<div class="cal-hour-rows">
76+
<div
77+
class="cal-hour-rows"
78+
#dayEventsContainer
79+
mwlDroppable
80+
(drop)="eventDroppedWithinContainer = true">
7681
<div class="cal-events">
7782
<div
7883
#event
@@ -86,15 +91,16 @@ export interface DayViewEventResize {
8691
[resizeEdges]="{top: dayEvent.event?.resizable?.beforeStart, bottom: dayEvent.event?.resizable?.afterEnd}"
8792
[resizeSnapGrid]="{top: eventSnapSize, bottom: eventSnapSize}"
8893
[validateResize]="validateResize"
89-
(resizeStart)="resizeStarted(dayEvent, $event, dayViewContainer)"
94+
(resizeStart)="resizeStarted(dayEvent, $event, dayEventsContainer)"
9095
(resizing)="resizing(dayEvent, $event)"
9196
(resizeEnd)="resizeEnded(dayEvent)"
9297
mwlDraggable
93-
[dragAxis]="{x: false, y: dayEvent.event.draggable && currentResizes.size === 0}"
94-
[dragSnapGrid]="{y: eventSnapSize}"
95-
[validateDrag]="validateDrag"
96-
(dragPointerDown)="dragStart(event, dayViewContainer)"
97-
(dragEnd)="eventDragged(dayEvent, $event.y)"
98+
[dropData]="{event: dayEvent.event, isInternal: true}"
99+
[dragAxis]="{x: !snapDraggedEvents && dayEvent.event.draggable && currentResizes.size === 0, y: dayEvent.event.draggable && currentResizes.size === 0}"
100+
[dragSnapGrid]="snapDraggedEvents ? {y: eventSnapSize} : {}"
101+
[validateDrag]="snapDraggedEvents ? validateDrag : false"
102+
(dragPointerDown)="dragStarted(event, dayEventsContainer)"
103+
(dragEnd)="dragEnded(dayEvent, $event)"
98104
[style.marginTop.px]="dayEvent.top"
99105
[style.height.px]="dayEvent.height"
100106
[style.marginLeft.px]="dayEvent.left + 70"
@@ -226,6 +232,11 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
226232
*/
227233
@Input() eventTitleTemplate: TemplateRef<any>;
228234

235+
/**
236+
* Whether to snap events to a grid when dragging
237+
*/
238+
@Input() snapDraggedEvents: boolean = true;
239+
229240
/**
230241
* Called when an event title is clicked
231242
*/
@@ -280,6 +291,11 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
280291
*/
281292
currentResizes: Map<DayViewEvent, DayViewEventResize> = new Map();
282293

294+
/**
295+
* @hidden
296+
*/
297+
eventDroppedWithinContainer = false;
298+
283299
/**
284300
* @hidden
285301
*/
@@ -379,10 +395,14 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
379395
}
380396

381397
eventDropped(
382-
dropEvent: { dropData?: { event?: CalendarEvent } },
398+
dropEvent: { dropData?: { event?: CalendarEvent; isInternal?: boolean } },
383399
segment: DayViewHourSegment
384400
): void {
385-
if (dropEvent.dropData && dropEvent.dropData.event) {
401+
if (
402+
dropEvent.dropData &&
403+
dropEvent.dropData.event &&
404+
!dropEvent.dropData.isInternal
405+
) {
386406
this.eventTimesChanged.emit({
387407
event: dropEvent.dropData.event,
388408
newStart: segment.date
@@ -393,15 +413,15 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
393413
resizeStarted(
394414
event: DayViewEvent,
395415
resizeEvent: ResizeEvent,
396-
dayViewContainer: HTMLElement
416+
dayEventsContainer: HTMLElement
397417
): void {
398418
this.currentResizes.set(event, {
399419
originalTop: event.top,
400420
originalHeight: event.height,
401421
edge: typeof resizeEvent.edges.top !== 'undefined' ? 'top' : 'bottom'
402422
});
403423
const resizeHelper: CalendarResizeHelper = new CalendarResizeHelper(
404-
dayViewContainer
424+
dayEventsContainer
405425
);
406426
this.validateResize = ({ rectangle }) =>
407427
resizeHelper.validateResize({ rectangle });
@@ -446,29 +466,37 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
446466
this.currentResizes.delete(dayEvent);
447467
}
448468

449-
dragStart(event: HTMLElement, dayViewContainer: HTMLElement): void {
469+
dragStarted(event: HTMLElement, dayEventsContainer: HTMLElement): void {
450470
const dragHelper: CalendarDragHelper = new CalendarDragHelper(
451-
dayViewContainer,
471+
dayEventsContainer,
452472
event
453473
);
454474
this.validateDrag = ({ x, y }) =>
455475
this.currentResizes.size === 0 && dragHelper.validateDrag({ x, y });
476+
this.eventDroppedWithinContainer = false;
456477
this.cdr.markForCheck();
457478
}
458479

459-
eventDragged(dayEvent: DayViewEvent, draggedInPixels: number): void {
460-
const pixelAmountInMinutes: number =
461-
MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight);
462-
const minutesMoved: number = draggedInPixels * pixelAmountInMinutes;
463-
const newStart: Date = this.dateAdapter.addMinutes(
464-
dayEvent.event.start,
465-
minutesMoved
466-
);
467-
let newEnd: Date;
468-
if (dayEvent.event.end) {
469-
newEnd = this.dateAdapter.addMinutes(dayEvent.event.end, minutesMoved);
480+
dragEnded(dayEvent: DayViewEvent, dragEndEvent: DragEnd): void {
481+
if (this.eventDroppedWithinContainer) {
482+
const draggedInPixelsSnapSize = roundToNearest(
483+
dragEndEvent.y,
484+
this.eventSnapSize
485+
);
486+
const pixelAmountInMinutes: number =
487+
MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight);
488+
const minutesMoved: number =
489+
draggedInPixelsSnapSize * pixelAmountInMinutes;
490+
const newStart: Date = this.dateAdapter.addMinutes(
491+
dayEvent.event.start,
492+
minutesMoved
493+
);
494+
let newEnd: Date;
495+
if (dayEvent.event.end) {
496+
newEnd = this.dateAdapter.addMinutes(dayEvent.event.end, minutesMoved);
497+
}
498+
this.eventTimesChanged.emit({ newStart, newEnd, event: dayEvent.event });
470499
}
471-
this.eventTimesChanged.emit({ newStart, newEnd, event: dayEvent.event });
472500
}
473501

474502
private refreshHourGrid(): void {

0 commit comments

Comments
 (0)