Viele Blogs haben einen Kalender am linken oder rechten Rand der
Seite, der aufzeigt, wann der Autor des Blogs Beiträge geschrieben hat. Mit
einem Klick auf einen Tag gelangt man zu den an diesem Tag eingestellten
Artikeln. Solche Kalender werden von der jeweiligen Blog-Software (z.B.
WordPress) automatisch erzeugt.
Webmaster, die nicht über eine solche Software zum Einstellen Ihrer
Beiträge verfügen, bzw. ihr Web auf einem ganz normalen Web-Server ohne
Zusatz-Features hosten, müssen sich einen solchen Kalender selbst schreiben.
So auch ich. Der erste Ansatz war natürlich, das Ganze, wie der Rest von
zerbit.de auch, in ASP zu entwickeln, aber das hat, wie bei
den meisten Blogs, den Nachteil, dass der Sprung zu einem anderen Monat über
einen entsprechenden Monatsselektor die Seite immer neu geladen werden muss.
Dieses Problem läßt sich eigentlich nur mit Javascript lösen, das
nicht auf dem Server, sondern auf dem Client ausgeführt wird. Einen
dynamischen Kalender mit Javascript zu erstellen ist ja nicht allzu
schwer, aber wie bekommt man es hin, die Tage, an denen Beiträge eingestellt
wurden, im Kalender zu verlinken? Dazu braucht man auswertbare Daten vom
Datenbank-Server, was wiederum für eine Server-Sprache wie ASP oder PHP
spricht.
Des Rätsels Lösung geistert seit einigen Monaten durch die einschlägigen
Medien: AJAX!
Dieser Begriff steht für "Asynchrones
Javascript und XML" und bezeichnet eine Technik Daten dynamisch auf eine
Web-Seite per Javascript nachzuladen. Das Ganze ist beileibe nicht neu, denn
die zugrunde liegende Technologie steht seit Jahren zur Verfügung, aber das
Ganze hat nun einen griffigen Namen und damit steht dem Hype nichts mehr im
Wege.
In Kurzform kann man AJAX so umschreiben: eine Seite wird komplett
geladen. Der Benutzer klickt auf einen Link oder eine Schaltfläche, wobei
eine Javascript-Funktion aufgerufen wird, die mittels des XMLHTTP-Objektes
vom Server eine XML-Datei abruft und verarbeitet. Diese XML-Datei liegt
jedoch nicht starr auf irgendeiner Festplatte, sondern wird dynamisch
mittels ASP, PHP oder einer anderen Server-Sprache erzeugt.
Für unseren Fall ist das genau das Richtige. Wir brauchen also eine
Javascript-Funktion, die beim Seitenaufbau eine tragende Tabelle erzeugt und
eine weitere, die dynamisch die Daten eines gewählten Monats in diese
Tabelle schreibt, wobei die Daten per XML vom Server abgeholt werden. Ein
Beispiel für eine Implementierung sehen Sie am linken Rand dieser Web-Seite
(Kalender).
Die Tabelle
Folgende kleine Javascript-Funktion erzeugt eine Tabelle mit insgesamt 7
Zeilen und 7 Spalten, wobei die erste Zeile für den Kalenderkopf und der
Rest für die einzelnen Tage verwendet wird.
function createCalendar()
{
var sHTML;
var i;
// Tabellen-Code zusammenstellen
sHTML = '<table class="cal">';
sHTML += '<tr><td id="calHead1">'
sHTML += '<a href=""><</a></td>';
sHTML += '<td id="calHead2" colspan="5"></td>';
sHTML += '<td id="calHead3">'
sHTML += '<a href="">></a></td></tr>';
sHTML += '<tr>';
// Tabelle mit 6 Zeilen und 7 Spalten erstellen
for(i = 1; i <= 42; i++) {
sHTML += '<td id="calCell' + i + '"> </td>';
// Umbruch nach jeder 7. Zelle
if(i==7 || i==14 || i==21 || i==28 || i==35) {
sHTML += '</tr><tr>'; }
}
sHTML += '</tr></table>';
// Kalender ausgeben
document.write(sHTML);
}
Wir müssen darauf achten jeder Zelle eine eindeutige ID mitzugeben, denn
später muss auf jede einzelne Zelle per getElementById-Methode
zugegriffen werden, um die entsprechenden Daten unterzubringen. Die ID's des
Kaelnderkopfes heißen in unserem Fall calHead1, calHead2
und calHead3. Die ID's der einzelnen Tageszellen heißen
fortlaufend calCell1, calCell2, calCell3,
et cetera.
Mit der letzten Anweisung (document.write(sHTML);) wird der
zusammengestellte Tabellen-Code an der aktuellen Stelle in der HTML-Seite
ausgegeben. Hierzu wird folgender HTML-Code verwendet:
<script type="text/javascript">createCalendar();</script>
Die XML-Datei
Als nächstes brauchen wir die Daten der Beiträge im XML-Format. Da wir
lediglich Links im Kalender erzeugen wollen, mit denen der Benutzer zu einer
speziell angefertigten ASP- oder PHP-Seite mit den Beiträgen eines Monats
surfen kann, brauchen wir einzig und allein die Anzahl der Beiträge für
jeden Tag eines bestimmten Monats. Die Struktur einer solchen "XML-Datei" ist
relativ simpel:
<content>
<day id="1">0</day>
<day id="2">3</day>
<day id="2">1</day>
...
<day id="30">1</day>
<day id="31">0</day>
</content>
Jeder Tag eines Monats besitzt ein DAY-Tag, daß die Anzahl der Beiträge
einschließt. Wie man XML-Dateien z.B. mit ASP direkt auf dem Server erzeugt,
kann im zerbit.de-Artikel RSS-Feed direkt mit ASP erzeugen
nachgelesen werden.
In unserem Fall existiert eine ASP-Seite mit dem dem
Namen contentcount.asp, die zwei Parameter auswerten kann:
m für den Monat
und y für das Jahr. Der Aufruf für die oben abgebildete XML-Ausgabe für den September 2005
sieht dann so aus:
contentcount.asp?m=9&y=2005
Füllen der Tabelle mit Daten
Direkt unter den Aufruf der Funktion createCalendar auf der
HTML-Seite, legen wir den Aufruf der Daten-Hauptfunktion
getCalendar, die die Befüllung der Tabelle mit den einzelnen
Tagen inkl. der Links erledigt:
<script type="text/javascript">getCalendar();</script>
Diese Funktion ist eine Hüllenfunktion, die eine weitere Funktion (fillCalendar)
aufruft, sobald die XML-Daten vom Server empfangen wurden. Dieser Umweg ist
notwendig, da wir zum Laden der Daten das XMLHTTP-Objekt verwenden und es
eine gewisse Zeit braucht, bis die Daten an den Client übertragen wurden. Die Funktion im einzelnen:
Wir benötigen zunächst zwei globale Variablen, die außerhalb der Funktion
deklariert werden:
var xmlHttp = false;
var sDate;
Die ersten Zeilen im Code der Funktion stellen sicher, daß die Funktion auch
läuft, wenn keine Parameter übergeben wurden. In diesem Fall wird der
aktuelle Monat berechnet.
Wichtig: In Javascript beginnt die Monatszählung bei 0,
d.h. die Zahl 0 repräsentiert den Januar, 1 den Februar, et cetera. Dies
muss bei der Übergabe des Parameters iMonth und im folgenden
Code beachtet werden.
function getCalendar(iMonth, iYear)
{
// aktuelles Datum in Variable speichern
sDate = new Date();
// wenn Parameter leer, dann aktuellen Monat verwenden
if (iMonth == null) {
iMonth = sDate.getMonth();
iYear = sDate.getFullYear(); }
...
Weiter geht es mit der Initialisierung des XMLHTTP-Objekts. Hier müssen
wir, aufgrund der unterschiedlichen Umsetzungen der einzelnen Browser, eine
Weiche einbauen, um das Objekt auch sicher zu initialisieren:
...
// Mozilla und Co.
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest(); }
// IE
else if (window.ActiveXObject) {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); }
else {
// Falls nicht unterstützt
xmlHttp = false; }
...
Nun stellen wir in einer Variablen die URL wie oben zusammen mit der
XML-Daten abgeholt werden. Dabei ist darauf zu achten, daß die Variable
iMonth um 1 erhöht wird, um den richtigen Monat abzurufen.
Anschließend wird die Methode open des XMLHTTP-Objekts
ausgeführt, d.h. der Datentransport wird gestartet. Wichtig hierbei ist der
letzte Parameter, der angibt, ob die Daten asynchron oder nicht geladen
werden sollen.
...
// URL zusammenstellen und Inhalt über GET asynchron holen
var sXMLUrl = 'http://meinserver/contentcount.asp?m=' + (iMonth + 1) + '&y=' + iYear;
xmlHttp.open("GET", sXMLUrl, true);
...
Was nun folgt ist die Zuweisung einer Inline-Funktion zur Eigenschaft
onreadystatechange. Damit wird erreicht, daß die Funktion fillCalendar
ausgeführt wird, sobald das Objekt den Ladestatus 4 ("loaded")
und den Dokumentenstatus 200 ("OK") meldet. In der letzten Zeile wird der so
aufgebaute Request dann an den Web-Server gesendet:
...
xmlHttp.onreadystatechange = function() {
// Objekt meldet "loaded"
if (xmlHttp.readyState == 4) {
// Objekt meldet "OK"
if (xmlHttp.status == 200) {
// Funktion zum Füllen der Tabelle ausführen
fillCalendar(iMonth, iYear); }
}
}
xmlHttp.send(null);
}
Nun zur Funktion fillCalendar, die eigentliche Arbeit
erledigt. Die ersten Zeilen beschäftigen sich zunächst mit der
Initialisierung eines Monats-Arrays und einigen benötigten
Datumsberechnungen:
function fillCalendar(iMonth, iYear)
{
// Monats-Array bilden
var aMonths = new Array(
'Jan.', 'Febr.', 'Mär.', 'Apr.',
'Mai', 'Jun.', 'Jul.', 'Aug.',
'Sep.', 'Okt.', 'Nov.', 'Dez.');
// Monate ermitteln
var iThisMonth = new Date(iYear, iMonth, 1);
var iPrevMonth = new Date(iYear, iMonth - 1, 1);
var iNextMonth = new Date(iYear, iMonth + 1, 1);
// Erster Wochentag und Anzahl Tage/Monat ermitteln
var iFirstWeekday = iThisMonth.getDay();
if (iFirstWeekday == 0) iFirstWeekday = 7;
var iDaysInMonth = Math.floor((iNextMonth.getTime()
- iThisMonth.getTime()) / (1000 * 60 * 60 * 24));
...
Als nächstes wird der Tabellenkopf anhand der in der HTML-Tabelle
definierten ID's befüllt. Zu beachten ist, daß die
beiden Monats-Links zum vorherigen bzw. nächsten Monat wiederum
die oben dargestellte Funktion getCalendar aufrufen, um die
Daten eines anderen Monats in die Tabelle einzutragen:
...
// Link zu vorherigem Monat
var sPrev = '<a href="javascript: getCalendar('
+ iPrevMonth.getMonth() + ','
+ iPrevMonth.getFullYear() + ')"><</a>';
hItem = document.getElementById("calHead1")
hItem.innerHTML = sPrev;
// Überschrift aus Monats-Array
hItem = document.getElementById("calHead2")
hItem.innerHTML = aMonths[iMonth] + ' ' + iYear;
// Link zu nächstem Monat
var sNext = '<a href="javascript: getCalendar('
+ iNextMonth.getMonth() + ','
+ iNextMonth.getFullYear() + ')">></a>';
hItem = document.getElementById("calHead3")
hItem.innerHTML = sNext;
...
Da wir einen Monatskalender darstellen wollen, müssen wir in der Tabelle
zunächst alle Zellen der ersten Zeile mit Blanks füllen, deren Tage im
letzten Monat liegen:
...
// Leere Tage am Anfang auffüllen
for(iCellId=1; iCellId < iFirstWeekday; iCellId++) {
hItem = document.getElementById("calCell" + iCellId);
hItem.innerHTML = ' '; }
...
Nun kommen wir zu den Tages des darzustellenden Monats. Hier werden
zunächst zwei Variablen initialisiert, wobei die erste das XML-Dokument
darstellt, dass wir über die Eigenschaft responseXML des
XMLHTTP-Objekts erhalten. Es beinhaltet den kompletten Code des
XML-Dokuments.
...
// XML-Objekt initialisieren
var xmlDok = xmlHttp.responseXML;
// Variable initialieren
var iCellDay=1;
...
Die einzelnen Tage werden über eine Schleife generiert, die alle Tage des
Monats durchläuft:
...
// Schleife über alle Tage des Monats
for(iCellId = iFirstWeekday; iCellDay <= iDaysInMonth; iCellId++) {
...
Die erste Aktion innerhalb der Schleife ist die Anzahl der Beiträge für
den aktuell zu verarbeitenden Tag aus den XML-Daten zu bestimmen. Hierzu
verwenden wir die DOM-Funktion getElementsByTagName und sehen
uns den Index des benötigen Tags an. Ist dieser 0, so wird als
Ausgabe-String lediglich die Zahl des Tages verwendet. Bei einem Wert größer
Null, enthält der Ausgabe-String einen Link zu einer ASP-Seite, die die
einzelnen Beiträge eines Tages ausgibt.
...
// Anzahl Beiträge aus XML ermitteln
iItemCount = xmlDok.getElementsByTagName("day")
[iCellDay - 1].firstChild.nodeValue;
// Zellinhalt bestimmen (Link auf Beiträge oder nicht)
if (iItemCount == 0) {
sDayHTML = iCellDay; }
else {
var sDayString = iCellDay + "." + (iMonth + 1) + "." + iYear;
sDayHTML = '<a href="/showdatum.asp?day='
+ sDayString + '">' + iCellDay + '</a>';
}
// Zellinhalt zuweisen
hItem = document.getElementById("calCell" + iCellId)
hItem.innerHTML = sDayHTML;
iCellDay++;
}
...
Damit wäre die Schleife über alle Monatstage erledigt. Zum Abschluss der
Funktion brauchen wir der Vollständigkeit halber noch etwas Code zum
Auffüllen der restlichen Zellen mit Blanks für die Tage der Woche die
bereits im neuen Monat liegen.
...
// Leere Tage am Ende auffüllen
for(iCellId; iCellId <= 42; iCellId++) {
hItem = document.getElementById("calCell" + iCellId)
hItem.innerHTML = ' '; }
}
Die Elemente der HTML-Tabelle können nun noch entweder direkt über
Funktion createCalendar oder während dem Befüllen mit Daten mit CSS-Klassen
versehen werden, um das Ganze etwas aufzuhübschen.
Downloads
ajax_calendar.js