Roadie ist das Frontend-Framework-AddOn für REDAXO. Es liefert ein Komponenten-System, Asset-Management, Icon-System, Media-Pool-Erweiterungen, Backend-Widgets und Utilities — und baut auf WebAwesome als Web-Component-Bibliothek auf.
Die genutzte WebAwesome-Version ist in src/addons/roadie/package.json deklariert und wird über Yarn Workspaces automatisch bereitgestellt.
- Setup
- Asset Management
- Komponenten-System
- Image-Komponente
- Icon-System
- Section & Layout
- Slice Management
- Media Pool Erweiterungen
- ArticleKey
- ArticleUsage – Löschschutz
- Backend-Widgets
- Console Commands
- Utilities
- REDAXO >= 5.20
- PHP >= 8.3
- Node.js + yarn
- Symfony Webpack Encore (
@symfony/webpack-encore)
WebAwesome (@awesome.me/webawesome) wird nicht separat installiert — es ist als Dependency im Roadie-AddOn deklariert (src/addons/roadie/package.json) und wird über den Yarn-Workspace-Mechanismus automatisch ins Root-node_modules hochgezogen.
Die package.json im Projektstamm definiert:
"workspaces": [
"src/addons/*"
]Das bedeutet: Alle AddOns in src/addons/ werden als Yarn Workspaces behandelt. Ihre dependencies landen gemeinsam im Root-node_modules. So kann das Roadie-AddOn eigene npm-Abhängigkeiten mitbringen, ohne dass diese manuell im Projekt eingetragen werden müssen.
yarn installyarn watch # Entwicklung mit File-Watcher
yarn dev-server # Entwicklung mit Webpack Dev Server (HTTPS)
yarn build # Produktions-Build
yarn generate:icon-manifest # Icon-Manifest neu generieren (nach SVG-Änderungen)
yarn generate:component-imports # Wird automatisch von watch/dev-server aufgerufennpm-Alternativen:
npm run watch
npm run dev-server
npm run buildwatch und dev-server rufen roadie:generate-component-imports automatisch vor dem Build auf.
Der Build nutzt Symfony Webpack Encore mit folgenden Entrypoints:
| Entrypoint | Datei | Zweck |
|---|---|---|
app |
assets/app.js |
Frontend — Styles, WebAwesome, Icon-Libraries, SVG-Sprites |
backend |
assets/backend.js |
REDAXO-Backend — Styles, Backend-spezifische WA-Komponenten |
Wichtige Konfiguration:
assets/icons/→public/build/icons/project/(eigene SVG-Icons)node_modules/@material-symbols/svg-300/sharp/→public/build/icons/material-sharp/(Material Icons)assets/svgs/→ SVG-Sprites viasvg-sprite-loader(inline<use>)- Webpack-Warning-Filter für WebAwesome Dynamic Imports (bekanntes False-Positive beim statischen Analysieren des WA-Autoloaders)
import '@awesome.me/webawesome/dist/styles/webawesome.css';
import '@awesome.me/webawesome/dist/styles/native.css';
import './styles/style.scss';
import './roadie-component-imports'; // Auto-generiert
import '@awesome.me/webawesome/dist/translations/de.js';
import { registerIconLibrary, allDefined } from '@awesome.me/webawesome/dist/webawesome.js';
// Icon-Libraries registrieren
registerIconLibrary('default', {
resolver: (name) => `/build/icons/material-sharp/${name}.svg`,
mutator: (svg) => svg.setAttribute('fill', 'currentColor'),
});
registerIconLibrary('project', {
resolver: (name) => `/build/icons/project/${name}.svg`,
mutator: (svg) => svg.setAttribute('fill', 'currentColor'),
});
// Warten bis alle WA-Komponenten im DOM registriert sind
(async () => await allDefined())();roadie-component-imports.js wird automatisch durch roadie:generate-component-imports generiert und enthält nur die tatsächlich genutzten WA-Komponenten. Nicht manuell bearbeiten.
import '@awesome.me/webawesome/dist/styles/webawesome.css';
import './backend/styles/style.scss';
import '@awesome.me/webawesome/dist/translations/de.js';
import { registerIconLibrary } from '@awesome.me/webawesome/dist/webawesome.js';
// Feste Auswahl an WA-Komponenten für das REDAXO-Backend
import '@awesome.me/webawesome/dist/components/button/button.js';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
// … weitere Backend-Komponenten
registerIconLibrary('default', {
resolver: name => `/build/icons/material-sharp/${name}.svg`,
});Backend-Komponenten werden nicht auto-generiert, sondern fest eingetragen — nur was das Backend tatsächlich braucht.
Alles, was Roadie zur Laufzeit benötigt, wird im Project-Addon konfiguriert. Typische boot.php:
use Yakamara\Project\Article\ArticleKey;
use Yakamara\Project\Icons\IconLibrary;
use Yakamara\Roadie\Article\ArticleKeyRegistry;
use Yakamara\Roadie\Asset\AssetResolver;
use Yakamara\Roadie\Component\Image\ImageBreakpointValues;
use Yakamara\Roadie\Component\Template;
use Yakamara\Roadie\Icons\IconRegistry;
use Yakamara\Roadie\Section\SectionManager;
use Yakamara\Roadie\Section\SectionVariant;
use Yakamara\Roadie\Widget\ColorPicker;
$addon = rex_addon::get('project');
// 1. Eigene Komponenten-Templates registrieren
Template::addDirectory($addon->getPath('lib/Component/MyComponent/templates'));
// 2. Icon-System konfigurieren
IconRegistry::setDefaultLibrary('material-sharp');
IconRegistry::setIconsDirectory(rex_path::base('assets/backend/icons'));
IconLibrary::register(); // Aliase registrieren
// 3. Bildbreiten & Breakpoints
ImageBreakpointValues::setValues([
'Sm' => 576,
'Md' => 768,
'Lg' => 1280,
'Xl' => 1440,
]);
// 4. Section-Varianten
SectionManager::registerVariants(
SectionVariant::Plain,
SectionVariant::Neutral,
);
// 5. Artikel-Keys
ArticleKeyRegistry::register(ArticleKey::class, 'project');
// 6. Backend-Assets (nur auf content/edit-Seite)
if (rex::isBackend() && is_object(rex::getUser())) {
if ('content/edit' === rex_be_controller::getCurrentPage()) {
rex_view::addJsFile(rex_addon::get('roadie')->getAssetsUrl('iconpicker.js'));
rex_view::addCssFile(rex_addon::get('roadie')->getAssetsUrl('iconpicker.css'));
rex_view::addJsFile(rex_addon::get('roadie')->getAssetsUrl('colorpicker.js'));
rex_view::addCssFile(rex_addon::get('roadie')->getAssetsUrl('colorpicker.css'));
}
$backendAssets = (new AssetResolver())->getEntrypointFiles('backend');
foreach ($backendAssets['js'] as $url) {
rex_view::addJsFile($url, ['defer' => 'defer']);
}
foreach ($backendAssets['css'] as $url) {
rex_view::addCssFile($url);
}
}AssetResolver liest manifest.json und entrypoints.json aus dem Webpack-Build und liefert korrekte URLs — auch für den Dev-Server.
use Yakamara\Roadie\Asset\AssetResolver;
$resolver = new AssetResolver(); // Default: public/build/ (rex_path::frontend('build'))
$url = $resolver->getAssetUrl('app.js');
$files = $resolver->getEntrypointFiles('backend'); // ['js' => [...], 'css' => [...]]Für Templates gibt es die statische Fassade Asset:
use Yakamara\Roadie\Asset\Asset;
echo Asset::url('fonts/my-font.woff2');
echo Asset::preloadFont('fonts/my-font.woff2'); // <link rel="preload">
echo Asset::scriptTags('app'); // <script>-Tags des Entrypoints
echo Asset::linkTags('app'); // <link rel="stylesheet">-Tags
// SVG inline einbetten (mit Barrierefreiheits-Attributen)
echo Asset::svgInline('icons/logo.svg', label: 'Logo');
echo Asset::svgInline('icons/deco.svg'); // aria-hidden="true"
// SVG-Symbol-Referenz (<svg><use>)
echo Asset::svgSymbol('icon-arrow', label: 'Weiter');svgInline() setzt automatisch role="img" + aria-label wenn ein Label angegeben ist, sonst aria-hidden="true".
Asset::scriptTags() und Asset::linkTags() erzeugen die <script>- und <link>-Tags für den jeweiligen Webpack-Entrypoint. Typische Einbindung im REDAXO-HTML-Template:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?= Asset::linkTags('app') ?>
</head>
<body>
<?= $this->getArticle() ?>
<?= Asset::scriptTags('app') ?>
</body>
</html>Der defer-Wert ist bei scriptTags() bereits eingebaut — die Tags werden mit defer-Attribut ausgegeben.
Komponenten-Klassen liegen in lib/Component/{Name}/{Name}.php, Templates in lib/Component/{Name}/templates/{Name}.php. Alle Komponenten erweitern Component und sind direkt per echo oder in Templates nutzbar.
Template-Verzeichnisse registrieren (in boot.php):
use Yakamara\Roadie\Component\Template;
Template::addDirectory($addon->getPath('lib/Component/MyComponent/templates'));use Yakamara\Roadie\Component\Button\Button;
use Yakamara\Roadie\Component\Button\ButtonAppearance;
use Yakamara\Roadie\Component\Icon\Icon;
echo new Button(
label: 'Mehr erfahren',
href: '/ueber-uns',
appearance: ButtonAppearance::Accent,
);
echo new Button(
label: 'Senden',
start: new Icon(name: 'send'),
);Alle Komponenten akzeptieren ein HtmlAttributes-Objekt für zusätzliche Attribute:
use Yakamara\Roadie\Component\HtmlAttributes;
$attrs = new HtmlAttributes([
'data-tracking' => 'cta',
'class' => 'my-button',
]);
echo new Button(
label: 'Klick',
attributes: $attrs,
);Für zusammengesetzte Inhalte steht Component::slot() zur Verfügung:
echo new Dialog(
content: Component::slot('<p>Inhalt</p>'),
footer: Component::slot(
new Button(label: 'Schließen'),
'footer',
),
);Für bedingtes oder dynamisches Rendering innerhalb von Komponenten:
use Yakamara\Roadie\Component\Html;
$content = new Html(function () use ($items) {
foreach ($items as $item) {
echo new Card(title: $item->title);
}
});| Kategorie | Komponenten |
|---|---|
| Aktion | Button, ButtonGroup, CopyButton |
| Formular | Input, Textarea, NumberInput, Select, Combobox, Listbox, Checkbox, Radio, RadioGroup, Switch, Slider, FileInput |
| Navigation | Breadcrumb, Dropdown, TabGroup, Tree |
| Overlay | Dialog, Drawer, Popup, Popover, Tooltip |
| Anzeige | Card, Callout, Details, Divider, Avatar, Badge, Tag, Scroller |
| Feedback | Spinner, Skeleton, ProgressBar, ProgressRing, Rating |
| Medien | Image, AnimatedImage, Animation, Carousel, Comparison, ZoomableFrame |
| Formatierung | FormatDate, FormatNumber, FormatBytes, RelativeTime |
| Sonstiges | Icon, Link, Page, SplitPanel, QrCode, Sparkline, ColorPicker |
Erzeugt barrierefreie <picture>-Elemente mit responsiven Bildbreiten, Formatkonvertierung und optionaler Bildunterschrift.
use Yakamara\Roadie\Component\Image\Image;
echo new Image(
imageName: 'REX_MEDIA[1]',
mediaManagerType: 'my_type',
);ImageResolution ist ein Enum mit vier Stufen, die jeweils vordefinierte Pixelbreiten abdecken:
| Case | Standardwerte |
|---|---|
ImageResolution::Small |
200, 400, 800 |
ImageResolution::Medium |
1200, 1600 |
ImageResolution::Large |
1920, 2400 |
ImageResolution::All |
alle obigen kombiniert |
use Yakamara\Roadie\Component\Image\ImageResolution;
echo new Image(
imageName: 'REX_MEDIA[1]',
mediaManagerType: 'my_type',
resolutions: ImageResolution::Medium,
sizes: '(max-width: 768px) 100vw, 50vw',
);Alternativ kann ein eigenes Breiten-Array übergeben werden:
resolutions: [400, 800, 1200],Die Standardwerte der Enum-Cases können projektspezifisch überschrieben werden (in project/boot.php):
use Yakamara\Roadie\Component\Image\ImageResolutionValues;
ImageResolutionValues::setValues([
'Small' => [200, 400, 800],
'Medium' => [1200, 1600],
'Large' => [1920, 2400],
]);Die Image-Komponente erzeugt URLs der Form images/{type}/datei@200w.webp. Damit diese funktionieren, sind zwei Voraussetzungen nötig:
1. .htaccess — Rewrite-Regel für responsive Images
Im Projektverzeichnis muss folgende Regel eingetragen sein (der Kommentar markiert den Separator @):
# -roadie- = Separator for responsive images
RewriteRule ^images/([^/]*)/(([^/]*)@?([0-9]*)w?\.(jpeg|jpg|avif|webp|png)) %{ENV:BASE}/index.php?rex_media_type=$1&rex_media_file=$2&roadie=true&%{QUERY_STRING} [B]2. MediaManager — roadie_responsive-Effect
In jedem genutzten Medientyp muss der Effect roadie_responsive (rex_effect_roadie_responsive) als erster Effect eingetragen sein. Er liest Breite (@200w) und Format (.webp) aus dem Dateinamen und überschreibt damit die konfigurierten Effekt-Parameter aller nachfolgenden Effekte zur Laufzeit — nur wenn ?roadie=true im Request gesetzt ist.
Wichtig: Steht der Effect nicht an erster Stelle, greifen die nachfolgenden Resize-/Format-Effekte vor der Anpassung und der responsive Mechanismus funktioniert nicht korrekt.
URL-Schema:
datei@200w.jpg→ Originaldatei in 200 px Breite, JPEGdatei.jpg@200w.webp→ Originaldatei in 200 px Breite, konvertiert zu WebP
Erlaubte Breiten werden über ImageResolutionValues validiert — nur registrierte Werte werden akzeptiert, sonst wird keine Größenanpassung vorgenommen.
3. FocusPoint (empfohlen)
Das AddOn focuspoint (FriendsOfREDAXO) ermöglicht es, am Medium im Backend einen Fokuspunkt zu definieren. Statt des Standard-resize-Effects wird dann focuspoint_fit genutzt: Er schneidet das Bild so zu, dass der markierte Fokuspunkt immer im sichtbaren Bereich bleibt.
Typische Effekt-Reihenfolge in einem Medientyp:
roadie_responsive← muss an erster Stelle stehenfocuspoint_fit(Breite + Höhe, Zoom nach Bedarf) — oderresizewenn kein Zuschnitt nötigimage_format(optional, für AVIF/WebP-Konvertierung)
Viewport-Breakpoints für ImageBreakpointValues in project/boot.php setzen:
use Yakamara\Roadie\Component\Image\ImageBreakpointValues;
ImageBreakpointValues::setValues([
'Sm' => 576,
'Md' => 768,
'Lg' => 1280,
'Xl' => 1440,
]);Keys müssen PascalCase sein — Lowercase wird stillschweigend ignoriert.
ImageMotif definiert ein alternatives Bild ab einem bestimmten Breakpoint. fromBreakpoint erwartet einen ImageBreakpoint-Enum-Case (Xs, Sm, Md, Lg, Xl).
use Yakamara\Roadie\Component\Image\ImageBreakpoint;
use Yakamara\Roadie\Component\Image\ImageMotif;
echo new Image(
imageName: 'REX_MEDIA[1]',
mediaManagerType: 'my_type',
motifs: [
new ImageMotif(
mediaManagerType: 'landscape',
fromBreakpoint: ImageBreakpoint::Md,
),
new ImageMotif(
mediaManagerType: 'wide',
fromBreakpoint: ImageBreakpoint::Lg,
resolutions: ImageResolution::Large, // überschreibt globale resolutions
),
],
);Hinweis:
ImageMotifhat keinimageName-Parameter — der Dateiname kommt immer vomimageNameder übergeordnetenImage-Komponente. Nur dermediaManagerType(Seitenverhältnis, Zuschnitt) wechselt.
echo new Image(
imageName: 'REX_MEDIA[1]',
mediaManagerType: 'my_type',
formats: ['avif', 'webp'], // Fallback: Original-Format
);echo new Image(
imageName: 'REX_MEDIA[1]',
mediaManagerType: 'my_type',
figure: true,
caption: true, // Aus Media-Metadaten
copyright: true, // Aus Media-Metadaten
);Das Icon-System besteht aus drei Schichten: SVG-Dateien auf der Festplatte → Manifest als optimierter Cache → Registry als Laufzeit-API. IconPicker und Icon-Komponente greifen beide auf dieselbe Registry zu.
assets/backend/icons/
├── material-sharp/ ← Default-Library
│ ├── arrow_right.svg
│ └── close.svg
└── project/ ← Weitere Libraries (z. B. eigene SVGs)
├── facebook.svg
└── instagram.svg
Jeder Unterordner ist eine Library. Der Name der Default-Library wird via setDefaultLibrary() konfiguriert.
use Yakamara\Roadie\Icons\IconRegistry;
IconRegistry::setDefaultLibrary('material-sharp');
IconRegistry::setIconsDirectory(rex_path::base('assets/backend/icons'));
IconRegistry::setMetaFile(rex_path::base('assets/backend/icons/meta.json')); // OptionalDas Manifest (var/data/addons/roadie/icons.json) wird aus den SVG-Dateien generiert und von der Registry gecacht. Nach jeder Änderung an SVG-Dateien neu ausführen:
php bin/console roadie:generate-icon-manifestSVGs werden automatisch bereinigt: width/height entfernt, fill auf currentColor normalisiert, Whitespace komprimiert. Vollständige Dokumentation unter Console Commands → roadie:generate-icon-manifest.
Optionale Meta-Datei für Labels und Suchbegriffe im IconPicker:
{
"arrow_right": { "label": "Pfeil rechts", "keywords": ["navigation", "weiter"] },
"project/facebook": { "label": "Facebook", "keywords": ["social"] }
}Keys ohne Library-Präfix gelten für die Default-Library, mit library/name-Präfix für weitere Libraries.
Icons aus Nicht-Default-Libraries können mit einem Alias versehen werden, sodass kein library-Parameter im PHP-Code nötig ist:
IconRegistry::alias('social-facebook', 'facebook', 'project');
// Verwendung: new Icon(name: 'social-facebook') — library wird automatisch aufgelöstZentrale Klasse mit allen genutzten Icon-Namen als Konstanten. Verhindert Hardcoding von Strings, macht Umbenennen trivial und registriert Aliase an einer Stelle.
namespace Yakamara\Project\Icons;
use Yakamara\Roadie\Icons\IconRegistry;
final class IconLibrary
{
// Navigation
public const string CLOSE = 'close';
public const string HAMBURGER = 'menu';
public const string NAV_NEXT = 'chevron_right';
public const string NAV_PREV = 'chevron_left';
// Social (library: 'project' — eigene SVGs)
public const string SOCIAL_FACEBOOK = 'social-facebook';
public const string SOCIAL_INSTAGRAM = 'social-instagram';
public static function register(): void
{
IconRegistry::alias(self::SOCIAL_FACEBOOK, 'facebook', 'project');
IconRegistry::alias(self::SOCIAL_INSTAGRAM, 'instagram', 'project');
}
}Registrierung in project/boot.php:
IconLibrary::register();use Yakamara\Roadie\Component\Icon\Icon;
use Yakamara\Project\Icons\IconLibrary;
// Default-Library
echo new Icon(
name: IconLibrary::CLOSE,
);
// Per Alias — kein library-Parameter nötig
echo new Icon(
name: IconLibrary::SOCIAL_FACEBOOK,
);
// Explizite Library
echo new Icon(
name: 'facebook',
library: 'project',
);
// Mit ARIA-Label (aria-label + role="img")
echo new Icon(
name: 'star',
label: 'Favorit',
);Ohne label wird aria-hidden="true" gesetzt.
Der IconPicker zeigt alle Libraries aus dem Manifest in einem modalen Dialog an. Der gespeicherte Wert ist icon-name (Default-Library) oder library:icon-name.
// Im Modul-Input:
echo IconPicker::widget('REX_INPUT_VALUE[1]', 'REX_VALUE[1]');
// Im Modul-Output — Icon-Komponente löst library:name automatisch auf:
if ($value = 'REX_VALUE[1]') {
echo new Icon(name: $value);
}Weitere Details unter Backend-Widgets → IconPicker.
Section ist ein Platzhalter-basiertes Wrapper-System für Sektionen. Frontend: Platzhalter {{{SECTION_...}}} werden nach dem Rendering durch echte Tags ersetzt. Backend: Platzhalter werden mit roadie-section-Klasse umhüllt.
use Yakamara\Roadie\Section\Section;
$section = new Section(
attributes: [
'class' => 'my-section',
'data-variant' => 'brand',
],
tag: 'section',
innerAttributes: ['class' => 'container'],
);
echo $section->getPlaceholder(); // {{{SECTION_xxxxxx}}} — Öffnender Tag
// ... Slice-Inhalt ...
echo '</div></section>'; // Schließender Tag manuell setzenRegistrierung verfügbarer Varianten (in project/boot.php):
use Yakamara\Roadie\Section\SectionManager;
use Yakamara\Roadie\Section\SectionVariant;
SectionManager::registerVariants(
SectionVariant::Plain,
SectionVariant::Neutral,
);Aktuelle Variante im Slice-Output lesen:
$manager = SectionManager::getInstance()->init();
$variant = $manager->getCurrentVariant(); // SectionVariant::Plain
if ($manager->is(SectionVariant::Neutral)) {
// Neutrale Gestaltung
}SliceManager verwaltet die Überschriften-Hierarchie innerhalb von Artikel-Slices und verhindert doppelte <h1>-Tags.
use Yakamara\Roadie\Slice\SliceManager;
use Yakamara\Roadie\Slice\SliceType;
$manager = SliceManager::getInstance()->init();
$manager->setCurrentSliceType($slice, SliceType::NEW_SECTION);
// Heading-Tag für die aktuelle Tiefe ermitteln
$tag = $manager->getHeadingTag(); // 'h1', 'h2', …
// Heading rendern
echo $manager->renderHeading('Mein Titel');
echo $manager->renderHeading('Mein Titel', ['class' => 'headline']);
// HTML mit angepassten Heading-Levels ausgeben
echo $manager->processHtml($htmlWithHeadings);Slice-Typen:
| Typ | Bedeutung |
|---|---|
NEW_SECTION |
Beginnt einen neuen Abschnitt auf gleicher Ebene |
SUB_SECTION |
Beginnt einen Unterabschnitt (Tiefe +1) |
CONTINUATION |
Fortsetzung des aktuellen Abschnitts (gleiche Tiefe) |
Roadie erweitert den REDAXO Media Pool um sprachspezifische Metadaten-Felder.
Im Medien-Formular werden automatisch folgende Felder ergänzt:
- Alt-Text pro Sprache (
med_alt,med_alt_2, …) - Bildunterschrift pro Sprache (
med_caption,med_caption_2, …) - Copyright
- Dekorativ-Toggle (überspringt Alt-Text-Pflicht)
Yakamara\Roadie\MediaPool\Media ist ein typsicherer Wrapper um rex_media:
use Yakamara\Roadie\MediaPool\Media;
$media = Media::get('image.jpg');
$media->getAlt(); // Sprachspezifisch
$media->getCaption(); // Sprachspezifisch
$media->getCopyright();
$media->isDecorative();
$media->getDimensions(); // [width, height]
$media->getDimensionsByMediaManagerType('my_type'); // [width, height] nach EffektErmöglicht projektweit eindeutige Bezeichner für wichtige Artikel (Impressum, Startseite, Team …), die unabhängig von der Artikel-ID sind. Werte werden in rex_config gespeichert und können per Console-Command oder Backend-UI gepflegt werden.
namespace Yakamara\Project\Article;
enum ArticleKey: string
{
case Imprint = 'imprint';
case Team = 'team';
case Contact = 'contact';
}use Yakamara\Project\Article\ArticleKey;
use Yakamara\Roadie\Article\ArticleKeyRegistry;
ArticleKeyRegistry::register(ArticleKey::class, 'project');Der zweite Parameter ist der rex_config-Namespace (identisch mit dem Addon-Namen).
use Yakamara\Project\Article\ArticleKey;
use Yakamara\Roadie\Article\ArticleResolver;
$article = ArticleResolver::get(ArticleKey::Imprint);
$url = ArticleResolver::getUrl(ArticleKey::Imprint);
$urlDe = ArticleResolver::getUrl(ArticleKey::Imprint, clang: 1);getUrl() gibt '#' zurück, wenn kein Artikel hinterlegt ist.
ArticleResolver::set(ArticleKey::Imprint, articleId: 42);
ArticleResolver::remove(ArticleKey::Imprint);Die Pflegeseite ist im Backend unter System → Roadie → Artikel-Keys erreichbar. Alle registrierten Enums werden dort mit einem Linkmap-Feld angezeigt.
php bin/console roadie:article-key list
php bin/console roadie:article-key set imprint 42
php bin/console roadie:article-key remove imprintSind mehrere Enums mit gleichem Key-Wert registriert, wird ein Fehler mit den betroffenen Namespaces ausgegeben.
Solange ein Artikel einem Key zugewiesen ist, kann er nicht gelöscht werden. Das gilt auch für das Löschen der übergeordneten Kategorie.
Verhindert das Löschen von Artikeln, die noch referenziert werden. Die Prüfung greift auf ART_PRE_DELETED, das sowohl beim direkten Artikel-Löschen als auch beim Löschen einer Kategorie ausgelöst wird.
Roadie registriert automatisch folgende Checker:
| Checker | Prüft |
|---|---|
ArticleSliceUsageChecker |
link1–link10 und linklist1–linklist10 in rex_article_slice |
MetaInfoUsageChecker |
REX_LINK_WIDGET- und REX_LINKLIST_WIDGET-Felder in rex_article |
ArticleKeyUsageChecker |
Alle via ArticleKeyRegistry registrierten Enum-Keys |
use Yakamara\Roadie\ArticleUsage\ArticleUsageChecker;
ArticleUsageChecker::addChecker(static function (int $articleId): array {
$usages = [];
// Prüflogik …
if ($found) {
$usages[] = 'Wird in Komponente X verwendet';
}
return $usages;
});Der Callable erhält die Artikel-ID und gibt eine Liste von menschenlesbaren Verwendungs-Beschreibungen zurück.
Assets werden in project/boot.php auf der content/edit-Seite geladen:
if (rex::isBackend() && 'content/edit' === rex_be_controller::getCurrentPage()) {
rex_view::addJsFile(rex_addon::get('roadie')->getAssetsUrl('iconpicker.js'));
rex_view::addCssFile(rex_addon::get('roadie')->getAssetsUrl('iconpicker.css'));
rex_view::addJsFile(rex_addon::get('roadie')->getAssetsUrl('colorpicker.js'));
rex_view::addCssFile(rex_addon::get('roadie')->getAssetsUrl('colorpicker.css'));
}Ermöglicht die Auswahl eines Icons aus einer oder mehrerer SVG-Libraries.
use Yakamara\Roadie\Widget\IconPicker;
echo IconPicker::widget('REX_INPUT_VALUE[1]', 'REX_VALUE[1]');Gespeicherter Wert: Icon-Name der Default-Library (arrow_right) oder library:name für weitere Libraries.
Moduloutput:
use Yakamara\Roadie\Icons\IconRegistry;
$parsed = IconRegistry::parseValue('REX_VALUE[1]');
$icon = IconRegistry::get($parsed['library'], $parsed['name']);
// $icon['svg'] enthält den bereinigten SVG-MarkupErmöglicht die Auswahl einer vordefinierten Farbe als Key (kein Hex-Wert).
use Yakamara\Roadie\Widget\ColorPicker;
echo ColorPicker::widget('REX_INPUT_VALUE[2]', 'REX_VALUE[2]');Gespeicherter Wert: Farb-Key (z.B. primary, transparent) oder leerer String.
Farben registrieren (in project/boot.php):
ColorPicker::registerGroup(
group: 'brand',
label: 'Markenfarben',
colors: [
'primary' => ['color' => '#003366', 'name' => 'Primärfarbe'],
'secondary' => ['color' => '#668899', 'name' => 'Sekundärfarbe'],
],
);Moduloutput:
$colorKey = ColorPicker::validate('REX_VALUE[2]');Der Farb-Key wird typischerweise als data-*-Attribut ans Element übergeben und per CSS ausgewertet — kein style-Attribut, da CSP keine Inline-Styles erlaubt:
// Ausgabe im Template:
echo '<section data-color="' . rex_escape($colorKey) . '">';
// CSS:
// [data-color="primary"] { --section-color: var(--color-primary); }Ermöglicht die visuelle Auswahl eines Layouts anhand von SVG-Vorschauen. Rendert eine wa-radio-group mit je einem wa-radio appearance="button" pro Option.
use Yakamara\Roadie\Widget\LayoutPicker\LayoutPicker;
use Yakamara\Roadie\Widget\LayoutPicker\LayoutPickerOption;
echo new LayoutPicker(
options: [
new LayoutPickerOption(
value: '1col',
label: '1-spaltig',
svg: LayoutPickerSvg::build([
new LayoutPickerSvgBlock(
col: 1,
span: 12,
),
]),
),
new LayoutPickerOption(
value: '2col',
label: '2-spaltig',
svg: LayoutPickerSvg::build([
new LayoutPickerSvgBlock(
col: 1,
span: 6,
),
new LayoutPickerSvgBlock(
col: 7,
span: 6,
),
]),
),
],
name: 'REX_INPUT_VALUE[3]',
value: 'REX_VALUE[3]',
);Gespeicherter Wert: Der value-String der gewählten Option (z.B. 2col).
SVG-Helfer:
LayoutPickerSvg::build() generiert SVG-Vorschauen auf Basis eines 12-Spalten-Rasters. Das SVG ist immer 100 Einheiten breit, die Höhe ist variabel. Spalten und Blöcke verwenden currentColor und passen sich damit automatisch ans CSS-Farbschema an.
use Yakamara\Roadie\Widget\LayoutPicker\LayoutPickerSvgBlock;
use Yakamara\Roadie\Widget\LayoutPicker\LayoutPickerSvg;
// Vollbreite
LayoutPickerSvg::build([
new LayoutPickerSvgBlock(
col: 1,
span: 12,
),
]);
// Zwei gleiche Spalten
LayoutPickerSvg::build([
new LayoutPickerSvgBlock(
col: 1,
span: 6,
),
new LayoutPickerSvgBlock(
col: 7,
span: 6,
),
]);
// Sidebar-Layout mit Header, größere Höhe
LayoutPickerSvg::build(
blocks: [
new LayoutPickerSvgBlock(
col: 1,
span: 12,
height: 0.2,
),
new LayoutPickerSvgBlock(
col: 1,
span: 3,
height: 0.8,
y: 0.2,
),
new LayoutPickerSvgBlock(
col: 4,
span: 9,
height: 0.8,
y: 0.2,
),
],
height: 48,
);LayoutPickerSvgBlock-Parameter:
| Parameter | Typ | Bedeutung |
|---|---|---|
col |
int |
Startspalte, 1-basiert (1–12) |
span |
int |
Spaltenbreite (1–12) |
height |
float |
Höhe als Anteil der verfügbaren Höhe (0.0–1.0), Standard 1.0 |
y |
float |
Vertikaler Versatz als Anteil (0.0–1.0), Standard 0.0 |
Moduloutput:
$layout = 'REX_VALUE[3]' ?: '1col';Styling: Über .layout-picker, .layout-picker--option und .layout-picker--label. SVG-Größe z.B. via .layout-picker wa-radio svg { width: 4rem; height: auto; }.
Liest alle SVG-Dateien aus dem konfigurierten Icons-Verzeichnis und schreibt das Manifest nach var/data/addons/roadie/icons.json.
php bin/console roadie:generate-icon-manifestVoraussetzung: IconRegistry::setDefaultLibrary() und IconRegistry::setIconsDirectory() sind in project/boot.php gesetzt.
Verzeichnisstruktur: Jeder Unterordner im Icons-Verzeichnis wird als eigene Library behandelt. Der Ordnername entspricht dem Library-Namen. Der über setDefaultLibrary() konfigurierte Ordner wird als Default markiert — Icons daraus werden ohne Library-Präfix gespeichert.
assets/backend/icons/
└── material-sharp/ ← Default-Library ("material-sharp")
├── arrow-right.svg
└── close.svg
SVG-Bereinigung: Jede Datei wird automatisch normalisiert:
- XML-Deklaration, DOCTYPE und Kommentare werden entfernt
<title>und<desc>werden entferntwidth/height-Attribute am<svg>-Tag werden entfernt (Sizing via CSS)fill="*"wird auffill="currentColor"gesetzt (fill="none"bleibt erhalten)- Whitespace wird normalisiert
Optionale Meta-Datei für Labels und Suchbegriffe (via IconRegistry::setMetaFile()):
{
"arrow-right": { "label": "Pfeil rechts", "keywords": ["navigation", "weiter"] },
"material-sharp/close": { "label": "Schließen", "keywords": ["x", "entfernen"] }
}Ohne Meta-Datei wird der Dateiname als Label verwendet (arrow-right → „Arrow Right").
Scannt das gesamte Projekt nach verwendeten <wa-*>-Tags und Roadie-Komponenten und generiert assets/roadie-component-imports.js für den Webpack-Build.
php bin/console roadie:generate-component-importsWird automatisch aufgerufen vor jedem Build via package.json-Scripts (watch, build, dev-server). Die generierte Datei nicht manuell bearbeiten.
Gescannte Verzeichnisse:
src/templates/— REDAXO-Templatessrc/modules/— REDAXO-Modulesrc/addons/project/— Project-Addon (PHP-Klassen, Templates)assets/scripts/components/— Projektspezifische JS-Komponenten
Was generiert wird:
| Quelle | Ergebnis |
|---|---|
<wa-button> in PHP/HTML |
import '@awesome.me/webawesome/dist/components/button/button.js' |
use Yakamara\Roadie\Component\Button\Button |
SCSS aus lib/Component/Button/styles/ + JS aus lib/Component/Button/scripts/ |
JS-Klasse mit static componentName in assets/scripts/components/ |
register(MyComponent) im ComponentRegistry |
Wenn rex_yform im Code erkannt wird, werden zusätzlich alle src/addons/*/ytemplates/roadie/-Verzeichnisse nach <wa-*>-Tags gescannt.
wa-*-Tags ohne entsprechendes Package (nicht in node_modules/@awesome.me/webawesome/dist/components/) werden als Warnung ausgegeben, aber nicht importiert.
Verwaltet die Zuordnung von Artikel-Keys zu REDAXO-Artikeln auf der Kommandozeile.
# Alle gesetzten Keys auflisten
php bin/console roadie:article-key list
# Einen Key setzen
php bin/console roadie:article-key set imprint 42
# Einen Key entfernen
php bin/console roadie:article-key remove imprintSind mehrere Enums mit gleichem Key-Wert registriert, wird ein Fehler mit den betroffenen Namespaces ausgegeben. Weitere Details unter ArticleKey.
Erzeugt eindeutige, ARIA-konforme IDs für id/aria-labelledby-Verknüpfungen:
use Yakamara\Roadie\Util\Aria;
$id = Aria::id(); // z. B. "g4a3f7b2c" (beginnt immer mit einem Buchstaben)
$id = Aria::lastId(); // Letzte generierte ID wiederverwendenSetzt die PHP-Locale anhand der REDAXO-Spracheinstellungen (clang_locale, clang_setlocale):
use Yakamara\Roadie\Util\Locale;
Locale::setDefault();Prüft Dateitypen anhand von Extension und MIME-Typ:
use Yakamara\Roadie\Util\FileTypeDetector;
$detector = new FileTypeDetector('/pfad/zur/datei.jpg');
$detector->exists();
$detector->isRasterImage(); // avif, bmp, gif, jpeg, jpg, png, webp
$detector->isSvg();
$detector->isAudio(); // aac, flac, mp3, ogg, wav, webm
$detector->isVideo(); // avi, mov, mp4, mpeg, ogg, webm
$detector->getMimeType();Hilfstrait für BackedEnum-Klassen:
use Yakamara\Roadie\Trait\EnumToArrayTrait;
enum MyEnum: string {
use EnumToArrayTrait;
case Foo = 'foo';
case Bar = 'bar';
}
MyEnum::values(); // ['foo', 'bar']
MyEnum::names(); // ['Foo', 'Bar']
MyEnum::arrayByValues(); // ['foo' => <MyEnum::Foo>, 'bar' => <MyEnum::Bar>]
MyEnum::arrayByNames(); // ['Foo' => <MyEnum::Foo>, 'Bar' => <MyEnum::Bar>]