Stell dir vor, du könntest all deine kreativen und teils zeitlich aufwändigen Anpassungen an einem Template vornehmen, ohne Angst zu haben, dass sie bei einem Update verloren gehen.

Genau das ermöglichen die Child Templates, die seit der Version von Joomla 4.1. ein wesentlicher Bestandteil sind.

Sie erlauben es dir, ein bestehendes Template zu erweitern und anzupassen, ohne den ursprünglichen Code zu verändern. Mit nur einer einzigen XML-Datei öffnet sich eine Welt voller Möglichkeiten. Du kannst ab diesem Zeitpunkt an CSS anpassen, JavaScript hinzufügen und sogar Layout-Überschreibungen vornehmen - alles sicher und vor zukünftigen Updates geschützt.

Das ist doch bestimmt schwierig, oder nicht? Entscheide selbst: Navigiere in deinem Backend zu “System”→“Site Templates”→“Cassiopeia - Details und Dateien”. Dort findest du den Button “Child Template erstellen”. Nun kannst du deinem Child Template einen Namen geben (fast frei wählbar – beachte dabei die Validierung) und entscheiden, ob du einen vorhandenen Stil kopieren möchtest oder lieber mit einem leeren Child Template neu startest. Ein letzter Klick auf “Child Template erstellen”, und schon bist du bereit loszulegen!

Diese Funktion bringt nicht nur Freude, sondern auch Effizienz und Flexibilität in deinen Entwicklungsprozess. Lass uns gemeinsam einen Blick darauf werfen, wie diese kleinen Wunder funktionieren und wie sie deine Arbeit mit Joomla verändern können.

Die Installation von Joomla

Als erfahrener Joomla-Nutzer kennst du die Installation des CMS sicherlich im Schlaf. Falls du jedoch neu in der Welt von Joomla bist, findest du eine ausführliche und leicht verständliche Anleitung zur Installation auf der offiziellen Joomla-Dokumentationsseite. Dort werden alle notwendigen Schritte detailliert erklärt, sodass du schnell und unkompliziert durchstarten kannst.

Let the magic begin

Installation erfolgreich? Perfekt! Ein Blick ins Frontend zeigt dir das bekannte Bild:

01_child_templates-cassiopeia_frontend
Screenshot: Frontend-Ansicht vom originalen Cassiopeia-Template

Das Standard-Template von Joomla: Cassiopeia. Doch wie kannst du dieses nach deinen Bedürfnissen anpassen – und das Ganze auch noch updatesicher? Genau hier kommen die Child Templates ins Spiel.

Was ist überhaupt ein Child Template?

Ein Child Template ist im Grunde eine Erweiterung eines bestehenden Templates – des sogenannten Eltern-Templates. Es übernimmt standardmäßig alle Dateien und Funktionen des Eltern-Templates, erlaubt dir jedoch, gezielt einzelne Dateien wie CSS, JavaScript oder Layouts zu überschreiben oder anzupassen. Das Besondere daran: Alle nicht angepassten Dateien werden weiterhin vom Eltern-Template geladen. So bleibt dein Child Template schlank und übersichtlich.

Dieses Konzept eröffnet Entwickler:innen und Designer:innen völlig neue Möglichkeiten. Du kannst beispielsweise saisonale Anpassungen wie ein weihnachtliches Design erstellen oder individuelle Layouts für bestimmte Seiten umsetzen – und das alles, ohne das ursprüngliche Template zu gefährden.

Die Umsetzung

Was manchmal kompliziert klingt, ist in Wirklichkeit ganz einfach! Wie bereits beschrieben, benötigst du nur ein paar Klicks im Backend von Joomla. Ich nehme dich Schritt für Schritt mit:

Die Administrationsoberfläche

Melde dich zuerst wie gewohnt in deiner Administrationsoberfläche von Joomla an:

02_child_templates-dashboard_joomla-admin
Screenshot: Dashboardansicht im Administrationsbereich von Joomla 5

Navigiere dann zu “System”→“Site Templates”→“Cassiopeia - Details und Dateien”. Wir nutzen das Standard-Template Cassiopeia, um unser Child Template zu erstellen.

Du befindest dich nun hier:

03_child_templates-editor_cassiopeia
Screenshot: Editoransicht von “Details und Dateien” im Cassiopeia-Template

Oben in der Zeile findest du einen Button “Child Template erstellen”. Folge diesem Dialog, um dein Child Template vorzubereiten.

04_child_templates-dialog_neues-childtemplate
Screenshot: Dialog zur Erstellung eines neues Child Templates

Noch ein Klick auf “Child Template erstellen” und dein eigenes Child Template wurde angelegt. Aber was hat sich geändert? Es sieht noch alles so aus wie vorher.

Das ist richtig. Verlasse einmal den Dialog über den Button “Schließen”.

05_child_templates-vorhandene_site_templates
Screenshot: Ansicht der vorhandenen Site Templates in Joomla 5

Nun siehst du, dass sich hier etwas geändert hat. Dein Child Template “Cassiopeia_tonino_gerns” taucht hier nun auf. Schauen wir uns hier die Dateien unter “Details und Dateien” einmal genau an.

Details und Dateien von meinem Child Template

06_child_templates-editor_childtemplate
Screenshot: Editoransicht von “Details und Dateien” vom erstellten Child Template

Joomla hat uns hier schon einiges an Arbeit abgenommen und alle erforderlichen Dateien und Ordner angelegt, die wir für die weitere Bearbeitung brauchen.

Die wichtigste Datei “templateDetails.xml”. Doch was ist diese Datei und was kann ich damit machen?

Die Datei templateDetails.xml ist das Herzstück jedes Joomla-Templates und spielt eine zentrale Rolle in der Verwaltung und Darstellung eines Templates. Sie enthält alle Metadaten, die Joomla benötigt, um das Template im Backend anzuzeigen und als Option bereitzustellen. Dazu gehören grundlegende Informationen wie der Name des Templates, der Autor, die Version sowie die Definition von Dateien, Ordnern und Sprachdateien, die das Template verwendet. Zudem können hier Parameter und Einstellungen definiert werden, die dem Nutzer im Backend zur Konfiguration angeboten werden.

Die Struktur der Datei folgt einem klaren XML-Format. Sie beginnt mit einer XML-Deklaration und enthält innerhalb der <extension>-Tags alle relevanten Informationen zum Template. 

Für Templates, die als Eltern-Templates fungieren können, wird ein <inheritable>-Tag mit dem Wert 1 hinzugefügt. Bei Child Templates wird dieser Wert auf 0 gesetzt. Dieses Tag signalisiert Joomla, ob ein Template Child Templates unterstützen kann oder nicht.

Besonders bei Child Templates ist die templateDetails.xml entscheidend, da sie oft die einzige Datei ist, die ein solches Template enthält. Hier wird auch das Eltern-Template referenziert, indem dessen Name angegeben wird. Zusätzlich können in dieser Datei spezifische Anpassungen vorgenommen werden, wie z. B. das Hinzufügen neuer Modulpositionen oder zusätzlicher Felder für benutzerdefinierte Einstellungen. Die templateDetails.xml ist somit nicht nur eine technische Notwendigkeit, sondern auch ein mächtiges Werkzeug für Entwickler, um Templates flexibel und effizient zu gestalten.

Aber schauen wir uns die Datei doch einmal an:

<?xml version="1.0" encoding="UTF-8"?>
<extension type="template" client="site">
  <name>cassiopeia_tonino_gerns/name>
  <version>1.0</version>
  <creationDate>Dezember 2024</creationDate>
  <author>Tonino Gerns</author>
  <authorEmail>Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.</authorEmail>
  <copyright>(C) 2017 Open Source Matters, Inc.</copyright>
  <description>TPL_CASSIOPEIA_XML_DESCRIPTION</description>
  <positions>
    <position>topbar</position>
    <position>below-top</position>
    <position>menu</position>
    <position>search</position>
    <position>banner</position>
    <position>top-a</position>
    <position>top-b</position>
    <position>main-top</position>
    <position>main-bottom</position>
    <position>breadcrumbs</position>
    <position>sidebar-left</position>
    <position>sidebar-right</position>
    <position>bottom-a</position>
    <position>bottom-b</position>
    <position>footer</position>
    <position>debug</position>
    <!-- used directly in the error.php and included here so the position will appear in the list of available positions -->
    <position>error-403</position>
    <position>error-404</position>
  </positions>
  <config>
    <fields name="params">
      <fieldset name="advanced">
        <field name="brand" type="radio" label="TPL_CASSIOPEIA_BRAND_LABEL" default="1" layout="joomla.form.field.radio.switcher" filter="options">
          <option value="0">JNO</option>
          <option value="1">JYES</option>
        </field>
        <field name="logoFile" type="media" schemes="http,https,ftp,ftps,data,file" validate="url" relative="true" default="" label="TPL_CASSIOPEIA_LOGO_LABEL" showon="brand:1"/>
        <field name="siteTitle" type="text" default="" label="TPL_CASSIOPEIA_TITLE" filter="string" showon="brand:1"/>
        <field name="siteDescription" type="text" default="" label="TPL_CASSIOPEIA_TAGLINE_LABEL" description="TPL_CASSIOPEIA_TAGLINE_DESC" filter="string" showon="brand:1"/>
        <field name="useFontScheme" type="groupedlist" label="TPL_CASSIOPEIA_FONT_LABEL" default="0">
          <option value="0">JNONE</option>
          <group label="TPL_CASSIOPEIA_FONT_SYSTEM">
            <option value="system">TPL_CASSIOPEIA_FONT_SYSTEM_SELECT</option>
          </group>
          <group label="TPL_CASSIOPEIA_FONT_GROUP_LOCAL">
            <option value="media/templates/site/cassiopeia/css/global/fonts-local_roboto.css">Roboto (local)</option>
          </group>
          <group label="TPL_CASSIOPEIA_FONT_GROUP_WEB">
            <option value="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@100;300;400;700&amp;display=swap">Fira Sans (web)</option>
            <option value="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100;300;400;700&amp;family=Roboto:wght@100;300;400;700&amp;display=swap">Roboto + Noto Sans (web)</option>
          </group>
        </field>
        <field name="systemFontBody" type="list" label="TPL_CASSIOPEIA_FONT_SYSTEM_BODY" default="" validate="options" showon="useFontScheme:system">
          <option value="">JSELECT</option>
          <option value="Charter, 'Bitstream Charter', 'Sitka Text', Cambria, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_TRANSITIONAL</option>
          <option value="'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_OLDSTYLE</option>
          <option value="Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_HUMANIST</option>
          <option value="Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_GEOMETRIC</option>
          <option value="Optima, Candara, 'Noto Sans', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_CLASSICAL</option>
          <option value="Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_GROTESQUE</option>
          <option value="'Nimbus Mono PS', 'Courier New', 'Cutive Mono', monospace">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_MONOSPACE</option>
          <option value="ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_CODE</option>
          <option value="Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_INDUSTRIAL</option>
          <option value="ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_ROUNDED</option>
          <option value="Rockwell, 'Rockwell Nova', 'Roboto Slab', 'DejaVu Serif', 'Sitka Small', serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_SLAB</option>
          <option value="Superclarendon, 'Bookman Old Style', 'URW Bookman', 'URW Bookman L', 'Georgia Pro', Georgia, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_ANTIQUE</option>
          <option value="Didot, 'Bodoni MT', 'Noto Serif Display', 'URW Palladio L', P052, Sylfaen, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_DIDONE</option>
          <option value="'Segoe Print', 'Bradley Hand', Chilanka, TSCu_Comic, casual, cursive">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_HANDWRITTEN</option>
        </field>
        <field name="systemFontHeading" type="list" label="TPL_CASSIOPEIA_FONT_SYSTEM_HEADING" default="" validate="options" showon="useFontScheme:system">
          <option value="">JSELECT</option>
          <option value="Charter, 'Bitstream Charter', 'Sitka Text', Cambria, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_TRANSITIONAL</option>
          <option value="'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_OLDSTYLE</option>
          <option value="Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_HUMANIST</option>
          <option value="Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_GEOMETRIC</option>
          <option value="Optima, Candara, 'Noto Sans', source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_CLASSICAL</option>
          <option value="Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_GROTESQUE</option>
          <option value="'Nimbus Mono PS', 'Courier New', 'Cutive Mono', monospace">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_MONOSPACE</option>
          <option value="ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_CODE</option>
          <option value="Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_INDUSTRIAL</option>
          <option value="ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_ROUNDED</option>
          <option value="Rockwell, 'Rockwell Nova', 'Roboto Slab', 'DejaVu Serif', 'Sitka Small', serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_SLAB</option>
          <option value="Superclarendon, 'Bookman Old Style', 'URW Bookman', 'URW Bookman L', 'Georgia Pro', Georgia, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_ANTIQUE</option>
          <option value="Didot, 'Bodoni MT', 'Noto Serif Display', 'URW Palladio L', P052, Sylfaen, serif">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_DIDONE</option>
          <option value="'Segoe Print', 'Bradley Hand', Chilanka, TSCu_Comic, casual, cursive">TPL_CASSIOPEIA_FONT_SYSTEM_STACK_SYSTEM_HANDWRITTEN</option>
        </field>
        <field name="noteFontScheme" type="note" description="TPL_CASSIOPEIA_FONT_NOTE_TEXT" class="alert alert-warning"/>
        <field name="colorName" type="filelist" label="TPL_CASSIOPEIA_COLOR_NAME_LABEL" default="colors_standard" fileFilter="^custom.+[^min]\.css$" exclude="^colors.+" stripext="true" hide_none="true" hide_default="true" directory="media/templates/site/cassiopeia/css/global/" validate="options">
          <option value="colors_standard">TPL_CASSIOPEIA_COLOR_NAME_STANDARD</option>
          <option value="colors_alternative">TPL_CASSIOPEIA_COLOR_NAME_ALTERNATIVE</option>
        </field>
        <field name="fluidContainer" type="radio" layout="joomla.form.field.radio.switcher" default="0" label="TPL_CASSIOPEIA_FLUID_LABEL">
          <option value="0">TPL_CASSIOPEIA_STATIC</option>
          <option value="1">TPL_CASSIOPEIA_FLUID</option>
        </field>
        <field name="stickyHeader" type="radio" label="TPL_CASSIOPEIA_STICKY_LABEL" layout="joomla.form.field.radio.switcher" default="0" filter="integer">
          <option value="0">JNO</option>
          <option value="1">JYES</option>
        </field>
        <field name="backTop" type="radio" label="TPL_CASSIOPEIA_BACKTOTOP_LABEL" layout="joomla.form.field.radio.switcher" default="0" filter="integer">
          <option value="0">JNO</option>
          <option value="1">JYES</option>
        </field>
      </fieldset>
    </fields>
  </config>
  <files>
    <filename>templateDetails.xml</filename>
    <folder>html</folder>
  </files>
  <media folder="media" destination="templates/site/cassiopeia_tonino_gerns">
    <folder>css</folder>
    <folder>js</folder>
    <folder>images</folder>
    <folder>scss</folder>
  </media>
  <inheritable>0</inheritable>
  <parent>cassiopeia</parent>
</extension>

Hier gehen wir jetzt nicht auf jede einzelne Einstellung ein. Gerne kannst du in der offiziellen Dokumentation von Joomla mehr über den Zweck der templateDetails.xml-Datei erfahren.

Die für uns spannenden Parameter sind einmal:

<name>cassiopeia_tonino_gerns</name>

Hier definieren wir den Namen unseres Child Templates. Hier finden wir nun auch den Namen wieder, den wir im Dialog “Child Template erstellen” ausgesucht haben. Vorangestellt ist das Parent Template, also Cassiopeia. 

Spannend ist nun z. B. der Block:

<positions>
    <position>topbar</position>
    <position>below-top</position>
    ….
</positions>

Hier finden wir alle von Cassiopeia bereitgestellten Template-Positionen. Diese sind notwendig, um zum Beispiel Module zu positionieren. Wir können nun in unserem Child Template neue Positionen hinzufügen und frei in unserer index.php platzieren, oder vorhandene Positionen entfernen.

<media folder="media" destination="templates/site/cassiopeia_tonino_gerns">
    <folder>css</folder>
    <folder>js</folder>
    <folder>images</folder>
    <folder>scss</folder>
  </media>

Mit diesem Abschnitt definieren wir, wo unsere Assets gespeichert werden. Joomla hat uns hier schon die Arbeit abgenommen und unsere Ordnerstruktur unter “/media/templates/site/cassiopeia_tonino_gerns” angelegt.

Neu mit der Verwendung von Child Templates sind die beiden Parameter

<inheritable>0</inheritable>
<parent>cassiopeia</parent>

“inheritable” gibt an, dass das Template selbst nicht als Eltern-Template für andere Child Templates dienen kann. Der Wert 0 gibt an, dass es nicht vererbbar ist.

“parent” definiert das Eltern-Template, in unserem Fall “Cassiopeia”. Wenn man nun in die “Dateien und Details” vom Cassiopeia-Template schaut, sieht man, dass dort der Parameter “inheritable” auf “1” gesetzt wurde.

Genug vom Hintergrundwissen: Nun möchte ich aber etwas sehen!

Unser Child Template von Joomla

Ich denke, dass wir uns nun genug um das Technische gekümmert haben. Was bringt mir das jetzt alles?

Gehen wir das anhand eines praktischen Beispiels einmal durch.

Wir wollen unser Template gerne ein wenig weihnachtlich gestalten. Hierfür reichen uns erst einmal die Farben aus. Dann hätten wir gerne unser Hauptmenü als Navbar und zu guter Letzt möchten wir ein wenig “Schnee” auf unserer Seite platzieren.

Fangen wir an.

Die user.css Datei erstellen

Als erstes erstellen wir unsere CSS-Datei. Hierzu können wir die Datei per FTP hochladen und bearbeiten, oder weiterhin über die Administrationsoberfläche von Joomla arbeiten. Wir navigieren wieder in die “Dateien und Details” von unserem Child Template und klicken auf “Neue Datei”. 

Im Dialog wählen wir links den Pfad “/media/templates/site/cassiopeia_tonino_gerns” und dort den Order “css” aus. Vergeben als Dateiname “user” und als Dateityp wählen wir “.css” aus und klicken auf “Erstellen”.

Hier habe ich mal eine Kleinigkeit vorbereitet. Wir fügen dort folgenden CSS-Code einmal ein und speichern die Datei:

:root {
  --cassiopeia-accent: #1e1e3f;   
  --cassiopeia-bg1:   #1e1e3f;  
  --cassiopeia-bg2:   #16162c;     
  --cassiopeia-bg3:   #0d0d1a;    
  --cassiopeia-highlight: #6B6B6B;    
  --cassiopeia-text:  #000;         
}

.container-header {
  background: radial-gradient(circle at center, var(--cassiopeia-bg1), var(--cassiopeia-bg2) 70%, var(--cassiopeia-bg3) 100%);
  background-size: cover;
  background-repeat: no-repeat;
  padding-bottom: 1.5rem;
  position: relative;
  overflow: hidden;
  z-index: 0;
}

.container-header::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: radial-gradient(circle, rgba(107,107,107,0.15), transparent 70%);
  opacity: 0.5;
  pointer-events: none;
  z-index: 1;
}

.container-header .container-nav {
  background-color: #FFF;
  border-radius: 10px;
  padding: 1rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
  position: relative;
  z-index: 3;
}

.container-header > .grid-child:first-child {
  padding-bottom: 3rem;
}

.container-header .mod-menu > li > a,
.container-header .mod-menu > li > span {
  color: var(--cassiopeia-accent);
  text-transform: uppercase;
  font-weight: bold;
  transition: color 0.3s ease;
}

.container-header .mod-menu > li > a:hover,
.container-header .mod-menu > li > span:hover {
  color: var(--cassiopeia-highlight);
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  color: var(--cassiopeia-text);
}

a {
  color: var(--cassiopeia-accent);
  transition: color 0.3s ease;
}

a:hover, a:focus {
  color: var(--cassiopeia-highlight);
}

.btn-info,
.btn-primary,
.btn-primary:focus,
.btn-primary:hover {
  background-color: var(--cassiopeia-accent);
  border-color: var(--cassiopeia-accent);
  color: #FFF;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.btn-check:focus + .btn-info,
.btn-info:focus,
.btn-info:hover,
.btn-primary:hover {
  background-color: var(--cassiopeia-highlight);
  border-color: var(--cassiopeia-highlight);
  color: #FFF;
}

Ein Blick in das Frontend verrät uns, dass noch gar nichts passiert ist. Warum?

Child Template nutzen

Wir müssen zuerst unser Child Template als Standard definieren. Es gibt aber auch Szenarien, wo man anhand der Child Templates vielleicht nur einzelne Unterseiten anders darstellen möchte - was nun?

Hier liefert uns Joomla zwei Möglichkeiten. 

1. Child Template als Standard definieren

Wir können unser Child Template als Standard definieren. Wenn wir dies machen, wird dieses Template für all unsere Seiten genutzt.

Dafür navigieren wir zu “System”→“Site Template Stile”.

07_child_templates-templatestile_standard
Screenshot: Ansicht von Templates: Stile mit Cassiopeia als Standard-Template

Hier finden wir nun unser erstelltes Child Template wieder. Mit einem Klick auf das graue Kreissymbol unterhalb des goldenen Sterns, können wir unser Child Template als Standard definieren. 

08_child_templates-templatestile_childtemplate
Screenshot: Ansicht von Templates: Stile mit dem Child Template als Standard-Template

Das war’s. 

Möchten wir unser Child Template aber nur auf einer bestimmten Seite nutzen, haben wir hier die zweite Möglichkeit:

2. Child Template einem Menüpunkt zuweisen

Wir navigieren zu unserem gewünschten Menüpunkt, auf dem wir unser Child Template anwenden möchten. Hierzu klicken wir auf “Menüs”→“Main Menu”. Aktuell haben wir nur einen einzigen Menüpunkt “Home”. Diesen editieren wir nun einmal. Als erstes hätte ich gerne, dass mein Menüpunkt nicht “Home”, sondern “Start” heißt. Ich ändere also den Titel ab und lösche den Alias raus, Joomla generiert hier anhand des Titels automatisch nach dem Speichern einen neuen Alias.

Im Reiter “Details” finden wir ganz unten nun die Option, die für uns wichtig ist: Template-Stil. Diese Option steht im Standard auf “ - Standard verwenden - “, Joomla nutzt also den Template-Stil, der als Standard definiert ist - siehe Option 1.

Möchten wir generell aber einen anderen Standard nutzen und nur für unseren Menüpunkt unser Child Template auswählen, können wir nun über das Auswahlfeld unser Child Template auswählen:

09_child_templates-stil_menuezuweisung
Screenshot: Zuweisung eines bestimmten Templates für einen Menüpunkt

nur noch speichern, erledigt.

Das Hauptmenü als Navbar definieren

Als nächstes möchten wir unser Hauptmenü, was im Standard auf der Modulposition “sidebar-right” angezeigt wird, gerne nach oben verschieben und unterhalb von unserem Brand platzieren. Hierfür liefert Cassiopeia schon die richtige Modulposition. Wir navigieren über “Inhalt”→“Site Module” zu unserem “Hauptmenü”. Dort wählen wir rechts unter “Position” die Position “menu” von unserem Child Template aus. Dies findet ihr, wenn ihr etwas in der Auswahlliste nach unten scrollt.

Wie schaut’s nun aus? Sehen wir doch mal nach:

10_child_templates-frontend_childtemplate
Screenshot: Frontend-Ansicht vom Child Template

Und ein klein wenig mehr galaktischer Look

Lass uns noch schnell einen kleinen galaktischen Look auf unserer Seite platzieren. Hierfür nutzen wir ein wenig JavaScript. Wir navigieren wieder zu “System”→“Site Templates”→“Cassiopeia_tonino_gerns - Details und Dateien” und erstellen eine neue Datei. Links wählen wir den Pfad “/media/templates/site/cassiopeia_tonino_Gerns” und markieren den Ordner “js”. Als Dateinamen vergeben wir “template” und als Dateityp “.js”, wir erstellen die Datei und fügen folgenden Code ein:

document.addEventListener("DOMContentLoaded", function () {
  const header = document.querySelector(".container-header");
  if (!header) return;
  const canvas = document.createElement("canvas");
  canvas.style.position = "absolute";
  canvas.style.top = "0";
  canvas.style.left = "0";
  canvas.style.width = "100%";
  canvas.style.height = "100%";
  canvas.style.zIndex = "-1";
  header.appendChild(canvas);
  const ctx = canvas.getContext("2d");
  let bgStars = [];
  let fgStars = [];
  let shootingStars = [];
  function resizeCanvas() {
    canvas.width = header.clientWidth;
    canvas.height = header.clientHeight;
    initStars();
  }
  function initStars() {
    bgStars = [];
    fgStars = [];
    const area = canvas.width * canvas.height;
    const bgCount = Math.floor(area / 30000);
    const fgCount = Math.floor(area / 15000);
    for (let i = 0; i < bgCount; i++) {
      bgStars.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        radius: Math.random() * 0.8 + 0.3,
        glow: Math.random() * 0.2 + 0.1
      });
    }
    for (let i = 0; i < fgCount; i++) {
      fgStars.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        radius: Math.random() * 1.5 + 0.5,
        glow: Math.random() * 0.4 + 0.2
      });
    }
  }
  function drawStars() {
    bgStars.forEach(star => {
      ctx.beginPath();
      ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
      ctx.shadowColor = `rgba(224,224,224,${star.glow * 0.5})`;
      ctx.shadowBlur = 4;
      ctx.fillStyle = `rgba(224,224,224,${star.glow})`;
      ctx.fill();
      ctx.shadowBlur = 0;
    });
    fgStars.forEach(star => {
      ctx.beginPath();
      ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
      const gradient = ctx.createRadialGradient(star.x, star.y, star.radius, star.x, star.y, star.radius * 4);
      gradient.addColorStop(0, `rgba(224,224,224,${star.glow})`);
      gradient.addColorStop(1, "rgba(224,224,224,0)");
      ctx.fillStyle = gradient;
      ctx.fill();
    });
  }
  function spawnShootingStar() {
    const startX = Math.random() * canvas.width;
    const startY = Math.random() * canvas.height * 0.5;
    const length = Math.random() * 60 + 40;
    const angle = Math.PI / 4 + (Math.random() * Math.PI / 8 - Math.PI / 16);
    const speed = Math.random() * 3 + 4;
    const lifetime = Math.random() * 30 + 40;
    shootingStars.push({
      x: startX,
      y: startY,
      length,
      angle,
      speed,
      lifetime,
      age: 0,
      trail: []
    });
  }
  function updateShootingStars() {
    for (let i = shootingStars.length - 1; i >= 0; i--) {
      const star = shootingStars[i];
      star.age++;
      const dx = star.speed * Math.cos(star.angle);
      const dy = star.speed * Math.sin(star.angle);
      star.x += dx;
      star.y += dy;
      star.trail.push({ x: star.x, y: star.y });
      if (star.trail.length > 15) star.trail.shift();
      if (star.age > star.lifetime) {
        shootingStars.splice(i, 1);
      }
    }
  }
  function drawShootingStars() {
    shootingStars.forEach(star => {
      ctx.beginPath();
      ctx.moveTo(star.x, star.y);
      for (let j = star.trail.length - 1; j >= 0; j--) {
        ctx.lineTo(star.trail[j].x, star.trail[j].y);
      }
      ctx.strokeStyle = "rgba(224,224,224,0.4)";
      ctx.lineWidth = 2;
      ctx.stroke();
      ctx.save();
      ctx.shadowColor = "rgba(224,224,224,0.8)";
      ctx.shadowBlur = 8;
      ctx.beginPath();
      ctx.arc(star.x, star.y, 2, 0, Math.PI * 2);
      ctx.fillStyle = "rgba(224,224,224,0.8)";
      ctx.fill();
      ctx.restore();
    });
  }
  let shootingStarTimer = 0;
  function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawStars();
    updateShootingStars();
    drawShootingStars();
    shootingStarTimer++;
    if (shootingStarTimer > 100 && Math.random() < 0.03) {
      spawnShootingStar();
      shootingStarTimer = 0;
    }
    requestAnimationFrame(animate);
  }
  window.addEventListener("resize", resizeCanvas);
  resizeCanvas();
  animate();
});

Das Ergebnis, oder doch nicht?

11_child_templates-frontend_childtemplate-js
Screenshot: Das Frontend mit JavaScript, um den Header dynamisch zu gestalten

Das sieht doch schon gut aus. Aber warte mal… kann ich CSS- und JS-Dateien nicht auch einfach so in Cassiopeia anlegen, und diese sind trotzdem updatesicher? Das stimmt. Dann schöpfen wir doch jetzt mal das volle Potenzial aus. Und wie? Wir erstellen ein Override von der index.php. Wieso? Weil wir nun gerne an die Stelle, wo aktuell unser Hauptmenü ist, eine Suche integrieren möchten und die Navigation soll oberhalb des Breadcrumb wandern. Jetzt könnten wir das Suchmodul doch einfach auf die Position “menu” setzen - auch das stimmt. Aber das ist ein wenig irreführend. Also beginnen wir mit unserem Override der index.php.

Dazu wechseln wir wieder zu unseren Template Dateien und erstellen eine “Neue Datei”. Wir wählen links unseren Pfad “/templates/cassiopeia_tonino_gerns” aus, den Dateiname “index” und den Dateityp “.php” und erstellen diese. 

Schauen wir uns jetzt unser Frontend an:

12_child_templates-leeres_frontend
Screenshot: Leeres Frontend durch die leere index.php

Huch? Hier ist ja nichts mehr zu sehen. Und das ist auch gut so, so sehen wir, dass unser Override bereits funktioniert. Nun haben wir ja aber auch nicht die ganze Arbeit gemacht, um eine leere Seite zu erzeugen.

Wir wechseln nun also zu “System”→“Site Templates” und schauen uns die Dateien vom Eltern-Template an. Dazu klicken wir auf “Cassiopeia - Details und Dateien” und öffnen dort die “index.php”. Wir kopieren uns den gesamten Inhalt:

<?php

/**
 * @package     Joomla.Site
 * @subpackage  Templates.cassiopeia
 *
 * @copyright   (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

/** @var Joomla\CMS\Document\HtmlDocument $this */

$app   = Factory::getApplication();
$input = $app->getInput();
$wa    = $this->getWebAssetManager();

// Browsers support SVG favicons
$this->addHeadLink(HTMLHelper::_('image', 'joomla-favicon.svg', '', [], true, 1), 'icon', 'rel', ['type' => 'image/svg+xml']);
$this->addHeadLink(HTMLHelper::_('image', 'favicon.ico', '', [], true, 1), 'alternate icon', 'rel', ['type' => 'image/vnd.microsoft.icon']);
$this->addHeadLink(HTMLHelper::_('image', 'joomla-favicon-pinned.svg', '', [], true, 1), 'mask-icon', 'rel', ['color' => '#000']);

// Detecting Active Variables
$option   = $input->getCmd('option', '');
$view     = $input->getCmd('view', '');
$layout   = $input->getCmd('layout', '');
$task     = $input->getCmd('task', '');
$itemid   = $input->getCmd('Itemid', '');
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
$menu     = $app->getMenu()->getActive();
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';

// Color Theme
$paramsColorName = $this->params->get('colorName', 'colors_standard');
$assetColorName  = 'theme.' . $paramsColorName;

// Use a font scheme if set in the template style options
$paramsFontScheme = $this->params->get('useFontScheme', false);
$fontStyles       = '';

if ($paramsFontScheme) {
    if (stripos($paramsFontScheme, 'https://') === 0) {
        $this->getPreloadManager()->preconnect('https://fonts.googleapis.com/', ['crossorigin' => 'anonymous']);
        $this->getPreloadManager()->preconnect('https://fonts.gstatic.com/', ['crossorigin' => 'anonymous']);
        $this->getPreloadManager()->preload($paramsFontScheme, ['as' => 'style', 'crossorigin' => 'anonymous']);
        $wa->registerAndUseStyle('fontscheme.current', $paramsFontScheme, [], ['rel' => 'lazy-stylesheet', 'crossorigin' => 'anonymous']);

        if (preg_match_all('/family=([^?:]*):/i', $paramsFontScheme, $matches) > 0) {
            $fontStyles = '--cassiopeia-font-family-body: "' . str_replace('+', ' ', $matches[1][0]) . '", sans-serif;
			--cassiopeia-font-family-headings: "' . str_replace('+', ' ', $matches[1][1] ?? $matches[1][0]) . '", sans-serif;
			--cassiopeia-font-weight-normal: 400;
			--cassiopeia-font-weight-headings: 700;';
        }
    } elseif ($paramsFontScheme === 'system') {
        $fontStylesBody    = $this->params->get('systemFontBody', '');
        $fontStylesHeading = $this->params->get('systemFontHeading', '');

        if ($fontStylesBody) {
            $fontStyles = '--cassiopeia-font-family-body: ' . $fontStylesBody . ';
            --cassiopeia-font-weight-normal: 400;';
        }
        if ($fontStylesHeading) {
            $fontStyles .= '--cassiopeia-font-family-headings: ' . $fontStylesHeading . ';
    		--cassiopeia-font-weight-headings: 700;';
        }
    } else {
        $wa->registerAndUseStyle('fontscheme.current', $paramsFontScheme, ['version' => 'auto'], ['rel' => 'lazy-stylesheet']);
        $this->getPreloadManager()->preload($wa->getAsset('style', 'fontscheme.current')->getUri() . '?' . $this->getMediaVersion(), ['as' => 'style']);
    }
}

// Enable assets
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
    ->useStyle('template.active.language')
    ->registerAndUseStyle($assetColorName, 'global/' . $paramsColorName . '.css')
    ->useStyle('template.user')
    ->useScript('template.user')
    ->addInlineStyle(":root {
		--hue: 214;
		--template-bg-light: #f0f4fb;
		--template-text-dark: #495057;
		--template-text-light: #ffffff;
		--template-link-color: var(--link-color);
		--template-special-color: #001B4C;
		$fontStyles
	}");

// Override 'template.active' asset to set correct ltr/rtl dependency
$wa->registerStyle('template.active', '', [], [], ['template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);

// Logo file or site title param
if ($this->params->get('logoFile')) {
    $logo = HTMLHelper::_('image', Uri::root(false) . htmlspecialchars($this->params->get('logoFile'), ENT_QUOTES), $sitename, ['loading' => 'eager', 'decoding' => 'async'], false, 0);
} elseif ($this->params->get('siteTitle')) {
    $logo = '<span title="' . $sitename . '">' . htmlspecialchars($this->params->get('siteTitle'), ENT_COMPAT, 'UTF-8') . '</span>';
} else {
    $logo = HTMLHelper::_('image', 'logo.svg', $sitename, ['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'], true, 0);
}

$hasClass = '';

if ($this->countModules('sidebar-left', true)) {
    $hasClass .= ' has-sidebar-left';
}

if ($this->countModules('sidebar-right', true)) {
    $hasClass .= ' has-sidebar-right';
}

// Container
$wrapper = $this->params->get('fluidContainer') ? 'wrapper-fluid' : 'wrapper-static';

$this->setMetaData('viewport', 'width=device-width, initial-scale=1');

$stickyHeader = $this->params->get('stickyHeader') ? 'position-sticky sticky-top' : '';

// Defer fontawesome for increased performance. Once the page is loaded javascript changes it to a stylesheet.
$wa->getAsset('style', 'fontawesome')->setAttribute('rel', 'lazy-stylesheet');
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">

<head>
    <jdoc:include type="metas" />
    <jdoc:include type="styles" />
    <jdoc:include type="scripts" />
</head>

<body class="site <?php echo $option
    . ' ' . $wrapper
    . ' view-' . $view
    . ($layout ? ' layout-' . $layout : ' no-layout')
    . ($task ? ' task-' . $task : ' no-task')
    . ($itemid ? ' itemid-' . $itemid : '')
    . ($pageclass ? ' ' . $pageclass : '')
    . $hasClass
    . ($this->direction == 'rtl' ? ' rtl' : '');
?>">
    <header class="header container-header full-width<?php echo $stickyHeader ? ' ' . $stickyHeader : ''; ?>">

        <?php if ($this->countModules('topbar')) : ?>
            <div class="container-topbar">
                <jdoc:include type="modules" name="topbar" style="none" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('below-top')) : ?>
            <div class="grid-child container-below-top">
                <jdoc:include type="modules" name="below-top" style="none" />
            </div>
        <?php endif; ?>

        <?php if ($this->params->get('brand', 1)) : ?>
            <div class="grid-child">
                <div class="navbar-brand">
                    <a class="brand-logo" href="/<?php echo $this->baseurl; ?>/">
                        <?php echo $logo; ?>
                    </a>
                    <?php if ($this->params->get('siteDescription')) : ?>
                        <div class="site-description"><?php echo htmlspecialchars($this->params->get('siteDescription')); ?></div>
                    <?php endif; ?>
                </div>
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
            <div class="grid-child container-nav">
                <?php if ($this->countModules('menu', true)) : ?>
                    <jdoc:include type="modules" name="menu" style="none" />
                <?php endif; ?>
                <?php if ($this->countModules('search', true)) : ?>
                    <div class="container-search">
                        <jdoc:include type="modules" name="search" style="none" />
                    </div>
                <?php endif; ?>
            </div>
        <?php endif; ?>
    </header>

    <div class="site-grid">
        <?php if ($this->countModules('banner', true)) : ?>
            <div class="container-banner full-width">
                <jdoc:include type="modules" name="banner" style="none" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('top-a', true)) : ?>
            <div class="grid-child container-top-a">
                <jdoc:include type="modules" name="top-a" style="card" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('top-b', true)) : ?>
            <div class="grid-child container-top-b">
                <jdoc:include type="modules" name="top-b" style="card" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('sidebar-left', true)) : ?>
            <div class="grid-child container-sidebar-left">
                <jdoc:include type="modules" name="sidebar-left" style="card" />
            </div>
        <?php endif; ?>

        <div class="grid-child container-component">
            <jdoc:include type="modules" name="breadcrumbs" style="none" />
            <jdoc:include type="modules" name="main-top" style="card" />
            <jdoc:include type="message" />
            <main>
                <jdoc:include type="component" />
            </main>
            <jdoc:include type="modules" name="main-bottom" style="card" />
        </div>

        <?php if ($this->countModules('sidebar-right', true)) : ?>
            <div class="grid-child container-sidebar-right">
                <jdoc:include type="modules" name="sidebar-right" style="card" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('bottom-a', true)) : ?>
            <div class="grid-child container-bottom-a">
                <jdoc:include type="modules" name="bottom-a" style="card" />
            </div>
        <?php endif; ?>

        <?php if ($this->countModules('bottom-b', true)) : ?>
            <div class="grid-child container-bottom-b">
                <jdoc:include type="modules" name="bottom-b" style="card" />
            </div>
        <?php endif; ?>
    </div>

    <?php if ($this->countModules('footer', true)) : ?>
        <footer class="container-footer footer full-width">
            <div class="grid-child">
                <jdoc:include type="modules" name="footer" style="none" />
            </div>
        </footer>
    <?php endif; ?>

    <?php if ($this->params->get('backTop') == 1) : ?>
        <a href="#top" id="back-top" class="back-to-top-link" aria-label="<?php echo Text::_('TPL_CASSIOPEIA_BACKTOTOP'); ?>">
            <span class="icon-arrow-up icon-fw" aria-hidden="true"></span>
        </a>
    <?php endif; ?>

    <jdoc:include type="modules" name="debug" style="none" />
</body>

</html>

und fügen diese in unsere eben erstellte “index.php” ein. Ein Blick in das Frontend zeigt uns, dass nun wieder alles so ist wie vorher. Sehr gut.

Wir erinnern uns, dass wir ein paar Modulpositionen tauschen wollten. Cassiopeia liefert und schon eine Position “menu” und eine Position “search”, wir müssen uns also nicht extra eine erstellen - wäre das aber notwendig, haben wir ja bereits gelernt, wie einfach dies wäre.

Uns interessieren nun gezielt die Zeilen 172 - 183:

<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
            <div class="grid-child container-nav">
                <?php if ($this->countModules('menu', true)) : ?>
                    <jdoc:include type="modules" name="menu" style="none" />
                <?php endif; ?>
                <?php if ($this->countModules('search', true)) : ?>
                    <div class="container-search">
                        <jdoc:include type="modules" name="search" style="none" />
                    </div>
                <?php endif; ?>
            </div>
        <?php endif; ?>

Was sehen wir hier? Hier steht was von “menu” und “search”, das sieht doch schon mal gut aus. Warte mal, “search” steht dort auch schon? Vielleicht müssen wir das ja gar nicht in unserem Template neu positionieren, sondern können den vorhandenen Code nutzen. Lass uns das doch mal prüfen.

Wir schließen die Datei vorerst wieder über “Speichern & Schließen” und wechseln zu “Inhalt”→Site Module” und erstellen ein neues Modul. Wenn wir nach dem Modultyp gefragt werden, suchen wir uns das Modul “Suchindex” heraus. Wir vergeben einen kreativen Titel, in unserem Fall “Suche”, wählen unter den Positionen die Position “search” aus, verbergen “Bezeichnung Suchfeld” und schließen den Dialog über “Speichern & Schließen”. 

Schauen wir uns das Frontend noch einmal an:

13_child_templates-frontend_mit_suche
Das Frontend mit aktiviertem Suchmodul

Tatsächlich, die Suche ist schon fast an der richtigen Stelle. Das erspart uns ein wenig Arbeit. Gehen wir nun zurück in unsere “index.php” und zu den genannten Zeilen 172 - 183.

Nun möchten wir ja nicht mehr, dass unser Menü dort erscheint. Schauen wir uns also als erstes die Zeile 172 genau an:

<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>

Was macht diese PHP IF-Anweisung? Sie schaut nach, ob wir Module auf der Position “menu” oder “search” haben, ist dies der Fall, springen wir in die Anweisung hinein. Hier können wir aber direkt unsere erste Änderung vornehmen, denn hier brauchen wir nicht mehr schauen, ob Module auf der Position “menu” dort vorhanden sind.

Wir ändern diese Zeile in:

<?php if ($this->countModules('search', true)) : ?>

Nun prüft unsere Anweisung nur noch, ob Module auf der Position “search” vorhanden sind.

Als nächstes schauen wir uns die Zeilen 174 - 176 genau an:

<?php if ($this->countModules('menu', true)) : ?>
   <jdoc:include type="modules" name="menu" style="none" />
<?php endif; ?>

Dieser Block prüft, ob ein Modul auf der Position “menu” vorhanden ist und wenn ja, lädt er die entsprechenden Module. Da wir dies nicht mehr wollen, können wir diese Zeilen also entfernen. Speichert euch die drei Zeilen ab (oder kopiert sie später aus dieser Anleitung) und entfernt diese aus eurer index.php.

Als nächstes schauen wir uns die neuen Zeilen 174 - 176 an:

<?php if ($this->countModules('search', true)) : ?>
    <div class="container-search">
        <jdoc:include type="modules" name="search" style="none" />
    </div>
 <?php endif; ?>

Dieser Block prüft, ob ein Modul auf der Position “search” vorhanden ist und wenn ja, öffnet er einen neuen Container mit der Klasse “container-search” und innerhalb dieses Containers lädt er die entsprechenden Module auf der Position “search”. Aber warte mal, hatten wir diese Abfrage nicht bereits? Richtig! Daher können wir diesen Block ein wenig anpassen, uns interessiert hier nur noch der Inhalt innerhalb der IF-Anweisung. Wir nehmen uns also die Zeilen 175 - 177 und fügen diese in Zeile 174 ein.

Nun sollten unsere Zeilen 172 - 178 wie folgt aussehen:

<?php if ($this->countModules('search', true)) : ?>
            <div class="grid-child container-nav">
                <div class="container-search">
                   <jdoc:include type="modules" name="search" style="none" />
                </div>
            </div>
<?php endif; ?>

Zum Schluss wollten wir nun ja gerne unsere Navbar oberhalb des “Breadcrumbs” platzieren. Hierfür durchsuchen wir unsere “index.php” also nach einem Breadcrumb-Modul, oder ihr folgt meiner Anweisung und springt einmal in Zeile 207, hier solltet ihr folgendes vorfinden:

<jdoc:include type="modules" name="breadcrumbs" style="none" />

Perfekt. Nun kopieren wir unsere drei Zeilen, die wir vorhin gesichert haben und platzieren diese direkt oberhalb des Breadcrumb-Moduls. Dafür verschieben wir dieses Modul eine Zeile tiefer in Zeile 208 und in Zeile 207 fügen wir unseren Code ein.

Der div-Container mit der Klasse “container-component” in Zeile 206-217 sollte nun also wie folgt aussehen:

<div class="grid-child container-component">
            <?php if ($this->countModules('menu', true)) : ?>
                <jdoc:include type="modules" name="menu" style="none" />
            <?php endif; ?>
            <jdoc:include type="modules" name="breadcrumbs" style="none" />
            <jdoc:include type="modules" name="main-top" style="card" />
            <jdoc:include type="message" />
            <main>
                <jdoc:include type="component" />
            </main>
            <jdoc:include type="modules" name="main-bottom" style="card" />
</div>

Speichern wir das Ganze einmal ab und schauen erneut in das Frontend:

14_child_templates-frontend_mit_positionstausch
Screenshot: Das Frontend mit geänderten Positionen und Anpassungen an der index.php

Das sieht doch schon gut aus. Zumindest fast. Die Elemente sind dort wo wir sie wollen, jetzt müssen wir diese nur noch ein wenig im Design anpassen.

Wir öffnen also unsere “user.css” und ersetzen den Inhalt mit:

:root {
  --cassiopeia-accent: #1e1e3f;   
  --cassiopeia-bg1:   #1e1e3f;  
  --cassiopeia-bg2:   #16162c;     
  --cassiopeia-bg3:   #0d0d1a;    
  --cassiopeia-highlight: #6B6B6B;    
  --cassiopeia-text:  #000;         
}

.container-header {
  background: radial-gradient(circle at center, var(--cassiopeia-bg1), var(--cassiopeia-bg2) 70%, var(--cassiopeia-bg3) 100%);
  background-size: cover;
  background-repeat: no-repeat;
  padding-bottom: 1.5rem;
  position: relative;
  overflow: hidden;
  z-index: 0;
}

.container-header::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: radial-gradient(circle, rgba(107,107,107,0.15), transparent 70%);
  opacity: 0.5;
  pointer-events: none;
  z-index: 1;
}


.container-header > .grid-child:first-child {
  padding-bottom: 3rem;
}

.container-component .mod-menu {
  background-color: #FFF;
  border-radius: 10px;
  padding: 1rem;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  position: relative;
  z-index: 3;
  top: -3rem;
}

.container-component .mod-menu > li > a,
.container-component .mod-menu > li > span {
  color: var(--cassiopeia-accent);
  text-transform: uppercase;
  font-weight: bold;
  transition: color 0.3s ease;
}

.container-component .mod-menu > li > a:hover,
.container-component .mod-menu > li > span:hover {
  color: var(--cassiopeia-highlight);
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
  color: var(--cassiopeia-text);
}

a {
  color: var(--cassiopeia-accent);
  transition: color 0.3s ease;
}

a:hover, a:focus {
  color: var(--cassiopeia-highlight);
}

.btn-info,
.btn-primary,
.btn-primary:focus,
.btn-primary:hover {
  background-color: var(--cassiopeia-accent);
  border-color: var(--cassiopeia-accent);
  color: #FFF;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.btn-check:focus + .btn-info,
.btn-info:focus,
.btn-info:hover,
.btn-primary:hover {
  background-color: var(--cassiopeia-highlight);
  border-color: var(--cassiopeia-highlight);
  color: #FFF;
}

.container-search {
    top: -3rem;
    position: relative;
}

Ein letzter Blick in das Frontend:

15_child_templates-finale_gestaltung
Der finale Look unseres Child Templates im Frontend

Das sieht doch super aus. Die restlichen optischen Anpassungen und Verfeinerungen überlasse ich euch und euren kreativen Köpfen. Dieses Tutorial soll euch nur einen Einblick in die Materie verschaffen und euch animieren, fortan Child Templates zu verwenden. 

Child Templates sind eine geniale Ergänzung für Joomla. Sie machen es möglich, kreative Ideen umzusetzen, ohne Updatesicherheit zu verlieren. Egal ob saisonale Designs oder individuelle Anpassungen – mit Child Templates hast du alle Freiheiten. 

Wichtiger Hinweis: Natürlich kann es auch vorkommen, dass sich der Joomla Core so verändert, dass eine Anpassung an euren Overrides notwendig wird, aber euer Code geht dabei nicht verloren.

Best Practice

Update-Sicherheit: Änderungen am Template sollten immer per Child Template erfolgen, statt das Original-Template zu verändern. So bleiben Anpassungen bei Updates des Eltern-Templates erhalten. Dies ist einer der Hauptgründe für Child Templates.

Minimal überschreiben: Im Child Template sollte man nur die Dateien hinzufügen oder ändern, die tatsächlich angepasst werden müssen. Es ist also nicht nötig (und nicht ratsam), das komplette Template zu duplizieren. Stattdessen erbt das Child Template alle nicht überschriebenen Dateien vom Parent. Dieses Vorgehen hält den Wartungsaufwand gering – wenn z.B. im Eltern-Template ein Bug in einer unüberschriebenen Datei behoben wird, profitiert das Child automatisch davon.

Namensgebung und Struktur: Es ist empfehlenswert, dem Child Template einen aussagekräftigen Namen zu geben, der auf seine Funktion hinweist (z.B. MeinTemplate-grün für eine grüne Variante des Templates). Zudem sollte die Verzeichnisstruktur sauber sein – meist enthält ein Child Template zunächst nur die templateDetails.xml und ggf. einzelne Ordner (wie css/ oder html/) für überschriebene Dateien. 

Prüfung nach Updates: Nach einem Joomla- oder Template-Update ist es Best Practice, die Child-Template-Anpassungen zu überprüfen. Insbesondere wenn man Override-Dateien (z.B. Layout-Overrides in /html) im Child hat, sollte man kontrollieren, ob das Update des Eltern-Templates Änderungen an diesen Bereichen brachte. Joomla 4.2+ besitzt z.B. eine „Overrides überprüfen“-Funktion, die anzeigt, ob Overrides veraltet sind.

Ich hoffe, dieser Einblick hat dich inspiriert! Viel Spaß beim Experimentieren.

Beste Grüße
Tonino

Mastodon