Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to copy events via ctrl, drag & drop in FullCalendar v5 - pure javascript only

I would like to implement CTRL + Drag & Drop in FullCalendar v5 using PURE JavaScript only.

I did my reseach on topic and found that this was discussed as a new feature UI request on FC github. There are few suggestions how to do that, even working ones.

arshaw posted on Aug 19, 2015

You can use eventDrop to create this feature. jsEvent has a ctrlKey
on which you can test. Copy the event, received as a parameter, in a new variable.
revertFunc to make go back, and then apply renderEvent with the new variable created.

chris-verclytte posted on Apr 11, 2016 does nothing for me

If it can help, here is a little trick I use waiting for this new feature to be integrated.
In eventDrop callback

    // If no alt key pressed we move the event, else we duplicate it
    if (!jsEvent.altKey) {
        // Handle drag'n'drop copy
    } else {
        // Handle drag'n'drop duplication
        // Here I add the event to event source
        _addEventToSelectedSource(event.start, event.end);
        // "Disable" the default behavior using revertFunc to avoid event moving
         revertFunc();
    }
The only problem with this is that the copied event disappears during drag'n'drop due to https://github.com/fullcalendar/fullcalendar/blob/master/src/common/Grid.events.js#L273

I like the best solution by AllyMurray posted on Jul 13, 2018

For anyone that comes across this issue, I have created a solution that should give you a starting point to work from. It uses the same approach as external events and leaves the original event in place.

https://stackoverflow.com/a/51327702/3891834
https://codepen.io/ally-murray/full/JBdaBV/

But I do not know how to implement this solution in pure javascript.

Could anyone help? I prefer the copy to work the way that CTRL press means copy so the original event stays in original position.

jsFiddle

like image 322
Radek Avatar asked May 08 '21 16:05

Radek


1 Answers

I have a minimal solution that works. It consists in cloning the moved event at its original date if the Ctrl key is being held.

To test this snippet, just click into the input at the top of the page before testing, otherwise the result iframe doesn't have the focus and doesn't fire keydown and keyup events.

// Beginning of the workaround for this: https://github.com/fullcalendar/fullcalendar/blob/3e89de5d8206c32b6be326133b6787d54c6fd66c/packages/interaction/src/dnd/PointerDragging.ts#L306
const ctrlKeyDescriptor = Object.getOwnPropertyDescriptor(
  MouseEvent.prototype,
  'ctrlKey'
);

// Always return false for event.ctrlKey when event is of type MouseEvent
ctrlKeyDescriptor.get = function() {
  return false;
};

Object.defineProperty(MouseEvent.prototype, 'ctrlKey', ctrlKeyDescriptor);
// End of the workaround

let calendarEl = document.getElementById('calendar-container');

// Hold the ctrlKey state, emit events to the subscribers when it changes
const ctrlKeyChanged = (function() {
  let ctrlHeld = false;
  let subscriptions = [];
  ['keydown', 'keyup'].forEach(x =>
    document.addEventListener(x, e => {
      // emit only when the key state has changed
      if (ctrlHeld !== e.ctrlKey) subscriptions.forEach(fun => fun(e.ctrlKey));

      ctrlHeld = e.ctrlKey;
    })
  );

  function subscribe(callback) {
    subscriptions.push(callback);
    callback(ctrlHeld); // Emit the current state (case when Ctrl is already being held)
  }

  function unsubscribe(callback) {
    const index = subscriptions.indexOf(callback);
    subscriptions.splice(index, 1);
  }

  return { subscribe, unsubscribe };
})();

const extractEventProperties = ({ title, start, end, allDay }) => ({
  title,
  start,
  end,
  allDay
});

const callbackKey = Symbol();

let calendar = new FullCalendar.Calendar(calendarEl, {
  editable: true,
  droppable: true,
  eventDragStart: e => {
    let event;
    const callback = ctrlKey => {
      if (ctrlKey) {
        event = calendar.addEvent(extractEventProperties(e.event));
      } else {
        event && event.remove();
      }
    };
    ctrlKeyChanged.subscribe(callback);
    // store the callback for further unsubscribe
    e.event.setExtendedProp(callbackKey, callback); 
  },
  // stop listening when the event has been dropped
  eventDragStop: e => ctrlKeyChanged.unsubscribe(e.event.extendedProps[callbackKey]),
  events: [
    {
      title: 'event1',
      start: new Date,
      allDay: true // will make the time show
    },
    {
      title: 'event2',
      start: new Date().setDate(new Date().getDate() + 1),
      end: new Date().setDate(new Date().getDate() + 1)
    },
    {
      title: 'event3',
      start: new Date().setDate(new Date().getDate() - 1),
      allDay: true
    }
  ]
});

calendar.render();
<link href="https://cdn.jsdelivr.net/npm/[email protected]/main.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/main.min.js"></script>

<input placeholder="Click here to give the focus to the result iframe">
<div id="calendar-container"></div>

The main difficulty is that Fullcalendar disables the drag behavior when the Ctrl key is being held:

// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
function isPrimaryMouseButton(ev: MouseEvent) {
  return ev.button === 0 && !ev.ctrlKey
}

You can see this code in the official repository here which is being called here

The workaround consists in mutating MouseEvent.prototype in order to always return false when accessing ctrlKey.

If you're interested, I've made the solution available:

  • As a Stackblitz demo with TypeScript
  • As a full standalone project with TypeScript and Webpack: https://github.com/Guerric-P/fullcalendar-drag-and-drop-copy
like image 97
Guerric P Avatar answered Oct 02 '22 12:10

Guerric P