Portlets
Mit JTL-Shop 5.0 wurde eine neue Möglichkeit geschaffen, Seiten des Onlineshops "live im Frontend" mit diversen Inhaltselementen anzureichern. Diese Inhaltselemente sind die sogenannten "Portlets". Sie können dem Onlineshop auch via Plugins hinzugefügt werden und im Anschluss über den "OnPage Composer" ("OPC") auf einer beliebigen Seite des Onlineshops platziert werden.
In einem Plugin kann sich ein Portlet-Verzeichnis befinden, welches folgende Dateien enthalten kann:
Dateiname | Notwendigkeit | Inhalt |
---|---|---|
[Portlet-Class-Name].php |
oblig. | Die PHP-Klasse des Portlets |
[Portlet-Class-Name].tpl |
optional | Template für die Darstellung des Portlets |
[Portlet-Class-Name].css |
optional | Stylesheet des Portlets in der finalen Ansicht |
preview.css |
optional | Stylesheet für die Anzeige des Portlets im Editor |
icon.svg |
optional | Icon für die Portlet-Palette im Editor |
editor_init.js |
optional | Javascript, welches beim Initialisieren des Editors ausgeführt wird |
configpanel.tpl |
optional | Template für ein benutzerdefiniertes Einstellungs-Modal |
Die Datei [Portlet-Class-Name].php
beinhaltet die Portlet-Klasse und bildet somit das Herzstück des Portlets.
Der Klassenname und der Name der Datei müssen selbstverständlich gleich lauten und die Klasse muss sich zudem im
Namensraum Plugin\[Plugin-ID]\Portlets\[Portlet-Class-Name]
befinden.
Weiterhin wird diese Klasse von JTL\OPC\Portlet
abgeleitet.
Beispiel:
<?php declare(strict_types=1);
namespace Plugin\jtl_test\Portlets\MyPortlet;
use JTL\OPC\Portlet;
class MyPortlet extends Portlet
{
// ...
}
Die Portlet-Klasse
Durch das Überschreiben von Methoden der Elternklasse ist es möglich, ein eigenes Portlet zu definieren. Folgende Methoden sind hierfür prädestiniert:
Methode | Verwendung |
---|---|
getPropertyDesc(): array »» |
Definieren der Portlet-Einstellungen |
getPropertyTabs(): array »» |
Definieren der Reiter im Config-Modal des Portlets |
getButtonHtml(): string »» |
Festlegen der Darstellung des Portlet-Buttons |
getPreviewHtml(PortletInstance $instance): string »» |
Festlegen des Vorschau-Markups im OPC-Editor |
getFinalHtml(PortletInstance $instance): string »» |
Festlegen der finalen Darstellung des Portlets |
getConfigPanelHtml(PortletInstance $instance): string »» |
Ändern der Darstellung der Portlet-Konfiguration |
Überschreibbare Methoden
getPropertyDesc()
Diese Methode definiert die einstellbaren Eigenschaften des Portlets und wie sie im Einstellungs-Dialog dargestellt werden.
Jede Einstellung ("Property") wird durch den Schlüssel (Property-ID) des assoziativen Arrays dargestellt, welches diese Methode zurückgibt.
Jede Property wird wiederum durch ein assoziatives Array beschrieben. Folgende Felder sind für alle Typen verfügbar:
- label: Bezeichnung im Config-Modal
- type: Property-Typ
- default: Vorbelegungs-Wert
- width: Breite, die das Input-Felds im Config-Modal einnimmt in % (default: 100)
Beispiel:
/**
* @return array
*/
public function getPropertyDesc(): array
{
return [
'some-text' => [
'label' => __('a text'),
'type' => 'text',
'width' => 30,
'default' => __('Hello world!'),
],
'type-select' => [
'label' => __('Alert Type'),
'type' => 'select',
'options' => [
'success' => __('Success'),
'info' => __('Info'),
'warning' => __('Warning'),
'danger' => __('Danger'),
],
'default' => 'info',
],
];
}
Property-Typen
Typ | Bedeutung, ggf. Optionen für diesen Property-Type |
---|---|
InputType::SELECT | Eine Select-Box mit verschiedenen Optionen "options" - Auswahlmöglichkeiten, assoz. Array (Wert => Anzeigename) |
InputType::RADIO | Eine Radio-Button Gruppe mit verschiedenen Optionen "options" - Auswahlmöglichkeiten, assoz. Array (Wert => Anzeigename) |
InputType::[TEXT|EMAIL|PASSWORD|NUMBER|DATE|TIME] | Einfache Eigenschaften diverser Typen |
InputType::CHECKBOX | Checkbox, setzt ein boolesches Flag |
InputType::COLOR | Ein RGB-Farbwert, konfigurierbar mit Color-Picker |
InputType::IMAGE | Stellt einen Bild-Uploader zur Verfügung und gibt die Bild-URL |
InputType::VIDEO | Stellt einen Video-Uploader zur Verfügung und wählt eine URL |
InputType::TEXT_LIST | Liste von Strings |
InputType::IMAGE_SET | Liste von Bildern (z. B. für Galerie oder Slider Portlets) |
InputType::ICON | Auswahl eines FontAwesome Icons |
InputType::HIDDEN | Verstecktes Input |
InputType::HINT | Hinweis |
getPropertyTabs()
Standardmäßig werden alle Properties des Portlets in einem einzelnen Tab dargestellt. Möchte man die Properties stattdessen in mehrere separate Tabs aufteilen, kann diese Methode überschrieben werden.
Die Methode gibt ein assoziatives Array zurück, mit dem die Properties des Config-Modals in verschiedene Reiter einsortiert werden. Die gewünschte Reiterbeschriftung legt man über die Array-Schlüssel fest.
Neben einer expliziten Aufzählung benutzerdefinierter Properties können mit den Strings 'styles'
oder
'animations'
auch die mitgelieferten Eigenschaften in jeweils einem dedizierten Reiter bereitgestellt werden.
Mögliche Werte für die Reiter sind:
[<Property-ID 1>, <Property-ID 2>, ...]
- ein Array von Property-IDs, die diesem Reiter angehören'styles'
- fügt dem Portlet die mitgelieferten Eigenschaften für Styling hinzu und zeigt sie in diesem Reiter an'animations'
- fügt dem Portlet die mitgelieferten Eigenschaften für Animationen hinzu und zeigt sie in diesem Reiter an
In getPropertyDesc()
aufgeführte, aber nicht zugeordnete Properties werden automatisch dem
Standard-Reiter "Allgemein" zugewiesen.
Beispiel:
/**
* @return array
*/
public function getPropertyTabs(): array
{
return [
'Icon' => [
'use-icon',
],
__('Styles') => 'styles',
__('Animation') => 'animations',
];
}
getButtonHtml()
Diese Methode verändert die Darstellung des in der Palette gezeigten Portlet-Buttons.
Beispiel:
/**
* @return string
*/
public function getButtonHtml(): string
{
return $this->getFontAwesomeButtonHtml('fas fa-film');
}
Im o. g. Beispiel wird ein Icon aus der FontAwesome-Familie gerendert anstatt der icon.svg
.
getPreviewHtml(PortletInstance $instance)
Diese Methode bestimmt die Darstellung des Portlets im OPC.
Es handelt sich hierbei noch nicht um die fertige Darstellung auf der Seite des Onlineshops!
Siehe dazu: getFinalHtml(PortletInstance $instance)
.
Beispiel:
/**
* @param PortletInstance $instance
* @return string
*/
public function getPreviewHtml(PortletInstance $instance): string
{
return $this->getHtml($instance, true);
}
getFinalHtml(PortletInstance $instance)
Diese Methode legt die Ausgabe für die finale Darstellung des Portlets fest.
Beispiel:
/**
* @param PortletInstance $instance
* @return string
*/
public function getFinalHtml(PortletInstance $instance): string
{
return $this->getHtml($instance);
}
getConfigPanelHtml(PortletInstance $instance)
Die Konfiguration eines Portlets erfolgt im Portlet-Config-Modal.
Die Darstellung dieses Modals wird vom Inhalt der Datei configpanel.tpl
bestimmt, welche sich im Portlet-Verzeichnis
befinden kann.
Diese Methode liefert diesen Inhalt aus und kann ihn durch Überschreiben natürlich modifizieren.
Beispiel:
/**
* @param PortletInstance $instance
* @return string
* @throws \Exception
*/
public function getConfigPanelHtml(PortletInstance $instance): string
{
return $this->getConfigPanelHtmlFromTpl($instance);
}
Portlet-Templates schreiben
Portlet-Templates sind für die Darstellung eines Portlets verantwortlich.
Standardmäßig wird die Smarty-Templatedatei <Portlet-Class>.tpl
aus dem Portlet-Ordner geladen und gerendert,
und zwar sowohl für die OPC-Editor-Ansicht als auch für die finale Ansicht.
Im Template-Kontext sind folgende Smarty-Variablen definiert:
$instance
- Die PortletInstance$portlet
- Das Portlet$isPreview
- Ein Flag für:true
= "aktuell in Editor-Ansicht",false
= "aktuell in finaler Ansicht"
Das gerenderte Markup sollte nur ein einziges DOM-Element ergeben.
Im Editor-Modus muss das Element das Attribut data-portlet="..."
aufweisen. Hierin stehen alle Daten, die für
die Verarbeitung im Editor notwendig sind.
Den Wert kann mit Hilfe der Methode {$instance->getDataAttribute()}
bezogen werden. Mit
{$instance->getProperty('<property-name>')}
können Property-Werte der Portlet-Instanz abgefragt werden.
Beispiel:
<h1 style="{$instance->getStyleString()}"
{if $isPreview}data-portlet="{$instance->getDataAttribute()}"{/if}
class="{$instance->getAnimationClass()}"
{$instance->getAnimationDataAttributeString()}>
{$instance->getProperty('text')}
</h1>
Extras
Damit ein Portlet Animationen übernimmt (falls konfiguriert), fügt man dem Portlet-Element folgenden Code hinzu: (siehe Zeilen 3 und 4 im obigen Beispiel)
{* ... *}
class="{$instance->getAnimationClass()}"
{$instance->getAnimationDataAttributeString()}
Dies setzt die eingestellte Animations-CSS-Klasse und die Animations-Parameter über data-*
-Attribute.
Damit ein Portlet auch benutzerdefinierte Style-Eigenschaften übernimmt, fügt man dem Portlet-Element ebenfalls noch folgendes Attribut hinzu:
style="{$instance->getStyleString()}"
Jede Portlet-Instanz hat eine nicht persistente, aber einheitliche ID und kann mit {$instance->getUid()}
abgerufen
werden. Dies ist zum Beispiel für Bootstrap-Tabs nützlich.
Portlets mit Sub-Areas
Portlets können Bereiche definieren, in denen weitere Portlets platziert werden.
Ein solcher Bereich ist ein Element mit der CSS-Klasse opc-area
.
Das Area-Element muss für die Editor-Ansicht eine ID mittels data-area-id="{$areaId}"
-Attribut definieren,
wobei $areaId
ein für das Portlet einheitlicher Bezeichner ist.
Für die Editor-Ansicht muss der Inhalt des Elements wie folgt gerendert werden:
{$instance->getSubareaPreviewHtml($areaId)}
Für die finale Ansicht muss der Inhalt des Elements wie folgt gerendert werden:
{$instance->getSubareaFinalHtml($areaId)}
Beispiel:
<div {if $isPreview}data-area-id="{$areaId}"{/if} class="opc-area">
{if $isPreview}
{$instance->getSubareaPreviewHtml($areaId)}
{else}
{$instance->getSubareaFinalHtml($areaId)}
{/if}
</div>
Portlet-Übersetzung
In Portlet-Klasse und Templates können Sprachvariablen abgerufen werden. Dies geschieht mittels:
{__("Text-ID")}
Übersetzungen können im .mo
-Dateiformat im Language-Verzeichnis des Plugins unter portlets/
abgelegt
werden.
Konkret wäre das dann:
plugins/[plugin-id]/locale/[language-tag]/portlets/[Portlet-Class].mo
Wird eine Übersetzung nicht gefunden, wird deren Text-ID unverändert ausgegeben.
Portlet-Vorlagen - Blueprints
Blueprints sind wiederverwendbare Portlet-Kompositionen bzw. -Vorlagen.
Diese Vorlagen können im OPC-Editor erstellt und exportiert werden. Sie finden Blueprints im Reiter "Vorlagen", wo sie auch importiert werden können.
Ebenso können Sie natürlich auch mit einem Plugin Blueprints ausliefern. Nähere Informationen dazu finden Sie im Abschnitt "Blueprints (ab 5.0.0)".
Benutzerdefinierte Portlet-Eigenschaften
Portlets können verschiedene Eigenschaften haben, die Benutzer frei konfigurieren können. Jeder Eigenschaft muss ein eigener Input-Typ zugewiesen werden, welcher definiert, wie die Eigenschaft im Einstellungsfenster dargestellt wird und welche Daten sie verarbeitet.
Unter admin/opc/tpl/config/config.*.tpl
ist für jeden InputType
des Cores ein eigenes Smarty-Snippet angelegt.
admin/opc/tpl/config/config.color.tpl
rendert z.B. für den Input Typ "Farbe" einen Colorpicker.
Über Plugins ist es möglich, den OnPage Composer um benutzerdefinierte Input-Typen zu erweitern. Dazu geht man wie folgt vor:
-
Legen Sie in der
info.xml
folgende Knoten an:
Unter dem Knoten<jtlshopplugin> ... <Install> ... <PortletInputTypes> <PortletInputType> <Name>my-input-type</Name> </PortletInputType> ... </PortletInputTypes> </Install> ... </jtlshopplugin>
PortletInputTypes
definieren Sie Ihre benutzerdefinierten Input-Typen. (hier z.B.my-input-type
) -
Legen Sie für jeden Input-Typen unter
<plugin-verzeichnis>/portlet_input_types/types/
ein eigenes Smarty-Snippet an, z.B.:<plugin-verzeichnis>/portlet_input_types/types/my-input-type.tpl
-
In dem Smarty-Snippet definieren Sie, wie die Eigenschaft im Portlet-Dialog dargestellt wird. Beispielsweise können Sie einen Range-Slider auf diese Art anzeigen:
Zur Verfügung stehen folgende Smarty-Variablen:<label for="config-{$propname}"> {$propdesc.label} </label> <input type="range" id="config-{$propname}" name="{$propname}" value="{$propval}" class="form-control">
$propname
: der eindeutige Name der Eigenschaft$propval
: der aktuelle Wert der Eigenschaft$propdesc
: die vollständige Beschreibung der Eigenschaft als assoziatives Array; enthält alle Felder wie sie in der methodePortlet::getPropertyDesc()
des Portlets definiert wurden.
Wichtig ist das das Snippet ein Eingabe-Element enthält wie z.B.
<input>
,<textarea>
oder<select>
. Dieses Element muss alsname
-Attribut den Wert{$propname}
enthalten, damit die Eigenschaft beim Speichern der Einstellung korrekt serialisiert wird.
Attention
Benutderdefinerte Eigenschaften werden nur korrekt dargestellt, wenn die Methode
Portlet::getConfigPanelHtml()
von der Portlet-Klasse nicht überschrieben wurde, bzw. wenn das
Einstellungsfenster über Portlet::getAutoConfigPanelHtml()
gerendert wurde.
-
Falls Sie vor dem Speichern der Einstellungen den konfigurierten Wert noch verarbeiten wollen, stellt der OnPage Composer ein Event zur Verfügung. In dem obigen Beispiel wollen Sie den Wert des Range-Sliders vielleicht noch vom Datentyp
String
zuNumber
konvertieren:<label for="config-{$propname}"> {$propdesc.label} </label> <input type="range" id="config-{$propname}" name="{$propname}" value="{$propval}" class="form-control"> <script> opc.once('process-portlet-property:{$propname}', data => { data.value = parseFloat(data.value); }); </script>
Fügen Sie Ihrem Snippet einfach ein
<script>
hinzu, in welchem Sie eine Callback-Funktion auf das Eventprocess-portlet-property:{$propname}
registrieren.Die JavaScript-Methode
opc.once()
registriert einen Event-Handler, der nur einmalig aufgerufen wird im Gegensatz zuopc.on()
. Das ist wichtig, sonst bleibt der Event-Handler auch nach dem Schließen der Einstellungen noch aktiv.Der Callback-Funktion wird ein
data
-Objekt übergeben mit dem vom Benutzer gesetzten Wertdata.value
. Dieser kann nach belieben verarbeitet und verändert werden, bevor die Portlet-Einstellungen gespeichert werden.
Ist das Plugin installiert und aktiv, dann ist der Input-Typ einsatzbereit. Portlet-Klassen können diesen nun auf folgende Art verwenden:
<?php declare(strict_types=1);
namespace Plugin\my_plugin\Portlets\MyPortlet;
use JTL\OPC\Portlet;
/**
* Class Alert
* @package Plugin\my_plugin\Portlets\MyPortlet
*/
class MyPortlet extends Portlet
{
/**
* @return array
*/
public function getPropertyDesc(): array
{
return [
'my-slider' => [
'label' => \__('a slider property'),
'type' => 'my_plugin.my-input-type',
],
];
}
}
In der Methode getPropertyDesc()
kann eine Eigenschaft über das Feld type
auf den benutzerdefinierten Input-Typen
zugreifen. Dabei muss dem Namensschema plugin_id.input_type_name
gefolgt werden, sprich stellen Sie dem Namen des
Input-Typen die Plugin-ID und einem .
voran um Namenskonflikte zu vermeiden. my_plugin
ist dabei das Plugin,
welches den Input-Typen zur Verfügung stellt.