Die Aufgabe eines Renderer ist es, eine Ausgabe für eine Komponente zu erzeugen. In den meisten Fällen erzeugt ein Renderer HTML-Code für die Webseite. In Spezialfällen können aber auch anderer Formate z.B. für E-Mail Inhalte erzeugt werden.
Die weitere Beschreibung bezieht sich auf Renderer die HTML-Code erzeugen solle, da dies der Haupt-Anwendungsfall für Renderer ist. Die generelle Funktionsweise der Renderer lässt sich hierüber ableiten.
Renderer und die Konfiguration von Renderern können komplexe Formen annehmen. Dies ist notwendig, um die Wiederverwertbarkeit und Updatefähigkeit der Renderer sicher zu stellen. Dadurch soll erreicht werden, dass für gleiche Anforderungen, aber unterschiedlichem Designs nicht immer neue Renderer implementiert werden müssen.
Renderer sollen keine Business-Logik enthalten. Diese sollte ausschließlich in den Komponenten und dessen Controllern liegen. Dem Renderer wird ein Model übergeben, in denen die dynamischen Daten enthalten sind. Mit der Renderer-Logik, die die HTML-Struktur definiert und den Model-Daten wird der HTML-Code erzeugt.
Jeder Renderer durchläuft verschiedene Phasen, die in Form von Methoden im Renderer implementiert werden können.
-
initState()
- Hier werden die States des Renderers initialisiert. Siehe Zustands-spezifische Konfiguration von CSS-Klassen. -
initRender()
- Hier werden die HTMLElemente des Renderers initialisiert. Siehe Konfiguration von Html-Elementen. -
beforeRender()
- Diese Methode wird vor derrender()
-Methode aufgerufen. Sie kann dazu verwenden werden, einen bestehenden Renderer zu erweitern (über Vererbung). - (
beforeRenderComponent()
) - Diese Methoden existiert nur für Container-Renderer und wird vor dem rendern jeder Unterkomponente aufgerufen. Sie kann dazu verwenden werden, einen bestehenden Renderer zu erweitern (über Vererbung). -
render()
- Mit dieser Methode werden die Daten herausgeschrieben. Zu beachten ist, dass diese Methode nicht für einen Container-Renderer aufgerufen wird. - (
afterRenderComponent()
) - Diese Methoden existiert nur für Container-Renderer und wird nach dem rendern jeder Unterkomponente aufgerufen. Sie kann dazu verwenden werden, einen bestehenden Renderer zu erweitern (über Vererbung). -
afterRender()
- Diese Methode wird nach derrender()
-Methode aufgerufen und kann dazu verwenden werden, einen bestehenden Renderer zu erweitern (über Vererbung).
Im einfachsten Fall besteht ein Renderer nur aus einer render()
-Methode, die die gewünschte Ausgabe erzeugt.
<?php
class HelloWorld extends \SP\SiteKit\Renderer\Renderer {
public function render() {
echo "<strong>Hello World</strong>";
}
}
oder
<?php
namespace SP\Example\Renderer;
class HelloWorld extends \SP\SiteKit\Renderer\Renderer {
public function render() { ?>
<strong>Hello World</strong>
<?php }
}
Dem Renderer steht (von der Komponenten bereit gestellt) ein Model zur Verfügung, in dem dynamische Daten enthalten sein können. Das Model für unseren HelloWord-Renderer können z.B. so aussehen:
<?php
namespace \SP\Example\Model;
class HelloWord extends \SP\SiteKit\Model\Model {
private $text;
public function getText() {
return $this->text;
}
public function setText($text) {
return $this->text = text;
}
}
Über die getModel()
-Methode des Renders wird das Model zurückgeliefert und kann verwendet werden.
<?php
class HelloWorld extends \SP\SiteKit\Renderer\Renderer {
public function render() {
echo "<strong>" . htmlspecialchars($this->getModel()->getText()) . "</strong>";
}
}
Für Renderer innerhalb eines Kunden-Moduls ist dieser Weg geeignet, um die Anforderungen des Renderers schnell zu implementieren. Renderer innerhalb des SiteKit sind aber dazu ausgelegt verschiedene Kunden-Anforderungen zu erfüllen und müssen daher flexibler sein. Die Anpassungen des Renderers werden über Konfigurationen erreicht.
Generelle Konfiguration
Für Renderer können Konfigurationen definiert werden. Dazu wird für jeden Renderer ein Type definiert.
namespace SP\Example\Renderer;
class HelloWorld extends \SP\SiteKit\Renderer\Renderer {
const TYPE = 'example.helloWorld';
public function render() { ... }
}
Das dem Type wird der Konfigurations-Schlüssel des Renderers ermittelt. In diesem Fall wäre das renderer.example.helloWord
.
Die Konfiguration des Renderers kann dann in einer Datei config/renderer/example/helloWord.php
abgelegt werden.
Existiert für den Renderer eine Vererbungs-Hierarchie wird diese berücksichtigt. Dabei werden zunächst die Konfigurationen der geerbten Klassen geladen auf aufeinander gelegt. Damit ergänzen und überschreiben sich die einzelnen Konfigurationen zu einer resultierenden Konfiguration des Renderers.
Für die Renderer steht eine Methode $this->getConfig()
zur Verfügung, mit der die Daten abgefragt werden können.
Beispiel-Konfiguration config/renderer/example/helloWord.php
:
return [
'renderHeadline' => false
];
Konfiguration verwenden:
public function initRender() {
if ($this->getConfig()->opt('renderHeadline', true)) {
...
}
}
Der Konfigurations-Wert kann mit opt($key, $defaultValue)
oder mit get($key)
abgefragt werden. Wobei die get
-Methode eine
Exception auslöst, wenn der Konfigurations-Wert nicht existiert.
Konfiguration von Html-Elementen
Die Renderer-Konfiguration wird auch dazu verwendet, einzelnen HTML-Element zu konfigurieren. Z.B. um über die Konfiguration die CSS-Klassen oder den Tag-Namen des Elementes zu bestimmen. Um dies zu unterstützen wird das HtmlElement verwendet.
Ein HtmlElement
Objekt ist ähnlich wie ein DOM-Element und dient dazu ein einzelnes HTML-Element zu rendern.
$el = new HtmlElement('div');
$el->addClass('SP-Foo');
$el->setBodyEscaped('Hello World');
$el->render();
// oder
$el->renderOpen();
$el->renderBody();
$el->renderClose();
Der Renderer stellt eine Factory-Methode createElementFor($tagName, $parts)
zur Verfügung über die HtmlElement-Objekte erzeugt werden können.
Mit dieser Methode wird ein HtmlElement erstellt und dessen Konfiguration darauf angewendet.
private $container;
private $text;
public function initRender() {
$this->container = $this->createElementFor('div', 'base');
$this->text = $this->createElementFor('span', 'text');
$this->text->setBodyEscape($this->getModel()->getText());
}
public function render() {
$this->container->renderOpen();
$this->text->render();
$this->container->renderClose();
}
Die CSS-Klassen, Tag-Namen und Attribute können nun über die Konfiguration angepasst werden.
return [
'classes' => [
'base' => ['SP-Container', 'SP-Container--example']
],
'attributes' => [
'base' => [
'data-example' => [
'a' => 'A'
]
]
],
'tagName' => [
'base' => 'span'
]
];
Das gerenderte HTML würde dann so aussehen:
<span class="SP-Container SP-Container--example" data-example="{"a":"A"}">
<span>Mein Text</span>
</span>
Zustands- spezifische Konfiguration
Manchmal ist zustands- spezifisch eine andere CSS-Klasse notwendig. Um zustands- spezifisch CSS-Klasse zu ermöglichen, muss der Renderer states setzen, die in der Konfiguration ausgewertet werden können.
private $container;
public function initState() {
$this->setState('alignment', $this->getModel()->getAlign()); // es könnte z.B. 'left' oder 'right' gesetzt werden
}
public function initRender() {
$this->container = $this->createElementFor('div', 'base');
}
public function render() {
$this->container->renderOpen();
...
$this->container->renderClose();
}
Mit Hilfe der States können nun die CSS-Klassen der HtmlElemente zustands- spezifisch angepasst werden.
return [
...
'stateClasses' => [
'alignment' => [
'left' => [
'base' => 'SP-Container--left'
],
'right' => [
'base' => 'SP-Container--right'
]
]
]
];
Alternativ lässt sich in dem obigen Fall das auch mit einem Wildcard definieren, in dem mit dem Platzhalter ${state}
der korrekte Wert gesetzt wird.
return [
...
'stateClasses' => [
'alignment' => [
'*' => [
'base' => 'SP-Container--${state}'
]
]
]
];
Weiter können auch zustandsspezifische Attribute konfiguriert werden.
return [
...
'stateAttributes' => [
'style' => [
'default' => [
'list' => [
'data-sp-cards' => [
'options' => [
'cols' => 1,
'aspect' => 0.3333333333333333
],
...
]
]
]
]
]
];
Und auch Styles können zustandsspezifisch konfiguriert werden.
return [
'stateStyles' => [
'color' => [
'*' => [
'content' => [
'background-color' => '${state}'
]
]
]
],
];
Zustands- spezifische Konfiguration von Attributen
Manchmal ist zustands- spezifisch eine andere CSS-Klasse notwendig. Um zustands- spezifisch CSS-Klasse zu ermöglichen, muss der Renderer states setzen, die in der Konfiguration ausgewertet werden können.
Mit Hilfe der States können nun die CSS-Klassen der HtmlElemente zustands- spezifisch angepasst werden.
return [
...
'stateClasses' => [
'alignment' => [
'left' => [
'base' => 'SP-Container--left'
],
'right' => [
'base' => 'SP-Container--right'
]
]
]
];
Alternativ lässt sich in dem obigen Fall das auch mit einem Wildcard definieren, in dem mit dem Platzhalter ${state}
der korrekte Wert gesetzt wird.
return [
...
'stateClasses' => [
'alignment' => [
'*' => [
'base' => 'SP-Container--${state}'
]
]
]
];
Konfiguration von Icon-Elementen
Icon-Elemente sind spezialisierte Html-Elemente mit zusätzlichen Konfigurationsmöglichkeiten. Ein Icon-Element wird über die Methode
$this->createIconElementFor($usage, $part)
erzeugt. Mit $usage
wird der Icon-Typ angegeben. $part
gibt den Konfigurations-Schlüssel an,
über den das Element wie bei einem normalen HtmlElement konfiguriert werden kann.
Für Icons kann in der Konfiguration ein Mapping definiert werden, wenn der im Renderer definierte Type ($usage
) für den konkreten Fall nicht
richtig ist.
Verwendung im Renderer:
private $icon;
public function initRender() {
$this->icon = $this->createIconElementFor('circle', 'myIcon');
}
public function render() {
$this->icon->render();
}
Icon-Konfiguration:
return [
'classes' => [
'myIcon' => 'SP-MyIcon'
]
'icon' => [
'mapping' => [
'circle' => 'rectangle'
]
]
];
gerendert wird dann folgends HTML
<svg class="SPi SPi-rectangle SP-MyIcon" aria-hidden="true" role="img">
<use xlink:href="#SPi-rectangle"></use>
</svg>
Generell gültige Mappings können auch in dem Konfigurations-Schlüssel renderer.html.arvedui.html.helper.icon.mapping
definiert werden.
Mehrsprachigkeit
Wenn Renderer eigene Wörter/Texte definieren, muss dafür gesorgt werden, dass diese in mehreren Sprachen unterstützt werden. Dazu muss in der Renderer-Konfiguration das zu verwendende Sprachpaket angegeben werden.
Renderer-Konfiguration
return [
'translationPackage' => 'example.helloWorld'
];
Sprachdefinitionen werden in JSON unterhalb des lang
-Verzeichnisses definiert.
Für die deutsche Sprache würde die Sprach-Datei lang/de/example/helloWord.json
verwendet werden. In dieser Sprach-Datei können beliebig
strukturiert die Sprachdefinitionen eingetragen werden.
{
"example": {
"content" : "Hallo mein Name ist {lastname}... {firstname} {lastname}"
}
}
Texte, bei denen noch Werte eingefügt werden sollen, können mit benannten Platzhalten {name}
versehen werden.
Der Renderer kann auf die Sprachdefinition zugreifen und sie verwenden.
public function initRender() {
$headline->setBodyEscaped($this->translate('example.content', [
'firstname' => 'James',
'lastname' => 'Bond'
]));
}
URL-Rewriting
Alle URL’s die ein Renderer herausschreibt müssen über die Methoden $this->rewriteLink($url)
oder $this->rewriteResource($url)
je nachdem
ob die URL als Link oder als eine HTML-Resource wie eine Java-Script-, CSS-Datei oder eine URL für einen Ajax-Request verwendet wird.
Notwendig ist dass, da Microsites unterhalb der Domain der Haupt-Webseite oder unter einer eigenen Domain laufen können. Damit die Website in beiden Fällen korrekt funktioniert müssen unter Umständen URL’s angepasst werden. Dies kann über eine zentrale Stelle erfolgen (dem URL-Rewriter).
Container-Renderer
Die Aufgabe eines Renderer ist es, eine Ausgabe für eine Komponente zu erzeugen. Wenn die Komponente eine Container-Komponente ist, die weitere Komponenten enthält, muss auch der Renderer ein Container-Renderer sein.
In diesem Fall wird die render()
-Methode des Renderers nicht ausgeführt. Für den Container-Renderer stehen die Methoden
-
beforeRender()
-
beforeRenderComponent()
-
afterRenderComponent()
-
afterRender()
zur Verfügung, um das Umgebene HTML des Containers zu erzeugen. Die in der Container-Komponente, enthaltenen Komponenten werden dann durch ihre eigenen Renderer gerendert.
Über den Container-Renderer können die Renderer der Unter-Komponenten Container-spezifisch konfiguriert werden. Dazu wird der
Konfigurations-Schlüssel itemConfig
verwendet.
<?php
return [
'classes' => [
'base' => 'SP-Section'
],
'itemConfig' => [
'*' => [
'renderHeadline' => false
],
'code' => [
'classes' => [
'base' => ['SP-Code', 'SP-Section__item']
]
],
'teaserList' => [
'classes' => [
'base' => ['SP-TeaserList', 'SP-Section__item']
],
'itemConfig' => [
'teaser' => [
'renderKicker' => false
]
]
]
]
];
Unterhalb von itemConfig
wird der Komponenten-Typ als Schlüssel verwendet, um die Renderer-Konfigurationen dieser Komponente zu
ermitteln. Es ist möglich ein Wildcard (*
) zu verwenden, um die Renderer-Konfiguration auf alle Komponenten anzuwenden.
Bei dem Komponenten-Typ ist Vorsicht geboten was die Präfixe betrifft: einige der Basis-Komponenten müssen ohne Präfix
angesprochen werden, z.B. “section” statt “content.section”.
Partial-Renderer
Nicht nur Komponenten können verschachtelt sind, sondern auch Models. Mit Hilfe eines Partial-Renderer ist es möglich innerhalb eines Renderers für ein bestimmtes Model einen anderen Renderer aufzurufen. Dazu wird die Methode
$this->renderPartial(string, \SP\SiteKit\Model\Model)
verwendet.
<?php
class HelloWorld extends \SP\SiteKit\Renderer\Renderer {
public function render() {
$this->renderPartial('mylink', $this->getModel()->getSomeLink());
}
}
Der renderPartial()
-Methode wird ein Schlüssel-Wert übergeben, mit der der Partial-Renderer konfiguriert werden kann. Als zweites
Argument wird das Model mit dem gerendert werden soll übergeben.
Über den Schlüssel-Wert (hier mylink
) muss mit type
der Typ des Renderers angegeben werden, der verwendet werden soll. Alle weiteren
Konfigurationen werden dem Partial-Rendere übergeben und überschreiben bestehende Konfigurationen des Renderers für diesen Fall.
Konfiguration des Partial-Renderers
return [
'mylink' => [
'type' => 'content.link.link'
'classes' => [
'base' = 'SP-SpecialLink'
]
]
];
Delegator-Renderer
Ein Delegator-Renderer wird benötigt, wenn eine Liste von Models mit einem Partial-Renderer gerendert und dabei je noch Model ein anderer Renderer verwendet werden soll.
Beispiel hierfür ist ein LinkList-Model (\SP\SiteKit\Model\Content\LinkList
). Dieses Model enthält eine Liste von Link-Models
(\SP\SiteKit\Model\Content\Link\Link
). Hierbei kann es aber unterschiedliche Ausprägungen der Links geben. Z.B. ein Download-Link,
ein Email-Link, usw. Diese verschiedenen Link-Models müssen von verschiednene Renderern gerendert werden. Ein Delegator-Renderer
verwendet eine Konfiguration, um für ein bestimmtes Model den richtigen Renderer zu ermitteln.
Auszug aus dem LinkList-Renderer:
foreach ($this->getModel()->getItems() as $item) {
$this->item->renderOpen();
$this->renderPartial("link", $item);
$this->item->renderClose();
}
Für den Partial-Renderer wird über die Konfiguration ein Delegator-Renderer definiert.
config/renderer/html/arvedui/content/linkList.php
'link' => [
'type' => 'content.linkDelegator'
]
In der Konfiguration des Delegator-Renderer wird dann das Mapping zwischen Model und Renderer definiert.
config/renderer/html/arvedui/content/linkDelegator.php
return [
'delegates' => [
'link' => [
'modelType' => 'content.link.link',
'order' => 1
],
'download' => [
'modelType' => 'content.link.download',
'order' => 1
],
...
],
'link' => [
'type' => 'content.link.link'
],
'download' => [
'type' => 'content.link.download'
],
...
];
Unterhalb des Konfigurations-Schlüssels delegates
, werden die Model-Typen definiert, die delegiert werden können.
Der Delegator geht die Liste durch (sortiert nach dem order
-Wert) und prüft ob das Model einen entsprechenden
Model-Typ hat (Vererbung wird hierbei berücksichtigt). Die Konfigurations-Schlüssel des ermittelten Models (hier link
oder download
)
wird als Konfigurations-Schlüssel verwendet um damit einen Partial-Renderer aufzurufen.
Ist das Model Beispielsweise ein \SP\SiteKit\Model\Content\Link\Download-Model ist der ermittelte Konfigurations-Schlüssel download
.
Intern erfolgt das ein Partial-Renderer-Aufruf wie $this->renderPartial('download', $model)
. Ab hier greift die Partial-Renderer-Technik,
wobei über dem Konfigurations-Schüssel der Renderer-Type und ggf. weitere Konfiguration ermittelt werden um den Renderer aufzurufen.
Renderer-Helper
Renderer-Helper werden eingesetzt, wenn gleiche Funktionalitäten von verschiedenen Renderern genutzt werden sollen.
Hierbei werden dem Renderer-Helper üblicherweise HTMLElemente übergeben, die mit Helper-spezifischen Konfigurationen angereichert werden.
namespace SP\Example\Renderer\Helper\MyHelper;
class MyHelper extends \SP\SiteKit\Renderer\RendererHelper {
const TYPE = 'example.myHelper';
/** @var HtmlElement */
protected $container;
/** @param HtmlElement $container */
public function setContainer($container) {
$this->container = $container;
}
public function process() {
if ($this->container !== null) {
$this->container->addClasses($this->getClassesFor('base'));
}
}
}
Innerhalb eines Renderers kann der Helper dann verwendet werden
protected $container;
public function initRenderer() {
$this->container = $this->createElementFor($containerTag, 'base');
$helper = $this->createHelper('example.myHelper', 'myConfig');
$helper->setContainer($this->container);
$helper->process();
}
Der Helper wird über den Namen myHelper
identifiziert. Dazu muss ein Renderer-Helper-Mapping definiert werden über das bestimmt wird,
welche Klasse für diesen Helper verwendet werden soll.
config/componentModel/html.php
'rendererHelperMapping' => [
'example.*' => 'SP\Example\Renderer\Helper'
]
Über diese Konfiguration kann der vollständige Klassen-Name des Helpers emmittelt werden.
Renderer-Helper können, wie normale Renderer eine Konfiguration habe. Beim Verwendung des Helpers in einem anderen Renderer kann die Konfiguration ergänzt und überschrieben werden.
Im obigen Fall würde die ergänzende Konfiguration des Helpers unterhalb von myConfig
eingetragen werden.
Renderer-Konfiguration des Renderers, der den Helper verwendet.
return [
...
'myConfig' => [
'classes' => [
'base' => 'SP-MyClass'
]
]
];
Renderer Helper können nicht nur bestehende HtmlElemente anreichern, sondern auch selber Renderern. In dem Fall muss noch die Render-Methode implementiert werden.
namespace SP\Example\Renderer\Helper\MyHelper;
class MyHelper extends RendererHelper {
const TYPE = 'example.myHelper';
/** @var HtmlElement */
protected $container;
/** @var HtmlElement */
protected $el;
/** @param HtmlElement $container */
public function setContainer($container) {
$this->container = $container;
}
public function process() {
if ($this->container !== null) {
$this->container->addClasses($this->getClassesFor('base'));
}
$this->el = $this->createElementFor('div', 'el');
}
public function render() {
$this->el->render();
}
}
Innerhalb des Renderers kann die Methode dann aufgerufen werden.
protected $container;
protected $helper;
public function initRenderer() {
$this->container = $this->createElementFor($containerTag, 'base');
$this->helper = $this->createHelper('example.myHelper', 'myConfig');
$this->helper->setContainer($this->container);
$this->helper->process();
}
public function render() {
$this->container->renderOpen();
$this->helper->render();
$this->container->renderClose();
}
Hinweis:
Einige Helper-Klassen enthalten nur statische Methoden. Diese dienen auch dazu wiederkehrende Funktionalität für verschiedene Renderer bereitzustellen. Daher werden auch diese Klassen als Renderer-Helper bezeichnet.
Event-System
Es gibt die Möglichkeit in Renderern ein (eigenes) Event-System zu nutzen. Ein Beispiel für den Einsatz:
Event registrieren in config/renderer/events.php:
return [
'myEvent' => \SP\SiteKit\Renderer\Events\MyEvent::class
];
Listener hinzufügen:
// neue Renderer Phase; nach initState und vor initRender
public function initListeners() {
$this->getRendererContext()->getEventBus()->addListener(
'myEvent',
function($event) {
$this->handleMyEvent($event);
});
}
Event feuern:
public function initRender() {
$this->getRendererContext()->getEventBus()->fireEvent(
'myEvent',
new \SP\SiteKit\Renderer\Event\MyEvent("foo", "bar"));
}