Skip to content

Commit 95b9033

Browse files
author
Matt Lewis
committed
feat(dayView): allow resizing of events
BREAKING CHANGE: A dependency on the `angular-resizable-element` library has now been added. System.js users will need to add this to their config ``` 'angular-resizable-element': 'npm:angular-resizable-element/dist/umd/angular-resizable-element.js', ``` Part of #9
1 parent cce5136 commit 95b9033

9 files changed

+205
-9
lines changed

demo/demo.component.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
addWeeks,
1010
subWeeks,
1111
addMonths,
12-
subMonths
12+
subMonths,
13+
addHours
1314
} from 'date-fns';
15+
import { Subject } from 'rxjs/Subject';
1416
import {
1517
CalendarEvent,
16-
CalendarEventAction
18+
CalendarEventAction,
19+
CalendarEventTimesChangedEvent
1720
} from './../src'; // import should be from `angular2-calendar` in your app
1821

1922
const colors: any = {
@@ -74,18 +77,22 @@ const colors: any = {
7477
*ngSwitchCase="'month'"
7578
[viewDate]="viewDate"
7679
[events]="events"
80+
[refresh]="refresh"
7781
[activeDayIsOpen]="activeDayIsOpen"
7882
(dayClicked)="dayClicked($event.day)">
7983
</mwl-calendar-month-view>
8084
<mwl-calendar-week-view
8185
*ngSwitchCase="'week'"
8286
[viewDate]="viewDate"
83-
[events]="events">
87+
[events]="events"
88+
[refresh]="refresh">
8489
</mwl-calendar-week-view>
8590
<mwl-calendar-day-view
8691
*ngSwitchCase="'day'"
8792
[viewDate]="viewDate"
88-
[events]="events">
93+
[events]="events"
94+
[refresh]="refresh"
95+
(eventTimesChanged)="eventTimesChanged($event)">
8996
</mwl-calendar-day-view>
9097
</div>
9198
</div>
@@ -109,6 +116,8 @@ export class DemoComponent {
109116
}
110117
}];
111118

119+
refresh: Subject<any> = new Subject();
120+
112121
events: CalendarEvent[] = [{
113122
start: subDays(startOfDay(new Date()), 1),
114123
end: addDays(new Date(), 1),
@@ -125,6 +134,16 @@ export class DemoComponent {
125134
end: addDays(endOfMonth(new Date()), 3),
126135
title: 'A long event that spans 2 months',
127136
color: colors.blue
137+
}, {
138+
start: addHours(startOfDay(new Date()), 2),
139+
end: new Date(),
140+
title: 'A resizable event',
141+
color: colors.yellow,
142+
actions: this.actions,
143+
resizable: {
144+
beforeStart: true,
145+
afterEnd: true
146+
}
128147
}];
129148

130149
activeDayIsOpen: boolean = true;
@@ -172,4 +191,10 @@ export class DemoComponent {
172191
}
173192
}
174193

194+
eventTimesChanged({event, newStart, newEnd}: CalendarEventTimesChangedEvent): void {
195+
event.start = newStart;
196+
event.end = newEnd;
197+
this.refresh.next();
198+
}
199+
175200
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@
124124
"dependencies": {
125125
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.0",
126126
"@types/date-fns": "0.0.2",
127-
"calendar-utils": "0.0.35",
127+
"angular-resizable-element": "~0.5.0",
128+
"calendar-utils": "0.0.36",
128129
"date-fns": "^1.3.0"
129130
}
130131
}

src/calendar.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NgModule, ModuleWithProviders } from '@angular/core';
22
import { CommonModule } from '@angular/common';
3+
import { ResizableModule } from 'angular-resizable-element';
34
import { CalendarDayViewComponent } from './components/day/calendarDayView.component';
45
import { CalendarWeekViewComponent } from './components/week/calendarWeekView.component';
56
import { CalendarMonthViewComponent } from './components/month/calendarMonthView.component';
@@ -37,7 +38,7 @@ import { CalendarDateFormatter } from './providers/calendarDateFormatter.provide
3738
CalendarDate,
3839
CalendarEventTitlePipe
3940
],
40-
imports: [CommonModule],
41+
imports: [CommonModule, ResizableModule],
4142
exports: [CalendarDayViewComponent, CalendarWeekViewComponent, CalendarMonthViewComponent, CalendarDate],
4243
entryComponents: [CalendarTooltipWindowComponent]
4344
})

src/components/day/calendarDayView.component.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ const SEGMENT_HEIGHT: number = 30;
3232
<mwl-calendar-day-view-event
3333
*ngFor="let dayEvent of view?.events"
3434
[dayEvent]="dayEvent"
35-
(eventClicked)="eventClicked.emit({event: dayEvent.event})">
35+
[hourSegments]="hourSegments"
36+
[eventSnapSize]="eventSnapSize"
37+
(eventClicked)="eventClicked.emit({event: dayEvent.event})"
38+
(eventResized)="eventTimesChanged.emit($event)">
3639
</mwl-calendar-day-view-event>
3740
</div>
3841
<div class="cal-hour" *ngFor="let hour of hours" [style.minWidth.px]="view?.width + 70">
@@ -105,6 +108,11 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
105108
*/
106109
@Input() hourSegmentModifier: Function;
107110

111+
/**
112+
* The grid size to snap resizing and dragging of events to
113+
*/
114+
@Input() eventSnapSize: number = 30;
115+
108116
/**
109117
* Called when an event title is clicked
110118
*/
@@ -115,6 +123,12 @@ export class CalendarDayViewComponent implements OnChanges, OnInit, OnDestroy {
115123
*/
116124
@Output() hourSegmentClicked: EventEmitter<{date: Date}> = new EventEmitter<{date: Date}>();
117125

126+
/**
127+
* Called when an event is resized or dragged and dropped
128+
*/
129+
@Output() eventTimesChanged: EventEmitter<{newStart: Date, newEnd: Date, event: CalendarEvent}>
130+
= new EventEmitter<{newStart: Date, newEnd: Date, event: CalendarEvent}>();
131+
118132
hours: DayViewHour[] = [];
119133
view: DayView;
120134
width: number = 0;

src/components/day/calendarDayViewEvent.component.ts

+63-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
22
import { DayViewEvent } from 'calendar-utils';
3+
import { ResizeEvent } from 'angular-resizable-element';
4+
import addMinutes from 'date-fns/add_minutes';
35

46
@Component({
57
selector: 'mwl-calendar-day-view-event',
@@ -15,7 +17,13 @@ import { DayViewEvent } from 'calendar-utils';
1517
[style.borderColor]="dayEvent.event.color.primary"
1618
[class.cal-starts-within-day]="!dayEvent.startsBeforeDay"
1719
[class.cal-ends-within-day]="!dayEvent.endsAfterDay"
18-
[ngClass]="dayEvent.event.cssClass">
20+
[ngClass]="dayEvent.event.cssClass"
21+
mwlResizable
22+
[resizeEdges]="{top: dayEvent.event?.resizable?.beforeStart, bottom: dayEvent.event?.resizable?.afterEnd}"
23+
[resizeSnapGrid]="{top: eventSnapSize, bottom: eventSnapSize}"
24+
(resizeStart)="resizeStarted(dayEvent, $event)"
25+
(resizing)="resizing(dayEvent, $event)"
26+
(resizeEnd)="resizeEnded(dayEvent)">
1927
<mwl-calendar-event-title
2028
[event]="dayEvent.event"
2129
view="day"
@@ -29,6 +37,60 @@ export class CalendarDayViewEventComponent {
2937

3038
@Input() dayEvent: DayViewEvent;
3139

40+
@Input() hourSegments: number;
41+
42+
@Input() eventSnapSize: number;
43+
3244
@Output() eventClicked: EventEmitter<any> = new EventEmitter();
3345

46+
@Output() eventResized: EventEmitter<any> = new EventEmitter();
47+
48+
currentResize: {
49+
originalTop: number,
50+
originalHeight: number,
51+
edge: string
52+
};
53+
54+
resizeStarted(event: DayViewEvent, resizeEvent: ResizeEvent): void {
55+
this.currentResize = {
56+
originalTop: event.top,
57+
originalHeight: event.height,
58+
edge: typeof resizeEvent.edges.top !== 'undefined' ? 'top' : 'bottom'
59+
};
60+
}
61+
62+
resizing(event: DayViewEvent, resizeEvent: ResizeEvent): void {
63+
if (resizeEvent.edges.top) {
64+
event.top = this.currentResize.originalTop + +resizeEvent.edges.top;
65+
} else if (resizeEvent.edges.bottom) {
66+
event.height = this.currentResize.originalHeight + +resizeEvent.edges.bottom;
67+
}
68+
}
69+
70+
resizeEnded(dayEvent: DayViewEvent): void {
71+
72+
let segments: number;
73+
if (this.currentResize.edge === 'top') {
74+
segments = (dayEvent.top - this.currentResize.originalTop) / this.eventSnapSize;
75+
} else {
76+
segments = (dayEvent.height - this.currentResize.originalHeight) / this.eventSnapSize;
77+
}
78+
79+
dayEvent.top = this.currentResize.originalTop;
80+
dayEvent.height = this.currentResize.originalHeight;
81+
82+
const segmentAmountInMinutes: number = 60 / this.hourSegments;
83+
const minutesMoved: number = segments * segmentAmountInMinutes;
84+
let newStart: Date = dayEvent.event.start;
85+
let newEnd: Date = dayEvent.event.end;
86+
if (this.currentResize.edge === 'top') {
87+
newStart = addMinutes(newStart, minutesMoved);
88+
} else if (newEnd) {
89+
newEnd = addMinutes(newEnd, minutesMoved);
90+
}
91+
92+
this.eventResized.emit({newStart, newEnd, event: dayEvent.event});
93+
94+
}
95+
3496
}

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './providers/calendarMomentDateFormatter.provider';
33
export * from './providers/calendarNativeDateFormatter.provider';
44
export * from './providers/calendarDateFormatter.provider';
55
export * from './interfaces/calendarDateFormatter.interface';
6+
export * from './interfaces/calendarEventTimesChangedEvent.interface';
67
export * from './calendar.module';
78
export {CalendarEvent, EventAction as CalendarEventAction} from 'calendar-utils';
89

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CalendarEvent } from 'calendar-utils';
2+
3+
export interface CalendarEventTimesChangedEvent {
4+
event: CalendarEvent;
5+
newStart: Date;
6+
newEnd: Date;
7+
}

test/calendarDayView.component.spec.ts

+76-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import {
1212
CalendarMomentDateFormatter,
1313
CalendarDateFormatter,
1414
CalendarModule,
15-
MOMENT
15+
MOMENT,
16+
CalendarEventTimesChangedEvent
1617
} from './../src';
1718
import { CalendarDayViewComponent } from './../src/components/day/calendarDayView.component';
1819
import { Subject } from 'rxjs/Rx';
1920
import { spy } from 'sinon';
21+
import { triggerDomEvent } from './util';
2022

2123
describe('CalendarDayViewComponent component', () => {
2224

@@ -248,4 +250,77 @@ describe('CalendarDayViewComponent component', () => {
248250
fixture.destroy();
249251
});
250252

253+
it('should resize the event by dragging from the top edge', () => {
254+
const fixture: ComponentFixture<CalendarDayViewComponent> = TestBed.createComponent(CalendarDayViewComponent);
255+
fixture.componentInstance.viewDate = new Date('2016-06-27');
256+
fixture.componentInstance.events = [{
257+
title: 'foo',
258+
color: {primary: '', secondary: ''},
259+
start: moment('2016-06-27').add(4, 'hours').toDate(),
260+
end: moment('2016-06-27').add(6, 'hours').toDate(),
261+
resizable: {
262+
beforeStart: true
263+
}
264+
}];
265+
fixture.componentInstance.ngOnChanges({viewDate: {}, events: {}});
266+
fixture.detectChanges();
267+
document.body.appendChild(fixture.nativeElement);
268+
const event: HTMLElement = fixture.nativeElement.querySelector('.cal-event');
269+
const rect: ClientRect = event.getBoundingClientRect();
270+
let resizeEvent: CalendarEventTimesChangedEvent;
271+
fixture.componentInstance.eventTimesChanged.subscribe(event => {
272+
resizeEvent = event;
273+
});
274+
triggerDomEvent('mousedown', document.body, {clientY: rect.top, clientX: rect.left + 10});
275+
fixture.detectChanges();
276+
triggerDomEvent('mousemove', document.body, {clientY: rect.top - 30, clientX: rect.left + 10});
277+
fixture.detectChanges();
278+
expect(event.getBoundingClientRect().top).to.equal(rect.top - 30);
279+
triggerDomEvent('mouseup', document.body, {clientY: rect.top - 30, clientX: rect.left + 10});
280+
fixture.detectChanges();
281+
fixture.destroy();
282+
expect(resizeEvent).to.deep.equal({
283+
event: fixture.componentInstance.events[0],
284+
newStart: moment('2016-06-27').add(4, 'hours').subtract(30, 'minutes').toDate(),
285+
newEnd: moment('2016-06-27').add(6, 'hours').toDate()
286+
});
287+
});
288+
289+
it('should resize the event by dragging from the bottom edge', () => {
290+
const fixture: ComponentFixture<CalendarDayViewComponent> = TestBed.createComponent(CalendarDayViewComponent);
291+
fixture.componentInstance.viewDate = new Date('2016-06-27');
292+
fixture.componentInstance.events = [{
293+
title: 'foo',
294+
color: {primary: '', secondary: ''},
295+
start: moment('2016-06-27').add(4, 'hours').toDate(),
296+
end: moment('2016-06-27').add(6, 'hours').toDate(),
297+
resizable: {
298+
afterEnd: true
299+
}
300+
}];
301+
fixture.componentInstance.ngOnChanges({viewDate: {}, events: {}});
302+
fixture.detectChanges();
303+
document.body.appendChild(fixture.nativeElement);
304+
const event: HTMLElement = fixture.nativeElement.querySelector('.cal-event');
305+
const rect: ClientRect = event.getBoundingClientRect();
306+
let resizeEvent: CalendarEventTimesChangedEvent;
307+
fixture.componentInstance.eventTimesChanged.subscribe(event => {
308+
resizeEvent = event;
309+
});
310+
triggerDomEvent('mousedown', document.body, {clientY: rect.bottom, clientX: rect.left + 10});
311+
fixture.detectChanges();
312+
triggerDomEvent('mousemove', document.body, {clientY: rect.bottom + 30, clientX: rect.left + 10});
313+
fixture.detectChanges();
314+
expect(event.getBoundingClientRect().bottom).to.equal(rect.bottom + 30);
315+
expect(event.getBoundingClientRect().height).to.equal(150);
316+
triggerDomEvent('mouseup', document.body, {clientY: rect.top + 30, clientX: rect.left + 10});
317+
fixture.detectChanges();
318+
fixture.destroy();
319+
expect(resizeEvent).to.deep.equal({
320+
event: fixture.componentInstance.events[0],
321+
newStart: moment('2016-06-27').add(4, 'hours').toDate(),
322+
newEnd: moment('2016-06-27').add(6, 'hours').add(30, 'minutes').toDate()
323+
});
324+
});
325+
251326
});

webpack.config.umd.ts

+10
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ module.exports = {
5959
commonjs: 'date-fns/get_iso_week/index',
6060
commonjs2: 'date-fns/get_iso_week/index'
6161
},
62+
'date-fns/add_minutes': {
63+
root: ['dateFns', 'addMinutes'],
64+
commonjs: 'date-fns/add_minutes/index',
65+
commonjs2: 'date-fns/add_minutes/index'
66+
},
67+
'angular-resizable-element': {
68+
root: 'angularResizableElement',
69+
commonjs: 'angular-resizable-element',
70+
commonjs2: 'angular-resizable-element'
71+
}
6272
},
6373
module: {
6474
rules: [{

0 commit comments

Comments
 (0)