Frontend-Filter Umsetzung

Contao bietet zahlreiche Möglichkeiten zur Inhaltserstellung. Die jeweiligen Vor- und Nachteile können bei der Abwägung und Auswahl hilfreich sein. Als Beispiel hier anhand der Umsetzung eines »Frontend-Filters«:

Die animierte Filterung beliebiger Inhalte wird gerne bei der Darstellung von z. B. Referenzen herangezogen, ohne dass hierbei die Webseite neu geladen werden muss. Den zu filternden Inhalten müssen zunächst entsprechende Kategorien zugeordnet werden. Im Anschluss kann die Darstellung gezielt über diese Kategorien beeinflusst werden.

Umsetzung über eine Erweiterung

Für unsere Anforderung kannst du beispielsweise die Erweiterung codefog/contao-elements-filter nutzen. Weitere Informationen hierzu findest du dann auf der GitHub Seite des Autors.

Vorteil:
Eine Contao Erweiterung realisiert eine spezielle Aufgabe, ist zumeist kostenfrei und kann leicht installiert werden. Du musst dir dabei keine Gedanken um die eigentliche technische Umsetzung machen. Die Bearbeitung für dich oder weitere Redakteure erfolgt bequem über die bekannten Contao-Eingabemöglichkeiten. Eine Dokumentation, gerade bei kostenfreien Erweiterungen, erfolgt zumeist über die entsprechenden GitHub-Seiten. Als Alternative findest du hilfreiche Unterstützung über die Community im Contao Forum.

Nachteil:
Bei einem Contao-Update oder Wechsel der PHP-Version kann es vorkommen, dass die Erweiterung hierfür noch nicht ausgelegt ist. In diesem Fall bist du auf die Anpassungen des Autors angewiesen. Gerade bei kostenfreien Angeboten erhältst du aber auch dann zeitnah Hilfe mit Unterstützung der Contao Community.

Umsetzung ohne Erweiterung

Bekannte JavaScript Lösungen für unsere Anforderung sind z. B. Isotope oder MixItUp. Bei einer kommerziellen Nutzung ist hierbei der Erwerb von Lizenzen notwendig. Für unser Beispiel nutzen wir die Open Source Lösung Filterizr.

Nutzung von »Filterizr«

Beispiele und Dokumentation findest du auf der Filterizr Website und auf GitHub. Die Lösung kann wahlweise als »jQuery-Plugin« oder als »Vanilla JS« implementiert werden. Wir verwenden im folgenden Beispiel Letzteres.

Über den Download findest du im Anschluss in dem ZIP-Archiv das Verzeichnis »dist« mit der Datei »vanilla.filterizr.min.js« vor. Kopiere diese Datei in ein öffentliches Verzeichnis deiner Contao Installation unterhalb von »files«.

Für das »Filterizr« Script müssen die zu filternden Inhalte mit der CSS-Klasse filtr-item deklariert werden. Die Kategorie Zuordnung erfolgt über ein HTML5 Data-Attribut data-category. Ein beispielhafter HTML-Aufbau könnte demnach wie folgt aussehen und muss dann innerhalb von Contao abgebildet werden:

<ul>
  <li data-filter="all">Alle Tiere</li>
  <li data-filter="Hund">Nur Hunde</li>
  <li data-filter="Katze">Nur Katzen</li>
</ul>

<div class="filter-container">

  <div class="filtr-item" data-category="Hund">
    <img src="sample1.jpg" />
  </div>
  <div class="filtr-item" data-category="Katze">
    <img src="sample2.jpg" />
  </div>

</div>

<script type="text/javascript" src="files/MyPathToFile/vanilla.filterizr.min.js"></script>
<script>const filterizr = new Filterizr('.filter-container');</script>

Die obige HTML-Struktur kann mit den Contao eigenen Inhaltselementen erstellt werden. Hierbei verwenden wir für die HTML-Blöcke das Inhaltselement vom Typ »HTML« und für die eigentlichen Inhalte ein oder mehrere Element(e) vom Typ »Text«. Die Umsetzung im Contao Backend wäre daher:

Inhaltselement vom Typ »HTML«
Inhaltselement vom Typ »HTML«
Ein o. mehrere Inhaltselement(e) vom Typ »Text«
Inhaltselement vom Typ »HTML«

Mit Template-Anpassung

Es fehlt nur noch die Zuordnung unserer Kategorien über das HTML5 Data-Attribut. Im Inhaltselement vom Typ »Text« fehlt diese Eingabemöglichkeit. Wir können dies über angepasste Contao Templates realisieren.

Bei Eingabe von bestimmten, per Konvention festgelegten, Angaben im Bereich »Experteneinstellungen CSS-ID/Klasse« sollen diese über das Template als HTML5 Data-Attribut ausgegeben werden. Bei Eingabe von filtr-item DATA-Hund im Bereich CSS-Klasse möchten wir folgende Ausgabe erzielen:

...
<div class="ce_text filtr-item block" data-category="Hund">
...

Erstelle dir hierzu in dem von dir unter »Themes« vorgegebenen Template-Verzeichnis zwei neue Templates basierend auf »ce_text.html5« und »block_searchable.html5«.

Beispielsweise als »ce_text_filter.html5« und »block_searchable_filter.html5« und benutze das neue Template »ce_text_filter.html5« in deinen zu filternden Inhaltselementen vom Typ »Text«.

// ce_text_filter.html5

<?php $this->extend('block_searchable_filter'); ?>

<?php $this->block('content'); ?>

  <?php if (!$this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>

  <?php if ($this->addImage): ?>
    <?php $this->insert('image', $this->arrData); ?>
  <?php endif; ?>

  <?php if ($this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>

<?php $this->endblock(); ?>
// block_searchable_filter.html5

<?php
$strDelimiter = "DATA-";
$strPattern = '/'.$strDelimiter.'(.+?)\b/i';  
$strDataAttr = "data-category"; 
$strCSS = $this->class; 

if ( substr_count($strCSS, $strDelimiter) > 0 ) {

  preg_match_all($strPattern, $strCSS, $arrMatches, PREG_PATTERN_ORDER, 0);

  for( $i = 0; $i <= count($arrMatches); $i++) {
    $strCSS = str_replace($arrMatches[0][$i], "", $strCSS);          
    $arrMatchedValues[] = $arrMatches[1][$i];      
  }    
  $strData = $strDataAttr.'="'.rtrim(implode(", ", $arrMatchedValues), ", ").'"';
}
?>

<div class="<?= $strCSS ?> block"<?= $strData ?><?= $this->cssID ?><?php if ($this->style): ?> style="<?= $this->style ?>"<?php endif; ?>>

  <?php $this->block('headline'); ?>
    <?php if ($this->headline): ?>
      <<?= $this->hl ?>><?= $this->headline ?></<?= $this->hl ?>>
    <?php endif; ?>
  <?php $this->endblock(); ?>

  <?php $this->block('content'); ?>
  <?php $this->endblock(); ?>

</div>

Das Script erwartet die Inhalte innerhalb eines HTML-Blocks <div class="filter-container">...</div>. Zur übersichtlicheren Backend-Darstellung könntest du die Contao Accordeon Elemente »Umschlag Anfang« und »Umschlag Ende« zweckentfremden. Im Element »Umschlag Anfang« setzt du dann die CSS-Klasse filter-container ein.

Weiterhin haben wir einfachheitshalber die JavaScript-Referenzen direkt im Inhaltselement eingetragen. Alternativ könntest du diese auch als JavaScript Asset im Template hinterlegen.

Vorteil:
Du bist nicht auf Erweiterungen angewiesen und du hast die vollständige Kontrolle hinsichtlich der Umsetzung und der Pflege. Bei Contao Updates müssen u. U. lediglich mögliche Änderungen der Core Templates berücksichtigt werden.

Nachteil:
Für Template-Anpassungen in dieser Form sind zumindest rudimentäre PHP-Kenntnisse notwendig. Die Contao Community steht dir bei derartigen Fragen hilfreich zur Seite. Die Nutzung der HTML5 Data-Attribute ist für Redakteure nicht offensichtlich und bedarf entsprechender Dokumentation.

Mit Anpassung des »Data Container Arrays«

Für das nächste Beispiel übernehmen wir die bisherige Umsetzung über die Inhaltselemente. Zur Eingabe der HTML5 Data-Attribute werden wir für das Inhaltselement vom Typ »Text« allerdings ein neues, zusätzliches Eingabefeld erstellen und erweitern hierzu das Contao Data Container Array (DCA).

In der Developer Documentation findest du ein Beispiel zur Contao DCA Manipulation. In Zusammenhang mit Inhaltselementen ist hierbei die Contao-Datei tl_content.php und die entsprechende Datenbanktabelle tl_content verantwortlich die wir wie folgt erweitern:

Sofern noch nicht vorhanden, erstellst du dir in deinem Contao-Hauptverzeichnis ein neues Verzeichns contao/dca mit einer Datei tl_content.php:

// contao/dca/tl_content.php

use Contao\CoreBundle\DataContainer\PaletteManipulator;

$GLOBALS['TL_DCA']['tl_content']['fields']['myCustomDataAttributes'] = [
  'label'     => ['Data-Attribut', 'Hier können Sie Data-Attribute vergeben.'],
  'inputType' => 'keyValueWizard',
  'default'   => serialize([['key' => 'data-category']]),
  'eval'      => ['tl_class' => 'w50'],
  'exclude'   => true,
  'sql'       => "text NULL",
];

PaletteManipulator::create()
  ->addLegend('Einstellungen Data-Attribute', 'expert_legend', PaletteManipulator::POSITION_AFTER)
  ->addField('myCustomDataAttributes', 'Einstellungen Data-Attribute', PaletteManipulator::POSITION_APPEND)
  ->applyToPalette('text', 'tl_content')
;

Damit Contao diese Angaben übernimmt musst du im Anschluss über den Contao Manager im Bereich »Systemwartung« den »Anwendungs-Cache« aktualisieren. Rufe dann das Contao-Installtool auf. Dieses erkennt das neue Feld und bietet dir die Erstellung in der Datenbanktabelle »tl_content« an. Bei jeder Änderung der Datei »contao/dca/tl_content.php« wird dies dann erneut notwendig.

Das Inhaltselement vom Typ »Text« enthält nun ein neues Eingabefeld (als Schlüssel/Wert-Paar) für unsere HTML5 Data-Attribute unterhalb der »Experteneinstellungen«. Beispielsweise zur Angabe von data-category im Feld »Schlüssel« und einem Eintrag Hund im Feld »Wert«.

Zur Ausgabe auf der Webseite müssen wir auch hier wieder die Template-Dateien anpassen. Analog zum vorherigen Beispiel verwenden wir hierzu wieder die beiden Template Dateien »ce_text_filter.html5« und »block_searchable_filter.html5«.

// ce_text_filter.html5

<?php $this->extend('block_searchable_filter'); ?>

<?php $this->block('content'); ?>

  <?php if (!$this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>

  <?php if ($this->addImage): ?>
    <?php $this->insert('image', $this->arrData); ?>
  <?php endif; ?>

  <?php if ($this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>

<?php $this->endblock(); ?>
// block_searchable_filter.html5

<?php if ($this->myCustomDataAttributes) {
  $dataAttributesString = "";
  $dataAttributes = \StringUtil::deserialize($this->myCustomDataAttributes); 
  $parsedDataAttributes = [];

  foreach ($dataAttributes as $index=>$dataAttribute) {
    $parsedDataAttributes[] = 'data-' . str_replace('data-', '', $dataAttribute['key']) 
    . '="' . $dataAttribute['value'] 
    . '"';
  }
  $dataAttributesString = implode(' ' , $parsedDataAttributes);
}
?>

<div class="<?= $this->class ?> block"<?= $this->cssID ?><?php if ($this->style): ?> style="<?= $this->style ?>"<?php endif; ?> <?= $dataAttributesString ?>>

  <?php $this->block('headline'); ?>
    <?php if ($this->headline): ?>
      <<?= $this->hl ?>><?= $this->headline ?></<?= $this->hl ?>>
    <?php endif; ?>
  <?php $this->endblock(); ?>

  <?php $this->block('content'); ?>
  <?php $this->endblock(); ?>

</div>

Vorteil:
Du hast die vollständige Kontrolle hinsichtlich der Umsetzung und der Pflege. Die erforderlichen Angaben können von dir und deinen Redakteuren bequem über Eingabefelder gesetzt werden.

Nachteil:
Rudimentäre Kenntnisse in PHP und zum gut dokumentierten Contao DCA sind notwendig. Die Contao Community steht dir auch bei derartigen Fragen hilfreich zur Seite.

Mit »RockSolid Custom Elements«

Bei den »RockSolid Custom Elements« (RSCE) handelt es sich um eine Contao Erweiterung die dir die Erstellung individueller Inhaltselemente und Frontend-Module mit bequemen Eingabemöglichkeiten und deren Ausgabe in Contao ermöglicht.

Falls du dich fragen solltest, warum in diesem Kontext wieder eine Erweiterung vorgestellt wird:

Vorteil:
Du nutzt drei unterschiedliche Erweiterungen von verschiedenen Autoren z. B. einen »Frontend-Filter«, einen alternativen »Content-Slider« und deine favorisierte »Foto-Gallerie». Je mehr Erweiterungen zum Einsatz kommen, desto höher ist möglicherweise dein Aufwand bei kommenden Contao Updates.

Mit Einsatz von »RSCE« beschränkst du diesen Umstand auf eine einzige Erweiterung und du kannst dir und den Redakteuren für alle drei Anforderungen dennoch eine bequeme Bearbeitung innerhalb von Contao ermöglichen. Darüber hinaus wird die Erweiterung vom Autor Martin Auswöger (@ausi / Mitglied im Contao Core-Team) gepflegt und aktuell gehalten.

Nachteil:
Kenntnisse zum gut dokumentierten Contao DCA sind notwendig. Die Contao Community steht dir auch bei derartigen Fragen hilfreich zur Seite.

Die »RSCE« Erweiterung orientiert sich an den bestehenden Contao-Konventionen. Du benötigst lediglich zwei Dateien, die im angegebenen Template-Verzeichnis deines Themes angelegt werden.

Dabei handelt es sich einerseits um eine ».php« Konfigurationsdatei mit Contao DCA Informationen und einer ».html5« Template-Datei zur Ausgabe. Bei den Dateinamen musst du folgende Konvention berücksichtigen:

Der Name der Template-Datei muss mit »rsce_« beginnen, die Konfigurationsdatei muss den selben Namen wie das Template und zusätzlich das Suffix »_config« beinhalten: Beispielsweise »rsce_my_filter.html5« und »rsce_my_filter_config.php».

// rsce_my_filter_config.php

return array(
  'label' => array('Filter-Element', 'Inhalte für Frontend-Filter'),
  'types' => array('content'),
  'contentCategory' => 'texts',
  'standardFields' => array('headline', 'text', 'image', 'cssID'),
  'wrapper' => array(
    'type' => 'none',
  ),
  'fields' => array(
  'description' => array(
    'label' => array('Data-Attribut', 'Angabe eines o. mehrerer HTML Data-Attribut(e)'),
    'inputType' => 'group',
  ),
  'data' => array(
    'label'     => ['Data-Attribut:', 'Attribut-Bezeichnung / Attribut-Wert'],
    'inputType' => 'keyValueWizard',
    'default'   => serialize([['key' => 'data-category']]),
    'eval'      => ['tl_class' => 'w50'],
    ),
  ),
);
// rsce_my_filter.html5

<?php if ($this->data){

  $dataAttributesString = "";
  $dataAttributes = $this->data; 
  $parsedDataAttributes = [];

    foreach ($dataAttributes as $index=>$dataAttribute) {
      $parsedDataAttributes[] = 'data-' . str_replace('data-', '', $dataAttribute['key']) 
      . '="' . $dataAttribute['value'] 
      . '"';
    }
    $dataAttributesString = implode(' ' , $parsedDataAttributes);
}
?>

<div class="<?= $this->class ?> block" <?= $this->cssID ?> <?= $dataAttributesString ?>>
  <?php if ($this->headline): ?>
    <<?= $this->hl ?>><?= $this->headline ?></<?= $this->hl ?>>
  <?php endif; ?>

  <?php if ($this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>

  <?php if ($this->addImage): ?>
    <?php $this->insert('image', $this->arrData); ?>
  <?php endif; ?>

  <?php if (!$this->addBefore): ?>
    <?= $this->text ?>
  <?php endif; ?>
</div>

Hierüber erhältst du ein neues, eigenes Inhaltselement unter der Bezeichnug »Filter-Element» zur Auswahl. Dieses kannst du im Anschluss für die zu filternden Inhalte in Kombination mit den Inhaltselementen vom Typ »HTML« (s. o.) einsetzen.

Mit der »RSCE« Erweiterung könntest du dir auch eigene Umschlags-Elemente erstellen und diese statt der bisherigen Inhaltselemente vom Typ »HTML« verwenden.

Die Erweiterung »MetaModels« verfolgt einen ähnlichen Ansatz und konfrontiert dich dabei nicht mit einer direkten Contao »DCA Konfiguration«. Allerdings geht diese Erweiterung weit über die hier erfoderlichen Anforderungen hinaus. Die Lernkurve (s. Dokumentation) ist entsprechend höher.

Fazit

Contao bietet zahlreiche Möglichkeiten zur Umsetzung deiner Anforderungen. Die Art der Umsetzung ist immer eine Abwägung zwischen Komfort und späterem Update Aufwand. Gerade bei clientseitigen Lösungen, die lediglich auf ein Zusammenspiel von HTML, CSS und JavaScript beruhen, liefert Contao vielfältige Lösungen unabhängig von existierenden Erweiterungen.