External element dragging functionality allows you to make DOM elements outside of a calendar draggable onto the calendar, creating new events when dropped. This provides a powerful way to create events by dragging items from lists, sidebars, or other interface elements.
Makes external HTML elements draggable onto FullCalendar instances. Provides built-in visual feedback, touch support, and event data generation.
/**
* Makes an element external to any calendar draggable onto calendars
* @param el - The HTML element to make draggable
* @param settings - Configuration options for drag behavior
*/
export class Draggable {
constructor(el: HTMLElement, settings?: ExternalDraggableSettings);
/** Destroys the draggable instance and removes all event listeners */
destroy(): void;
/** Internal dragging implementation (read-only) */
readonly dragging: FeaturefulElementDragging;
/** Configuration settings (read-only) */
readonly settings: ExternalDraggableSettings;
}Usage Examples:
import { Draggable } from '@fullcalendar/interaction';
// Basic draggable element
const draggableEl = document.getElementById('external-events');
const draggable = new Draggable(draggableEl, {
eventData: {
title: 'My Event',
duration: '02:00'
}
});
// Dynamic event data based on element
const listEl = document.getElementById('event-list');
const listDraggable = new Draggable(listEl, {
itemSelector: '.draggable-item',
eventData: (el) => {
return {
title: el.textContent,
backgroundColor: el.dataset.color,
duration: el.dataset.duration || '01:00'
};
}
});
// Cleanup when done
draggable.destroy();
listDraggable.destroy();Configuration interface for customizing draggable behavior.
interface ExternalDraggableSettings {
/** Event data or function to generate event data when dropped */
eventData?: DragMetaGenerator;
/** CSS selector for draggable items within the element */
itemSelector?: string;
/** Minimum distance in pixels before drag starts */
minDistance?: number;
/** Delay in milliseconds for long press on touch devices */
longPressDelay?: number;
/** Parent element for drag mirror positioning */
appendTo?: HTMLElement;
}Property Details:
Type for specifying event data that will be created when an external element is dropped.
type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput);
interface DragMetaInput {
/** Duration to add to start time when dropped */
startTime?: DurationInput;
/** Duration of the event when dropped */
duration?: DurationInput;
/** Whether to create an event when dropped (default: true) */
create?: boolean;
/** Source ID for event creation */
sourceId?: string;
/** Any additional event properties (title, backgroundColor, etc.) */
[key: string]: any;
}Static Event Data:
const draggable = new Draggable(element, {
eventData: {
duration: '01:30',
title: 'Team Meeting',
backgroundColor: '#3788d8'
}
});Dynamic Event Data:
const draggable = new Draggable(element, {
eventData: (draggedEl) => {
const dataset = draggedEl.dataset;
return {
duration: dataset.duration || '01:00',
title: draggedEl.textContent || 'Untitled Event',
backgroundColor: dataset.color || '#3788d8',
category: dataset.category,
priority: dataset.priority
};
}
});Multiple Draggable Lists:
import { Draggable } from '@fullcalendar/interaction';
// Different types of events from different lists
const taskList = new Draggable(document.getElementById('tasks'), {
itemSelector: '.task-item',
eventData: (el) => ({
title: el.querySelector('.task-title').textContent,
backgroundColor: '#ff6b6b',
extendedProps: { type: 'task' }
})
});
const meetingList = new Draggable(document.getElementById('meetings'), {
itemSelector: '.meeting-item',
eventData: (el) => ({
title: el.querySelector('.meeting-title').textContent,
duration: '01:00',
backgroundColor: '#4ecdc4',
extendedProps: { type: 'meeting' }
})
});Touch-Optimized Configuration:
const mobileDraggable = new Draggable(element, {
minDistance: 10, // Small movement threshold for touch
longPressDelay: 800, // 800ms press to start drag
itemSelector: '.drag-handle' // Specific drag handles for precise control
});Custom Drag Mirror Positioning:
const constrainedDraggable = new Draggable(element, {
appendTo: document.getElementById('drag-container'), // Constrain mirror to container
eventData: { title: 'Constrained Event' }
});Cleanup and Memory Management:
import { Draggable } from '@fullcalendar/interaction';
// Always destroy draggable instances when no longer needed
const draggables = [];
function setupDraggables() {
const elements = document.querySelectorAll('.draggable-item');
elements.forEach(el => {
const draggable = new Draggable(el, {
eventData: { title: el.textContent }
});
draggables.push(draggable);
});
}
function cleanupDraggables() {
// Essential for preventing memory leaks
draggables.forEach(draggable => draggable.destroy());
draggables.length = 0;
}
// Clean up when component unmounts or page unloads
window.addEventListener('beforeunload', cleanupDraggables);Error Handling for Dynamic Event Data:
const safeDraggable = new Draggable(element, {
eventData: (el) => {
try {
// Safe parsing of element data
const data = JSON.parse(el.dataset.eventConfig || '{}');
return {
title: data.title || el.textContent || 'Untitled Event',
duration: data.duration || '01:00',
backgroundColor: data.color || '#3788d8',
// Provide fallbacks for all properties
extendedProps: {
source: 'external',
originalId: el.id,
...data.props
}
};
} catch (error) {
console.warn('Failed to parse event data:', error);
// Return safe fallback data
return {
title: el.textContent || 'Untitled Event',
duration: '01:00',
backgroundColor: '#ccc'
};
}
}
});Handling Dynamic DOM Changes:
// When DOM structure changes, recreate draggables
function refreshDraggables() {
// Clean up existing instances
cleanupDraggables();
// Check if elements still exist before creating new instances
const elements = document.querySelectorAll('.draggable-item');
if (elements.length === 0) {
console.warn('No draggable elements found');
return;
}
// Recreate draggables
setupDraggables();
}
// Use MutationObserver for automatic refresh
const observer = new MutationObserver((mutations) => {
let shouldRefresh = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
shouldRefresh = true;
}
});
if (shouldRefresh) {
refreshDraggables();
}
});
observer.observe(document.getElementById('draggable-container'), {
childList: true,
subtree: true
});External dragging works seamlessly with FullCalendar's event system:
const calendar = new Calendar(calendarEl, {
plugins: [interactionPlugin],
// Handle successful drops
drop: (info) => {
console.log('External element dropped:', info.draggedEl);
console.log('Drop location:', info.dateStr);
// Optionally remove the dragged element from its original location
info.draggedEl.parentNode?.removeChild(info.draggedEl);
},
// Handle event creation from external drag
eventReceive: (info) => {
console.log('New event created:', info.event.title);
console.log('Event data:', info.event.extendedProps);
}
});