Files
VITEC-website/VITEC-ContentBlocks-Setup.md
2026-05-18 14:46:25 +02:00

19 KiB

VITEC Content Blocks Setup — Step-by-Step

Strategie: Content Blocks (YAML-basiert, deklarativ) als Haupt-Pattern für alle VITEC Content Elements. nb-headless-content-blocks übernimmt automatisch das JSON-Mapping.

Stack-Basis bei dir (bereits vorhanden):

  • evomedien/vitec — deine Extension
  • Site Set evomedien/vitecset
  • friendsoftypo3/headless (headless: 1)
  • nb-headless-content-blocks (Bridge)
  • b13/container + itplusx/headless-container (für Layout-Wrapper, optional)

0. Aufräumen (zwei kleine Issues vorab)

0.1 nb-headless-content-blocks ins Site Set migrieren

Aktuell steht die Extension nur in der Site-Config, nicht im Site Set. Das sollte konsistent sein, damit das Set in sich abgeschlossen ist.

Datei: packages/vitec/Configuration/Sets/VitecSet/config.yaml (oder wo dein Set liegt)

name: evomedien/vitecset
label: VITEC Set
settings:
  website:
    background:
      color: '#386492'
dependencies:
  - typo3/fluid-styled-content
  - friendsoftypo3/headless
  - b13/container
  - itplusx/headless-container
  - nb-headless-content-blocks/headless-content-blocks    # NEU

Dann aus config/sites/<identifier>/config.yaml die Zeile - nb-headless-content-blocks/headless-content-blocks wieder rausnehmen — sie kommt jetzt transitiv über dein Set.

0.2 Version-Mismatch fixen

  • composer.json sagt 1.0.1
  • ext_emconf.php sagt 0.0.1

Einfach beide auf den gleichen Wert bringen (Empfehlung: 1.0.0 und bei jedem Deployment hochzählen). Nicht dringend, aber sauber:

// ext_emconf.php
'version' => '1.0.0',
'state'   => 'stable',     // 'alpha' wirkt im Produktivbetrieb komisch
// composer.json
"version": "1.0.0"

1. Ordnerstruktur anlegen

Content Blocks leben in der Extension unter einer festen Pfad-Konvention:

packages/vitec/
├── ContentBlocks/
│   └── ContentElements/
│       ├── hero-section/               ← erster Content Block (Beispiel)
│       │   ├── config.yaml             ← Feld-Definitionen
│       │   ├── assets/
│       │   │   └── icon.svg            ← Icon für CE-Wizard
│       │   ├── language/
│       │   │   └── labels.xlf          ← Übersetzungen
│       │   └── templates/
│       │       └── EditorPreview.html  ← optional: Backend-Preview
│       ├── feature-teaser/
│       ├── cta-banner/
│       └── ...
├── Classes/
├── Configuration/
├── composer.json
└── ext_emconf.php

Ein Content Block = ein Ordner. Name = Ordnername (kleingeschrieben, mit Bindestrichen).


2. Erster Content Block: VITEC Hero Section

Nachgebaut nach einem typischen Template aus deinen Figma-Wireframes: Eyebrow + Heading + Subline + Body + CTA + Hero-Image + Background-Variant.

2.1 config.yaml

Datei: packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml

name: vitec/hero-section
group: vitec
prefixFields: true
prefixType: full

fields:
  - identifier: eyebrow
    type: Text
    max: 50

  - identifier: header
    useExistingField: true
    required: true

  - identifier: subheader
    useExistingField: true

  - identifier: bodytext
    useExistingField: true
    enableRichtext: true

  - identifier: cta
    type: Link
    allowedTypes:
      - page
      - url
      - file
      - email

  - identifier: cta_label
    type: Text
    default: 'Learn more'
    max: 30

  - identifier: hero_image
    type: File
    minitems: 0
    maxitems: 1
    allowed: common-image-types
    extendedPalette: true

  - identifier: background_variant
    type: Select
    renderType: selectSingle
    default: none
    items:
      - label: None
        value: none
      - label: Orange
        value: orange
      - label: Blue
        value: blue
      - label: Graphite
        value: graphite
      - label: Midnight
        value: midnight

  - identifier: show_logo_wall
    type: Checkbox
    default: 0

Was passiert hier:

  • name: vitec/hero-section → TCA-Typ vitec_hero_section, Tabelle tt_content
  • group: vitec → eigene Sektion im CE-Wizard (muss noch registriert werden — siehe 2.5)
  • useExistingField: true → recycled TYPO3-Core-Felder (header, subheader, bodytext). Keine neuen DB-Spalten, konsistente Bedeutung
  • prefixFields: true + prefixType: full → neue Felder bekommen tx_vitec_hero_section_* als DB-Prefix (vermeidet Kollisionen)
  • Keine TCA-Override-Datei nötig — Content Blocks generiert das TCA automatisch

2.2 language/labels.xlf

Content Blocks erwartet XLF-Files nach einer festen Namens-Konvention. Jedes Feld bekommt automatisch einen Key <vendor>.<element>.<field>.label.

Datei: packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2">
  <file source-language="en" datatype="plaintext" original="messages">
    <body>
      <!-- Element-Labels -->
      <trans-unit id="title">
        <source>VITEC · Hero Section</source>
      </trans-unit>
      <trans-unit id="description">
        <source>Haupt-Hero mit Headline, CTA, Background-Variante</source>
      </trans-unit>

      <!-- Field-Labels -->
      <trans-unit id="eyebrow.label">
        <source>Eyebrow Text</source>
      </trans-unit>
      <trans-unit id="eyebrow.description">
        <source>Kleiner Label-Text über der Headline</source>
      </trans-unit>

      <trans-unit id="cta.label">
        <source>CTA Link</source>
      </trans-unit>

      <trans-unit id="cta_label.label">
        <source>CTA Button Text</source>
      </trans-unit>

      <trans-unit id="hero_image.label">
        <source>Hero Image</source>
      </trans-unit>

      <trans-unit id="background_variant.label">
        <source>Background Variant</source>
      </trans-unit>

      <trans-unit id="show_logo_wall.label">
        <source>Show Logo Wall below</source>
      </trans-unit>
    </body>
  </file>
</xliff>

Die Core-Felder (header, subheader, bodytext) brauchen keine Labels — sie erben die vom TYPO3-Core.

2.3 assets/icon.svg

Content Blocks erkennt Icons automatisch, wenn sie unter assets/icon.svg (oder icon.png) liegen.

Datei: packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F47937" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect x="3" y="3" width="18" height="18" rx="2"/>
  <path d="M3 9h18"/>
  <circle cx="8" cy="15" r="2"/>
  <path d="M13 15h5M13 18h3"/>
</svg>

Platzhalter — tausch später gegen die echten VITEC-Icons aus.

2.4 templates/EditorPreview.html (optional aber empfohlen)

Im Headless-Mode wird kein Frontend-Template gerendert. Aber für die Redakteure ist eine Backend-Preview sinnvoll, damit sie im Seiten-Modul sehen, was sie gerade anlegen.

Datei: packages/vitec/ContentBlocks/ContentElements/hero-section/templates/EditorPreview.html

<div style="padding: 1rem; border-left: 4px solid #F47937; background: #f8f8f8;">
    <div style="font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; color: #666;">
        VITEC · Hero Section — {data.background_variant}
    </div>
    <f:if condition="{data.eyebrow}">
        <div style="font-size: 0.75rem; color: #F47937; margin-top: 0.5rem;">{data.eyebrow}</div>
    </f:if>
    <h3 style="margin: 0.25rem 0;">{data.header}</h3>
    <f:if condition="{data.subheader}">
        <div style="color: #555;">{data.subheader}</div>
    </f:if>
    <f:if condition="{data.cta_label}">
        <div style="margin-top: 0.5rem;">
            <span style="display: inline-block; padding: 0.25rem 0.75rem; background: #F47937; color: white; border-radius: 3px; font-size: 0.8rem;">
                {data.cta_label} →
            </span>
        </div>
    </f:if>
</div>

2.5 Backend-Gruppe "VITEC" registrieren

Damit group: vitec aus der YAML eine saubere Sektion im CE-Wizard wird, muss die Gruppe einmal registriert sein:

Datei: packages/vitec/Configuration/page.tsconfig (deine existierende Datei — aktuell leer)

mod.wizards.newContentElement.wizardItems.vitec {
    header = VITEC
    show = *
    elements {
    }
}

Das reicht — Content Blocks hängt die einzelnen Elemente dann automatisch unter elements ein.


3. Installation & Test

3.1 Caches und TCA flushen

Nach jeder Content-Block-Änderung:

vendor/bin/typo3 cache:flush

Oder im Backend: Admin Tools → Maintenance → Flush all caches.

3.2 Datenbank-Schema updaten

Content Blocks erstellt automatisch neue Spalten (z.B. tx_vitec_hero_section_eyebrow, tx_vitec_hero_section_background_variant etc.):

vendor/bin/typo3 database:updateschema

Oder Backend: Admin Tools → Maintenance → Analyze Database Structure → Run.

3.3 Im Backend anlegen

  1. Seite im Seitenbaum öffnen → Page-Modul
  2. "Neues Inhaltselement" → Tab VITEC → "VITEC · Hero Section"
  3. Felder ausfüllen (Eyebrow, Heading, CTA, Image, Background-Variant "orange")
  4. Speichern

3.4 JSON prüfen

curl -s https://dev.vitec.com/testseite | jq '.content.colPos0[] | select(.type == "vitec_hero_section")'

Erwarteter JSON-Output (durch nb-headless-content-blocks automatisch erzeugt):

{
  "id": 42,
  "type": "vitec_hero_section",
  "colPos": 0,
  "categories": "",
  "appearance": {
    "layout": "default",
    "frameClass": "default",
    "spaceBefore": "",
    "spaceAfter": ""
  },
  "content": {
    "header": "Enterprise Video Solutions",
    "subheader": "Reliable. Scalable. Proven.",
    "bodytext": "<p>VITEC delivers mission-critical video...</p>",
    "tx_vitec_hero_section_eyebrow": "Why VITEC",
    "tx_vitec_hero_section_cta": {
      "href": "/success-stories",
      "target": null,
      "class": null,
      "title": null,
      "linkText": "t3://page?uid=15",
      "additionalAttributes": []
    },
    "tx_vitec_hero_section_cta_label": "Learn more",
    "tx_vitec_hero_section_hero_image": [
      {
        "publicUrl": "https://dev.vitec.com/fileadmin/.../hero.png",
        "properties": { ... }
      }
    ],
    "tx_vitec_hero_section_background_variant": "orange",
    "tx_vitec_hero_section_show_logo_wall": false
  }
}

Struktur passt zu deinem existierenden textpic-Pattern — content.* mit allen Feldern nebeneinander.


4. JSON-Schema aufräumen (Field-Namen kürzen)

Du hast sicher gemerkt: die Feldnamen im JSON sind lang (tx_vitec_hero_section_eyebrow). Das kommt vom prefixFields: true — nötig für DB-Konsistenz, aber unschön fürs Frontend.

Lösung: Via nb-headless-content-blocks EventListener die Keys umbenennen.

4.1 EventListener anlegen

Datei: packages/vitec/Classes/EventListener/NormalizeContentBlockKeys.php

<?php
declare(strict_types=1);

namespace Evomedien\Vitec\EventListener;

use Netzbewegung\NbHeadlessContentBlocks\Event\ModifyArrayRecursiveToArrayEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'vitec/normalize-content-block-keys')]
final class NormalizeContentBlockKeys
{
    public function __invoke(ModifyArrayRecursiveToArrayEvent $event): void
    {
        $key = $event->getKey();

        // Strip prefix "tx_vitec_<element>_" from field names
        // e.g. "tx_vitec_hero_section_eyebrow" → "eyebrow"
        if (preg_match('/^tx_vitec_[a-z_]+?_([a-z_]+)$/', $key, $matches)) {
            $event->setKey($matches[1]);
        }
    }
}

4.2 Services.yaml

Datei: packages/vitec/Configuration/Services.yaml (anlegen falls nicht vorhanden)

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Evomedien\Vitec\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

AsEventListener Attribute + autoconfigure = Event-Listener wird automatisch registriert. Keine weitere Konfiguration nötig.

4.3 Resultat nach Cache-Flush

{
  "type": "vitec_hero_section",
  "content": {
    "header": "Enterprise Video Solutions",
    "eyebrow": "Why VITEC",
    "cta": { ... },
    "cta_label": "Learn more",
    "hero_image": [ ... ],
    "background_variant": "orange",
    "show_logo_wall": false
  }
}

Saubere, lesbare Keys — React-Kollege glücklich.


5. Pattern für weitere Content Blocks

Für jeden neuen Content Block brauchst du:

packages/vitec/ContentBlocks/ContentElements/<name>/
├── config.yaml              ← Felder definieren
├── language/labels.xlf      ← Labels übersetzen
├── assets/icon.svg          ← Icon
└── templates/EditorPreview.html   ← optional

Dann:

vendor/bin/typo3 database:updateschema
vendor/bin/typo3 cache:flush

Fertig. Keine PHP-Boilerplate, keine TypoScript-Overrides, kein TCA-Gefummel.

Template-Vorschlag für deine 20 Figma-Templates

Nummerierung/Naming-Vorschlag an den Figma-Templates entlang:

# Name Content Block
01 Hero vitec/hero-section
02 Feature-Teaser Grid vitec/feature-teaser-grid
03 CTA Banner vitec/cta-banner
04 Quote / Testimonial vitec/testimonial
05 Logo Wall vitec/logo-wall
06 Product Card Grid vitec/product-card-grid
... ... ...
16 Content Page V1 vitec/content-page-v1

Wenn du einen Block gebaut hast, ist jeder weitere 10-20 Minuten Arbeit.


6. Wann brauchst du trotzdem b13/container?

Antwort: Für echte Layout-Wrapper mit nested Content Elements. Beispiele:

  • 2-Spalten-Section: Ein Container, in dessen linker Spalte ein hero-section + rechts ein testimonial liegt
  • Tabs / Accordion: ein Tab-Container mit mehreren Content Blocks je Tab
  • Grid mit freier CE-Wahl: 3-column-grid, wo der Redakteur pro Spalte frei wählt

Für diese Fälle:

  1. b13/container Container registrieren (wie in der vorherigen Anleitung beschrieben, per PHP TCA-Override)
  2. itplusx/headless-container mappt ihn automatisch ins JSON
  3. Die Kinder sind dann Content Blocks → das Pattern spielt sauber zusammen

Laut Doku von nb-headless-content-blocks: "Support for EXT:container" ist eingebaut — die zwei Extensions beißen sich nicht.


7. Advanced: Sammlungen (Collections)

Für wiederkehrende Items (z.B. 3 Teaser-Cards in einem Grid) gibt es den Collection-Type:

name: vitec/feature-teaser-grid
group: vitec
fields:
  - identifier: header
    useExistingField: true

  - identifier: teasers
    type: Collection
    minitems: 1
    maxitems: 6
    fields:
      - identifier: icon
        type: File
        maxitems: 1
        allowed: common-image-types
      - identifier: title
        type: Text
        required: true
      - identifier: description
        type: Textarea
      - identifier: link
        type: Link

Im JSON kommt das dann als Array raus:

{
  "type": "vitec_feature_teaser_grid",
  "content": {
    "header": "Our Solutions",
    "teasers": [
      { "title": "...", "description": "...", "icon": [...], "link": {...} },
      { "title": "...", "description": "...", "icon": [...], "link": {...} }
    ]
  }
}

8. Cheatsheet: Field-Types

YAML type Zweck JSON-Output-Typ
Text Einzeiliger Text string
Textarea Mehrzeilig; mit enableRichtext: true → RTE string (HTML bei RTE)
Number int/float number
Checkbox Boolean boolean
Select renderType: selectSingle Dropdown string (value)
Select renderType: selectMultipleSideBySide Multi-Select string (comma-sep)
Radio Radio-Button-Gruppe string
Link TYPO3-Link (Page/URL/File/Email) object (href, target, linkText…)
File File-Reference array of file-objects
Color Color-Picker string (hex)
DateTime Datum/Zeit string (ISO)
Collection Wiederholbare Feldgruppen array of objects
Category TYPO3-Kategorien array
Relation Referenz zu anderen Records array

9. Fehlerbild-Cheatsheet

Symptom Ursache Fix
Content Block erscheint nicht im CE-Wizard Cache oder Gruppe nicht registriert cache:flush, dann Backend-User-Session refresh
Spalten fehlen in DB (column not found) database:updateschema nicht ausgeführt vendor/bin/typo3 database:updateschema
Feld-Label bleibt englisch/identifier XLF-Key-Konvention falsch Key muss exakt <identifier>.label heißen (ohne Vendor-Prefix)
JSON enthält veraltete Struktur nb-headless-content-blocks nicht geladen oder Site-Set greift nicht TypoScript-Analyzer prüfen
EventListener greift nicht Services.yaml nicht geladen oder autoconfigure: false Services.yaml prüfen, Cache flush
Icon wird nicht angezeigt falsche Position oder SVG-Fehler Pfad muss assets/icon.svg relativ zum CB-Ordner sein
"Type does not exist" nach Rename TCA-Cache veraltet cache:flush --group=system

10. Quickstart

# 1. Aufräumen (einmalig)
# - nb-headless-content-blocks ins Site Set migrieren
# - Version in ext_emconf.php / composer.json angleichen

# 2. Ordnerstruktur für ersten Block
mkdir -p packages/vitec/ContentBlocks/ContentElements/hero-section/{assets,language,templates}

# 3. Files anlegen:
# - config.yaml
# - language/labels.xlf
# - assets/icon.svg
# - templates/EditorPreview.html (optional)

# 4. DB + Cache
vendor/bin/typo3 database:updateschema
vendor/bin/typo3 cache:flush

# 5. Backend: CE anlegen, speichern

# 6. Test
curl -s https://dev.vitec.com/testseite | jq '.content.colPos0'

11. Nächste Schritte

Nachdem der erste Content Block läuft:

  1. EventListener für saubere Keys einbauen (Schritt 4)
  2. Die 20 Templates aus Figma durchgehen → pro Template ein Content Block
  3. Wenn Layouts mit nested CEs gebraucht werden → b13/container dazupacken
  4. Custom Records (tx_vitec_market, _solution, _product, _story) — Content Blocks kann auch Record Types. Siehe YAML reference → RecordTypes
  5. m:n Relationen zwischen Records — via Relation-Field-Type (mit allowed und maxitems)

Sobald der Hero-Section-Block JSON liefert, melden — dann bauen wir zusammen den ersten Record Type (tx_vitec_market) und die bidirektionalen Relationen.


Referenzen