Nachdem ich im ersten Teil meine Motivation für die Benutzung von Grav und die Erstellung eines eigenen Themes erklärt habe, werde ich nun zum interessanten Teil kommen. Hier wird es darum gehen, wie ich das Theme "cleaulem-blog" für Grav erstellt habe.

Ich kann allen, die sich für dieses Thema interessieren und sich vielleicht auch daran versuchen möchten, sehr die Grav-Dokumentation empfehlen. Dort wird sehr detailliert und verständlich auf die Einzelheiten der Benutzung von Grav eingegangen. Man findet dort alles, was man braucht, um mit Grav zu arbeiten und das System an die eigenen Wünsche anzupassen.

Inhaltsverzeichnis

Anpassung eines Themes

Wenn man ein neues CMS-Theme erstellen möchte, gibt es mehrere Vorgehensweisen. Man kann zum Beispiel einfach von Grund auf ein neues Theme anlegen. Das habe ich als erstes auch versucht, aber schnell gemerkt, dass ich damit nicht weiterkam. Denn ich hatte einfach keine Ahnung, wie es dann weiterging.

Also habe ich ein bereits existierendes Theme an meine Wünsche angepasst. Das ist eine sehr gute Methode, um sich mit der Funktionsweise eines technischen Systems vertraut zu machen. Ich habe Bestandteile aus einzelnen Theme-Komponenten verändert und entfernt, um zu sehen, was dann passiert. Durch dieses Learning by Doing habe ich dann irgendwann ein Verständnis dafür entwickelt, wie dieses Theme funktioniert.

Ursprünglich wollte ich das Theme Quark, das aktuelle Standard-Theme von Grav, zu diesem Zweck anpassen. Doch ich merkte bald, dass Quark sehr komplex und mit Features beladen ist, so dass ich mich als Anfänger nicht wirklich darin zurechtfand. Also habe ich mich für Antimatter, dem alten Standard-Theme, als Spielwiese entschieden. Dieses Theme ist sehr ausgereift, aber immer noch simpel genug, um etwas damit anfangen zu können.

Die allgemeine Struktur eines Grav-Themes

In einer Grav-Installation findet man alle Daten, mit denen man als User arbeitet, im Ordner /user. Dort passiert alles, was man anpassen kann. Man könnte theoretisch auch die Dateien in anderen Ordnern bearbeiten, aber davon sollte man lieber die Finger lassen. In /user findet man also alle Inhalte, die Konfigurationsdateien, die installierten Plugins und die Themes.

Die Themes sind im Ordner /user/themes gespeichert. Jedes Theme hat dabei folgende Grundstruktur:

Grundstruktur eines Grav-Themes

Der Ordner css enthält die CSS-Dateien für das Theme, der Ordner fonts etwaige Schriftarten, die man in sein Theme einbinden kann, images die Bilddateien für die Darstellung von Elementen und Hintergrundgrafiken, js die JavaScript-Dateien für interaktive Elemente im Theme und templates die Template-Dateien im Twig-Format.

Im Grundordner des Themes sind einige Dateien enthalten, die vor allem dann relevant sind, wenn man sein Theme zum Download bereitstellen möchte. Da ich das ursprüngliche Antimatter-Theme modifiziert habe, musste ich diese Dateien natürlich entsprechend anpassen.

Die beiden Bilddateien screenshot.jpg und thumbnail.jpg werden benötigt, wenn man sein Theme auf der Grav-Website zum Download bereitstellen möchte. Der Benutzer hat dann so eine Vorschau, wie das Theme aussehen wird.

Die Datei LICENSE enthält die Lizenz, unter der man das Theme veröffentlicht. Im Normalfall stehen die Grav-Themes unter der MIT-Lizenz, so auch mein eigenes Theme. Die Datei README.md ist eine Markdown-Datei, in der man die allgemeine Dokumentation und die Benutzungsanweisung für das Theme findet. Grav-Themes haben in der Regel jeweils ein eigenes Github-Repository und in diesen ist README.md der Standard für die Dokumentation eines Projekts.

Die YAML-Dateien werden für die Konfiguration des Themes eingesetzt. Die Datei languages.yaml stellt dabei Texte zum Ausfüllen von Platzhalter in einer Anzahl unterstützter Sprachen zur Verfügung. Je nachdem, welche Sprache im Theme eingestellt ist, wird der Platzhaltertext in der jeweiligen Sprache verwendet.

Ansonsten ist hier nur noch blueprints.yaml noch von Interesse. In ihr werden die allgemeinen Eigenschaften des Themes festgelegt:

name: Cleaulem Blog
version: 1.0.0
description: Theme for the blog of cleaulem.de
icon: cleaulem-reversed
author:
  name: Christian Schmidt
  email: christian@cleaulem.de
homepage: https://yoursite.com/grav-theme-cleaulem-blog
demo: http://demo.yoursite.com
keywords: grav, theme, etc
bugs: https://yoursite.com/grav-theme-cleaulem-blog/issues
readme: https://yoursite.com/grav-theme-cleaulem-blog/blob/develop/README.md
license: MIT

Ich werde hier jetzt nicht auf die einzelnen Punkte der Konfiguration eingehen, weil ich sie für ziemlich selbsterklärend halte.

Das Template

Das Herzstück eines Themes in fast allen CMS ist das Template. Darin wird die Grundstruktur für die Darstellung der Website festgelegt. Das Template beinhaltet Dateien mit der HTML-Struktur der Seite. Darin sind in einer bestimmten Syntax Elemente eingebunden, an denen das CMS dann erkennen kann, welche Inhalte an welche Stelle beim Seitenaufruf dynamisch eingefügt werden sollen.

Das Template war für mich der schwerste Teil bei der Erstellung des Themes, da ich das hier zum ersten Mal gemacht habe. Ich habe hier aber auch am meisten gelernt. Gerade durch das Verändern der Template-Dateien habe ich nach und nach ein Verständnis für ihre Funktionsweise entwickelt. Ich will hier nur die wichtigsten Grundlagen dieses Themas darstellen. Bei weiterem Interesse verweise ich auf die Grav-Doku.

Die Twig-Engine

Grav benutzt für Themes das Twig-Format. Twig ist eine Template-Engine für PHP, mit der sich einfach solche dynamischen Elemente einfügen lassen. Es stellt dazu eine recht einfache Syntax zur Verfügung.

Die Template-Dateien haben die Endung .html.twig. Die Struktur des Templates sieht folgendermaßen aus:

Grundstruktur des Twig-Templates

Im Wurzelordner von .../templates sind die Vorlagen für die verschiedenen Seitentypen angelegt: blog.html.twig für die Startseite eines Blogs, in dem alle Artikel aufgelistet sind, default.html.twig für eine normale Seite, error.html.twig für die Ausgabe einer Fehlermeldung und item.html.twig für die Anzeige eines einzelnen Blog-Artikels.

Im Ordner .../templates/partials befinden sich die einzelnen Bestandteile, man könnte auch sagen Module, aus der eine Seite aufgebaut ist. Die wichtigste Datei in diesem Ordner ist dabei base.html.twig. In ihr ist das Grundgerüst der Ausgabe in HTML gespeichert.

Im Grunde enthält diese Datei reinen HTML-Code. Darin sind Elemente in Twig-Syntax eingebettet. Die Grundstruktur von base.html.twig sieht folgendermaßen aus, wobei ich das Markup gekürzt habe:

<!DOCTYPE html>
<html lang="de">
<head>
{% block head %}
    Hier sind die Bestandteile des HTML-heads definiert
{% endblock head%}
</head>

<body class="{{ page.header.body_classes }}">

{% block header %}
    <header id="header">
        Hier wird der Kopf der Seite mit Logo u.a. eingefügt
    {% block header_navigation %}
        Hier wird die Seitennavigation eingefügt
    {% endblock %}
    </header>
{% endblock %}

<div id="container">

{% block body %}
    <main id="main" class="{{ class }}">
        {% block content %}
            Hier wird der Inhalt der Seite eingefügt!
        {% endblock %}
    </main>
{% endblock %}

{% block sidebar %}
    Hier wird der Inhalt der Sidebar eingefügt
{% endblock %}

</div>

{% block bottom %}
    Hier werden in den Seitenfuß diverse JavaScript-Dateien eingebunden
{% endblock %}

</body>
</html>

Man kann hier bereits ein Grundsyntax von Twig-Anweisungen erkennen. In doppelten geschweiften Klammern ({{ ... }}) wird das Ergebnis eines Ausdrucks ausgegeben, während die einfache geschweifte Klammer mit dem Prozentzeichen ({% ... %}) logische Anweisungen ausführt.

In base.html.twig sind Blöcke definiert, die jeweils einen Inhaltsbereich darstellen. Der wichtigste hierbei ist {% block content %}, in den der Inhalt der jeweils aufgerufenen Seite ausgegeben wird. In anderen Blöcken werden auch die Inhalte anderer html.twig-Dateien eingefügt, z.B. in {% block sidebar %} der Inhalt von sidebar.html.twig mit dem Befehl {% include ... %}:

{% block sidebar %}
    <aside id="right">
        {% include 'partials/sidebar.html.twig' with {'blog':page} %}
    </aside>
{% endblock %}

Vererbung in den Template-Dateien

Wie wird jetzt aus einer Twig-Datei eine fertige HTML-Ausgabe? Die Inhalte der Website sind als Markdown-Dateien unter dem Ordner /user/pages in jeweils eigenen Unterordnern gespeichert. Je nachdem, wie diese Markdown-Datei benannt ist, wird sie nach der entsprechenden Template-Datei verarbeitet und in HTML umgewandelt. Wenn man beispielsweise die Datei default.md nennt, wird sie als normale Seite dargestellt, heißt sie item.md, dann als Blogbeitrag.

Schauen wir uns doch mal als Beispiel die Datei default.html.twig an. Ihr gesamter Inhalt sieht so aus:

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content }}
{% endblock %}

In der ersten Zeile sehen wir den Befehl {% extends 'partials/base.html.twig' %}. Durch diesen Befehl "erbt" default.html.twig alle Eigenschaften von base.html.twig und erweitert sie mit den folgenden Befehlen. In diesem Fall bekommt der Block content den Inhalt {{ page.content }}, der einfach den Inhalt der default.md als HTML ausgibt. In base.html.twig ist der Block content leer, bekommt also erst hier einen Inhalt.

Schauen wir uns als anderes Beispiel mal an, welchen Inhalt der content in item.html.twig bekommt:

{% block content %}
    <div id="item">
        {% include 'partials/blog_item.html.twig' [...] %}
    </div>
{% endblock %}

Hier wird also der Inhalt der Datei blog_item.html.twig eingebunden. In dieser Datei selbst sind dann entsprechende Formatierungsanweisungen für einen Blogbeitrag enthalten, so auch der Befehl {{ page.content }}, der dann den Inhalt von item.md ausgibt.

Darstellung von Metadaten in einem Twig-Template

Ich möchte noch eine Sache erwähnen, die vor allem im Zusammenhang mit Blogbeiträgen wichtig ist. Und zwar beinhaltet jede Markdown-Datei, in der Inhalte gespeichert sind, auch einen Header, in dem Metadaten zu der Seite in YAML eingetragen sind. Bei einer default.md sieht der dann beispielsweise so aus:

---
title: Seitentitel
---

Das ist sozusagen die Minimalversion eines solchen Headers. Es gibt noch sehr viel mehr Metadaten, die man dort eintragen kann, wie man in der Grav-Doku sehen kann.

Ein Blogbeitrag hat natürlich auch bestimmte Metadaten, sehr viel mehr als eine einfache Seite. Diese sind dann auch im Header gespeichert. Als Beispiel nehme ich hier den Header dieses Artikels:

---
title: Ein eigenes Blog-Theme für Grav, Teil 2
date: 02/24/2019
author: Cleaulem
taxonomy:
    category: Digital Humanities
    tag:
      - Grav
      - CMS
      - Content Management System
      - Theme
      - Template
      - CSS
      - Twig
---

Ich denke, die einzelnen Elemente sind wieder selbsterklärend. Auf der Seite, in der der Artikel dann im Browser zu sehen ist, tauchen diese Metadaten schließlich im HTML-Code auf. Dies wird ebenfalls durch Twig-Befehle realisiert. Dazu sehen wir uns mal den Twig-Code für die Ausgabe des Datums und des Autors an:

<div class="blog-item-date">
    <time [...]>
        {{ page.date|date("d.m.Y") }}
    </time><span>—</span>
    <span class="blog-item-author">{{ page.header.author }}</span>
</div>

Hier wird durch den Befehl {{ page.date|date("d.m.Y") }} das Datum aus den Seitenkopf ausgegeben. Dabei gibt page.date an, dass der Wert des Metadateneintrags date hier ausgegeben werden soll. Hinter der Pipe (|) wird anschließend das Format bestimmt, in dem das passieren soll.

Dasselbe passiert mit dem Befehl {{ page.header.author }}, der aus dem Seitenheader den Wert von author ausgeben soll. Wem es noch nicht aufgefallen ist: Bei page.date steht nicht dabei, dass sich diese Angabe auf den Header bezieht. Das liegt daran, dass page.date ein festgelegter Parameter in Grav ist, während author extra in dieser Seite definiert wurde, also entsprechend angesprochen werden muss.

CSS

Das Template ist vor allem für die struktur der Seite von großer Bedeutung. Aber man will ja auch, dass die Seite hübsch wird. Und dafür benutzt man CSS, mit dem man das Aussehen der einzelnen HTML-Elemente bestimmen kann.

Die selbst angepassten Designs werden im Ordner css in der Datei custom.css gespeichert. Grav unterstützt außerdem Sass und Less, zwei Frameworks die die Erstellung von CSS-Code erleichtern. Weil ich mich mit diesen beiden Sprachen aber noch nicht auseinandergesetzt habe, bin ich beim klassischen CSS geblieben.

Importiertes Design

Das Design meines Blogs habe ich von meiner Website entlehnt. Beide Seiten sind auch eng miteinander verbunden. Vom Aussehen her unterscheiden sie sich lediglich im Design des Seitenkopfs. Die Kopfgrafik mit dem Seitenlogo und der Schriftzug darunter sind unterschiedlich.

Das Interessante dabei: Beide Seiten laufen auf vollkommen verschiedenen CMS. Während dieser Blog in Grav läuft, ist meine Website mit dem CMS Contao erstellt. Man sieht daran, wie sehr bei CMS die Struktur und das Aussehen einer Seite voneinander getrennt ist, indem man zwei Seiten auf verschiedenen Systemem identisch gestalten kann.

Natürlich musste ich den CSS-Code an das Template in meinem Theme anpassen. Ich musste die Namen von ids und Klassen anpassen und generell die Unterschiede in der Struktur zweier verschiedener Templates berücksichtigen. Aber das war kein allzu großes Problem, vor allem weil ich ja das Template in Grav auch in dieser Hinsicht selbst anpassen konnte.

Design für mobile Endgeräte

Ein wichtiger Punkt bei der Gestaltung ist die Berücksichtigung mobiler Endgeräte. Als ich vor wenigen Tagen den ersten Artikel dieses Blogs online gestellt hatte, promotete ich ihn natürlich auch auf Twitter und bei meinen Kommiliton*innen. Und ich bekam sofort die Rückmeldung, dass der Blog auf mobilen Endgeräten schlecht aussieht.

Ich war mir natürlich vorher schon darüber bewusst, dass da noch etwas passieren musste, hatte es aber ein wenig auf die lange Bank geschoben. Ich hatte mich zuvor noch nicht wirklich mit diesem Thema auseinandergesetzt, habe dann aber so schnell es ging auf das Feedback reagiert und sofort an der Umsetzung eines responsiven Designs gearbeitet. Parallel habe ich das auch für meine Website gemacht, die ja auf denselben Designgrundlagen basiert.

Wenn man eine Seite für mobile Endgeräte optimieren will, ist der viewport-Tag im HTML-head ganz wichtig. Das ist mir aufgefallen, als ich für meine Website zwar alles ganz toll in CSS gecodet hatte, die Seite mobil aber immer noch in voller Auflösung dargestellt wurde. Bei der Fehlersuche entdeckte ich dann den viewport-Tag und hatte das Problem damit gelöst. Der viewport-Tag auf dieser Seite sieht so aus:

<meta name="viewport" content="width=device-width, initial-scale=1.0,
minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

Damit wird dem Browser gesagt, dass die Maßen der Seite immer der Darstellungsgröße des Geräts entsprechen sollen. Ansonsten wird auf mobilen Endgeräten die Seite einfach in einer Breite von 1024 Pixeln angezeigt und mit entsprechend winziger Schrift skaliert.

@media in CSS

Natürlich muss man auch in der CSS-Datei die Darstellung für mobile Endgeräte anpassen. Dies kann man sehr einfach mit den media-queries bewerkstelligen. Dabei werden unter bestimmte Bedingungen (z.B. einer bestimmten Fensterbreite) Eigenschaften in CSS festgelegt. Der media-query für diese Seite sieht folgendermaßen aus:

@media(min-width:800px) {
    #container {
        display: grid;
        grid-template-columns: 4fr minmax(200px,1fr);
    }
}

Die Bedingung min-width:800px bedeutet, dass diese Regeln nur dann gelten, wenn der sichtbare Bildschirmbereich breiter ist als 800 Pixel.

Für die Ansicht des #container-Elements habe ich mich übrigens für ein grid-Layout entschieden. Das ist zusammen mit dem flex-Layout ein sehr neuer Standard in CSS3, der es Entwicklern ermöglicht, sehr schnell und einfach responsive Layouts zu realisieren. Fast alle modernen Browser beherrschen diese neuen Eigenschaften, so dass ich denke, sie unbesorgt einsetzen zu können.

Nun ist es so, dass erst dann, wenn das Fenster breiter ist als 800 px, der #container als grid angezeigt wird. Ansonsten werden alle Elemente einfach untereinander angezeigt, was für schmale Bildschirme und für mobile Endgeräte die beste Darstellungsart ist.

Einbinden des CSS ins Template

Die erstellten CSS-Dateien müssen natürlich ins Template eingebunden werden, damit sie im HTML-Code auftauchen. Das sieht im Twig-Template so aus:

{% block stylesheets %}
    {% do assets.addCss('theme://css/custom.css', 100) %}
    {% do assets.addCss('theme://css/pagination.css', 100) %}
    {% do assets.addCss('theme://css/highlight/default.css', 100) %}
    {% do assets.addCss('theme://css/simplesearch.css', 100) %}
{% endblock %}

{{ assets.css() }}

Zuerst wird ein stylesheets-Block in Twig definiert, in den dann alle notwendigen CSS-Dateien mit einem bestimmten Befehl eingefügt werden. Anschließend wird durch den Befehl {{ assets.css() }} daraus der entsprechende HMTL-Code erzeugt.

Javascript

Das Einbinden von JavaScript-Dateien in den HMTL-head ist im Grunde nicht anders als bei den CSS-Dateien:

{% block javascripts %}
    {% do assets.addJs('jquery', 100) %}
    {% do assets.addJs('theme://js/highlight.pack.js') %}
{% endblock %}

{{ assets.js() }}

Beim Erstellen des Themes wollte ich auch highlight.js einbinden. Highlight.js ist ein Framework, dass Syntax-highlighting bei <pre> Elementen auf Websites zur Verfügung stellt. Es war ehrlich gesagt ziemlich leicht, highlight.js einzubinden. Ich habe die Dateien von der Website runterladen, die Datei highlight.pack.ja in den Ordner js verschoben, den Ordner highlight mit den darin enthalten CSS-Dateien in den Ordner css verschoben und anschließend die JavaScript-Datei ins Template eingebunden. Außerdem fügte ich noch den Tag <script>hljs.initHighlightingOnLoad();</script> in den HTML-head ein.

Ich war selbst erstaunt, wie schnell und einfach es war, das Framework einzubinden. Am Ende habe ich noch die Datei default.css angepasst, um die Darstellung des Syntax-highlighting an mein Theme anzupassen.

Backend und Plugins

Ich hatte im ersten Teil dieses Artikels ja erwähnt, dass man für Grav kein Backend braucht. Das ist richtig. Aber ein Backend macht einem gewisse Dinge sehr viel leichter.

Wenn man Grav herunterlädt, kann man entweder den Grav-Core herunterladen oder eine erweiterte Version mit dem admin-plugin. Dieses stellt ein Backend wie in anderen CMS zur Verfügung. Dieses Backend hat einige sehr große Vorteile.

Ein sehr gewichtiger Vorteil ist die Aktualisierung vom CMS und der installierten Plugins. Man bekommt automatisch angezeigt, wenn neue Versionen verfügbar sind und kann sie dann auf Knopfdruck updaten. Ohne Backend müsste man sich selbst darum kümmern und außerdem immer alle Komponenten im Auge behalten.

Es ist mit dem Backend sehr einfach, Plugins zu installieren. Jedes Plugin hat zwar eine Anleitung für die Installation, aber im Backend kann man ein Plugin einfach per Knopfdruck installieren.

Wenn man sich das Backend genauer anschaut, dann fällt einem irgendwann auf, dass die Einstellungen, die man vornehmen kann, exakt den Einstellungen in den YAML-Konfigurationsdateien entsprechen. Das ist interessant, weil man daran eben sieht, dass das Backend an sich nicht unbedingt notwendig ist, aber dem Administrator die Arbeit sehr erleichtern kann.

Probleme mit dem search-plugin

Im Allgemeinen haben alle Plugins eine sehr gute Dokumentation und sind in Installation und Handhabung recht handzahm. Aber ich hatte mit einem Plugin sehr große Probleme: dem search-plugin.

Dieses Plugin ermöglicht die Integration eines Suchfelds im Template, über das man die Website durchsuchen kann. Das Problem war nur: Die Suche hat nicht funktioniert. Wenn ich einen Suchbegriff eingegeben habe und dann Enter drückte, tauchte einfach nur die Startseite auf und der URL wurde ein seltsamer Parameter angehängt, der nichts tat.

Ich habe stundenlang verzweifelt nach einer Lösung gesucht, habe die README.md auf den Kopf gestellt, auf der Plugin-Website gesucht, in den Einstellungen und im Template herumgedoktert, ohne Erfolg zu haben. Am Ende fand ich die Lösung in einem gut versteckten Foreneintrag, in dem genau dieses Problem beschrieben wurde. Die Lösung war, dass die Datei simplesearch.js in den Fuß der Website eingebunden werden musste.

Als ich das getan hatte, funktionierte das Plugin endlich. Das Komische war nur, dass die simpleserach.js bereits durch den asset-Aufruf des Templates bereits eingebunden wurde. Im HTML-Quellcode war die Datei also zweimal eingebunden. Nahm ich den manuellen Tag aber heraus, funktionierte die Suche nicht mehr. Am komischsten war aber, dass ich eine Weile später nochmal den manuellen Tag herausnahm und die Suche trotzdem noch funktioniert. Naja, Hauptsache es geht!

Fazit

Das war also der Ausflug in die Welt der Theme-Erstellung in Grav. Es war für mich eine spannende Reise, auf der ich viel gelernt und neu kennengelernt habe. Dabei habe ich nicht nur Erfolge erlebt, sondern auch einige Rückschläge und Hindernisse erfahren. Aber am Ende war ich erfolgreich.

Ich fühle mich jetzt gerüstet, weitere solche Projekte in Zukunft angehen zu können. Durch das Erstellen des Themes habe ich meine Kenntnisse im Webdesign aufgefrischt und erweitert. Mir hat es auch sehr viel Spaß gemacht, mich so tief in dieses CMS einzugraben.

Was habe ich nun konkret mitgenommen? Ich habe Twig und YAML kennengelernt. Ich habe mich endlich mit Webdesign für mobile Endgeräte beschäftigt und kann in Zukunft Websites dementsprechend gestalten. Und dabei ist auch noch ein Theme für diesen Blog herausgekommen. Ich freue mich auf jeden Fall darauf, neue Artikel schreiben und hier veröffentlichen zu können.

Kommentar hinzufügen

Nächster Beitrag Vorheriger Beitrag