Neos Workshop Teil 3 - Eigene Content Elemente erstellen

Dank des mächtigen ContentRepository und dank der Werkzeuge welche uns Neos CMS mit Eel und FlowQuery an die Hand gibt, sind uns der Gestaltung eigener Content Elemente keine Grenzen gesetzt.

Ich liebe es wenn ein Plan funktioniert!

Daniel Lienert
Daniel ist immer auf der Suche nach technologisch innovativen aber dennoch nachhaltig stablilen Lösungen für unsere Kunden.
Lesedauer: ca. 7 Minuten

Willkommen zum dritten Teil unseres Neos Tutorials. Im ersten Teil wurde gezeigt wie sich eine lokale Entwicklungsumgebung für Neos mit Vagrant einfach aufsetzen lässt. Im zweiten Teil haben wir dann ein Site Package für unsere eigene Seite gestartet. In diesem Teil geht es nun um die Struktur und die Konfiguration von eigenen Content Typen.

Inhalt ordentlich strukturiert - Das Neos Content Repository

Um eigene Inhaltstypen zu definieren, ist es zunächst einmal wichtig die Datenstruktur hinter Neos zu verstehen, daher beginnt dieser Teil mit etwas (spannender) Theorie.

Jeglicher Inhalt in Neos wird als Knoten (Node) einer Baumstruktur (Tree) abgelegt.  Man kann sich die Struktur ähnlich dem Dateisystem eines Computers vorstellen. Jede Knoten hat einen eindeutigen Namen und einen Typ - ähnlich dem Typ einer Datei im Dateisystem - welcher die definierten Eigenschaften der Node festlegt.

Die Daten der Node werden in ihren Eigenschaften abgelegt. Natürlich kann jeder Knoten weitere Unterknoten besitzen. Die Wurzel des Baumes ist ein spezieller Knoten namens "Site".

Am besten lässt sich das an einer einfachen Webseite mit einer hierarchischen Menüstruktur veranschaulichen. Jede Seite wird durch eine Node vom Typ "Document" repräsentiert - Unterseiten der zweiten Menüeben sind dabei Kindknoten der ersten Ebene.

Auch der Inhalt der Seite, wie Text- oder Bildelemente sind Nodes, welche als Kindknoten an der jeweiligen Seite hängen. Dabei können auch Inhalte - wie beispielsweise ein 2-Spalten-Container - ihrerseits wieder Kindknoten haben. Die Inhalte lassen sich so beliebig tief verschachteln. Zusätzlich kann jede Node noch beliebige Eigenschaften, hier Properties genannt, besitzen.

Im Bild ist ein solcher Baum mit einer Seite Home und einer Seite "Weine aus aller Welt" zu sehen. Die Seiten können einfache StandardInhalte wie Texte oder Bilder beinhalten oder ein spezielles Contentelement, welches in unserem Beispiel einen Wein repräsentiert. Ein solches Element soll in diesem Teil des Workshops erstellt werden.

Abbildung der Baumstruktur (Neos CMS Content Repository Tree)

Definition eines neuen Inhaltstyps

Jeder neue Inhaltstyp beginnt mit der Definition eines eigenen NodeTypes. Die Datenstrukturen des NodeTypes werden in der Auszeichnungssprache YAML definiert. Hier werden auch MetaInformationen wie Editiermöglichkeiten, Hilfetexte und Beziehungen zu anderen Typen definiert. NodeType-Definitionen können beliebig vererbt werden, wobei auch Mehrfachvererbung möglich ist. Dadurch ist es möglich, einfache wiederverwendbare Datentypen zu defineren, welche sich dann mit ihren Eigenschaften zu größeren Definitionen verbinden lassen.

Neos definiert bereits eine Reihe von abstrakten Basis Typen, welche ein Grundverhalten eines Datenelementes festlegen.

  • Neos.Neos:Node: ist die Grundlage aller anderen definierten Typen.
  • Neos.Neos:Document: definiert die Grundlage aller Knoten welche sich wie Seiten verhalten.
  • Alle Inhalte einer Seite erben von den Typen Neos.Neos:Content oder Neos.Neos:ContentCollection. Während eine ContentCollection zur Strukturierung von Content dient - beispielsweise für einen Mehrspaltencontainer, ist der Content die Basis jedes einfachen Inhalts, wie einer Überschrift oder einem Bild.

Mit diesem Wissen können wir uns daran machen einen eigenen einfachen Inhaltstyp zu erstellen: passend zur Seite soll dies die Präsentation eines Weines mit Titel, Beschreibung, Bild und Rebsorte sein. Alle Dateien welche wir im folgenden anlegen, werden unter dem Verzeichnis des Site Packages angelegt. Der zweite Teil des Workshops veranschaulicht den grundlegenden Aufbau des Site Packages. 

Es hat sich als praktisch erwiesen, NodeType Definition auf mehrere Dateien zu verteilen um eine bessere Übersicht zu bewahren. Daher legen wir unsere Definition in einer neuen Datei NodeTypes.Wine.yaml im Verzeichnis Configuration an - wichtig ist, dass der Dateinamen mit "NodeTypes." beginnt, damit Neos diese automatisch einbindet. Strukturen in YAML werden alleine über die Einrückung mit zwei Leerzeichen definiert. 

Wir beginnen mit einer einfachen Definition eines Elementes mit den zwei Eigenschaften Titel und Beschreibung wie im Listing zu sehen. Die erste Zeile legt den Namen des NodeTypes fest. Mit superTypes in der Zeile darunter, wird festgelegt, von welchen anderen NodeTypes geerbt werden soll. Da es ein Inhaltselement werden soll, wird hier vom Basistyp ContentElement geerbt.

Unter dem Schlüssel "ui" sind immer Eigenschaften angesiedelt, welche die Oberfläche und Bedienelemente beschreiben. Mit Label wird dem Element ein aussagekräftiger Name gegeben. Ein Icon mit Wiedererkennungswert unterstützt den Redakteur bei der Auswahl - hier können alle Icons aus dem FontAwesome IconSet verwendet werden.
 

'WL.WeinLaden:Wine':
  superTypes:
    'Neos.Neos:Content': true
  ui:
    label: Wein
    icon: icon-glass
    inspector:
      groups:
        wine:
          label: Wein
  properties:
    title:
      type: string
      ui:
        label: 'Name'
        inlineEditable: true
        aloha:
          placeholder: 'Titel des Weins'
    description:
      type: string
      ui:
        label: 'Beschreibung'
        inlineEditable: true
        aloha:
          placeholder: 'Beschreibung des Weins'

Configuration/NodeTypes.Wine.yaml

Unter dem Bereich Inspector liegen jeweils Eigenschaften welche die Bedienelemente in der rechten Seitenleiste betreffen. Die Bedienelemente können in Gruppen zusammengefasst werden. Eine solche Gruppe mit dem Label "Wein" wird hier angelegt.

Als nächstes werden die Eigenschaften der Node definiert. Der Inhalt vom Typ "string" kann direkt "inline" auf der Seite bearbeitet werden, was mit der Eigenschaft inlineEditable:true angegeben wird. Aloha ist der Name des Editors, welcher dafür verwendet wird und welcher hier parametrisiert werden kann. Der hier definierte Placeholdertext wird angezeigt, wenn noch kein eigener Text angegeben wurde und unterstützt so den Redakteur beim Bearbeiten der Seite.

Nun geht es an die Anzeige: Neos erwartet das Template eines NodeTypes per Konvention im Verzeichnis Resources/Private/Templates/NodeTypes/<nodeTypeName>.html - nodeTypeName ist in unserem Fall "Wine". 

{namespace neos=Neos\Neos\ViewHelpers}
<div {attributes -> f:format.raw()}>
	<neos:contentElement.editable property="title" tag="h3" />
	<neos:contentElement.editable property="description" tag="p" />
</div>

In der ersten Zeile importieren wir die ViewHelper aus dem neos-Namespace, welche wir zur Anzeige der bearbeitbaren Elemente benötigen. Mit dem "contentElement.editable"-ViewHelper legen wir für die zuvor definierten Properties, bearbeitbare HTML Elemente an. Die Überschrift wird dabei mit dem H3-Tag gerendert und der Text mit einem Paragraph.

Fertig ist das erste eigene Inhaltselement, welches nun auch auf der Seite integriert werden kann.


Screenshot der Integration eines neuen Inhaltselementes
Ein Screenshot zeigt, wie Überschrift und Titel inline bearbeitet werden können

Auf gehts zur zweiten Runde - in dieser wollen wir das Wein-Inhaltselement zusätzlich um die Rebsorte und ein Bild erweitern.

Die Rebsorte soll nicht als Freitext eingegeben, sondern aus einer Liste von vorgegebenen Sorten selektiert werden können. Dies kann nicht direkt auf der Seite erfolgen, daher wird ab Zeile 9 der Inspector in der rechten Seitenleiste konfiguriert.
Das neue Feld wird der Gruppe "wine" hinzugefügt. Der Editor soll vom Typ "SelectBoxEditor " sein, welcher ein Auswahlfeld für die unter "values" konfigurierten Werte anzeigt.

Weiter zum Bild. Dieses wird mit dem Typ "ImageInterface" spezifiziert. Die Angabe des Typs sorgt auch dafür, dass ein Editor zum Upload oder zur Auswahl eines Bildes aus dem Medienmodul zur Verfügung steht.

Auch dieser Editor kann an eigene Anforderungen anpasst werden. In unserem Fall wollen wir, dass alle Bilder mit dem gleichen Seitenverhältnis angezeigt werden. Wird ein Bild mit einem anderen Seitenverhältnis hoch geladen soll dieses automatisch beschnitten werden.

Welcher Teil des Bildes dann angezeigt wird, kann vom Redakteur individuell bestimmt werden. Dazu definieren wir unter editorOptions einen Beschnitt (crop) mit einem fixen Seitenverhältnis (aspectRatio) von 3 zu 2.

    grape:
      type: string
      ui:
        label: Rebsorte
        help:
          message: Hier bitte die Rebsorte wählen
        reloadIfChanged: true
        inspector:
          group: wine
          editor: Neos.Neos/Inspector/Editors/SelectBoxEditor
          editorOptions:
            values:
              Merlot:
                label: Merlot
              Syrah:
                label: Syrah
              Chardonnay:
                label: Chardonnay
    image:
      type: Neos\Media\Domain\Model\ImageInterface
      ui:
        label: Bild
        reloadIfChanged: true
        inspector:
          group: wine
          editorOptions:
            crop:
              aspectRatio:
                locked:
                  width: 3
                  height: 2

Das Rendern des Bildelements im Template gelingt in diesem Fall am einfachsten mit dem Neos Image-Partial. Um dieses zu benutzen, muss per Fusion der PartialRootPath auf das richtige Verzeichnis gesetzt werden. Dazu legen wir unter Resources/Private/Fusion/NodeTypes/ eine neue Datei Wine.fusion an und fügen die folgenden Zeilen ein:

prototype(WL.WeinLaden:Wine) < prototype(Neos.Neos:Content) {
    partialRootPath = 'resource://Neos.NodeTypes/Private/Templates/NodeTypes/Partials'
}

Resources/Private/Fusion/NodeTypes/ Wine.fusion

Damit definieren wir einen neuen Fusion Prototyp für unser Content Element, welcher zum Rendern herangezogen wird. Durch das Erben vom Basistyp "Content" übernehmen wir einige Basiseinstellungen zur Darstellung eines Content Elements, welche wir für unser eigenes Element nun überschreiben oder erweitern können.

Diese Datei (sowie alle anderen Dateien im Verzeichnis NodeTypes) binden wir mit der folgenden Zeile am Ende der Datei Root.fusion ein

include: NodeTypes/*

Daraufhin kann das Template unseres Content Elements um die zusätzlichen Daten erweitert werden. Um das Bild anzuzeigen, verwenden wir nun das Image Partial. Dieses verlangt als minimale Anzahl an Argumente das Image Objekt sowie ein Text für den alt-Tag.

{namespace neos=Neos\Neos\ViewHelpers}
<div {attributes -> f:format.raw()}>
    <div class="thumbnail">
        <f:render partial="Image" arguments="{image:node.properties.image, alt:node.properties.title, maximumWidth:1600}" />
        <div class="caption">
            <neos:contentElement.editable property="title" tag="h3" />
            <neos:contentElement.editable property="description" tag="p" />
            <div class="row">
                <div class="col-md-6">Rebsorte:</div><div class="col-md-6"><b>{node.properties.grape}</b></div>
            </div>
        </div>
    </div>
</div>

Resources/Private/Templates/NodeTypes/Wine.html

Die Bilder in unserem Content Element werden so erst einmal in der vollen Originalauflösung ausgegeben. Die Auflösung lässt sich aber mit den zusätzlichen Argumenten maximumWidth und maximumHeight an das Image partial auf ein sinnvolles Maß für unseren Anwendungsfall einschränken.

Vorhande NodeTypes anpassen - Mehrspaltencontainer mit Bootstrap Markup

Screenshot eines Mehrspaltencontainers

Unser neues Content Element würde sich wunderbar in einer Spalte eines Mehrspaltencontainers machen. Neos bringt standardmäßig bereits Container für 2, 3 und 4 Spalten mit. Damit diese aber so gerendert werden, dass das Markup und die verwendeten CSS Klassen zum hier eingesetzten Bootstrap Framework passen, müssen ein paar kleinere Anpassungen vorgenommen werden. 
Die Anpassungen eignen sich überdies gut, um ein wenig mehr von der Funktionalität von Eel und FlowQuery kennen zu lernen.

Die NodeType Definition des Mehrspaltencontainers welche Neos mitbringt überschreiben wir in unserer eigenen Datei NodeTypes.BootstrapAdjustments.yaml.

In den Optionen für das Spalten-Layout werden die bisherigen Einträge mit einer Tilde (~) entfernt und dafür die Vorgaben für das Bootstrap 12-Spalten Grid eingetragen.

Im nächsten Schritt werden nun wir auch die Fusion Prototypen des Mehrspaltencontainers und der einzelnen Spalte überschrieben um die CSS Klassen für das Bootstrap Grid zu rendern. 

Das "attributes" array ist in allen Content Typen vorhanden, welche von der Defintion Neos.Neos:Content erben. Nach Konvention wird "attributes" in den äußeren HTML-Tag des Content Elements gerendert.

Im Prototype Neos.NodeTypes:MultiColumn setzen wir hier einfach die CSS Klasse "row" Im NodeType Neos.NodeTypes:MultiColumnItem wird es schon etwas spannender. Jede Spalte benötigt eine Klasse, welche Ihre Breite definiert. Aus dem ausgewählten Wert für das layout "9-3" soll also für die erste Spalte die CSS Klasse "col-md-9" und für die zweite Spalte "col-md-3" generiert werden. Dazu splitten wir den Layout-Wert mit dem Eel-Helper String.split() am "-" und verwenden für jede Spalte den Wert mit dem entsprechenden Spaltenindex.

'Neos.NodeTypes:TwoColumn':
  properties:
    'layout':
      defaultValue: '9-3'
      ui:
        inspector:
          editorOptions:
            values:
              '50-50': ~
              '75-25': ~
              '25-75': ~
              '66-33': ~
              '33-66': ~
              '9-3':
                label: '75% / 25%'
              '6-6':
                label: '50% / 50%'
              '3-9':
                label: '25% / 75%'

'Neos.NodeTypes:ThreeColumn':
  properties:
    layout:
      defaultValue: '4-4-4'
      ui:
        reloadIfChanged: true
        label: ''
        inspector:
          editorOptions:
            values:
              '33-33-33': ~
              '50-25-25': ~
              '25-50-25': ~
              '25-25-50': ~
              '4-4-4':
                label: '33% / 34% / 33%'

Configuration/NodeTypes.BootstrapAdjustments.yaml

prototype(Neos.NodeTypes:MultiColumn) {
    attributes.class = 'row'
    columns.iterationName = 'multiColumnIteration'
}

prototype(Neos.NodeTypes:MultiColumnItem) {
    attributes.class = ${'col-md-' + String.split(q(node).parent().property('layout'), '-')[multiColumnIteration.index]}
}

Fusion/NodeTypes/BootstrapAdjustments.fusion

In diesem Workshopteil haben wir gesehen, wie eigene NodeTypes in YAML definiert und mit Fusion und Fluid gerendert, sowie wie vorhandene NodeTypes einfach erweitert und angepasst werden können. Den Kompletten Stand des Site Packages kann im GitHub Repository zum Workshop nachvollzogen werden.

Teilen:

Weitere Beiträge

Einfach kann jeder ;)
Jörg Krohn, Entwicklung bei punkt.de
Arbeiten bei punkt.de