Das Kalenderprojekt ist so aufgebaut, dass es Euch immer wieder vor Herausforderungen stellt, die Euch dazu anregen sollen, selbst Lösungen zu recherchieren und zu implementieren. Eher selten wollen wir einen festen Lösungsansatz präsentieren. Dieses Projekt ist als Einstieg in die Webtechnologien HTML, CSS und Javascript gedacht.
Wir gehen davon aus, dass Ihr VSCode installiert habt. Andere Editoren gehen natürlich auch, aber dann mag das eine oder andere anders funktionieren.
HTML ist die Standard-Sprache für das WWW, genauer gesagt für den Inhalt von Webseiten. Warum die Fettschreibung? Weil es bei HTML nicht (oder kaum) darum geht, wie diese Inhalte präsentiert werden. Es geht nur darum, was diese Inhalte sind. Eine pure HTML-Seite ist deswegen sehr, sehr schlicht. Aber sie enthält eben schon alle Inhalte die eine HTML-Seite enthät. An einem Beispiel mal verdeutlicht:
Das ist die Webseite spiegel.de, wie wir sie kennen bzw. wie sie aussieht, wenn wir sie aufrufen:
Und das ist der HTML-Teil alleine:
Man erkennt, dass es da gewisse Unterschiede gibt. :-)
Das, was Webseiten hübsch macht, ist also eher nicht im HTML zu verorten. Aber: Jeder Inhalt muss im HTML stehen und kann dann mit anderen Methoden schöner werden.
Im Prinzip: Im Explorer an eine beliebige Stelle navigieren, dort dann rechte Maustause, neue Textdatei erstellen und dafür sorgen, dass die Dateiendung html ist, nicht txt. Mag sein, dass in Euren Windows-Einstellungen deaktiviert ist, dass Ihr die Dateiendung ändern könnt. Das müsst Ihr dafür deaktivieren (Google hilft) .....
.... oder Ihr geht direkt in einen Codeeditor Eurer Wahl, bei uns eben VSCode, erstellt dort ein neues Projekt (=neuer Ordner) und dann könnt ihr auch dort per Click einfach eine neue Datei erstellen und sie zum Beispiel index.html nennen.
index.html ist ein typischer Standard-Dateiname .... die Datei wird geöffnet, wenn Ihr eine Datei nicht direkt mit Namen ansteuert. Die meisten Server sind so konfiguriert, dass Ihr, wenn Ihr eine WWW-Adresse (URL) aufruft, dann, wenn ihr den Dateinamen nicht explizit erähnt, automatisch die index.html aufgerufen wird.
Und dann schreibt Ihr Eure ersten Zeilen, die ungefähr so aussehen könnten:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<h1>Mein Kalenderblatt: 08.02.2021</h1>
<p>Das ist mein erster Text</p>
</body>
</html>
Wenn Ihr einfach im Explorer diese Datei öffnet ... oder sie in einem Browser direkt öffnet oder auch aus VSCode heraus per Run/Debug (ggf. muss man da noch ein bisserl konfigurieren), dann erhaltet Ihr ungefähr das folgende Resultat im Browser Eurer Wahl:
Versucht zu verstehen, wie Euer Code zusammenhängt mit der Ausgabe. Der Code besteht aus ein paar Meta-Informationen, die uns zum Beispiel sagen:
Und all das findet sich auf der Seite wieder. Jetzt bauen wir den Code noch etwas aus:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt: 08.02.2021</h1>
</div>
<div id="kalenderblatt">
<p>Hier fehlt noch ein Kalenderblatt</p>
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein Montag und zwar der zweite Montag im Monat
Februar des Jahres 2021. Dieser Monat hat 28 Tage. Der 08.02.2021
ist kein gesetzlicher Feiertag in Hessen. Der nächste gesetzliche
Feiertag ist der 02.04.2021 (Karfreitag).
</p>
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>
Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge
aus
</li>
<li>
Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen
</li>
<li>
Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus
geht als erster Computerbug in die Geschichte ein.
</li>
</ul>
</div>
</body>
</html>
Schaut Euch auch dieses Ergebnis im Browser an. Im Code könnt Ihr sehen, dass
wir mit den <div></div>
Tags etwas Ordnung in das Ganze gebracht haben. Wir
haben sozusagen Abschnitte, die erstmal nix weiter als unsichtbare Kästen /
Container sind, geschaffen. Das hilft uns später, wenn wir die Bereiche anders
darstellen wollen.
Diese Grundstruktur, dass etwas mit einem öffnenden Tag, zB <div>
oder <p>
oder <ul>
geöffnet wird und dann eben so lange gilt, bis es mit </div>
oder
</p>
oder </ul>
wieder geschlossen wird, ist in ganz vielen beschreibenden
Bereichen ähnlich. Ihr findet das wieder in XML, aber auch in Flutter werden wir
auf ähnliche Strukturen stoßen. Ein HTML-Dokument ist immer eine Art Baumstruktur.
Gewöhnt Euch auch an diese Form der Einrückung. Wenn wir mit einem Tag etwas
öffnen, egal was, dann ist jede Zeile danach um einen Tab weiter eingerückt
(Tabs sind dann, je nach IDE, 2 oder 4 Leerzeichen). Schließen wir es wieder,
dann erfolgt das Schließen wieder auf der gleichen Eben wie das Öffnen und alles
danach ist auch auf der gleichen Ebene. Auf diese Weise können wir zum Beispiel
leicht sehen, wenn wir etwas geöffnet haben, aber vergessen haben, es auch
wieder zu schließen.
Das konsequente Arbeiten und Verstehen der Einrückungen ist essentiell auch für viele echte Programmiersprachen (HTML hat ja keine Programm-Ablauflogik, sondern stellt nur etwas dar, deswegen kann sie nicht als rechte Programmiersprache gelten).
Recherchiert selbständig die Tags, die Ihr noch nicht kanntet, also zum Beispiel
<ul>
und <li>
.
Eure erste Aufgabe ist nun, etwas zu basteln, was in etwa so aussieht:
Februar 2021
Kw | Mo | Di | Mi | Do | Fr | Sa | So |
---|---|---|---|---|---|---|---|
5 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
8 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Das dann bitte einbauen in den Bereich, der dafür in unserem Code vorgesehen war
.... Hinweis: Dafür könnt Ihr beispielsweise eine Tabelle in HTML benutzen, die
unter anderem die Tags <table>
, <tr>
und <td>
verwendet. Experimentiert
einfach damit. :-)
Aber zuerst: Wir wissen nicht, wie Euer bisheriger Fortschritt aussieht. Solltet Ihr gar nicht zurecht gekommen sein, könnt Ihr mit dem nachfolgenden HTML-Code wieder einsteigen. Dann versucht aber unbedingt, diesen Code Stück für Stück nachzuvollziehen und nicht einfach nur zu kopieren.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt: 08.02.2021</h1>
</div>
<div id="kalenderblatt">
<table>
<thead>
<tr>
<th colspan="8">Februar 2021</th>
</tr>
<tr>
<th>Kw</th>
<th>Mo</th>
<th>Di</th>
<th>Mi</th>
<th>Do</th>
<th>Fr</th>
<th>Sa</th>
<th>So</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
</tr>
<tr>
<td>6</td>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
</tr>
<tr>
<td>7</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>18</td>
<td>19</td>
<td>20</td>
<td>21</td>
</tr>
<tr>
<td>8</td>
<td>22</td>
<td>23</td>
<td>24</td>
<td>25</td>
<td>26</td>
<td>27</td>
<td>28</td>
</tr>
</tbody>
</table>
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein Montag und zwar der zweite Montag im Monat
Februar des Jahres 2021. Dieser Monat hat 28 Tage. Der 08.02.2021
ist kein gesetzlicher Feiertag in Hessen. Der nächste gesetzliche
Feiertag ist der 02.04.2021 (Karfreitag).
</p>
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>
Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge
aus.
</li>
<li>
Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen
</li>
<li>
Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus
geht als erster Computerbug in die Geschichte ein.
</li>
</ul>
</div>
</body>
</html>
Schaut Euch diese (oder Eure) Datei im Browser an und stellt fest: Das sieht immer noch doof aus, aber immerhin sind es Informationen. Das colspan-Attribut, das ich einmal verwende im Code, sagt "diese Zelle nimmt den Platz ein, den normalerweise X Zellen einnehmen". Dadurch erreiche ich, dass der Monat "Februar 2021" über die ganze Kalenderblatt-Breite dargestellt wird.
CSS-Dateien enthalten Informationen darüber, wie HTML-Inhalte dargestellt werden. Zuerst binden wir eine CSS-Datei ein. Das geschieht im Header Eurer HTML-Datei, zum Beispiel über eine Zeile wie diese:
<link rel="stylesheet" type="text/css" href="style.css" />
Wir sagen also dem Dokument, dass unter einem bestimmten Link / einer Adresse eine CSS-Datei liegt namens style.css (und da wir keine Verzeichnisinformationen dazu schreiben, ist die Datei am gleichen Ort zu suchen wie die HTML-Datei) und dass diese für unser Dokument als Stylesheet zu werten ist.
CSS kann auch auf andere Weise eingebunden werden, zum Beispiel über Style-Attribute bei den einzelnen Elementen der Webseite oder als Abschnitt innerhalb der HTML-Datei. Aber die Details überlassen wir anderen Medien, uns geht es nur um einen schnellen Überblick!
Nun erstellen wir auch im selben Verzeichnis eine Datei namens style.css und geben ihr für's erste den folgenden Inhalt.
#seitenkopf {
background-color: lime;
}
#kalenderblatt {
background-color: palevioletred;
}
#introtext {
background-color: powderblue;
}
#historie {
background-color: goldenrod;
}
Wenn alle Bezeichnungen denen in unserer CSS-Datei den Bezeichnungen (hinter dem Schlüsselwort id) in der HTML-Datei entsprechen, dann habt Ihr beim nächsten Öffnen der HTML im Browser den Alptraum jedes Designers vor Euch. Zugleich erkennt Ihr aber, dass die Definitionen in der CSS Auswirkungen auf die Darstellung der Informationen haben. Eine ID bezeichnet ein Element im HTML eindeutig. Das heißt die gleiche ID sollte auch nur einmal pro Datei vergeben werden. Das # in der CSS-Datei heißt: "Wende bitte auf alle Elemente mit der ID ... die nachfolgenden Formatierungsoptionen an". In unserem Fall haben wir halt jeweils nur die Hintergrundfarbe verändert.
Machen wir nun eine sinnvolle Änderung. Die Kalenderwoche wollen wir kleiner als die anderen Kalendereinträge und kursiv darstellen. Dazu brauchen wir zwei Sachen:
CSS-Regeln können sich auf alles mögliche beziehen. Sie können, wie gesehen, spezifisch auf ein einzelnes Element zielen, das über seine ID angesprochen wird. Sie können auch für bestimmte HTML-Tags gelten. Zum Beispiel:
/* Macht alle Texte innerhalb von <p></p> Tags fett. */
p {
font-weight: bold;
}
/* Setzt für alle Text-Absätze (<p></p> innerhalb von <div></div> */
/* Containern die Schriftgröße auf 24 Pixel. */
div p {
font-size: 24px;
}
/* Setzt die Schriftfarbe auf rot, aber nur für <p></p> Absätze */
/* innerhalb des div mit der ID "test" */
div#test p {
color: red;
}
Für unsere Zwecke ist das alles noch nicht ideal, denn wir wollen weder ein
bestimmtes Element, aber auch nicht alle Elemente eines bestimmten Typs (zum
Beispiel alle Tabellenzellen mit dem Tag <td>
) mit unserer Regel zur
Kursivschreibung anpassen, sondern nur bestimmte Tabellenzellen: Diejenige, die
Informationen zur Kalenderwoche enthalten. Für sowas gibt es Klassen in
HTML/CSS. Wir können jedem, also auch mehreren Elementen (im Gegensatz zur ID)
eine (oder gar mehrere Klassen) zuweisen. Dafür passt Ihr Euren Code an - und
zwar für jede Tabellenzelle, die eine Angabe zur Kalenderwoche enthält.
Beispiel:
<tr>
<td class="kw">5</td>
<td>1</td>
/* (und so weiter) */
Die Zelle mit der 5 ist eine Zelle, die sich auf die KW 5 bezieht. Die Zelle mit der 1 meint den 1. Februar 2021. Wir haben der Kalenderwochen-Zelle jetzt die Klasse "kw" zugewiesen, und das können wir mit allen anderen KW-Einträgen auch machen.
Und dann ergänzen wir unsere CSS-Datei beispielsweise um folgenden Eintrag:
.kw {
font-style: italic;
font-size: smaller;
}
Dieser Eintrag sagt: "Für alle Elemente der Klasse kw gilt folgende Formatierung"
Ihr habt alle Elemente in der Hand, auch wenn Ihr es noch nicht wisst, um die Seite hübsch zu gestalten. Ihr werdet viele CSS-Regeln recherchieren müssen, aber Ihr könnt alles tun. Zum Experimentieren hilft es ungemein, im Browser F12 zu drücken. Das geht bei den meisten Browsern. Ansonsten googelt, wie es geht. Dann seht Ihr einen Inspector. Fuchst Euch in die Bedienung ein, denn hier könnt Ihr CSS-Regeln testweise ändern und mit den Werten spielen .... Geht schneller als im Code zu schreiben und dann die Seite neu zu laden. Versucht es. :-)
Erste Anregungen:
Es geht tatsächlich alles, auch das Verschieben der Bereiche. :-)
Wir haben neue Aufgaben:
Der wesentliche Lerneffekt an dieser Stelle sollte sein: Es geht alles in CSS. Theoretisch kann man auch Animationen einbauen oder den Kalender als halbtransparente Folie über die Historie legen. Das müsst Ihr alles im Detail nicht kennen, aber Euch muss bewusst sein, wie umfangreich die Layout-Möglichkeiten im Web und (aus Konkurrenzgründen dann unmittelbar auch) überall sind.
Ihr habt jetzt eine optisch passable Seite für diesen Tag, bei mir der 08.02.2021. Bei mir sieht das Ganze folgendermaßen aus.
Man beachte, dass ich mir schon einen Bereich "debug" eingebaut habe, den ich später brauchen werde und den ich mehr oder weniger rot eingefärbt habe. Den würde ich wieder rausnehmen, wenn wir die Seite veröffentlichen (würden).
Ihr sollt meinen Code nicht kopieren, aber erreicht habe ich das mit folgender HTML-Datei:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="step3.css" />
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt: 08.02.2021</h1>
</div>
<div id="kalenderblatt">
<table>
<thead>
<tr>
<th colspan="8">Februar 2021</th>
</tr>
<tr>
<th class="kw">Kw</th>
<th class="mo">Mo</th>
<th class="di">Di</th>
<th class="mi">Mi</th>
<th class="do">Do</th>
<th class="fr">Fr</th>
<th class="sa">Sa</th>
<th class="so">So</th>
</tr>
</thead>
<tbody>
<tr>
<td class="kw">5</td>
<td class="mo">1</td>
<td class="di">2</td>
<td class="mi">3</td>
<td class="do">4</td>
<td class="fr">5</td>
<td class="sa">6</td>
<td class="so">7</td>
</tr>
<tr>
<td class="kw">6</td>
<td class="mo heute">8</td>
<td class="di">9</td>
<td class="mi">10</td>
<td class="do">11</td>
<td class="fr">12</td>
<td class="sa">13</td>
<td class="so">14</td>
</tr>
<tr>
<td class="kw">7</td>
<td class="mo">15</td>
<td class="di">16</td>
<td class="mi">17</td>
<td class="do">18</td>
<td class="fr">19</td>
<td class="sa">20</td>
<td class="so">21</td>
</tr>
<tr>
<td class="kw">8</td>
<td class="mo">22</td>
<td class="di">23</td>
<td class="mi">24</td>
<td class="do">25</td>
<td class="fr">26</td>
<td class="sa">27</td>
<td class="so">28</td>
</tr>
</tbody>
</table>
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein Montag und zwar der zweite Montag im Monat
Februar des Jahres 2021. Dieser Monat hat 28 Tage. Der 08.02.2021
ist kein gesetzlicher Feiertag in Hessen. Der nächste gesetzliche
Feiertag ist der 02.04.2021 (Karfreitag).
</p>
</div>
<div id="debug">
(Test)
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>
Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge
aus.
</li>
<li>
Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen.
</li>
<li>
Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus
geht als erster Computerbug in die Geschichte ein.
</li>
</ul>
</div>
</body>
</html>
und mit folgender CSS:
/*==============================================================================
// BODY: Grundlegender Aufbau des Grids //
==============================================================================*/
body {
display: grid;
grid-template-columns: 1fr 2fr;
/*Im Verhältnis 1:2 wird der Bildschirm aufgeteilt*/
margin: 0;
}
/*==============================================================================
// SEITENKOPF //
==============================================================================*/
#seitenkopf {
padding: 1em;
text-align: center;
background: darkblue;
color: white;
grid-column-start: 1;
grid-column-end: 3;
/*Seitenkopf geht über beide Spalten*/
}
#seitenkopf h1 {
margin: 0;
padding: 0;
}
/*==============================================================================
// INTROTEXT UND HISTORIE //
==============================================================================*/
#introtext,
#historie {
padding: 1em;
grid-column-start: 2;
/*Historie und Introtxt sind hingegen nur rechts in der Spalte zuhause*/
grid-column-end: 3;
margin: 1em;
font-size: 1.3em;
padding: 1em;
}
/*==============================================================================
// KALENDERBLATT //
==============================================================================*/
#kalenderblatt {
border-bottom: 1px solid darkblue;
/*Border unten um das gesamte Feld*/
border-right: 1px solid darkblue;
/*Border rechts um das gesamte Feld*/
}
#kalenderblatt table {
margin: 25px auto;
/*Oben und unten 25 Punkte Abstand zu allem (zu den Grenzen des DIVs), links und rechts auto = zentriert*/
border-right: 1px solid darkblue;
/*Border rechts um gesamte Tabelle*/
border-bottom: 1px solid darkblue;
/*Border unten an gesamter Tabelle*/
/*Die Border nach links und oben ziehen wir in den einzelnen Zellen*/
border-spacing: inherit;
}
#kalenderblatt table th {
border-top: 1px solid darkblue;
/* Jede th-Zelle bekommt oben / links eine Border*/
border-left: 1px solid darkblue;
}
#kalenderblatt table td {
background: white;
text-align: center;
padding: 0.25em;
/*Bisserl Platz um den Inhalt der einzelnen Zellen (Datumswerte)*/
border-top: 1px solid darkblue;
/*Jede td-Zelle erhält eine linke und obere Border, auf diese Weise ergibt sich dann das Gitternetz*/
border-left: 1px solid darkblue;
cursor: pointer;
/*Mauszeiger verändert Form, wenn wir über die Tabellenzellen fahren, um anzuzeigen, dass man hier sinnvollerweise klicken kann*/
}
#kalenderblatt td:hover {
background: yellow;
/*Hintergrund gelb, wenn man mit der Maus drüber fährt*/
}
#kalenderblatt td.heute {
background: pink;
/*Der heutige Tag wird markiert*/
}
#kalenderblatt td.heute:hover {
background: yellow;
/*Der heutige Tag wird auch gelb beim Drüberfahren*/
}
#kalenderblatt td.sa {
color: darkblue;
/*Samstage sind blau dargestellt*/
}
#kalenderblatt td.so {
color: red;
/*Sonnage sind rot dargestellt*/
}
#kalenderblatt td.kw {
font-style: italic;
/*Kw-Angaben kleiner und in Kursivschrift*/
font-size: 0.9rem;
cursor: unset;
/*Kw kann man nicht klicken, also Cursor anders*/
color: grey;
}
#kalenderblatt td.kw:hover {
background: unset;
/*Nix passiert, wenn man über KW hovert*/
}
/*==============================================================================
// DEBUG //
==============================================================================*/
#debug {
background: lightcoral;
border-right: 1px solid darkblue;
border-bottom: 1px solid darkblue;
}
Wie gesagt, Ihr sollt den Text nicht kopieren und es ist cool, wenn Eure Ergebnisse gar schöner sind. Mir hat es an der Stelle gereicht. Eine wichtige Anregung sind lediglich die Kommentare, die ich (insbesondere in der CSS) gemacht habe. In jeder Sprache gibt es Kommentarzeichen, die bestimmte Bereiche "auskommentieren". Das heißt, dass dort nicht nach Befehlen gesucht wird und soher auch keine Fehler auftreten, wenn man egal was hinein schreibt. Das dient vor allem Euch und den Leuten, die mit Eurem Code arbeiten müssen, als Hilfe. Hier habe ich, um Euch zu helfen, exzessiv kommentiert. In echt wäre das weniger, aber diese simplen Ordnungs-Kommentare, wie ich sie mit den Zwischenüberschriften in der CSS gemacht habe, helfen mir massiv bei der Übersicht. Gerade CSS-Dateien verkommen ohne jede Ordnung zu einem großen Chaos, da es irgendwann tausende kleiner Einzelvorschriften sind, ohne echten Zusammenhang.
HTML
<!-- Kommentar bis zu -->
CSS
/* Kommentar bis zu */
Javascript
// Kommentar bis zum Ende der Zeile
/* Kommentar bis zu */
Mit "Kommentar bis zu" kann man dann auch mehrzeilige Kommentare machen.
Die Chance ist recht hoch, dass Ihr, wenn Ihr die Seite heute aufruft, nicht das aktuelle Kalenderblatt seht, sondern das von gestern, vorgestern, letzter Woche oder einem komplett aus Eurer Sicht zufälligen Datum der Vergangenheit. Was, wenn wir aber immer das heutige Kalenderblatt zeigen wollen?
Eine Herangehensweise wäre, einfach eine Datei für jedes Datum in den nächsten 20 Jahren anzulegen. Aber es wäre definitiv kein eleganter Ansatz.
Die andere Herangehensweise ist, dass wir dem Computer beibringen müssen, alle Inhalte auf den heutigen Tag anzupassen. Und dafür nutzen wir nun Javascript. Javascript ist eine Programmiersprache (und ja, die erste, mit der wir uns nun befassen, weil HTML/CSS nur Inhalte bzw. Layout beschreiben). Sie wird vor allem im WWW benutzt (aber auch für Apps etc.). Im WWW-Bereich ist die Besonderheit, dass Javascipt-Code zusammen mit der HTML-Seite an den Besucher einer Webseite (Client) versendet wird und dass dessen Browser dann den Javascipt-Code ausführt. Er wird dann auf dem Client-Rechner interpretiert, also in Maschinensprache übersetzt. Daher ist Javascript nicht sonderlich performant, aber moderne Maschinen sind auch so schnell, dass das im Normalfall nicht ins Gewicht fällt. Im WWW-Bereich entlastet es sozusagen den Server, weil der Client die Berechnungen durchführt, nicht der Server.
Ähnlich wie bei der CSS-Einbindung müssen wir auch unserer HTML die
Javascript-Datei mitgeben, also das Javascript einbinden in unseren HTML-File.
Sowohl bei CSS als auch bei Javascript wäre es auch möglich, den Code
direkt in der HTML-Datei unterzubringen. Man müsste CSS-Code nur in
<style></style>
-Tags verpacken und den Javascript-Teil in
<script></script>
-Tags. Aber aus Übersichtlichkeitsgründen trenne ich das in
der Regel, erstelle also eine neue Javascript-Datei in unserem Projekt. Damit
Javascript erkannt wird, sollte sie die Dateierweitung .js haben. In meinem
Beispiel ist es "step4.js" (und ansonsten gibt es "step4.html" und "step4.css").
<script language="javascript" type="text/javascript" src="step4.js"></script>
Diese Zeile fügte ich in meine HTML-Datei im HEAD irgendwo hinzu. Die Position ist übrigens eigentlich egal. Bei umfangreichen Webseiten mit viel Suchmaschinenoptimierungen werden JS-Dateien immer sehr am Ende des HTML eingefügt, damit die Inhalte schonmal geladen sind, bevor die JS geladen wird. Aus Übersichtlichkeitsgründen packen wir es jetzt nach oben.
Packt mal als erstes in Eure Javascript-Datei nur eine Zeile:
alert('Javascript funktioniert!');
Wenn Ihr beim nächsten Laden Eurer Seite ein entsprechendes Fenster angezeigt bekommt, dann hat die Verknüpfung von HTML und JS geklappt.
Folgende JS können wir als Grundstock nutzen:
// Hier sagen wir dem Browser nur, dass erm nachdem die Seite vollständig geladen
// wurde, die Funktion main(ohne Argumente) aufrufen soll.
window.onload = function() {
main();
};
// Und hier ist die Funktion main() dann auch definiert.
function main() {
// Grund-Daten
var strDebug = "";
var datToday = new Date();
strDebug += "datToday: " + datToday.toDateString() + "<br/>"; // Ausgabe
var datTodayGerman = getDateGerman(datToday);
strDebug += "datTodayGerman: " + datTodayGerman + "<br/>"; // Ausgabe
// Wochentag
var weekday = datToday.getDay(); // ergibt den Tag der Woche als Zahl
// (von 0 = Sonntag bis 6 = Samstag)
strDebug += "weekday: " + weekday + "<br/>"; // Ausgabe
var weekdayGerman = getWeekdayGerman(weekday);
strDebug += "weekdayGerman: " + weekdayGerman + "<br/>";
// Ausgabe in das elDebug
var elDebug = document.getElementById("debug");
if (elDebug != null) {
elDebug.innerHTML = strDebug;
} else {
console.log("Debug-Element nicht gefunden.");
}
}
function getDateGerman(date) {
day = date.getDate();
month = date.getMonth();
month = month + 1; // Warum auch immer ... Javascript speichert Monate
// 0-basiert, also 0 = Januar, 11 = Dezember, daher hier
// Korrektur + 1
year = date.getFullYear();
// Man beachte: Man könnte hier nachfolgend nach dem if {} benutzen, aber da
// es sich nur um EINE nachfolgende Anweisung handelt, geht es auch so
if (String(day).length == 1) day = "0" + day;
// Nachfolgend alternativ MIT Klammern
if (String(month).length == 1) {
month = "0" + month;
}
dateGerman = day + "." + month + "." + year;
return dateGerman;
}
function getWeekdayGerman(weekdayIndex) {
if (weekdayIndex == 0) {
return "Sonntag";
} else if (weekdayIndex == 1) {
return "Montag";
} else if (weekdayIndex == 2) {
return "Dienstag";
} else if (weekdayIndex == 3) {
return "Mittwoch";
} else if (weekdayIndex == 4) {
return "Donnerstag";
} else if (weekdayIndex == 5) {
return "Freitag";
} else if (weekdayIndex == 6) {
return "Samstag";
}
}
Voraussetzung dafür, dass der Code funktioniert, ist, dass Ihr auch einen div-Bereich mit der id "debug" in Eurem HTML-Code habt. Bei mir war es der rote Bereich. Macht Euch das irgendwie so, dass das gegeben und erkennbar ist.
Und nun versucht, den Code nachzuvollziehen. Ich mach das jetzt quasi zeilenweise:
()
).null
beinhaltet, der ansonsten in Zukunft immer für Zusatsatzaufwand sorgen
würde.<br/>
, was einfach einen
Zeilenumbruch in HTML darstellt. Sprich: Was auch immer wieder später
hinzufügen, steht eine Zeile tiefer. Wir wollen das später alles ausgeben und
dann helfen die Umbrüche beim Lesen. <div id="debug">
Test
</div>
alert("Nichts gefunden")
stattdessen benutzen und dann wäre es ein Popup-Fenster stattdessen.Das war die void main(). Kommen wir nun zu den beiden Spezialfunktionen, die wir uns gebaut haben:
Die Funktion erwartet ein Datum als Parameter (das steht nirgendwo, aber so ist sie geschrieben. Würde man sie mit einem String füttern als Argument, gäbe es wohl einen Fehler).
1
wird "1"
) eine Länge von genau einem Zeichen hat. Das ist dann
der Fall, wenn unser Datum der erste bis neunte eines Monats ist. Wenn dem so
ist, dann verketten wir "0" und die Monatszahl. Ansonsten ist es eben die
Monatszahl. Im Hintergrund wandelt Javascript, das ja keine feste Typen kennt,
eine Zahl automatisch zu einem String um, wenn wir Strings verketten.Es gibt deutlich schlauere Methoden, wie man diese Umformatierungen vornimmt, aber hier werden bewusst viele if-Checks eingesetzt, damit Ihr Euch mit diesem grundlegenden Konstrukt der if-Bedingungen beschäftigt.
Guckt Euch die Webseite an. Ihr werdet sehen, dass, wenn alles richtig läuft, im Debug-Kasten korrekte Informationen zum heutigen Tag stehen. Dummerweise steht auf Eurer Seite ansonsten immer noch der alte Krempel. Schaut Euch im Inspektor an, was im Debug-Kasten steht: Dort steht das, was wir im Javascript reingeschrieben haben. Nicht mehr das, was laut HTML da stand. Folglich ist unsere Aufgabe, an anderen Stellen einen ähnlichen Effekt zu erzielen: Wir wollen die Altinformationen ersetzen durch neue Informationen. Damit das gelingen kann, müssen unsere Bereiche, wo zu aktualisierende Informationen stehen, auch per id direkt ansprechbar sein. Dafür ändere ich meinen HTML-Code an zuerst einmal zwei Stellen.
<div id="seitenkopf">
<h1>Mein Kalenderblatt: <span id="field1">(Datum deutsch)</span></h1>
</div>
Statt einem Datum steht dort nun ein <span></span>
-Element. Span ist ein
HTML-Element, das für sich genommen gar nix tut. Es definiert halt einen Bereich
in einem Text. Man könnte dann per CSS dafür sorgen, dass alle
<span class="bold">
-Bereiche fett geschrieben sind. Für sowas ist das
eigentlich gut. Uns dient es dazu, die Bereiche zu markieren, deren Inhalte wir
verändert wollen, denn nun haben diese Bereiche einen eindeutigen Bezeichner,
die id "field1" in diesem Fall. Der Text "(Datum deutsch)" dient mir nur als
Gedächtnisstütze, welche Info da rein soll - er wird ja eh überschrieben.
<div id="introtext">
<p>
Der 08.02.2021 ist ein <span id="field2">(Wochentag)</span> und
zwar der zweite Montag im Monat Februar des Jahres 2021. Dieser
Monat hat 28 Tage. Der 08.02.2021 ist kein gesetzlicher Feiertag in
Hessen. Der nächste gesetzliche Feiertag ist der 02.04.2021
(Karfreitag).
</p>
</div>
Das ist meine zweite Ersetzung. Die span mit der id "field2" (wir erinnern uns: IDs müssen eindeutig sein). Wir brauchen also einmal den korrekt formatierten Wochentag und einmal das deutsch formatierte Datum, um die Ersetzungen vornehmen zu können. Und wie der Zufall es will: Beide Informationen liegen uns ja in unserem JS-Code schon vor. Wir ergänzen unsere JS-Datei um ein paar Zeilen und erhalten unsere main-Funktion. **Wohlgemerkt: Alle anderen Teile der JS-Datei bleiben unverändert)
function main() {
// Grund-Daten
var strDebug = "";
var datToday = new Date();
strDebug += "datToday: " + datToday.toDateString() + "<br/>"; // Ausgabe
var datTodayGerman = getDateGerman(datToday);
strDebug += "datTodayGerman: " + datTodayGerman + "<br/>"; // Ausgabe
// Wochentag
var weekday = datToday.getDay(); // ergibt den Tag der Woche als Zahl (von 0 = Sonntag bis 6 = Samstag)
strDebug += "weekday: " + weekday + "<br/>"; // Ausgabe
var weekdayGerman = getWeekdayGerman(weekday);
strDebug += "weekdayGerman: " + weekdayGerman + "<br/>";
// Wir füllen die Informationen in den HTML-Code
document.getElementById("field1").innerHTML = datTodayGerman;
document.getElementById("field2").innerHTML = weekdayGerman;
// Ausgabe in das elDebug
var elDebug = document.getElementById("debug");
if (elDebug != null) {
elDebug.innerHTML = strDebug;
} else {
console.log("Debug-Element nicht gefunden.");
}
}
Neu sind dabei nur die folgenden Zeilen:
// Wir füllen die Informationen in den HTML-Code
document.getElementById("field1").innerHTML = datTodayGerman;
document.getElementById("field2").innerHTML = weekdayGerman;
An die beiden Stellen im Code werden also unsere berechneten Werte geschrieben und wenn alles gut geht, dann sind diese beiden Stellen der Seite nun aktualisiert.
Um mir die Übersicht zu erleichtern, hab ich mir noch folgende CSS-Regel eingerichtet:
span {
background: lightcoral;
}
Auf diese Weise sehe ich, welche Spans ich schon eingerichtet habe und welche nicht.
Jetzt könnt Ihr Euch eine Weile mit der Aktualisierung der Seite beschäftigen. Bezogen auf meinen Webseiteninhalt gilt:
Schon tatsächlich relativ fortgeschritten ist dann die Frage, wie man herausfinden kann, ob heute ein Feiertag ist. Mein erster Tipp wäre: Startet mit den fixen Feiertagen: Man kann relativ einfach prüfen, ob wir den 01.01., 01.05., 03.10., 25.12., 26.12. haben.
Viel Spaß
An dieser Stelle ist es nicht ratsam, direkt weiter zu hasten. Sowohl HTML, als auch CSS und Javascript sind ja für die meisten neu, und es sind gleich drei Sachen auf einmal. Stattdessem dreht sich dieser Teil des Tutorials eher um die Arbeitsweise generell.
Dieses englische Wort heißt einfach nur "Einrückung", also wenn ein Textabsatz am Anfang eingerückt ist, um Absatzwechsel besser kenntlich zu machen. Das steigert die Lesefreundlichkeit. Und ebenso ist es in (allen!) Programmiersprachen. Man vergleiche folgende HTML-Schnipsel:
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt:
<span id="field1">(Datum deutsch)</span>
</h1>
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein <span id="field2">(Wochentag)</span> und zwar
der zweite Montag im Monat Februar des Jahres 2021. Dieser Monat hat
28 Tage. Der 08.02.2021 ist kein gesetzlicher Feiertag in Hessen.
</p>
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge aus</li>
<li>Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen</li>
<li>Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus geht als
erster Computerbug in die Geschichte ein.</li>
</ul>
</div>
</body>
und
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt:
<span id="field1">(Datum deutsch)</span>
</h1>
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein <span id="field2">(Wochentag)</span> und zwar
der zweite Montag im Monat Februar des Jahres 2021. Dieser Monat hat
28 Tage. Der 08.02.2021 ist kein gesetzlicher Feiertag in Hessen.
</p>
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>
Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge
aus.
</li>
<li>
Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen
</li>
<li>
Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus
geht als erster Computerbug in die Geschichte ein.
</li>
</ul>
</div>
</body>
Im zweiten Bereich ist deutlich auf den ersten Blick erkennbar, dass
beispielsweise innerhalb des Kasten (div) erst ein Textabsatz (p) erscheint
und dann nach dem Textabsatz kommt eine ungeordnete Liste (ul), die wiederum
3 Listeneinträge (li) enthält. Im ersten Schnipsel muss man umständlich
suchen, wenn man sich die Frage stellt, wann ein bestimmtes div
wieder endet
und dergleichen mehr.
Auch beim Schreiben selbst hilft das ungemein. Angenommen man hat ein schließendes Tag vergessen. Wenn der Text korrekt eingerückt ist, dann sieht man das sofort, weil man nicht auf der gleichen Einrückungsebene das schließende Element hat. Die IDE hilft da auch: Sie zeigt eine Verbindungslinie vom öffnenden zum schließenden Tag. Diese Linie muss oben und unten das öffnende / schließende Element berühren. Sie darf nicht unterbrochen werden von anderen Elementen (weil das ein Formfehler wäre ... ein Element kann beliebig viele "Tochterelemente" haben, also Elemente unterhalb von sich selbst, aber jedes Element hat genau ein, klar zu definierendes Mutterelement). Das gilt in HTML, aber ebenso auch für jeden Programmcode.
Wenn einem der Programmcode durch die Einrückungen zu weit nach rechts rutscht, sodass man kaum noch was Sinnvolles sieht, dann hat das einen oder mehrere dieser Gründe:
Strg
+ +
oder Strg
+ -
.div
erstellst, dass die gesamte
Seite umfasst (sowas nennt man oft einen Wrapper), dann die 3 Spalten (als
div
) öffnest und dann innerhalb dieser div
-Elemente jeweils die Zeilen
als neue div
-Elemente platzierst. Das heißt, dass jeder tatsächliche Inhalt
schonmal eine Mutter (Zeile), eine Großmutter (Spalte) und eine Uroma
(Wrapper) hat, die jeweils nur dieser Struktur dienen.body
-Element im CSS als grid (recherchiere "css
grid") mit 3 Spalten und 4 Zeilen und platzierst dann hintereinander
einfach die 12 div
-Elemente, die im Grid platziert werden sollen. Jetzt hat
jeder tatsächliche inhalt nur noch eine Mutter, das div
, zu dem es nunmal gehört.<html>
). Allerdings gilt
das ja in allen Fällen.)Es gibt tatsächlich für mich nichts störenderes als nicht ordentlich
eingerückten Code. Oftmals hilft Euch die IDE bei der Arbeit. In VSCode
beispielsweise formatiert die Tastenkombination Shift
+ Alt
+ F
Euren Code
nach den für VSCode definierten Regeln. Nicht für jede Sprache ist das
vorhanden. Weitere Sprachen kann man als Erweiterungen installieren. Mitunter
gibt es auch alternative Formatierer als Erweiterungen.
Da heute ja jeder nur über Bildschirme wischt, sei an dieser Stelle mal gesagt, dass eine normalgroße Tastatur unerlässlich sein wird. Das muss kein Schnickschnack sein, für unter 20 Euro erhält man ja bereits eine gute Tastatur, die man dann ggf. auch an den Laptop oder das Pad anschließen kann. Das ist echt unerlässlich. Programmieren bedeutet Tippen. Größere Tasten bedeutet weniger Tippfehler. Mehr Tasten bedeuten mehr Möglichkeiten.
Generell muss man lernen, die Maus weniger und die Tasten mehr zu benutzen. Es gibt ein paar Tastatur-Funktionen, die man kennen sollte und die vielleicht nicht jedem geläufig sind:
Shift
drückt, während man sich per Pfeiltasten bewegt (aber auch
wenn man danach wohin clickt), dann markiert man damit Text. Das macht es
beispielsweise schnell möglich, etwas auszuschneiden (Strg
+ x
), zu
verschieben (ganze Zeile: Alt
+ Pfeiltaste
), kopieren (Strg
+ c
bzw. für
ganze Zeile: Shift
+ Alt
+ Pfeiltaste
, löschen (Enf
oder Rück
).Pos1
Tab
Shift
+ Tab
Alt
gedrückt halten und jede
Stelle anclickenStrg
+ #
Zudem gibt es tolle weitere Tricks in VSCode:
Shift
+ Strg
+ Leerzeichen
: Eines der wichtigsten Kommandos überhaupt:
Kommando-Palette ... Alle integrierten Befehle von VSCode.Strg
+ Leerzeichen
: Vorschläge von VSCode zur gegenwärtigen Code-Stelle.
Das ist das gleiche Menu, dass Euch sonst auch Vorschläge dazu macht, wie der
Befehl, den Ihr gerade tippt, wohl weiter geht. Mitunter habt Ihr so aber schon
die Möglichkeit, etwas auszuwählen, wo Ihr noch nichts getippt habt. Das ist
insbesondere sinnvoll, wenn man eine komplexe Funtion mit benannten Parametern
aufrust, wo man aber die Namen der Paramenter nicht genau kennt.Strg
+ ^
) und dann hat man zwei Fenster zum gleichen Code. Ihr könnt eines
auf die Nachschlagestelle einrichten und in dem anderen coden. Damit kann man
auch erreichen, dass man eine Datei bearbeitet und gleichzeitig eine zweite
sieht.Strg
+ k
+ w
(k
und w
kann man nacheinander drücken)Klickt Euch einfach mal durch die Menus und experimentiert. Neben jedem Menueintrag steht auch die Tastenkombination, die man stattdessen ausführen kann. Merkt Euch, was Ihr häufiger benutzt.
In meinem Code in den vorigen Lektionen hatte ich massiv viel Kommentare eingebaut. Das war natürlich nur, weil es Euch das Verständnis des Codes erleichtern sollte. Aber auch ansonsten sind Kommentare ein wichtiges Mittel. Dabei ist das richtige Maß wichtig:
Debugging ist der Prozes des Fehler-Suchens und -Bereinigens. Bei einer CSS oder HTML-Datei ist das ja relativ einfach: Man sieht sofort, wenn etwas nicht stimmt. Das fällt bei Code naturgemäß schwerer. Das Ergebnis ist nicht das erwartete, aber woran liegt das? Das ist von außen nicht immer einfach zu sehen. Fangen wir speziell mit Javascript an:
Shift
+ Strg
+ i
oder F12
oder Rechtsclick in
den Browser und "Untersuchen" oder sonst irgendwie öffet Ihr den Inspector. Dort
gibt es definitiv ein Tab namens "Konsole" (Console). Dort werden harte
Fehlermeldungen ausgegen. Beispiel aus Firefox. Hier können wir beispielsweise
sehen, dass ein Fehler in Zeile 17 unserer JS-Datei einen Fehler verursacht hat.
Das erleichtert natürlich die Suche:console.log(XXX)
etwas ausgeben, wobei XXX natürlich für das steht, was Ihr
ausgeben wollt. Das kann ein Text sein, wenn Ihr beispielsweise prüfen wollt,
ob der Code eine Fallunterscheidung auf der If-Seite oder der Else-Seite
durchlaufen habt, dann gebt, je nachdem, unterschiedliche Meldungen aus. Oder
gebt den Inhalt einer Variablen aus, damit Ihr sehen könnt, ob diese Variable
zu dem Zeitpunkt den erwarteten Wert hatte.Die konfortabelste Variante, Code auf Fehler hin zu untersuchen, ist VSCode selbst und die eingebauten Debugging-Tools. Kleines Problem allerdings: Das klappt so gut wie nie auf Anhieb (bei Flutter ja, aber bei Javascript selten). Wenn Ihr Glück habt, habt Ihr Glück.
Also: Prinzipiell erstmal: Man kann das Debugging per VSCode starten per Menu:
Relativ gut klappt es prinzipiell mit Chrome (mein Eindruck). Wenn die Standardvorgaben nicht klappen, dann müsst Ihr Euch in Eurem Project eine eigene Konfiguration einrichten per "Run" - "Add configuration" oder "Run - View configuration". Damit wird in Eurem Projekt ein Verzeichnis .vscode angelegt mit einer Datei "launch.json", in der die von Euch konfigurierten Debug-Möglichkeiten gespeichert werden. Mit dieser launch.json hab ich bei mir Edge und Firefox zum Laufen bekommen.
{
"version": "0.2.0",
"configurations": [
{
"name": "Microsoft Edge: This file",
"request": "launch",
"type": "pwa-msedge",
"file": "${file}"
},
{
"name": "Firefox: This file",
"type": "firefox",
"request": "launch",
"reAttach": true,
"file": "${file}"
}
]
}
Da kann man experimentieren. Prinzipiell zu erwähnen ist, dass diese Konfiguration das File im Browser öffnen, das Ihr gerade im Editor anschaut. Mit anderen Worten: Ihr müsst die passende HTML-Datei angucken, dann auf Run & Debug klicken. Die Javascript-Datei für sich genommen bringt da nix.
Egal, sollte das irgendwann irgendwie laufen, dann gilt: In VSCode könnt Ihr Breakpoints in Eurem Code setzen. Das sind Punkte, an denen der Browser aufhören soll, den Code auszuführen und ab denen Ihr der Ausführung live zusehen wollt. Das Setzen geschieht durch Klick links von der entsprechenden Programmzeilen-Nummer, wodurch dann dort ein roter Punkt angezeigt wird.
Wenn wir nun die entsprechende HTML debuggen, dann wird diese geladen und unser Javascript-Code wird eben nicht vollständig ausgeführt. Stattdessen erhalten wir einen Zustand wie diesen:
Wie man sieht: Der Code läuft noch, der Seiten-Lade-Indikator oben links bewegt sich. Die Ersetzungen in der Seite sind noch nicht vorgenommen. Die Debug-Box ist noch nicht gefüllt. Und im VSCode-Fenster sehen wir, dass die Code-Ausführung bei unserem ersten Breakpoint stoppte.
Wenn wir links auf den Button "Run" gehen, dann sehen wir in dem linken Tab lauter Informationen zu unserem Code. Variablen, die im Umfeld verwendet wurden, auch noch nicht definierte Variablen (undefined). Wir sehen an dem gelben Bereich, in welcher Zeile wir gerade sind. Wir können mit dem Mauszeiger auf Objekte zeigen und uns deren gegenwärtigen Inhalt anzeigen lassen und oben rechts haben wir jetzt ein Bedienelement, was ein wenig wie die Bedienelemente an einem Videoplayer aussehen. Hiermit können wir uns jetzt bewegen:
Mit diesem Tool lohnt sich das Spielen. Es ist unglaublich mächtig. Unten links kann man noch einstellen, welche Breakpoints gelten sollen. Hier werden die selbst definierten gelistet, aber es kann auch ausgewählt werden, ob Fehlermeldungen immer zum Anhalten führen sollen.
Wenn Ihr den Code aus Lektion 3 selbst getippt habt oder aber wenn Ihr Eure ersten Gehversuche aufgrund der Aufgabenstellungen in Lektion 3 unternehmt, dann gibt es automatisch mehr als genug Anlässe zum Debuggen, denn Fehler gibt es immer.
Was Code angeht, den Ihr selbst entwickelt: Tut Euch selbst den Gefallen und fertigt Euch vor dem Schreiben mal eine Skizze an, was Ihr erreichen wollt und in welcher Reigenfolge etc. Wenn die Funktionalität in PSeudocode richtig beschrieben ist, dann ist das 75% der Miete.
In jedem der bisherigen Kapitel wurde Euch auch von mir geschriebener Code in HTML, CSS und JS präsentiert. Eure Aufgabe war es dann, diesen Code nachzuvollziehen und für die Problemstellungen auszubauen.
Nachvollziehen heißt: Es mag ja sein, dass mein Code für Euch überraschend kommt. Ja, das ist sogar recht wahrscheinlich. Wenn dem so ist, dann ist es Eure Aufgabe, genau zu verstehen, was jede meiner Zeilen macht. Ändert sie gegebenenfalls, um ein anderes Ergebnis zu erzielen. Erschließt Euch die Wirkung mit Google und Tutorials etc. Ich will nicht, dass beim zweiten Lesen irgendwas davon überraschend ist.
Am Anfang geht es vor allem darum zu sehen, was alles möglich ist. Ihr habt insbesondere Folgendes gelernt:
Vergleichsweise unwichtig ist, ob ihr 10 oder 100 CSS-Eigenschaften auswendig kennt. Oder 10 oder 100 Javascript-Befehle. Wichtig ist aber, dass Ihr an dem JS-Code erkennen konntet, wie man sich Stück für Stück dem Ziel nähert. Wir wollten beispielsweise den Wochentag im Text stehen haben. Und das bedeutete, dass wir erst das aktuelle Datum herausfinden mussten, dann den Wochentag berechnen ließen und dann diese numerische Information in eine deutschsprachige Information verwandelten.
Die Frage nach den Feiertagen ist gar nicht mal so schwer, aber die würde an den meisten Tagen eh nur ausgeben, dass gerade kein Feiertag ist, also stellen wir das mal hinten an. Wenden wir uns stattdessen direkt dem Elefanten im Raum zu, und das ist das Kalenderblatt selbst. Ihr habt alle ein Kalenderblatt bereits gebaut und ich hoffe Ihr habt nicht nur meinen Code kopiert. Aber selbst wenn Ihr ihn kopiert habt, so solltet Ihr Euch damit beschäftigt haben, wieso er aussieht, wie er aussieht.
Und wenn wir nun unser Projekt dahingehend aufwerten wollen, dass wir für jedes beliebige Datum das passende Monats-Kalenderblatt zeichnen, dann müssen wir dahinter kommen, welche Informationen wir wirklich brauchen, um ein Kalenderblatt für einen beliebigen Monat zu zeichnen.
Und das ist die Frage, über die ihr bitte nachdenkt: Welche Informationen braucht Ihr, um das richtige Kalenderblatt für den August 2055 und für den Mai 1687 zu zeichnen?
Setzt Euch hin und versucht, die beiden Kalenderblätter zu zeichnen. Welche Informationen sind es, die Ihr braucht? Diskutiert es auch gemeinsam. Ich werde eine Antwort (es gibt auch andere, gar bessere) logischerweise im nächsten Tutorial-Step verraten müssen, aber es wäre wirklich gut, wenn Ihr sie selbst findet und nicht einfach in das nächste Tutorial klickt. Es ist ... schaffbar.
Kleiner Hinweis:
Wie gesagt, es gibt sicherlich andere und wahrscheinlich auch bessere Ansätze, aber ich werde nun meinen Ansatz teilen - und zwar schrittweise zuerst:
let htmlTabelle = calendarHTML(datToday);
document.getElementById("kalenderblatt").innerHTML = htmlTabelle;
Das ist der erste Schnipsel, direkt in der von uns mit main()
bezeichneten
Funktion. Was tun diese Zeilen? Nun, es wird einfach eine Variable deklariert
und mit dem Ergebnis der Funktion calendarHTML
, der das heutige Datum datToday
als Argument mitgegeben wird, gefüllt. Danach wird dieses Ergebnis einfach wie
gewohnt in das Dokument, also quasi die dargestellte HTML-Datei, eingefügt.
Mit anderen Worten: Was auch immer geschieht, muss in calendarHTML
passieren.
Und diese Funktion erschließen wir uns jetzt schrittweise.
function calendarHTML(date) {
let firstOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
let firstOfMonthWeekday = firstOfMonth.getDay();
let offsetStart;
if (firstOfMonthWeekday == 0) {
offsetStart = 6;
} else {
offsetStart = firstOfMonthWeekday - 1;
}
let firstOfCalendar = new Date(firstOfMonth);
firstOfCalendar.setDate(firstOfCalendar.getDate() - offsetStart);
Was geschieht?
date
(also das datToday
aus der main
-Funktion) liegt, nur eben an dem
Tag 1 dieses Monats. Das ist ziemlich genau die Definition eines Monatsersten,
oder nicht?firstOfMonth
gespeichert ist, die Methode getDay
an. Jedes Datumsobjekt
hat diese Methode, wie man der offiziellen Dokumentation entnehmen kann. Ich kann gar nicht häufig genug
betonen, dass Ihr immer möglichst häufig offizielle Dokumentationen lesen
sollt. Niemand verlangt von Euch, dass Ihr irgendwann alle Methoden des
Datum-Objektes von Javascript auswendig könnt. Aber Ihr solltet wissen, dass man
doch den Wochentag eines Datums irgendwie rauskriegen können muss und dann
in der Dokumentation gucken können. :-)offsetStart
und in diese wollen wir
speichern, wie viele Tage des Vormonats noch dargestellt werden, weil der aktuelle
Monat zum Beispiel an einem Mittwoch beginnt. Uns ist klar: Dann wären es 2 Tage.offsetStart = firstOfMonthWeekday -1;
let lastOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);
let lastOfMonthWeekday = lastOfMonth.getDay();
let offsetEnd;
if (lastOfMonthWeekday == 0) {
offsetEnd = 0;
} else {
offsetEnd = 7 - lastOfMonthWeekday;
}
let lastOfCalendar = new Date(lastOfMonth.getFullYear(), lastOfMonth.getMonth(), lastOfMonth.getDate() + offsetEnd);
Hier passiert in etwa das Gleiche wie oben, nur eben für das Ende des Kalenders:
date
,
einen Monat später und dann der 0te des Monats. Wir wissen inzwischen, dass
Javascript uns korrigiert. Der 0te eines Monats ist der letzte des Monats zuvor.
Also rechnen wir auf diese Weise den letzten des aktuellen Monats aus. Und die
wirklich schöne Eigenschaft von Javascript, dass es automatisch ein gültiges
Datum aus sinnlosen Eingaben (wie eben der Nullte eines Monats) macht, sorgt
auch dafür, dass diese Berechnung auch im Dezember klappt. Wir müssen uns also
nicht darum kümmern, dass das Jahr auch einen hochzählen muss. Wir springen
sozusagen in den 13. Monat, Tag 0. Und das ist dann der 31.12.7 - lastOfMonthWeekday
Tage, die wir zusätzlich darstellen müssen. Rechnet
es nach. Das sind 6 zusätzliche Tage, wenn der Letzte auf einen Montag (1) fällt,
5 zusätzliche Tage, wenn es ein Dienstag (2) ist - und so weiter. let html = "";
html += calendarHTML_head(firstOfMonth);
let indexDate = new Date(firstOfCalendar);
while (indexDate <= lastOfCalendar) {
html += calendarHTML_cell(indexDate);
indexDate.setDate(indexDate.getDate() + 1);
}
html += calendarHTML_footer();
html
und lassen diese mit einem leeren
String / Zeichenkette beginnen.calendarHTML_head
. Diese baut nur die ersten Zeile der Tabelle auf, und ich
werde mich nicht weiter mit dieser hier beschäftigen. Es geht um die Überschrift
und die Spaltenüberschriften (kw, Mo, Di, Mi, Do, Fr, Sa). Im fertigen Code
könnt Ihr alles nachlesen.
html += 'irgendwas';
. Das ist eine
Kurzschreibweise für html = html + 'irgenwas';
, also die Variable
html wird genommen, dann wird etwas hinten dran gehängt (hier das Wort "irgendwas")
und das, was dabei rauskommt, wird wieder in html
gespeichert.indexDate
und setzen sie auf den ersten
im Kalender abgebildeten Tag.while
-Schleife. Eine Schleife dieses Typs läuft so
lange, wie die Bedingung, die in den runden Klammern steht, gegeben ist. Man
könnte das Ganze ebenso gut in einer for
-Schleife abbilden.html
wird wieder verlängert und zwar um die Antwort der
Funktion calendarHTML_cell
, die aufgerufen wird mit dem Funktionsargument
indexDate
. In jedem Schleifendurchlauf kann sie also, je nach indexDate
ein anderes Resultat haben (und hat sie auch).html
noch ergänzt um die Antwort der Funktion
calendarHTML_footer
, die ohne Argument aufgerufen wird. Sie packt einfach
den richtigen Abschluss der Tabelle noch hinten dran. Der Aufruf ohne Argument
ist logisch, weil dieser Abschluss der Tabelle immer gleich ist, egal welches
Datum wir darstellen.Es ist also klar, dass der eigentlich interessante Teil des HTML-Codes in der
Funktion calendarHTML_cell
generiert wird. Und diese Funktion sehen wir uns
nun an:
function calendarHTML_cell(date) {
let html = "";
let cssClass = "";
let weekday = date.getDay();
if (weekday == 1) {
// Montag: Neue Zeile beginnen
html += "<tr>";
// und Zelle für die Kalenderwoche
html += '<td class="kw">' + getCalendarWeek(date) + "</td>";
}
let weekdayGerman = getWeekdayShortGerman(weekday);
html += '<td class="' + cssClass + '">' + date.getDate() + "</td>";
if (weekday == 0) {
html += "</tr>";
}
return html;
}
Hier passiert Folgendes:
html
definiert. Wichtig: Diese überschreibt in
keiner Form die gleichnamige Variable in unserer main
-Funktion. Variablen
haben einen Bereich, in dem sie gültig sind - und grob gesagt ist das der lokale
Kontext. In der Regel sucht Ihr nach den geschweiften Klammern, in denen die
Variablendeklaration liegt. Hier sind das einfach die geschweiften Klammern,
welche die gesamte Funktion calendarHTML_Cell
umfassen.
var
und den Befehl let
. Bei let
ist 100%ig korrekt, was ich sagte,
aber das var
-Schlüsselwort deklariert die Variable immer für die gesamte
Funktion. Hier würden let
und var
zum selben Ergebnis führen, weil der
lokale Kontext die Funktion ist. Wenn aber beispielsweise mit let
eine
Variable in einer Schleife definiert wird, dann ist die Variable nur in der
Schleife gültig, nicht in der gesamten Funktion drumherum.<tr>
-Tag öffnen. Außerdem müssen wir eine Tabellenzelle für die
Kalenderwochen-Info generieren.return
-Anweisung den generierten HTML-Code
an die aufrufende Funktion zurück.Diese wenigen Elemente, die zusammen nicht mal länger sind als es zuvor der HTML-Code für einen statischen, unveränderbaren Kalender war, generieren für jeden möglichen Monat den HTML-Code.
Nochmal zum Nachvollziehen mein gesamter Code zu diesem Zeitpunkt:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="step5.css" />
<script language="javascript" type="text/javascript" src="step5.js"></script>
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt: <span id="field1">(Datum deutsch)</span></h1>
</div>
<div id="kalenderblatt">
(Hier stand einst ein stolzer Kalender)
</div>
<div id="introtext">
<p>
Der 08.02.2021 ist ein <span id="field2">(Wochentag)</span> und zwar der zweite Montag im Monat Februar des Jahres 2021. Dieser Monat hat 28 Tage. Der 08.02.2021 ist kein gesetzlicher Feiertag in Hessen. Der nächste gesetzliche Feiertag ist
der 02.04.2021 (Karfreitag).
</p>
</div>
<div id="debug">
Test
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge aus</li>
<li>Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen</li>
<li>Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus geht als erster Computerbug in die Geschichte ein.</li>
</ul>
</div>
</body>
</html>
/*==============================================================================
// BODY: Grundlegender Aufbau des Grids //
==============================================================================*/
body {
display: grid;
grid-template-columns: 1fr 2fr;
/*Im Verhältnis 1:2 wird der Bildschirm aufgeteilt*/
margin: 0;
/*Kein leerer Bereich rund um den gesamten Body*/
}
/*==============================================================================
// SEITENKOPF //
==============================================================================*/
#seitenkopf {
padding: 1em;
text-align: center;
background: darkblue;
color: white;
grid-column-start: 1;
grid-column-end: 3;
/*Seitenkopf geht über beide Spalten*/
}
#seitenkopf h1 {
margin: 0;
padding: 0;
}
/*==============================================================================
// INTROTEXT UND HISTORIE //
==============================================================================*/
#introtext,
#historie {
grid-column-start: 2;
/*Historie und Introtxt sind hingegen nur rechts in der Spalte zuhause*/
grid-column-end: 3;
margin: 1em;
font-size: 1.3em;
padding: 1em;
}
/*==============================================================================
// KALENDERBLATT //
==============================================================================*/
#kalenderblatt {
border-bottom: 1px solid darkblue;
/*Border unten um das gesamte Feld*/
border-right: 1px solid darkblue;
/*Border rechts um das gesamte Feld*/
}
#kalenderblatt table {
margin: 25px auto;
/*Oben und unten 25 Punkte Abstand zu allem (zu den Grenzen des DIVs), links und rechts auto = zentriert*/
border-right: 1px solid darkblue;
/*Border rechts um gesamte Tabelle*/
border-bottom: 1px solid darkblue;
/*Border unten an gesamter Tabelle*/
/*Die Border nach links und oben ziehen wir in den einzelnen Zellen*/
border-spacing: inherit;
}
#kalenderblatt table th {
border-top: 1px solid darkblue;
/* Jede th-Zelle bekommt oben / links eine Border*/
border-left: 1px solid darkblue;
}
#kalenderblatt table td {
background: white;
text-align: center;
padding: 0.25em;
/*Bisserl Platz um den Inhalt der einzelnen Zellen (Datumswerte)*/
border-top: 1px solid darkblue;
/*Jede td-Zelle erhält eine linke und obere Border, auf diese Weise ergibt sich dann das Gitternetz*/
border-left: 1px solid darkblue;
cursor: pointer;
/*Mauszeiger verändert Form, wenn wir über die Tabellenzellen fahren, um anzuzeigen, dass man hier sinnvollerweise klicken kann*/
}
#kalenderblatt td:hover {
background: yellow;
/*Hintergrund gelb, wenn man mit der Maus drüber fährt*/
}
#kalenderblatt td.heute {
background: pink;
/*Der heutige Tag wird markiert*/
}
#kalenderblatt td.heute:hover {
background: yellow;
/*Der heutige Tag wird auch gelb beim Drüberfahren*/
}
#kalenderblatt td.sa {
color: darkblue;
/*Samstage sind blau dargestellt*/
}
#kalenderblatt td.so {
color: red;
/*Sonnage sind rot dargestellt*/
}
#kalenderblatt td.kw {
font-style: italic;
/*Kw-Angaben kleiner und in Kursivschrift*/
font-size: 0.9rem;
cursor: unset;
/*Kw kann man nicht klicken, also Cursor anders*/
color: grey;
}
#kalenderblatt td.kw:hover {
background: unset;
/*Nix passiert, wenn man über KW hovert*/
}
/*==============================================================================
// DEBUG //
==============================================================================*/
#debug {
background: lightcoral;
border-right: 1px solid darkblue;
border-bottom: 1px solid darkblue;
}
span {
background: lightcoral;
}
window.onload = function() {
main();
};
function main() {
let strDebug = "";
let datToday = new Date();
strDebug += "datToday: " + datToday.toDateString() + "<br/>"; // Ausgabe
let datTodayGerman = getDateGerman(datToday);
strDebug += "datTodayGerman: " + datTodayGerman + "<br/>"; // Ausgabe
// Wochentag
let weekday = datToday.getDay(); // ergibt den Tag der Woche als Zahl (von 0 = Sonntag bis 6 = Samstag)
strDebug += "weekday: " + weekday + "<br/>"; // Ausgabe
let weekdayGerman = getWeekdayGerman(weekday);
strDebug += "weekdayGerman: " + weekdayGerman + "<br/>";
document.getElementById("field1").innerHTML = datTodayGerman;
document.getElementById("field2").innerHTML = weekdayGerman;
let htmlTabelle = calendarHTML(datToday);
document.getElementById("kalenderblatt").innerHTML = htmlTabelle;
let elDebug = document.getElementById("debug");
if (elDebug != null) {
elDebug.innerHTML = strDebug;
} else {
console.log("Debug-Element nicht gefunden.");
}
}
function getCalendarWeek(date) {
let thursday = getThursday(date);
let cwYear = thursday.getFullYear();
let thursdayCw1 = getThursday(new Date(cwYear, 0, 4));
let cw = Math.floor(
1.5 + (thursday.getTime() - thursdayCw1.getTime()) / 86400000 / 7
);
return cw;
}
function getThursday(date) {
var thursday = new Date();
thursday.setTime(date.getTime() + (3 - ((date.getDay() + 6) % 7)) * 86400000);
return thursday;
}
function getDateGerman(date) {
day = date.getDate();
month = date.getMonth();
month = month + 1; // Warum auch immer ... Javascript speichert Monate 0-basiert, also 0 = Januar, 11 = Dezember, daher hier Korrektur + 1
year = date.getFullYear();
// Man beachte: Man könnte hier nachfolgend nach dem if {} benutzen, aber da es sich nur um EINE nachfolgende Anweisung handelt, geht es auch so
if (String(day).length == 1) day = "0" + day;
// Nachfolgend alternativ MIT Klammern
if (String(month).length == 1) {
month = "0" + month;
}
dateGerman = day + "." + month + "." + year;
return dateGerman;
}
function getMonthGerman(monthIndex) {
let arr = [
"Fehler",
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember",
];
return arr[monthIndex];
}
function getWeekdayGerman(weekdayIndex) {
let arr = [
"Sontag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
"Sonntag",
];
return arr[weekdayIndex];
}
function getWeekdayShortGerman(weekdayIndex) {
let arr = ["so", "mo", "di", "mi", "do", "fr", "sa", "so"];
return arr[weekdayIndex];
}
function calendarHTML(date) {
// Erster des Monats berechnen
let firstOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
// Wochentag des Monatsersten bestimmen
let firstOfMonthWeekday = firstOfMonth.getDay();
// Wenn der Monat nicht mit einem Montag startet, müssen wir Tage des Vormonats
// ebenfalls darstellen
let offsetStart;
if (firstOfMonthWeekday == 0) {
offsetStart = 6;
} else {
offsetStart = firstOfMonthWeekday - 1;
}
let firstOfCalendar = new Date(firstOfMonth);
firstOfCalendar.setDate(firstOfCalendar.getDate() - offsetStart);
// Letzte des Monats ist der 0te des Folgemonats
let lastOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);
// Wenn der Letzte des Monats nicht zufällig ein Sonntag ist, müssen wir auch Tage des nächsten Monats
// in unserem Kalender darstellen
let lastOfMonthWeekday = lastOfMonth.getDay();
let offsetEnd;
if (lastOfMonthWeekday == 0) {
offsetEnd = 0;
} else {
offsetEnd = 7 - lastOfMonthWeekday;
}
// Einfach Tage aufaddieren
let lastOfCalendar = new Date(lastOfMonth.getFullYear(), lastOfMonth.getMonth(), lastOfMonth.getDate() + offsetEnd);
// Jetzt beginnt das eigentliche Zeichnen des Kalenders
let html = "";
html += calendarHTML_head(firstOfMonth);
let indexDate = new Date(firstOfCalendar);
while (indexDate <= lastOfCalendar) {
html += calendarHTML_cell(indexDate);
indexDate.setDate(indexDate.getDate() + 1);
}
html += calendarHTML_footer();
return html;
}
function calendarHTML_head(date) {
// console.log("calendarHTML_head: " + date);
let nameOfMonth = getMonthGerman(date.getMonth() + 1);
// console.log("calendarHTML_head: " + nameOfMonth);
let html =
`<table>
<thead>
<tr>
<th colspan = "8">` +
nameOfMonth +
`</th>
</tr>
<tr>
<th class = "kw" > Kw </th>
<th class = "mo" > Mo </th>
<th class = "di" > Di </th>
<th class = "mi" > Mi </th>
<th class = "do" > Do </th>
<th class = "fr" > Fr </th>
<th class = "sa" > Sa </th>
<th class = "so" > So </th>
</tr>
</thead>
<tbody>`;
return html;
}
function calendarHTML_cell(date) {
let html = "";
let cssClass = "";
let weekday = date.getDay();
if (weekday == 1) {
// Montag: Neue Zeile beginnen
html += "<tr>";
// und Zelle für die Kalenderwoche
html += '<td class="kw">' + getCalendarWeek(date) + "</td>";
}
let weekdayGerman = getWeekdayShortGerman(weekday);
cssClass += " " + weekdayGerman;
html += '<td class = "' + cssClass.trim() + '">' + date.getDate() + "</td>";
if (weekday == 0) {
html += "</tr>";
}
return html;
}
function calendarHTML_footer() {
html = "</tbody></table>";
return html;
}
Die Aufgaben für den nächsten Abschnitt wären nun:
Im Prinzip ist für jeden Kalendertag zu prüfen, ob es ein Feiertag ist. Dieses
sollte man der Übersichtlichkeit halber auslagern in eine Funktion, die
zum Beispiel isHoliday
oder istFeiertag
nennen könnte und die true
returnt, wenn es sich um einen Feiertag handelt.
Was nun die Feiertagsroutine selbst angeht, solltet Ihr die folgenden Infos im Hinterkopf haben:
Direkt zu Beginn unseres Javascript-Programms legen wir fest, dass datToday
heute ist. Wir können diese Festlegung in den window.onLoad-Bereich
verschieben und der main-Funktion das per Argument mitteilen. Dann wird es
möglich sein, von anderer Stelle stattdessen andere Werte (statt heute) in die
Funktion einzuschleusen.
Es ist von jedem sichtbaren Element im HTML aus möglich, eine Javascript-Funktion auszulösen.
<td oncCick="unsereFunktion(3)">Tabellenzelle</td>
Diese Tabellenzelle würde beispielsweise bei einem Click die Funktion
unsereFunktion
mit dem Argument 3 ausrufen. So könnten wir auch einen
Neuaufbau des Kalenders auslösen.
Zum Abschluss unseres Projektes wenden wir uns den zwei offenen Themen zu:
Vorbemerkung: Ich habe im Vergleich zum letzten Tutorial ein paar Stellen umgeschrieben. Es kann daher sein, dass Ihr leichte Abweichungen zu Eurem Code feststellt.
Wir haben im letzten Abschnitt eine Funktion entwickelt, welche die einzelnen
Zellen der Kalenderblatt-Tabelle zeichnet. Bei mir hat sie den Namen geändert
und heißt jetzt getDayHTML
, aber sie tut noch im Wesentlichen das Gleiche.
function getDayHTML(date, today, holidayArray) {
let html = "";
let cssClass = "";
let weekday = date.getDay();
if (weekday == 1) {
html += "<tr>";
html += '<td class="kw">' + getCalendarWeek(date) + "</td>";
}
let weekdayGerman = getWeekdayShortGerman(weekday);
cssClass += " " + weekdayGerman;
if (holidayArray.includes(date.getTime())) {
cssClass += " feiertag";
}
if (date.getMonth() == today.getMonth() && date.getDate() == today.getDate()) {
cssClass += " heute";
}
html += '<td class = "' + cssClass.trim() + '" onClick="changeTime(' + date.getTime() + ')">' + date.getDate() + "</td>";
if (weekday == 0) {
html += "</tr>";
}
return html;
}
Gehen wir den Code mal zusammen durch:
date
erhält sie die Information darüber, welchen Tag sie
zeichnen soll.today
erhält sie das heutige Datum (damit wir nicht in
jedem Durchgang erneut "heute" berechnen müssen).holidayArray
erhält sie einen Array (also eine Liste von
Werten), der alle Feiertage nicht als unser übliches Date
-Objekt, sondern
als "Millisekunden nach dem 01.01.1970"-Werte enthält.html
wird später den eigentlichen HTML-Code für die Zelle enthalten und
als Ergebnis dieser Funktion zurück an die aufrufende Funktion gegeben, welche
den gesamten HTML-Code für die Kalenderblatt-Tabelle generiert.cssClass
enthält die Klasse(n), die unsere Tabellenzeile bekommt, was dann
im Zusammenspiel mit der CSS-Datei die Formatierung der Tabellenzelle beeinflusst.
Hier spielt dann eine Rolle, ob es sich beispielsweise um den heutigen Tag
handelt oder eben auch ob es ein Feiertag ist.weekday
den Wochentag (0 = Sonntag bis 6 = Samstag)
ab.getWeekdayShortGerman
,
um ein Kürzel wie "mo", "di", "mi", etc. zu bekommen. Das ist so ähnlich wie zuvor
bei den vollen Kalendernamen und können wir uns daher sparen in Sachen Erklären.holidayArray
das zu zeichnende
Datum (in Millisekunden) enthält. Wenn dem so ist, dann wir an die CSS-Variable
nun auch der String "feiertag" angehängt (zusammen mit einem vorangestellten
Leerzeichen, damit die Strings nicht aneinanderhängen wie "mofeiertag", sondern
eben zusammen dann "mo feiertag" ergeben).onClick
, mit der wir uns später noch beschäftigen
werden. Ansonsten ist aber alles recht ähnlich zu früher.Bezogen auf die Feiertage gilt also: Es wird geprüft, ob unser zu zeichnender
Tag in diesem ominösen holidayArray
enthalten ist. Folglich ist die Frage,
wie dieser Array zustande kommt. Dieser wird in der an der aufrufenden Stelle
zuvor wie folgt definiert.
let holidayArray = getHolidayArrayHessen(datToday);
Mit anderen Worten: Die Funktion getHolidayArrayHessen
wird uns zeigen, wie
die Feiertagssuche vonstatten ging. Schauen wir sie uns an:
function getHolidayArrayHessen(date) {
let year = date.getFullYear();
let array = [];
array.push(new Date(year - 1, 11, 25).getTime()); // 1. Weihnachtstag Vorjahr
array.push(new Date(year - 1, 11, 26).getTime()); // 2. Weihnachtstag Vorjahr
array.push(new Date(year, 0, 1).getTime()); // Neujahr
array.push(new Date(year, 4, 1).getTime()); // Tag der Arbeit
array.push(new Date(year, 9, 3).getTime()); // Tag der Dt. Einheit
array.push(new Date(year, 11, 25).getTime()); // 1. Weihnachtstag
array.push(new Date(year, 11, 26).getTime()); // 2. Weihnachtstag
array.push(new Date(year + 1, 0, 1).getTime()); // Neujahr nächstes Jahr
let osterSonntag = getEasterSunday(year);
array.push(osterSonntag.getTime());
let osterMontag = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 1);
array.push(osterMontag.getTime());
let christiHimmelfahrt = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 39);
array.push(christiHimmelfahrt.getTime());
let pfingstMontag = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 50);
array.push(pfingstMontag.getTime());
let fronLeichnam = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 60);
array.push(fronLeichnam.getTime());
return array;
}
Hier passiert Folgendes:
year
.array
angelegt und diese wird als leerer Array
initialisiert, also sozusagen als Liste ohne Einträge.array.push
in die Liste eingetragen (bzw. als jeweils letztes Element dem Array hinzugefügt).
getEasterSunday
der Ostersonntag bestimmt. Den einen oder anderen interessiert jetzt sicherlich, wie der Ostersonntag berechnet wurde, aber das könnt Ihr dann sehen, wenn ich den gesamten Code in diesem Tutorialabschnitt veröffentliche. Für uns ist es jetzt nicht so wichtig. Konzentrieren wir uns lieber auf das Wesentliche: In dieser Funktion wird also ein Array (eine Liste) zusammengestellt, der alle Feiertage eines Jahres (plus ein bisserl was von Vorgänger- und Nachfolger-Jahr) beinhaltet. Durch Testen, ob ein bestimmtes Datum in diesem Array vorkommt, können wir dann an anderer Stelle leicht testen, ob es sich um einen Feiertag handelt.
Ganz am Anfang unseres Javascript-Codes haben wir immer das aktuelle Datum
festgelegt, in der Variablen datToday
gespeichert und dann eigentlich immer
ausgehend von diesem Wert gearbeitet. Mein Ansatz war nun, einfach diese
zentrale Festlegung, dass datToday
= heute ist, eher optional zu gestalten.
Dafür habe ich als erstes die Wurzel all unseres Javascript-Codes leicht verändert.
window.onload = function() {
main();
};
function main() {
changeTime(new Date().getTime());
}
function changeTime(timeToday) {
let strDebug = "";
datToday = new Date(timeToday);
// ...
Wir rufen immer noch die Funktion main
auf, sobald die HTML-Datei fertig
geladen wurde. Die Funktion main
ist nun aber viel kürzer. Sie ruft wiederum
die Funktion changeTime
auf und übergibt unser aktuelles Datum (als Zeit
in Millisekunden seit dem 01.01.1970).
Die Funktion changeTime
ist quasi die alte main
-Funktion. Aber anstatt
immer das heutige Datum in datToday
zu speichern, speichert sie stattdessen
die Zeit, die der Funktion via Argument übergeben wurde. Sie rechnet den
Millisekunden-Wert in ein Datum-Objekt um und der Rest der Funktion nutzt
dieses so konstruierte Datum, um dann all das zu machen, was diese Funktion
früher auch schon konnte.
Der Clou ist nun, dass wir die Funktion changeTime
auch an anderer Stelle
aufrufen können, um so automatisch die Felder im Text und den Kalender
neu zu berechnen. Wir tun das an zwei Stellen:
function calendarHTML_head(date) {
let nameOfMonth = getMonthGerman(date.getMonth() + 1);
let lastOfMonthBefore = new Date(date.getFullYear(), date.getMonth(), 0).getTime();
let firstOfNextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1).getTime();
let html =
`<table>
<thead>
<tr>
<th class="selectable" onClick="changeTime(` + lastOfMonthBefore + `)"><</th>
<th colspan = "6">` + nameOfMonth + `</th>
<th class="selectable" onClick="changeTime(` + firstOfNextMonth + `)">></td>
</tr>
<tr>
<th class = "kw" > Kw </th>
<th class = "mo" > Mo </th>
<th class = "di" > Di </th>
<th class = "mi" > Mi </th>
<th class = "do" > Do </th>
<th class = "fr" > Fr </th>
<th class = "sa" > Sa </th>
<th class = "so" > So </th>
</tr>
</thead>
<tbody>`;
return html;
}
Im Kopf der Tabelle sind nun zwei neue Tabellenzellen vorhanden. Diese zeigen
dann eine einfache Schaltfläche, in die wir jeweils nur ein Zeichen schreiben,
rechts >
und link <
. Diese Schaltflächen lassen uns dann komplett den
Monat zu wechseln.
Die onClick
-Eigenschaft sagt dem Browser, dass er die Funktion changeTime
aufrufen soll, wenn man die Schaltfläche clickt. Als Argument geben wir jeweils
ein festgelegtes Datum im vorigen/nächsten Monat an die Funktion weiter
(wie immer als Wert in Millisekunden). Auf diese Weise können wir uns nun
hier durch die Monats klicken.
Ebenso wird diese Funktion eingebunden beim Bauen jeder einzelnen Tabellenzelle. Wir haben den Ort auch in diesem Tutorial besprochen. Es geht um diese Zeile:
html += '<td class = "' + cssClass + '" onClick="changeTime(' + date.getTime() + ')">' + date.getDate() + "</td>";
Auch hier wird also die Funktion changeTime
aufgerufen, und das aktuell zu
zeichnende Datum wird übergeben. Auf diese Weise kann man also zu jedem im
Kalender dargestellten Datum wechseln.
Wie es fast schon Tradition ist, hier nun der gesamte Code. Zuerst die HTML-Datei.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="step6.css" />
<script language="javascript" type="text/javascript" src="step6.js"></script>
<title>Kalender vom 08.02.2021</title>
</head>
<body>
<div id="seitenkopf">
<h1>Mein Kalenderblatt: <span id="field_datum_d_1">(Datum deutsch)</span></h1>
</div>
<div id="kalenderblatt">
(Hier stand einst ein stolzer Kalender)
</div>
<div id="introtext">
<p>
Der <span id="field_datum_d_2">(DatumD)</span> ist ein
<span id="field_weekday_1">(Wochentag)</span> und zwar der
<span id="field_howmany_1">(wievielte)</span>
<span id="field_weekday_2">(Wochentag)</span> im Monat
<span id="field_month_1">(MonatD)</span> Jahres
<span id="field_year_1">(Jahr)</span>. Dieser Monat hat
<span id="field_days_1">(AnzTage)</span> Tage.
<span id="field_feiertag_1">
(Satz zum Feiertag)
</span>
</p>
</div>
<div id="debug">
Test
</div>
<div id="historie">
<p>
Historische Ereignisse am 08.02.
</p>
<ul>
<li>Vor 23 Jahren: Die Schwarzwaldklinik strahlt ihre 1000. Folge aus</li>
<li>Vor 40 Jahren: Ein Schrank fällt vom Dach in Wolfhagen</li>
<li>Vor 3020 Jahren: Ein Schnitzfehler auf einem sumerischen Abakus geht als erster Computerbug in die Geschichte ein.</li>
</ul>
</div>
</body>
</html>
Nun die CSS-Datei:
/*==============================================================================
// BODY: Grundlegender Aufbau des Grids //
==============================================================================*/
body {
display: grid;
grid-template-columns: 1fr 2fr;
/*Im Verhältnis 1:2 wird der Bildschirm aufgeteilt*/
margin: 0;
/*Kein leerer Bereich rund um den gesamten Body*/
}
/*==============================================================================
// SEITENKOPF //
==============================================================================*/
#seitenkopf {
padding: 1em;
text-align: center;
background: darkblue;
color: white;
grid-column-start: 1;
grid-column-end: 3;
/*Seitenkopf geht über beide Spalten*/
}
#seitenkopf h1 {
margin: 0;
padding: 0;
}
/*==============================================================================
// INTROTEXT UND HISTORIE //
==============================================================================*/
#introtext,
#historie {
grid-column-start: 2;
/*Historie und Introtxt sind hingegen nur rechts in der Spalte zuhause*/
grid-column-end: 3;
margin: 1em;
font-size: 1.3em;
padding: 1em;
}
/*==============================================================================
// KALENDERBLATT //
==============================================================================*/
#kalenderblatt {
border-bottom: 1px solid darkblue;
/*Border unten um das gesamte Feld*/
border-right: 1px solid darkblue;
/*Border rechts um das gesamte Feld*/
}
#kalenderblatt table {
margin: 25px auto;
/*Oben und unten 25 Punkte Abstand zu allem (zu den Grenzen des DIVs), links und rechts auto = zentriert*/
border-right: 1px solid darkblue;
/*Border rechts um gesamte Tabelle*/
border-bottom: 1px solid darkblue;
/*Border unten an gesamter Tabelle*/
/*Die Border nach links und oben ziehen wir in den einzelnen Zellen*/
border-spacing: inherit;
}
#kalenderblatt table th {
border-top: 1px solid darkblue;
/* Jede th-Zelle bekommt oben / links eine Border*/
border-left: 1px solid darkblue;
}
#kalenderblatt table td {
background: white;
text-align: center;
padding: 0.25em;
/*Bisserl Platz um den Inhalt der einzelnen Zellen (Datumswerte)*/
border-top: 1px solid darkblue;
/*Jede td-Zelle erhält eine linke und obere Border, auf diese Weise ergibt sich dann das Gitternetz*/
border-left: 1px solid darkblue;
/*Mauszeiger verändert Form, wenn wir über die Tabellenzellen fahren, um anzuzeigen, dass man hier sinnvollerweise klicken kann*/
}
#kalenderblatt td:hover,
#kalenderblatt th.selectable:hover {
background: yellow;
cursor: pointer;
/*Hintergrund gelb, wenn man mit der Maus drüber fährt*/
}
#kalenderblatt td.heute {
background: pink;
/*Der heutige Tag wird markiert*/
}
#kalenderblatt td.heute:hover {
background: yellow;
/*Der heutige Tag wird auch gelb beim Drüberfahren*/
}
#kalenderblatt td.sa {
color: darkblue;
/*Samstage sind blau dargestellt*/
}
#kalenderblatt td.so,
#kalenderblatt td.feiertag {
color: red;
/*Sonnage sind rot dargestellt*/
}
#kalenderblatt td.kw {
font-style: italic;
/*Kw-Angaben kleiner und in Kursivschrift*/
font-size: 0.9rem;
cursor: unset;
/*Kw kann man nicht klicken, also Cursor anders*/
color: grey;
}
#kalenderblatt td.kw:hover {
background: unset;
/*Nix passiert, wenn man über KW hovert*/
}
/*==============================================================================
// DEBUG //
==============================================================================*/
#debug {
background: lightcoral;
border-right: 1px solid darkblue;
border-bottom: 1px solid darkblue;
}
span {
background: lightcoral;
}
Und zuguterletzt der Javascript-Teil.
window.onload = function() {
main();
};
function main() {
changeTime(new Date().getTime());
}
function changeTime(timeToday) {
// Grund-Daten
let strDebug = "";
datToday = new Date(timeToday);
let datTodayGerman = getDateGerman(datToday);
// Wochentag
let weekday = datToday.getDay(); // ergibt den Tag der Woche als Zahl (von 0 = Sonntag bis 6 = Samstag)
let weekdayGerman = getWeekdayGerman(weekday);
// Wievielte Wochentag dieser Art
let wievielte = Math.floor((datToday.getDate() - 1) / 7) + 1;
let wievielteGerman = getWievielteGerman(wievielte);
// Monat
let monthGerman = getMonthGerman(datToday.getMonth() + 1);
// Javascript korrigiert automatisch. Der 0te des Folgemonats ist der letzte diesen Monats
let lastOfMonth = new Date(datToday.getFullYear(), datToday.getMonth() + 1, 0);
let days = lastOfMonth.getDate();
// Feiertage
let holidayHTML = '';
let holidayArray = getHolidayArrayHessen(datToday);
if (holidayArray.includes(datToday.getTime())) {
holidayHTML = 'Der ' + datTodayGerman + ' ist ein gesetzlicher Feiertag.';
} else {
holidayHTML = 'Der ' + datTodayGerman + ' ist kein gesetzlicher Feiertag. ';
}
// Wir füllen die Informationen in den HTML-Code
document.title = 'Kalenderblatt: ' + datTodayGerman;
document.getElementById("field_datum_d_1").innerHTML = datTodayGerman;
document.getElementById("field_datum_d_2").innerHTML = datTodayGerman;
document.getElementById("field_weekday_1").innerHTML = weekdayGerman;
document.getElementById("field_weekday_2").innerHTML = weekdayGerman;
document.getElementById("field_howmany_1").innerHTML = wievielteGerman;
document.getElementById("field_month_1").innerHTML = monthGerman;
document.getElementById("field_year_1").innerHTML = datToday.getFullYear();
document.getElementById("field_days_1").innerHTML = days;
document.getElementById("field_feiertag_1").innerHTML = holidayHTML;
let htmlTabelle = getCalendarHTML(datToday, holidayArray);
document.getElementById("kalenderblatt").innerHTML = htmlTabelle;
// Ausgabe in das elDebug
let elDebug = document.getElementById("debug");
if (elDebug != null) {
elDebug.innerHTML = strDebug;
} else {
console.log("Debug-Element nicht gefunden.");
}
}
function getCalendarWeek(date) {
let thursday = getThursday(date);
let cwYear = thursday.getFullYear();
let thursdayCw1 = getThursday(new Date(cwYear, 0, 4));
let cw = Math.floor(
1.5 + (thursday.getTime() - thursdayCw1.getTime()) / 86400000 / 7
);
return cw;
}
function getThursday(date) {
var thursday = new Date();
thursday.setTime(date.getTime() + (3 - ((date.getDay() + 6) % 7)) * 86400000);
return thursday;
}
function getDateGerman(date) {
day = date.getDate();
month = date.getMonth();
month = month + 1; // Warum auch immer ... Javascript speichert Monate 0-basiert, also 0 = Januar, 11 = Dezember, daher hier Korrektur + 1
year = date.getFullYear();
// Man beachte: Man könnte hier nachfolgend nach dem if {} benutzen, aber da es sich nur um EINE nachfolgende Anweisung handelt, geht es auch so
if (String(day).length == 1) day = "0" + day;
// Nachfolgend alternativ MIT Klammern
if (String(month).length == 1) {
month = "0" + month;
}
dateGerman = day + "." + month + "." + year;
return dateGerman;
}
function getMonthGerman(monthIndex) {
let arr = [
"Fehler",
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember",
];
return arr[monthIndex];
}
function getWeekdayGerman(weekdayIndex) {
let arr = [
"Sontag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
"Sonntag",
];
return arr[weekdayIndex];
}
function getWeekdayShortGerman(weekdayIndex) {
let arr = ["so", "mo", "di", "mi", "do", "fr", "sa", "so"];
return arr[weekdayIndex];
}
function getWievielteGerman(number) {
let arr = ["erste", "zweite", "dritte", "vierte", "fünfte"];
return arr[number - 1];
}
function getCalendarHTML(date, holidayArray) {
// Erster des Monats berechnen
let firstOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
let firstOfMonthWeekday = firstOfMonth.getDay();
// Wenn der Monat nicht mit einem Montag startet, müssen wir Tage des Vormonats
// ebenfalls darstellen
let howManyDaysBeforeFirst;
if (firstOfMonthWeekday == 0) {
howManyDaysBeforeFirst = 6;
} else {
howManyDaysBeforeFirst = firstOfMonthWeekday - 1;
}
let firstOfCalendar = new Date(firstOfMonth.getFullYear(), firstOfMonth.getMonth(), 1 - howManyDaysBeforeFirst);
let lastOfMonth = new Date(firstOfMonth.getFullYear(), firstOfMonth.getMonth() + 1, 0);
let howManyDaysAfterLast = lastOfMonth.getDay() == 0 ? 0 : 7 - lastOfMonth.getDay();
let lastOfCalendar = new Date(lastOfMonth.getFullYear(), lastOfMonth.getMonth(), lastOfMonth.getDate() + howManyDaysAfterLast);
// HTML-Generierung startet
let html = '';
html += calendarHTML_head(firstOfMonth);
for (var x = firstOfCalendar; x <= lastOfCalendar; x = new Date(x.getFullYear(), x.getMonth(), x.getDate() + 1)) {
html += getDayHTML(x, date, holidayArray);
}
html += calendarHTML_footer();
return html;
}
function calendarHTML_head(date) {
let nameOfMonth = getMonthGerman(date.getMonth() + 1);
let lastOfMonthBefore = new Date(date.getFullYear(), date.getMonth(), 0).getTime();
let firstOfNextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1).getTime();
let html =
`<table>
<thead>
<tr>
<th class="selectable" onClick="changeTime(` + lastOfMonthBefore + `)"><</th>
<th colspan = "6">` + nameOfMonth + `</th>
<th class="selectable" onClick="changeTime(` + firstOfNextMonth + `)">></td>
</tr>
<tr>
<th class = "kw" > Kw </th>
<th class = "mo" > Mo </th>
<th class = "di" > Di </th>
<th class = "mi" > Mi </th>
<th class = "do" > Do </th>
<th class = "fr" > Fr </th>
<th class = "sa" > Sa </th>
<th class = "so" > So </th>
</tr>
</thead>
<tbody>`;
return html;
}
function getDayHTML(date, today, holidayArray) {
// console.log(date);
let html = "";
let cssClass = "";
let weekday = date.getDay();
if (weekday == 1) {
// Montag: Neue Zeile beginnen
html += "<tr>";
// und Zelle für die Kalenderwoche
html += '<td class="kw">' + getCalendarWeek(date) + "</td>";
}
let weekdayGerman = getWeekdayShortGerman(weekday);
cssClass += weekdayGerman;
if (holidayArray.includes(date.getTime())) {
cssClass += " feiertag";
}
if (date.getMonth() == today.getMonth() && date.getDate() == today.getDate()) {
cssClass += " heute";
}
// Eigentliche HTML-Generation
html += '<td class = "' + cssClass + '" onClick="changeTime(' + date.getTime() + ')">' + date.getDate() + "</td>";
if (weekday == 0) {
// Sontag: Zeile beenden
html += "</tr>";
}
return html;
}
function getHolidayArrayHessen(date) {
let year = date.getFullYear();
let array = [];
array.push(new Date(year - 1, 11, 25).getTime()); // 1. Weihnachtstag Vorjahr
array.push(new Date(year - 1, 11, 26).getTime()); // 2. Weihnachtstag Vorjahr
array.push(new Date(year, 0, 1).getTime()); // Neujahr
array.push(new Date(year, 4, 1).getTime()); // Tag der Arbeit
array.push(new Date(year, 9, 3).getTime()); // Tag der Dt. Einheit
array.push(new Date(year, 11, 25).getTime()); // 1. Weihnachtstag
array.push(new Date(year, 11, 26).getTime()); // 2. Weihnachtstag
array.push(new Date(year + 1, 0, 1).getTime()); // Neujahr nächstes Jahr
let osterSonntag = getEasterSunday(year);
array.push(osterSonntag.getTime());
let osterMontag = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 1);
array.push(osterMontag.getTime());
let christiHimmelfahrt = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 39);
array.push(christiHimmelfahrt.getTime());
let pfingstMontag = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 50);
array.push(pfingstMontag.getTime());
let fronLeichnam = new Date(year, osterSonntag.getMonth(), osterSonntag.getDate() + 60);
array.push(fronLeichnam.getTime());
return array;
}
function calendarHTML_footer() {
html = "</tbody></table>";
return html;
}
function getEasterSunday(Jahr) { // Erstellt von Ralf Pfeifer (www.arstechnica.de)
// Falls ausserhalb des gültigen Datumsbereichs, kein Ergebnis zurueckgeben
if ((Jahr < 1970) || (2099 < Jahr)) return null;
var a = Jahr % 19;
var d = (19 * a + 24) % 30;
var Tag = d + (2 * (Jahr % 4) + 4 * (Jahr % 7) + 6 * d + 5) % 7;
if ((Tag == 35) || ((Tag == 34) && (d == 28) && (a > 10))) { Tag -= 7; }
var OsterDatum = new Date(Jahr, 2, 22)
OsterDatum.setTime(OsterDatum.getTime() + 86400000 * Tag)
return OsterDatum;
}
Ihr könnt weiter an dem Kalender basteln. Und ihr solltet pausieren, bis ihr zumindest das Thema Datenbanken gelernt habt. Das nächste Kapitel wird sich dann mit den historischen Ereignissen befassen, die ja noch fix sind im Kalender.