I have a calendar and different types of events. I can drag and drop these events into my calendar. Now I want to resize the events onto multiple days, then let go and automatically save the start and end of the event into my database. I can’t get it working properly, how I want it.
This are my tables, i use SQLite3:
CREATE TABLE "event_entwicklung" (
"id" INTEGER,
"title" TEXT NOT NULL,
"start" TEXT NOT NULL,
"end" TEXT,
"description" TEXT,
"background_color" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
);
This is the PHP file where my calendar is loaded:
<?php
session_start();
include_once "../../../Backend/database.php";
include_once "lb_kalender.php";
$db = new Database();
?>
<!DOCTYPE html>
<html lang='en'>
<body data-username="<?php echo htmlspecialchars($_SESSION['username']); ?>">
<div class="background-blur">
<div id="calendar-container">
<div id="calendar"></div>
</div>
<div class='Kalender_übersicht_links'>
<h3>Ereignis-Liste</h3>
<ul id="external-events" class="event-list">
</ul>
</div>
</body>
</html>
This is the JS file:
document.addEventListener('DOMContentLoaded', function() {
var username = document.body.dataset.username;
var calendarEl = document.getElementById('calendar');
var calendarContainer = document.getElementById('calendar-container');
function resizeCalendar() {
const calendarContainer = document.getElementById('calendar-container');
const leftDiv = document.querySelector('.kalender_übersicht_links');
const topDiv = document.querySelector('.kalender_übersicht_oben');
const leftWidth = leftDiv ? leftDiv.offsetWidth : 0;
const topHeight = topDiv ? topDiv.offsetHeight : 0;
calendarContainer.style.width = `${window.innerWidth - leftWidth}px`;
calendarContainer.style.height = `${window.innerHeight - topHeight}px`;
calendarContainer.style.left = `${leftWidth}px`;
calendarContainer.style.top = `${topHeight}px`;
}
window.addEventListener('resize', resizeCalendar);
resizeCalendar();
function getCalendarType() {
const path = window.location.pathname;
if (path.includes('kalender_entwicklung')) return 'entwicklung';
if (path.includes('kalender_support')) return 'support';
if (path.includes('kalender_gesamt')) return 'gesamt';
return 'entwicklung';
}
const calendarType = getCalendarType();
var calendar = new FullCalendar.Calendar(calendarEl, {
locale: 'de',
themeSystem: 'bootstrap',
droppable: true,
selectable: true,
editable: true,
eventResizableFromStart: true,
navLinks: true,
weekNumbers: true,
firstDay: 1,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'multiMonthYear,dayGridMonth'
},
views: {
multiMonthYear: {
type: 'multiMonth',
duration: { years: 1 },
buttonText: 'Year'
},
dayGridMonth: {
buttonText: 'Month'
},
},
viewDidMount: function(view) {
if (view.view.type === 'multiMonthYear') {
calendarContainer.style.width = '90%';
calendarContainer.style.height = '90%';
} else {
calendarContainer.style.width = '90%';
calendarContainer.style.height = '90%';
}
},
eventClick: function(info) {
if (info.event.extendedProps && info.event.extendedProps.isHoliday) {
Swal.fire({
title: info.event.title,
text: 'Dies ist ein gesetzlicher Feiertag. Das heißt keine Arbeit :)',
icon: 'info',
confirmButtonText: 'OK'
});
return;
}
const eventTitle = info.event.title || '';
const usernameLower = username.trim().toLowerCase();
const isOwnEvent =
eventTitle.trim().toLowerCase().endsWith(' - ' + usernameLower) ||
eventTitle.trim().toLowerCase() === usernameLower;
if (!isOwnEvent) {
Swal.fire({
title: 'Nicht erlaubt',
text: 'Sie können nur Ihre eigenen Ereignisse bearbeiten.',
icon: 'warning',
confirmButtonText: 'OK'
});
return;
}
info.jsEvent.preventDefault();
function getDateInputValue(dateObj) {
if (!dateObj) {
const now = new Date();
return now.toISOString().slice(0, 10);
}
if (dateObj instanceof Date) {
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
if (typeof dateObj === 'string') {
return dateObj.slice(0, 10);
}
return '';
}
const startValue = getDateInputValue(info.event.start);
const endValue = '';
const modal = document.createElement('div');
modal.className = 'event-modal';
modal.innerHTML = `
<div class="modal-content">
<h3>Event bearbeiten</h3>
<label for="event-title">Titel:</label>
<input type="text" id="event-title" value="${info.event.title}">
<label for="event-description">Beschreibung:</label>
<textarea id="event-description">${info.event.extendedProps.description || ''}</textarea>
<label for="event-start">Start:</label>
<input type="date" id="event-start" value="${startValue}">
<label for="event-end">Ende:</label>
<input type="date" id="event-end" value="${endValue}">
<div class="button-container">
<button id="save-event">Speichern</button>
<button id="delete-event">Löschen</button>
</div>
<button id="close-modal">Schließen</button>
</div>
`;
document.body.appendChild(modal);
document.getElementById('save-event').addEventListener('click', function() {
const newTitle = document.getElementById('event-title').value;
const newDescription = document.getElementById('event-description').value;
const newStart = document.getElementById('event-start').value;
const newEnd = document.getElementById('event-end').value;
function dateToISO(dateStr) {
if (!dateStr) return null;
return dateStr + 'T00:00:00';
}
const startISO = dateToISO(newStart);
const endISO = dateToISO(newEnd);
Swal.fire({
title: 'Speichern',
text: 'Möchten Sie die Änderungen speichern?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Ja, speichern',
cancelButtonText: 'Abbrechen'
}).then((result) => {
if (result.isConfirmed) {
info.event.setProp('title', newTitle);
info.event.setExtendedProp('description', newDescription);
info.event.setStart(startISO ? startISO : null);
info.event.setEnd(endISO ? endISO : null);
const backgroundColor = info.event.backgroundColor || info.event.extendedProps.backgroundColor || null;
fetch('../../API/update_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: info.event.id,
title: newTitle,
description: newDescription,
calendarType: calendarType,
start: startISO ? startISO : null,
end: endISO ? endISO : null,
background_color: backgroundColor
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire('Gespeichert!', 'Die Änderungen wurden gespeichert.', 'success');
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Fehler!', 'Es gab ein Problem beim Speichern der Änderungen.', 'error');
});
document.body.removeChild(modal);
}
});
});
document.getElementById('delete-event').addEventListener('click', function() {
Swal.fire({
title: 'Löschen',
text: 'Möchten Sie dieses Event wirklich löschen?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Ja, löschen',
cancelButtonText: 'Abbrechen'
}).then((result) => {
if (result.isConfirmed) {
fetch('../../API/delete_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: info.event.id,
calendarType: calendarType
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
info.event.remove();
Swal.fire('Gelöscht!', 'Das Ereignis wurde gelöscht.', 'success');
}
document.body.removeChild(modal);
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Fehler!', 'Es gab ein Problem beim Löschen des Ereignis.', 'error');
document.body.removeChild(modal);
});
}
});
});
document.getElementById('close-modal').addEventListener('click', function() {
document.body.removeChild(modal);
});
},
eventContent: function(arg) {
let title = arg.event.title || '';
let desc = arg.event.extendedProps && arg.event.extendedProps.description ? arg.event.extendedProps.description : '';
let descPreview = desc.length > 20 ? desc.substring(0, 20) + '...' : desc;
let arrayOfDomNodes = [];
let titleEl = document.createElement('div');
titleEl.innerText = title;
titleEl.className = 'event-title';
arrayOfDomNodes.push(titleEl);
if (descPreview) {
let descEl = document.createElement('div');
descEl.innerText = descPreview;
descEl.className = 'event-desc-preview';
arrayOfDomNodes.push(descEl);
}
return { domNodes: arrayOfDomNodes };
},
drop: function(info) {
const eventData = JSON.parse(info.draggedEl.dataset.event);
const newEvent = {
title: eventData.title,
start: info.dateStr,
end: null,
description: eventData.description || '',
background_color: eventData.backgroundColor || null,
calendarType: calendarType,
};
fetch('../../API/save_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newEvent)
})
.then(response => response.json())
},
eventDrop: function(info) {
fetch('../../API/update_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: info.event.id,
title: info.event.title,
description: info.event.extendedProps.description || '',
calendarType: calendarType,
start: info.event.start ? info.event.start.toISOString() : null,
end: info.event.end ? info.event.end.toISOString() : null
})
});
},
eventResize: function(info) {
fetch('../../API/update_event.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: info.event.id,
title: info.event.title,
description: info.event.extendedProps.description || '',
calendarType: calendarType,
start: info.event.start ? info.event.start.toISOString() : null,
end: info.event.end ? info.event.end.toISOString() : null
})
});
},
eventOrder: function(a, b) {
if (a.extendedProps && a.extendedProps.isHoliday) return -1;
if (b.extendedProps && b.extendedProps.isHoliday) return 1;
return 0;
},
});
function getEasterDate(year) {
var f = Math.floor,
G = year % 19,
C = f(year / 100),
H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,
I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),
J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,
L = I - J,
month = 3 + f((L + 40) / 44),
day = L + 28 - 31 * f(month / 4);
return new Date(year, month - 1, day);
}
function getGermanHolidays(year) {
const pad = n => String(n).padStart(2, '0');
const holidays = [
{ title: 'Neujahr', date: `${year}-01-01` },
{ title: 'Heilige Drei Könige', date: `${year}-01-06` },
{ title: 'Tag der Arbeit', date: `${year}-05-01` },
{ title: 'Mariä Himmelfahrt', date: `${year}-08-15` },
{ title: 'Tag der Deutschen Einheit', date: `${year}-10-03` },
{ title: 'Reformationstag', date: `${year}-10-31` },
{ title: 'Allerheiligen', date: `${year}-11-01` },
{ title: '1. Weihnachtstag', date: `${year}-12-25` },
{ title: '2. Weihnachtstag', date: `${year}-12-26` }
];
const easter = getEasterDate(year);
const easterStr = `${easter.getFullYear()}-${pad(easter.getMonth() + 1)}-${pad(easter.getDate())}`;
const goodFriday = new Date(easter); goodFriday.setDate(easter.getDate() - 2);
const easterMonday = new Date(easter); easterMonday.setDate(easter.getDate() + 1);
const ascension = new Date(easter); ascension.setDate(easter.getDate() + 39);
const pentecost = new Date(easter); pentecost.setDate(easter.getDate() + 49);
const pentecostMonday = new Date(easter); pentecostMonday.setDate(easter.getDate() + 50);
const corpusChristi = new Date(easter); corpusChristi.setDate(easter.getDate() + 60);
holidays.push(
{ title: 'Karfreitag', date: `${goodFriday.getFullYear()}-${pad(goodFriday.getMonth() + 1)}-${pad(goodFriday.getDate())}` },
{ title: 'Ostersonntag', date: easterStr },
{ title: 'Ostermontag', date: `${easterMonday.getFullYear()}-${pad(easterMonday.getMonth() + 1)}-${pad(easterMonday.getDate())}` },
{ title: 'Christi Himmelfahrt', date: `${ascension.getFullYear()}-${pad(ascension.getMonth() + 1)}-${pad(ascension.getDate())}` },
{ title: 'Pfingstsonntag', date: `${pentecost.getFullYear()}-${pad(pentecost.getMonth() + 1)}-${pad(pentecost.getDate())}` },
{ title: 'Pfingstmontag', date: `${pentecostMonday.getFullYear()}-${pad(pentecostMonday.getMonth() + 1)}-${pad(pentecostMonday.getDate())}` },
{ title: 'Fronleichnam', date: `${corpusChristi.getFullYear()}-${pad(corpusChristi.getMonth() + 1)}-${pad(corpusChristi.getDate())}` }
);
return holidays;
}
const year = new Date().getFullYear();
const holidays = getGermanHolidays(year).map(h => ({
title: h.title,
start: h.date,
allDay: true,
editable: false,
durationEditable: false,
backgroundColor: '#ffe0e0',
borderColor: '#ffaaaa',
textColor: '#b30000',
display: 'block',
isHoliday: true,
classNames: ['is-holiday-event']
}));
holidays.forEach(event => calendar.addEvent(event));
fetch(`../../API/get_events.php?calendarType=${calendarType}`)
.then(response => response.json())
.then(data => {
if (data.success) {
data.events.forEach(event => {
calendar.addEvent({
...event,
backgroundColor: event.background_color || undefined,
editable: true,
durationEditable: true,
});
});
}
})
.catch(error => {
console.error('Error:', error);
});
calendar.render();
var externalEvents = [
{ title: `Geplant - ${username}`, id: '1', backgroundColor: '#FFDE97', description: 'Geplanter Urlaub' },
{ title: `Bestätigt - ${username}`, id: '2', backgroundColor: '#BEF7C1', description: 'Bestätigter Urlaub' },
{ title: `Homeoffice - ${username}`, id: '3', backgroundColor: '#C3C1FD', description: 'Im Homeoffice' },
{ title: `Berufsschule - ${username}`, id: '4', backgroundColor: '#F3B7B7', description: 'In der Berufsschule' },
];
var eventList = document.getElementById('external-events');
externalEvents.forEach(function(event) {
var eventEl = document.createElement('li');
eventEl.className = 'fc-event';
let descPreview = event.description ? event.description.substring(0, 20) : '';
if (event.description && event.description.length > 20) descPreview += '...';
eventEl.innerHTML = `<span class="event-title">${event.title}</span><br><span class="event-desc-preview">${descPreview}</span>`;
eventEl.dataset.event = JSON.stringify(event);
eventList.appendChild(eventEl);
});
new FullCalendar.Draggable(eventList, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
const data = JSON.parse(eventEl.dataset.event);
if (data.backgroundColor) {
data.backgroundColor = data.backgroundColor;
}
return data;
}
});
const buttons = document.querySelectorAll('.event-filter-btn');
function resetCalendarFilter() {
document.querySelectorAll('.fc-event').forEach(ev => {
ev.classList.remove('calendar-event-faded');
});
buttons.forEach(b => {
b.classList.remove('active-filter');
b.style.opacity = '1';
});
}
buttons.forEach(btn => {
btn.addEventListener('mousedown', function(e) {
e.preventDefault();
});
btn.addEventListener('click', function(e) {
e.stopPropagation();
if (btn.classList.contains('active-filter')) {
resetCalendarFilter();
return;
}
buttons.forEach(b => b.classList.remove('active-filter'));
btn.classList.add('active-filter');
const type = btn.dataset.type;
document.querySelectorAll('.fc-event').forEach(ev => {
ev.classList.remove('calendar-event-faded');
});
document.querySelectorAll('.fc-event').forEach(ev => {
const titleEl = ev.querySelector('.event-title');
const title = titleEl ? titleEl.textContent : ev.textContent || '';
if (!title.toLowerCase().includes(type.toLowerCase())) {
ev.classList.add('calendar-event-faded');
}
});
buttons.forEach(b => {
if (b !== btn) b.style.opacity = '0.3';
else b.style.opacity = '1';
});
});
});
document.addEventListener('click', function(e) {
const isButton = e.target.closest('.event-filter-btn');
if (!isButton) {
resetCalendarFilter();
}
});
function updateStarButtons() {
const favs = JSON.parse(localStorage.getItem('favoriteCalendars') || '[]');
document.querySelectorAll('.star-btn').forEach(btn => {
const cal = btn.dataset.calendar;
const star = btn.querySelector('.star');
if (favs.includes(cal)) {
star.innerHTML = '★';
star.style.color = 'gold';
} else {
star.innerHTML = '☆';
star.style.color = '#bbb';
}
});
}
document.querySelectorAll('.star-btn').forEach(btn => {
btn.addEventListener('click', function() {
const cal = this.dataset.calendar;
let favs = JSON.parse(localStorage.getItem('favoriteCalendars') || '[]');
if (favs.includes(cal)) {
favs = favs.filter(f => f !== cal);
} else {
favs.push(cal);
}
localStorage.setItem('favoriteCalendars', JSON.stringify(favs));
updateStarButtons();
});
});
updateStarButtons();
});
This is the database file:
class Database {
private static $connection = null;
function __construct() {
if (self::$connection == null) {
$env = parse_ini_file(__DIR__ . '/../.env');
self::$connection = new SQLite3($env["DATABASE_PATH"], SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
self::$connection->enableExceptions(true);
}
}
public function saveEvent($title, $start, $end = null, $description = null, $background_color = null, $calendarType = 'entwicklung') {
$table = $this->getEventTable($calendarType);
$stmt = self::$connection->prepare("INSERT INTO $table (title, start, end, description, background_color) VALUES (:title, :start, :end, :description, :background_color)");
$stmt->bindValue(':title', $title, SQLITE3_TEXT);
$stmt->bindValue(':start', $start, SQLITE3_TEXT);
$stmt->bindValue(':end', $end, SQLITE3_TEXT);
$stmt->bindValue(':description', $description, SQLITE3_TEXT);
$stmt->bindValue(':background_color', $background_color, SQLITE3_TEXT);
$stmt->execute();
}
public function getEvents($calendarType = 'entwicklung') {
if ($calendarType === 'gesamt') {
$query = "SELECT id, title, start, end, description, background_color, 'entwicklung' as calendarType FROM event_entwicklung
UNION ALL
SELECT id, title, start, end, description, background_color, 'support' as calendarType FROM event_support";
$result = self::$connection->query($query);
} else {
$table = $this->getEventTable($calendarType);
$stmt = self::$connection->prepare("SELECT id, title, start, end, description, background_color FROM $table");
$result = $stmt->execute();
}
$events = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$events[] = $row;
}
return $events;
}
public function updateEvent($id, $title, $description = null, $calendarType = 'entwicklung', $start = null, $end = null) {
$table = $this->getEventTable($calendarType);
$sql = "UPDATE $table SET title = :title, description = :description";
if ($start !== null) $sql .= ", start = :start";
if ($end !== null) $sql .= ", end = :end";
$sql .= " WHERE id = :id";
$stmt = self::$connection->prepare($sql);
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
$stmt->bindValue(':title', $title, SQLITE3_TEXT);
$stmt->bindValue(':description', $description, SQLITE3_TEXT);
if ($start !== null) $stmt->bindValue(':start', $start, SQLITE3_TEXT);
if ($end !== null) $stmt->bindValue(':end', $end, SQLITE3_TEXT);
$stmt->execute();
}
public function deleteEvent($id, $calendarType = 'entwicklung') {
$table = $this->getEventTable($calendarType);
$stmt = self::$connection->prepare("DELETE FROM $table WHERE id = :id");
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
$stmt->execute();
}
This is the save_event file:
<?php
require_once __DIR__ . '/../../Backend/database.php';
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
$title = $data['title'] ?? '';
$start = $data['start'] ?? '';
$end = $data['end'] ?? null;
$description = $data['description'] ?? null;
$background_color = $data['background_color'] ?? null;
$calendarType = $data['calendarType'] ?? 'entwicklung';
try {
$db = new Database();
$db->saveEvent($title, $start, $end, $description, $background_color, $calendarType);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>
This is the get_event file:
<?php
require_once __DIR__ . '/../../Backend/database.php';
header('Content-Type: application/json');
$calendarType = isset($_GET['calendarType']) ? $_GET['calendarType'] : 'entwicklung';
try {
$db = new Database();
$events = $db->getEvents($calendarType);
echo json_encode(['success' => true, 'events' => $events]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>
and this the update_event file:
<?php
require_once __DIR__ . '/../../Backend/database.php';
header('Content-Type: application/json');
$data = json_decode(file_get_contents('php://input'), true);
$id = $data['id'] ?? null;
$title = $data['title'] ?? '';
$description = $data['description'] ?? null;
$calendarType = $data['calendarType'] ?? 'entwicklung';
$start = $data['start'] ?? null;
$end = $data['end'] ?? null;
if ($id === null) {
echo json_encode(['success' => false, 'error' => 'ID fehlt']);
exit;
}
try {
$db = new Database();
$db->updateEvent($id, $title, $description, $calendarType, $start, $end);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>
Could someone help me ?