FOR calendar - FullCalendar Modul
Frameworkunabhängige Umsetzung.
Achtung, für einen produktiven Einsatz sollte man FullCalendar lokal installieren.
Eingabe
<?php
// Eingabe-Code für das REDAXO-Modul
/* Eingabe */
?>
<div class="form-group">
<label>Zeitraum für initiale Ladung (in Monaten)</label>
<input type="number" name="REX_INPUT_VALUE[1]" value="<?= htmlspecialchars(rex_escape('REX_VALUE[1]')) ?>" min="1" max="24" class="form-control">
</div>
<div class="form-group">
<label>Kategorien (optional, leer = alle anzeigen)</label>
<?php
// Kategorien-Auswahl generieren
$sql = rex_sql::factory();
$categories = $sql->getArray('SELECT id, name_' . rex_clang::getCurrentId() . ' as name FROM ' . rex::getTable('forcal_categories') . ' WHERE status = 1 ORDER BY name');
?>
<select name="REX_INPUT_VALUE[2][]" class="form-control" multiple>
<?php
$selected_categories = explode('|', 'REX_VALUE[2]');
foreach ($categories as $category) {
$selected = in_array($category['id'], $selected_categories) ? 'selected' : '';
echo '<option value="' . $category['id'] . '" ' . $selected . '>' . htmlspecialchars(rex_escape($category['name'])) . '</option>';
}
?>
</select>
<p class="help-block">Mehrere mit STRG/CMD-Taste auswählen, keine Auswahl = alle Kategorien</p>
</div>
<div class="form-group">
<label>Initiale Ansicht</label>
<select name="REX_INPUT_VALUE[3]" class="form-control">
<option value="dayGridMonth" <?= 'REX_VALUE[3]' == "dayGridMonth" ? "selected" : "" ?>>Monatsansicht</option>
<option value="timeGridWeek" <?= 'REX_VALUE[3]' == "timeGridWeek" ? "selected" : "" ?>>Wochenansicht</option>
<option value="timeGridDay" <?= 'REX_VALUE[3]' == "timeGridDay" ? "selected" : "" ?>>Tagesansicht</option>
<option value="listMonth" <?= 'REX_VALUE[3]' == "listMonth" ? "selected" : "" ?>>Listenansicht (Monat)</option>
<option value="listWeek" <?= 'REX_VALUE[3]' == "listWeek" ? "selected" : "" ?>>Listenansicht (Woche)</option>
</select>
</div>
<div class="form-group">
<label>Kalender-Höhe (in Pixel, leer = auto)</label>
<input type="number" name="REX_INPUT_VALUE[4]" value="<?= htmlspecialchars(rex_escape('REX_VALUE[4]')) ?>" min="300" class="form-control">
<p class="help-block">Empfohlen: 600-800 für Monats- und Wochenansicht, leer lassen für automatische Höhe</p>
</div>
<div class="form-group">
<label>Weitere Einstellungen</label>
<div class="checkbox">
<label>
<input type="checkbox" name="REX_INPUT_VALUE[5]" value="1" <?= 'REX_VALUE[5]' == 1 ? "checked" : "" ?>> Wochenenden hervorheben
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="REX_INPUT_VALUE[6]" value="1" <?= 'REX_VALUE[6]' == 1 ? "checked" : "" ?>> Navigationsbuttons anzeigen
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="REX_INPUT_VALUE[7]" value="1" <?= 'REX_VALUE[7]' == 1 ? "checked" : "" ?>> Heute-Button anzeigen
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="REX_INPUT_VALUE[8]" value="1" <?= 'REX_VALUE[8]' == 1 ? "checked" : "" ?>> Ansichts-Wechsler anzeigen
</label>
</div>
</div>
Ausgabe
<?php
/* Ausgabe */
?>
<div class="forcal-fullcalendar REX_ARTICLE_ID REX_CLANG_ID">
<!-- Der Kalender wird hier eingefügt -->
<div id="forcal-calendar-REX_ARTICLE_ID-REX_CLANG_ID"></div>
<!-- Details-Modal -->
<div class="modal" id="forcal-event-modal-REX_ARTICLE_ID-REX_CLANG_ID">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title event-title"></h4>
<button type="button" class="close-button" aria-label="Schließen">
<span>×</span>
</button>
</div>
<div class="modal-body">
<div class="event-date"></div>
<div class="event-time"></div>
<div class="event-category"></div>
<div class="event-venue"></div>
<div class="event-teaser"></div>
<div class="event-description"></div>
</div>
<div class="modal-footer">
<button type="button" class="close-button">Schließen</button>
</div>
</div>
</div>
</div>
</div>
<?php
// Monate für initiale Ladung (Standard: 3)
$months = (int)'REX_VALUE[1]' > 0 ? (int)'REX_VALUE[1]' : 3;
// Ausgewählte Kategorien
$categories = 'REX_VALUE[2]' != '' ? explode('|', 'REX_VALUE[2]') : null;
// Initiale Ansicht
$initialView = 'REX_VALUE[3]' != '' ? 'REX_VALUE[3]' : 'dayGridMonth';
// Kalender-Höhe
$calendarHeight = 'REX_VALUE[4]' != '' ? (int)'REX_VALUE[4]' : 'auto';
// Weitere Einstellungen
$highlightWeekends = 'REX_VALUE[5]' == 1;
$showNavButtons = 'REX_VALUE[6]' == 1;
$showTodayButton = 'REX_VALUE[7]' == 1;
$showViewSwitcher = 'REX_VALUE[8]' == 1;
// CSS für den Kalender und das Modal
$css = '
<style>
/* FullCalendar Styling */
.forcal-fullcalendar .fc-event {
cursor: pointer;
}
.forcal-fullcalendar .fc-day-sat, .forcal-fullcalendar .fc-day-sun {
' . ($highlightWeekends ? 'background-color: rgba(0,0,0,0.02);' : '') . '
}
.event-date, .event-time, .event-category, .event-venue {
margin-bottom: 10px;
}
.event-teaser {
margin-bottom: 15px;
font-style: italic;
}
.event-description {
margin-top: 20px;
}
/* Modal Styling */
.modal {
display: none; /* Versteckt das Modal standardmäßig */
position: fixed;
z-index: 1000; /* Oberhalb des restlichen Inhalts */
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto; /* Ermöglicht Scrollen, falls der Inhalt zu groß ist */
background-color: rgba(0, 0, 0, 0.4); /* Hintergrund mit Transparenz */
}
.modal.show {
display: block; /* Zeigt das Modal an */
}
.modal-dialog {
position: relative; /* Für absolute Positionierung des Inhalts */
margin: 100px auto; /* Zentriert das Modal */
width: 80%; /* Breite des Modals */
max-width: 800px; /* Maximale Breite */
}
.modal-content {
background-color: #fff;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 100%;
border-radius: 5px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19); /* Schatten */
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
}
.modal-title {
font-size: 1.5rem;
margin: 0;
}
.close-button {
color: #aaa;
font-size: 28px;
font-weight: bold;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
.close-button:hover,
.close-button:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.modal-body {
padding: 10px 0;
}
.modal-footer {
text-align: right;
margin-top: 20px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
</style>';
// JavaScript-Code
$js = '
<script>
document.addEventListener("DOMContentLoaded", function() {
// Kalenderelement
const calendarEl = document.getElementById("forcal-calendar-REX_ARTICLE_ID-REX_CLANG_ID");
// API-Basis-URL
const apiBaseUrl = window.location.origin + "/index.php?rex-api-call=forcal_exchange";
// Aktuelle Sprache fest auf Deutsch setzen
let locale = "de";
// Die folgende Zeile auskommentiert, damit die Sprache immer Deutsch bleibt
// ' . (rex_clang::getCurrentId() > 1 ? 'locale = "en";' : '') . '
// Startdatum (heute)
const today = new Date();
const startDate = today.toISOString().split("T")[0];
// Enddatum (X Monate in der Zukunft)
const endDate = new Date(today.getFullYear(), today.getMonth() + ' . $months . ', today.getDate()).toISOString().split("T")[0];
// API-URL mit Parametern
let apiUrl = apiBaseUrl + "&start=" + startDate + "&end=" + endDate + "&short=0";
// Kategoriefilter hinzufügen, falls vorhanden
' . ($categories ? 'apiUrl += "&category=' . implode(',', $categories) . '";' : '') . '
// FullCalendar initialisieren
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: "' . $initialView . '",
height: ' . ($calendarHeight !== 'auto' ? $calendarHeight : '"auto"') . ',
headerToolbar: {
left: "' . ($showNavButtons ? 'prev,next' : '') . ' ' . ($showTodayButton ? 'today' : '') . '",
center: "title",
right: "' . ($showViewSwitcher ? 'dayGridMonth,timeGridWeek,timeGridDay,listMonth' : '') . '"
},
locale: "de", // Fest auf Deutsch setzen
firstDay: 1, // 1 = Montag als erster Tag der Woche
buttonText: {
today: "Heute",
month: "Monat",
week: "Woche",
day: "Tag",
list: "Liste"
},
dayHeaderFormat: { weekday: "short", day: "numeric" },
titleFormat: { year: "numeric", month: "long" },
events: function(info, successCallback, failureCallback) {
// Events von der forCal-API laden
fetch(apiUrl)
.then(response => response.json())
.then(data => {
// Events formatieren
const events = data.map(event => {
return {
id: event.id,
title: event.title,
start: event.start,
end: event.end,
allDay: event.date_time.full_time,
backgroundColor: event.color || "#3788d8",
borderColor: event.color || "#3788d8",
// Zusätzliche Daten für das Modal speichern
extendedProps: {
date: event.date_time.date,
time: event.date_time.time,
category: event.category_name,
venue: event.venue_name,
teaser: event.teaser,
description: event.text
}
};
});
successCallback(events);
})
.catch(error => {
console.error("Fehler beim Laden der Events:", error);
failureCallback(error);
});
},
eventClick: function(info) {
// Event-Details im Modal anzeigen
const modal = document.getElementById("forcal-event-modal-REX_ARTICLE_ID-REX_CLANG_ID");
const modalTitle = modal.querySelector(".event-title");
const modalDate = modal.querySelector(".event-date");
const modalTime = modal.querySelector(".event-time");
const modalCategory = modal.querySelector(".event-category");
const modalVenue = modal.querySelector(".event-venue");
const modalTeaser = modal.querySelector(".event-teaser");
const modalDescription = modal.querySelector(".event-description");
const closeModalButtons = modal.querySelectorAll(".close-button"); // Beides auswählen
// Modal-Inhalt füllen
modalTitle.textContent = info.event.title;
modalDate.innerHTML = "<strong>' . rex_i18n::msg('forcal_entry_date') . ':</strong> " + info.event.extendedProps.date;
if (info.event.extendedProps.time) {
modalTime.innerHTML = "<strong>' . rex_i18n::msg('forcal_starttime') . ':</strong> " + info.event.extendedProps.time;
} else {
modalTime.innerHTML = "<strong>' . rex_i18n::msg('forcal_starttime') . ':</strong> ' . rex_i18n::msg('forcal_checkbox_full_time') . '";
}
modalCategory.innerHTML = "<strong>' . rex_i18n::msg('forcal_category') . ':</strong> " + (info.event.extendedProps.category || "");
if (info.event.extendedProps.venue) {
modalVenue.innerHTML = "<strong>' . rex_i18n::msg('forcal_entry_venue') . ':</strong> " + info.event.extendedProps.venue;
modalVenue.style.display = "block"; // Vanilla JS equivalent to show()
} else {
modalVenue.style.display = "none"; // Vanilla JS equivalent to hide()
}
if (info.event.extendedProps.teaser) {
modalTeaser.innerHTML = info.event.extendedProps.teaser;
modalTeaser.style.display = "block"; // Vanilla JS equivalent to show()
} else {
modalTeaser.style.display = "none"; // Vanilla JS equivalent to hide()
}
if (info.event.extendedProps.description) {
modalDescription.innerHTML = info.event.extendedProps.description;
modalDescription.style.display = "block"; // Vanilla JS equivalent to show()
} else {
modalDescription.style.display = "none"; // Vanilla JS equivalent to hide()
}
// Modal anzeigen
modal.classList.add("show");
// Event-Listener für das Schließen des Modals (mit Klick auf den Close-Button)
const hideModal = () => {
modal.classList.remove("show");
};
closeModalButtons.forEach(button => { // Für alle Buttons hinzufügen
button.addEventListener("click", hideModal);
});
// Event-Listener für das Schließen des Modals (mit Klick außerhalb des Modals)
window.addEventListener("click", (event) => {
if (event.target === modal) {
hideModal();
}
});
}
});
// Kalender rendern
calendar.render();
});
</script>';
?>
<!-- FullCalendar 6.x via CDN - cdnjs.com -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/6.1.15/index.global.min.js" integrity="sha512-PneTXNl1XRcU6n5B1PGTDe3rBXY04Ht+Eddn/NESwvyc+uV903kiyuXCWgL/OfSUgnr8HLSGqotxe6L8/fOvwA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<?= $css ?>
<?= $js ?>