Skip to content

Commit

Permalink
feat(week-view): allow customising where events can be dragged
Browse files Browse the repository at this point in the history
Closes #1183
  • Loading branch information
mattlewis92 committed May 23, 2020
1 parent 40a1d31 commit cd12d3c
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type CalendarDayViewBeforeRenderEvent = CalendarWeekViewBeforeRenderEvent
[snapDraggedEvents]="snapDraggedEvents"
[allDayEventsLabelTemplate]="allDayEventsLabelTemplate"
[currentTimeMarkerTemplate]="currentTimeMarkerTemplate"
[validateEventTimesChanged]="validateEventTimesChanged"
(eventClicked)="eventClicked.emit($event)"
(hourSegmentClicked)="hourSegmentClicked.emit($event)"
(eventTimesChanged)="eventTimesChanged.emit($event)"
Expand Down Expand Up @@ -183,6 +184,14 @@ export class CalendarDayViewComponent {
*/
@Input() currentTimeMarkerTemplate: TemplateRef<any>;

/**
* Allow you to customise where events can be dragged and resized to.
* Return true to allow dragging and resizing to the new location, or false to prevent it
*/
@Input() validateEventTimesChanged: (
event: CalendarEventTimesChangedEvent
) => boolean;

/**
* Called when an event title is clicked
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ export interface CalendarWeekViewBeforeRenderEvent extends WeekView {
[dragSnapGrid]="snapDraggedEvents ? { x: dayColumnWidth } : {}"
[validateDrag]="validateDrag"
[touchStartLongPress]="{ delay: 300, delta: 30 }"
(dragStart)="dragStarted(eventRowContainer, event)"
(dragStart)="
dragStarted(eventRowContainer, event, allDayEvent, false)
"
(dragging)="allDayEventDragMove()"
(dragEnd)="dragEnded(allDayEvent, $event, dayColumnWidth)"
>
Expand Down Expand Up @@ -314,7 +316,7 @@ export interface CalendarWeekViewBeforeRenderEvent extends WeekView {
[ghostDragEnabled]="!snapDraggedEvents"
[ghostElementTemplate]="weekEventTemplate"
[validateDrag]="validateDrag"
(dragStart)="dragStarted(dayColumns, event, timeEvent)"
(dragStart)="dragStarted(dayColumns, event, timeEvent, true)"
(dragging)="dragMove(timeEvent, $event)"
(dragEnd)="dragEnded(timeEvent, $event, dayColumnWidth, true)"
>
Expand Down Expand Up @@ -575,6 +577,14 @@ export class CalendarWeekViewComponent
*/
@Input() currentTimeMarkerTemplate: TemplateRef<any>;

/**
* Allow you to customise where events can be dragged and resized to.
* Return true to allow dragging and resizing to the new location, or false to prevent it
*/
@Input() validateEventTimesChanged: (
event: CalendarEventTimesChangedEvent
) => boolean;

/**
* Called when a header week day is clicked. Adding a `cssClass` property on `$event.day` will add that class to the header element
*/
Expand Down Expand Up @@ -1023,37 +1033,56 @@ export class CalendarWeekViewComponent
* @hidden
*/
dragStarted(
eventsContainer: HTMLElement,
event: HTMLElement,
dayEvent?: WeekViewTimeEvent
eventsContainerElement: HTMLElement,
eventElement: HTMLElement,
event: WeekViewTimeEvent | WeekViewAllDayEvent,
useY: boolean
): void {
this.dayColumnWidth = this.getDayColumnWidth(eventsContainer);
this.dayColumnWidth = this.getDayColumnWidth(eventsContainerElement);
const dragHelper: CalendarDragHelper = new CalendarDragHelper(
eventsContainer,
event
eventsContainerElement,
eventElement
);
this.validateDrag = ({ x, y, transform }) =>
this.allDayEventResizes.size === 0 &&
this.timeEventResizes.size === 0 &&
dragHelper.validateDrag({
x,
y,
snapDraggedEvents: this.snapDraggedEvents,
dragAlreadyMoved: this.dragAlreadyMoved,
transform,
});
this.validateDrag = ({ x, y, transform }) => {
const isAllowed =
this.allDayEventResizes.size === 0 &&
this.timeEventResizes.size === 0 &&
dragHelper.validateDrag({
x,
y,
snapDraggedEvents: this.snapDraggedEvents,
dragAlreadyMoved: this.dragAlreadyMoved,
transform,
});
if (isAllowed && this.validateEventTimesChanged) {
const newEventTimes = this.getDragMovedEventTimes(
event,
{ x, y },
this.dayColumnWidth,
useY
);
return this.validateEventTimesChanged({
type: CalendarEventTimesChangedEventType.Drag,
event: event.event,
newStart: newEventTimes.start,
newEnd: newEventTimes.end,
});
}

return isAllowed;
};
this.dragActive = true;
this.dragAlreadyMoved = false;
this.lastDraggedEvent = null;
this.eventDragEnterByType = {
allDay: 0,
time: 0,
};
if (!this.snapDraggedEvents && dayEvent) {
if (!this.snapDraggedEvents && useY) {
this.view.hourColumns.forEach((column) => {
const linkedEvent = column.events.find(
(columnEvent) =>
columnEvent.event === dayEvent.event && columnEvent !== dayEvent
columnEvent.event === event.event && columnEvent !== event
);
// hide any linked events while dragging
if (linkedEvent) {
Expand Down
199 changes: 196 additions & 3 deletions projects/angular-calendar/test/calendar-week-view.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,102 @@ describe('calendarWeekView component', () => {
expect(eventDropped).to.have.been.calledOnce;
});

it('should allow the all day event to be dragged and dropped and control where it can be dragged', () => {
const fixture: ComponentFixture<CalendarWeekViewComponent> = TestBed.createComponent(
CalendarWeekViewComponent
);
fixture.componentInstance.viewDate = new Date('2016-12-08');
fixture.componentInstance.events = [
{
title: 'foo',
start: moment('2016-12-08').add(4, 'hours').toDate(),
end: moment('2016-12-08').add(6, 'hours').toDate(),
draggable: true,
allDay: true,
},
];
fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} });
fixture.detectChanges();
document.body.appendChild(fixture.nativeElement);
// remove the header as it was causing the test to fail
const header: HTMLElement = fixture.nativeElement.querySelector(
'.cal-day-headers'
);
header.parentNode.removeChild(header);
const event: HTMLElement = fixture.nativeElement.querySelector(
'.cal-event-container'
);
const dayWidth: number = event.parentElement.offsetWidth / 7;
const eventPosition: ClientRect = event.getBoundingClientRect();
const eventDropped = sinon.spy();
const validateEventTimesChanged = sinon
.stub()
.onFirstCall()
.returns(true)
.onSecondCall()
.returns(false);
fixture.componentInstance.validateEventTimesChanged = validateEventTimesChanged;
fixture.componentInstance.eventTimesChanged
.pipe(take(1))
.subscribe(eventDropped);
triggerDomEvent('mousedown', event, {
clientX: eventPosition.left,
clientY: eventPosition.top,
button: 0,
});
fixture.detectChanges();
triggerDomEvent('mousemove', document.body, {
clientX: eventPosition.left,
clientY: eventPosition.top,
});
fixture.detectChanges();
triggerDomEvent('mousemove', document.body, {
clientX: eventPosition.left - dayWidth,
clientY: eventPosition.top,
});
fixture.detectChanges();
const ghostElement = event.nextSibling as HTMLElement;
expect(Math.round(ghostElement.getBoundingClientRect().left)).to.equal(
Math.round(eventPosition.left - dayWidth) + 1
);
triggerDomEvent('mousemove', document.body, {
clientX: eventPosition.left - dayWidth * 2,
clientY: eventPosition.top,
});
fixture.detectChanges();
expect(Math.round(ghostElement.getBoundingClientRect().left)).to.equal(
Math.round(eventPosition.left - dayWidth) + 1
);
triggerDomEvent('mouseup', document.body, {
clientX: eventPosition.left - dayWidth * 2,
clientY: eventPosition.top,
button: 0,
});
fixture.detectChanges();
fixture.destroy();
expect(eventDropped.getCall(0).args[0]).to.deep.equal({
type: 'drag',
event: fixture.componentInstance.events[0],
newStart: moment('2016-12-07').add(4, 'hours').toDate(),
newEnd: moment('2016-12-07').add(6, 'hours').toDate(),
allDay: true,
});
expect(eventDropped).to.have.been.calledOnce;
expect(validateEventTimesChanged).to.have.been.calledTwice;
expect(validateEventTimesChanged.getCall(0).args[0]).to.deep.equal({
type: 'drag',
event: fixture.componentInstance.events[0],
newStart: moment('2016-12-07').add(4, 'hours').toDate(),
newEnd: moment('2016-12-07').add(6, 'hours').toDate(),
});
expect(validateEventTimesChanged.getCall(1).args[0]).to.deep.equal({
type: 'drag',
event: fixture.componentInstance.events[0],
newStart: moment('2016-12-06').add(4, 'hours').toDate(),
newEnd: moment('2016-12-06').add(6, 'hours').toDate(),
});
});

it('should allow all day events to be dragged outside of the calendar', () => {
const fixture: ComponentFixture<CalendarWeekViewComponent> = TestBed.createComponent(
CalendarWeekViewComponent
Expand All @@ -709,7 +805,6 @@ describe('calendarWeekView component', () => {
fixture.componentInstance.events = [
{
title: 'foo',
color: { primary: '', secondary: '' },
start: moment('2016-12-08').add(4, 'hours').toDate(),
end: moment('2016-12-08').add(6, 'hours').toDate(),
draggable: true,
Expand Down Expand Up @@ -833,7 +928,6 @@ describe('calendarWeekView component', () => {
fixture.componentInstance.events = [
{
title: 'foo',
color: { primary: '', secondary: '' },
start: moment('2016-06-27').add(4, 'hours').toDate(),
end: moment('2016-06-27').add(6, 'hours').toDate(),
resizable: {
Expand Down Expand Up @@ -887,7 +981,6 @@ describe('calendarWeekView component', () => {
fixture.componentInstance.events = [
{
title: 'foo',
color: { primary: '', secondary: '' },
start: moment('2016-06-27').add(4, 'hours').toDate(),
end: moment('2016-06-27').add(6, 'hours').toDate(),
resizable: {
Expand Down Expand Up @@ -1829,6 +1922,106 @@ describe('calendarWeekView component', () => {
});
});

it('should control dragging time events', () => {
const fixture: ComponentFixture<CalendarWeekViewComponent> = TestBed.createComponent(
CalendarWeekViewComponent
);
fixture.componentInstance.viewDate = new Date('2018-07-29');
fixture.componentInstance.events = [
{
start: moment(new Date('2018-07-29'))
.startOf('day')
.add(3, 'hours')
.toDate(),
end: moment(new Date('2018-07-29'))
.startOf('day')
.add(1, 'day')
.add(18, 'hours')
.toDate(),
title: 'foo',
draggable: true,
},
];
const validateEventTimesChanged = sinon
.stub()
.onFirstCall()
.returns(true)
.onSecondCall()
.returns(false);
fixture.componentInstance.validateEventTimesChanged = validateEventTimesChanged;
fixture.componentInstance.ngOnChanges({ viewDate: {}, events: {} });
fixture.detectChanges();
document.body.appendChild(fixture.nativeElement);
const event = fixture.nativeElement.querySelector('.cal-event-container');
const rect: ClientRect = event.getBoundingClientRect();
let dragEvent: CalendarEventTimesChangedEvent;
fixture.componentInstance.eventTimesChanged.pipe(take(1)).subscribe((e) => {
dragEvent = e;
});
triggerDomEvent('mousedown', event, {
clientX: rect.right,
clientY: rect.bottom,
button: 0,
});
fixture.detectChanges();
triggerDomEvent('mousemove', event, {
clientX: rect.right,
clientY: rect.bottom,
});
fixture.detectChanges();
triggerDomEvent('mousemove', event, {
clientX: rect.right,
clientY: rect.bottom + 95,
});
fixture.detectChanges();
expect(event.getBoundingClientRect().top).to.equal(rect.top + 90);
triggerDomEvent('mousemove', event, {
clientX: rect.right,
clientY: rect.bottom + 180,
});
fixture.detectChanges();
expect(event.getBoundingClientRect().top).to.equal(rect.top + 90);
triggerDomEvent('mouseup', event, {
clientX: rect.right,
clientY: rect.bottom + 180,
button: 0,
});
fixture.detectChanges();
fixture.destroy();
expect(dragEvent).to.deep.equal({
type: 'drag',
allDay: false,
event: fixture.componentInstance.events[0],
newStart: moment(fixture.componentInstance.events[0].start)
.add(90, 'minutes')
.toDate(),
newEnd: moment(fixture.componentInstance.events[0].end)
.add(90, 'minutes')
.toDate(),
});
expect(validateEventTimesChanged).to.have.been.calledTwice;
expect(validateEventTimesChanged.getCall(0).args[0]).to.deep.equal({
type: 'drag',
event: fixture.componentInstance.events[0],
newStart: moment(fixture.componentInstance.events[0].start)
.add(90, 'minutes')
.toDate(),
newEnd: moment(fixture.componentInstance.events[0].end)
.add(90, 'minutes')
.toDate(),
});
expect(validateEventTimesChanged.getCall(1).args[0]).to.deep.equal({
type: 'drag',
event: fixture.componentInstance.events[0],
newStart: moment(fixture.componentInstance.events[0].start)
.add(180, 'minutes')
.toDate(),
newEnd: moment(fixture.componentInstance.events[0].end)
.add(180, 'minutes')
.toDate(),
});
});

it('should drag time events back to their original position while snapping to a grid', () => {
const fixture: ComponentFixture<CalendarWeekViewComponent> = TestBed.createComponent(
CalendarWeekViewComponent
Expand Down
10 changes: 10 additions & 0 deletions projects/demos/app/demo-app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ import { ClipboardModule } from 'ngx-clipboard';
label: 'RTL',
},
},
{
path: 'validate-drag-and-resize',
loadChildren: () =>
import('./demo-modules/validate-drag-and-resize/module').then(
(m) => m.DemoModule
),
data: {
label: 'Validate dragging and resizing',
},
},
{
path: '**',
redirectTo: 'kitchen-sink',
Expand Down
Loading

0 comments on commit cd12d3c

Please sign in to comment.