Search code examples
angularfullcalendarprimengprimeng-calendar

Unable to drag and drop external events into FullCalendar dayGridView


I am going crazy with a very strange problem that I am facin using PrimeNG FullCalendar component, this one: https://primefaces.org/primeng/showcase/#/fullcalendar

That is based on the Angular FullCalendar component, this one: https://fullcalendar.io/

In particular I am trying to implement a behavior like the last link where draggable event are mooved into the calendar.

Differently from the link example my draggable events have a start and an end time. For example I want drag an evet into a specific day of my calendar specifying that it start at 07:00 and that end at 15:00 (this because my calendar represents a worki shift calendar where put person working in a specific time range).

Here you can find my entire code: https://bitbucket.org/dgs_poste_team/soc_calendar/src/master/

Ok as you can see in the app-component view I have:

<div class="container">
  <div class="row">
    <div class="col-12">
      <app-header></app-header>
    </div>

    <div class="row">
      <div class="col-4">
        <p>DRAGGABLE</p>
        <app-people-list></app-people-list>
      </div>
      <div class="col-8">
        <app-fullcalendar></app-fullcalendar>
      </div>
    </div>
  </div>
</div>

On the left there is the <app-people-list> component showing the list of event\people that I can drag into my calendar for a specific work shift.

On the right side I have the <app-fullcalendar> component rendering my calendar.

Into the PeopleListComponent class (the controller of the app-people-list component) I have this ngAfterViewInit method:

ngAfterViewInit() {
  console.log("PEOPLE LIST ngAfterViewInit() START !!!")
  var self = this

  new Draggable(this.draggablePeopleExternalElement.nativeElement, {
    itemSelector: '.fc-event',
    eventData: function(eventEl) {
      console.log("DRAG !!!");
      console.log("SELECTED SHIFT: " + self.selectedShift.value);

      //var returnedEvent = self.createEventObject(self.selectedShift.value, eventEl.innerText);

      //return returnedEvent;

      var returnedEvent = {
        title: eventEl.innerText,
        startTime: "18:00",
        duration: {
          hours: 8
        }
      };

      return returnedEvent;
    }
  });

}

As you can see it is creating a Draggable object used to drag an event\person of my list into the calendar. At the moment it is returning an object like this:

var returnedEvent = {
  title: eventEl.innerText,
  startTime: "18:00",
  duration: { hours: 8 }
};

where I put the title of the person\event (it works fine). Then I put (at the moment it is static, then I will make it dynamic) the startTime that is starting time of the event (for example: I drag the person\event into a specific day, it means that this event have to start at the 18:00 of the day in which the event was dragged) and the duration meaning that the event end 8 hours after the starting time.

Ok, in this case it seems to works "fine", in fact when I drag a person\event into a specific day it appears in the calendar, here is a Screenshot:

enter image description here

For example in the previous screenshot I dragged the PERSONA 2 event into the day having date 22 Februrary 2017 and it is here (the other events came from a static array of events defined into my code)

First strange thing noticed: I expected that thus event starts with something that indicates the starting time (as for the events set for the 9 February that starts with 4p indicating that this event starts at 16:00 o'clock of this specific day).

Ok then the very strange thing: if I change the startTime property of my returnedEvent object with a value that is less than 16:00 it doesn't work anymore !!!

For example, using:

var returnedEvent = {
  title: eventEl.innerText,
  startTime: "16:00",
  duration: {hours: 8}
};

that means: the event start at 4pm of the selected day in the calendar and end at 00:00 of the same day. When I drag this event into the calendar the event doesn't appear on the calendar. It is not rendered !!! It happens for all the startTime previous 17:00, and I can't understand why. Into the JavaScript console I am obtaining no error message.

For completeness, into the FullcalendarComponent class (the class that controll the full calendar component) I have:

@Component({
  selector: 'app-fullcalendar',
  templateUrl: './fullcalendar.component.html',
  styleUrls: ['./fullcalendar.component.css']
})
export class FullcalendarComponent implements OnInit {

  events: any[];
  options: any;
  header: any;

  //people: any[];

  @ViewChild('fullcalendar') fullcalendar: FullCalendar;


  constructor(private eventService: EventService) {}

  ngOnInit() {
    this.eventService.getEvents().then(events => {
      this.events = events;
    });

    this.options = {
      plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
      defaultDate: '2017-02-01',
      header: {
        left: 'prev,next',
        center: 'title',
        right: 'dayGridMonth,timeGridWeek,timeGridDay'
      },
      editable: true,

      dateClick: (dateClickEvent) => { // <-- add the callback here as one of the properties of `options`
        console.log("DATE CLICKED !!!");
      },

      eventClick: (eventClickEvent) => {
        console.log("EVENT CLICKED !!!");
      },

      eventDragStop: (eventDragStopEvent) => {
        console.log("EVENT DRAG STOP !!!");
      },

      eventReceive: (eventReceiveEvent) => {
        //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected shift: " + eventReceiveEvent.selectedShift);
        //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected start: " + eventReceiveEvent.event.start);

        console.log(eventReceiveEvent);
        this.eventService.addEvent(eventReceiveEvent);
      }
    };

  }

}

As you can see it contains this method:

eventReceive: (eventReceiveEvent) => {
  //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected shift: " + eventReceiveEvent.selectedShift);
  //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected start: " + eventReceiveEvent.event.start);

  console.log(eventReceiveEvent);
  this.eventService.addEvent(eventReceiveEvent);
}

that receive the previous event emitted by the people list component and put this event into an array (calling a service), it works fine.

I am really not understanding what could be wrong with my code and why using a startTime value < "17:00" it is not working.

What is wrong? What am I missing? How can I change my code to solve this issue?


Solution

  • When dragging an event into a dayGridMonth view, FullCalendar doesn't respect allDay value set in original event definition and always sets allDay: true. That means, by default, events dragged into a dayGridMonth view are considered/supposed to be all-day events.

    However, when you create a timed event with startTime and duration set, that event is not an all-day event any more. And when you drag such an event into a dayGridMonth view FullCalendar doesn't allow to drop that event into the view.

    What you describe as strangest thing is that, when you create following event;

    var returnedEvent = {
      title: eventEl.innerText,
      startTime: "16:00",
      duration: {hours: 8}
    };
    

    this events end time is after today and FullCalendar considers this as an all-day event. And since this is an all-day event it is allowed to drop it into an all-day slot.

    However, when you create following event;

    var returnedEvent = {
      title: eventEl.innerText,
      startTime: "15:00",
      duration: {hours: 8}
    };
    

    this events end time is today and FullCalendar considers this not as an all-day event but a timed event. And since this is not an all-day event it is not allowed to drop it into an all-day slot.

    You can observe same behavior by switching to week or day views and try dropping events into the all-day row at the top. It doesn't allow to drop timed-events into that row while it allows to drop timed-events into any other row.

    As a result; you need a way to workaround for FullCalendar to allow timed events to be dropped into an all-day slot. Simple as said :) it is enough to convert all-day events into timed-events after events are parsed by FullCalendar. This can be done in eventReceive callback.

    eventReceive: receivedEvent => {
      receivedEvent.event.setAllDay(false, {maintainDuration: true})
    }
    

    Long story short, in your FullcalendarComponent;

    eventReceive: (eventReceiveEvent) => {
      //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected shift: " + eventReceiveEvent.selectedShift);
      //console.log("EXTERNAL EVENT LAND ON THE CALENDAR. Title: " + eventReceiveEvent.event.title + " Selected start: " + eventReceiveEvent.event.start);
    
      //console.log(eventReceiveEvent);
      eventReceiveEvent.event.setAllDay(false, {maintainDuration: true})
      this.eventService.addEvent(eventReceiveEvent);
    }
    

    But beware that this will affect all dragged events. So, you might want to set all-day to false based on a condition that can be determined by some flag defined during initial event creation. This very much depends on your use case.

    Related docs:
    https://fullcalendar.io/docs/eventReceive
    https://fullcalendar.io/docs/external-dragging
    https://fullcalendar.io/docs/event-parsing
    https://fullcalendar.io/docs/Event-setAllDay
    https://fullcalendar.io/docs/allDayMaintainDuration

    I hope it helps, good luck.