< SHOPWARE FRONTEND EDITING />

Entwicklerhandbuch

In dieser Anleitung wird beschrieben, wie man technische Erweiterungen an dem Frontend Editing Plugin vornimmt.

Stellen Sie vor dem Start sicher, dass das Frontend Editing Plugin in Ihrem Shopware korrekt installiert und aktiviert ist.

Wenn Sie im Umgang mit dem Frontend Editor vertraut werden möchten, lesen Sie bitte das Benutzerhandbuch.

< Plugin erstellen />

Erstellen Sie im ersten Schritt ein neues Shopware Plugin, in welchem Sie die Erweiterungen vornehmen möchten.

Für die Beispiele innerhalb dieser Anleitung wurde ein Plugin mit dem Namen "RopiFrontendEditingExtensions" erstellt. Das vollständige Beispiel-Plugin können Sie hier herunterladen.

< Inhaltsbereiche erstellen />

Da Inhaltsbereiche einfache DIV-Elemente sind, müssen Sie um eigene Inhaltsbereiche zu erstellen, nur die entsprechenden Shopware Templates erweitern.

Erstellen Sie die Template-Datei "RopiFrontendEditingExtensions/src/Resources/views/storefront/page/product-detail/index.html.twig" und fügen Sie den Beispielcode ein. Löschen Sie anschließend den Shopware-Cache und navigieren Sie über den Frontend Editor auf eine beliebige Produktdetailseite. Sie sehen nun über dem Produkttitel den neuen Inhaltsbereich.

Mit dem Attribut "data-ropi-content-area" wird ein HTML-Element als Inhaltsbereich markiert. Dadurch lässt sich das HTML-Element später im Frontend Editor mit Inhaltselementen via Drag and Drop befüllen. Jeder Inhaltsbereich muss innerhalb einer Seite einen eindeutigen Bezeichner haben (im Beispiel "myArea").

Innerhalb des HTML-Elements wird die Twig-Funktion "ropi_frontend_editing_render_content_area" aufgerufen. Diese Funktion rendert später im Frontend die Inhaltselemente des Inhaltsbereiches. Hier ist zu beachten, dass der erste Parameter stets den gleichen Bezeichner haben muss, wie das Attribut "data-ropi-content-area". Die Twig-Variable ropiFrontendEditingContentDocument wird vom Frontend Editing Plugin automatisch definiert und enthält die Struktur der Inhaltselemente, die im Inhaltsbereich gerendert werden sollen.

Achtung: Alle HTML-Elemente mit dem Attribut "data-ropi-content-area", müssen Block-Elemente sein und sollten die CSS-Eigenschaften "margin", "padding", "background", "outline" und "position" nicht verändern, da diese aus Usability-Gründen von dem Frontend Editor im Bearbeitungsmodus überschrieben werden.

{% sw_extends '@Storefront/storefront/page/product-detail/index.html.twig' %}

{% block page_product_detail %}

    <div style="padding:16px;border: solid 1px #ff1493">
        <div data-ropi-content-area="myArea"
             style="/* Styling properties 'margin', 'background', 'outline' and 'position' are not allowed for this element */">
            {{ ropi_frontend_editing_render_content_area('myArea', ropiFrontendEditingContentDocument.structure.children) }}
        </div>
    </div>

    {{ parent() }}
{% endblock %}
Der neue Inhaltsbereich

< Inhaltselemente erstellen />

In diesem Beispiel erstellen wir ein Inhaltselement namens "Hello". Legen Sie hierfür als erstes ein Twig-Template unter dem Dateipfad "RopiFrontendEditingExtensions/src/Resources/views/content-editor/elements/hello/element.html.twig" an.

Beachten Sie hierbei, dass Templates für Inhaltselemente immer dem Dateipfad "{PluginName}/src/Resources/views/content-editor/elements/{element-name}/element.html.twig" entsprechen. Inhaltselemente dürfen auf der obersten Ebene nur aus einem HTML-Element bestehen und müssen diverse spezielle Attribute definiert haben, um vom Frontend Editor als solche korrekt erkannt zu werden. Diese Attribute beinhalten beispielsweise die ID, den Namen und die Konfigurationen des Inhaltselements. Die Attribute müssen Sie nicht selbst definieren, sondern können einfach über die Datei "editor-attributes.html.twig" inkludiert werden. Die "general-classes.html.twig" bindet ein paar Hilfsklassen ein, die beispielsweise den konfigurationsabhängigen Weißraum erzeugen.

Das Inhaltselement soll eine Begrüßung samt Namen ausgeben, welche über den Frontend Editor konfiguriert werden. Des Weiteren beinhaltet das Inhaltselement einen pflegbaren Text mit dem Bezeichner "someEditableText". Falls Sie mehrere pflegbare Texte innerhalb eines Inhaltselements haben, müssen diese verschiedene Bezeichner haben.

<div
    {% sw_include '@RopiFrontendEditing/content-editor/elements/common/editor-attributes.html.twig' %}
    class="{% sw_include '@RopiFrontendEditing/content-editor/elements/common/general-classes.html.twig' %}">

    <h4>
    {% if data.configuration.greeting|trim %}
        {{ data.configuration.greeting }}
    {% endif %}

    {% if data.configuration.name|trim %}
        {{ data.configuration.name }}
    {% endif %}
    </h4>

    <h4>The following text is editable:</h4>
    <div data-ropi-content-editable="someEditableText">{{ data.contents.text|raw }}</div>
</div>

Hinweis: Standardmäßig werden bei pflegbaren Texten für Absätze DIV-Tags erstellt. Seit der Version 1.1.4 kann über das Attribut "data-ropi-content-editable-defaultParagraphSeparator" definiert werden, welches Tag für Absätze erstellt werden soll (z.B. defaultParagraphSeparator="p", um P-Tags statt DIV-Tags zu verwenden)

<div data-ropi-content-editable-defaultParagraphSeparator="p" data-ropi-content-editable="someEditableText">{{ data.contents.text|raw }}</div>

Als letztes muss das neue Inhaltselement noch im Frontend Editor registriert werden. Außerdem müssen noch die Konfigurationsmöglichkeiten für dieses Inhaltselement definiert werden.

Da der Frontend Editor vollständig auf Web Components basiert, kann dieser einfach über HTML erweitert werden. Deshalb erweitern wir die Template-Datei des Frontend Editors selbst. Legen Sie dazu die Datei "RopiFrontendEditingExtensions/src/Resources/views/content-editor/content-editor.html.twig" an und befüllen diese mit dem Beispielquellcode.

Der besseren Übersicht halber, lagern wir die Definition des Inhaltselement in die Datei "definition.html.twig" aus und inkludieren diese über Twig.

{% sw_extends '@RopiFrontendEditing/content-editor/content-editor.html.twig' %}

{% block content_elements %}

    {{ parent() }}

    {% sw_include '@RopiFrontendEditingExtensions/content-editor/elements/hello/definition.html.twig' %}
{% endblock %}
Legen Sie die Datei "RopiFrontendEditingExtensions/src/Resources/views/content-editor/hello/definition.html.twig" dem Beispiel entsprechend an.

Das neue Inhaltselement wird über ein neues "ropi-content-element"-Tag registriert.

Das type-Attribut muss dem Schema "{BundleName}/{ElementName}" entsprechen.

Über das Attribut "icon" kann ein beliebiges Material Icon für das Inhaltselement definiert werden.

Das "color"-Attribut legt fest, in welcher Farbe das Icon und die Hilfslinien im Frontend Editor dargestellt werden soll.

Der Wert für das Attribut "group" ist frei wählbar und definiert unter welcher Gruppe das neue Inhaltselement im Frontend Editor gelistet werden soll.

Mit dem Attribut "name" wird der sprechende Name des Inhaltselements festgelegt.

Im Attribut "languagespecificsettings" kann eine kommaseparierte Liste von Konfigurationensschlüsseln hinterlegt werden, welche sprachabhängig sind. Diese Einstellungen würden bei einem Import mit der Option "Sprachspezifische Daten behalten" nicht überschrieben werden. Im Beispiel haben wir diese auf "greeting" gesetzt.

Für das Attribut "src" sollte immer "{{ path('ropi.frontend-editing.element.render') }}" verwendet werden. Das Attribut definiert, welche URL vom Frontend Editor zum Rendern des Inhaltselements aufgerufen wird.

Innerhalb des "ropi-content-element"-Tags muss sich ein "template"-Tag als Kindknoten befinden. Dieses kann mit beliebigem HTML befüllt werden, welches definiert, wie das Konfigurationsmenü für das Inhaltselement aussehen soll. Wichtig ist hierbei, wie bei gewöhnlichen HTML-Formularen auch, dass bei allen Eingabefeldern das Attribut "name" gesetzt und definiert ist. Denn über den darüber definierten Bezeichner, greifen Sie im Template auf die Werte zu. Im Beispielcode hat das "name"-Attribut von unserem "Greetings"-Texteingabefeld den Wert "greeting" und somit kann im Template des Inhaltselements über die Variable "data.configuration.greeting" auf den konfigurierten Wert zugegriffen werden.
Wie Sie sehen, werden im Beispielcode einige Custom-Tags verwendet, welche Ihnen die Arbeit erleichtern. Sie können natürlich aber auch gewöhnliche HTML-Elemente verwenden. Wenn Sie jedoch komplexe HTML-Elemente benötigen, welche JavaScript verwenden, sollten Sie diese als Web Components einbinden. Um mehr über Web Components zu erfahren, klicken Sie hier .
<ropi-content-element
        type="RopiFrontendEditingExtensions/hello"
        icon="extension"
        color="#ff1493"
        group="{% trans %}ropi-frontend-editing.contentElement.definition.group.other{% endtrans %}"
        name="Hello"
        languagespecificsettings="greeting"
        src="{{ path('ropi.frontend-editing.element.render') }}">
    <template>
        <ropi-tabs scrollable tablistposition="bottom">
            <ropi-touchable slot="tab">
                {% trans %}ropi-frontend-editing.contentElement.definition.tab.general{% endtrans %}
            </ropi-touchable>
            <div slot="tabpanel">
                <div class="ropi-margin-h">
                    <ropi-textfield name="name">
                        <div slot="placeholder">Name</div>
                    </ropi-textfield>
                </div>
                <div class="ropi-margin-h">
                    <ropi-textfield name="greeting">
                        <div slot="placeholder">Greeting</div>
                    </ropi-textfield>
                </div>
            </div>
            {% for breakpoint in breakpoints %}
                <ropi-touchable slot="tab">{{ breakpoint.name }}</ropi-touchable>
                <div slot="tabpanel">
                    {% sw_include '@RopiFrontendEditing/content-editor/elements/common/settings/general.html.twig' %}
                </div>
            {% endfor %}
        </ropi-tabs>
    </template>
</ropi-content-element>
Löschen Sie den Shopware-Cache und laden Sie den Frontend Editor neu. Sie sollten nun das neue Hello-Element sehen.

Im Verzeichnis "RopiFrontendEditing/src/Resources/views/content-editor/elements" finden Sie die Definitionen zu den Standard-Inhaltselementen. Nutzen Sie diese als Referenz, wenn Sie Ihre eigenen Inhaltselemente erstellen. Des Weiteren sehen Sie dort, wie die Custom-Tags verwendet werden können.
Hello-Element
Jedes mal, wenn ein Inhaltselement gerendert wird, wird ein entsprechendes Event ausgelöst. So können Sie z. B. auch komplexere Businesslogik für Ihr Inhaltselement implementieren (z.B. Datenbankabfragen etc.). Im Beispielcode können Sie sehen, wie Sie sich auf das Render-Event des Hello-Elements subscriben.
<?php declare(strict_types=1);

namespace Ropi\FrontendEditingExtensions\ContentEditor\Renderer\Subscriber;

use Ropi\FrontendEditing\ContentEditor\Renderer\Events\ContentElementRenderEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class HelloElementRenderSubscriber implements EventSubscriberInterface
{

    public static function getSubscribedEvents(): array
    {
        return [
            ContentElementRenderEvent::getNameForContentElementType('RopiFrontendEditingExtensions/hello') => 'onRender'
        ];
    }

    public function onRender(ContentElementRenderEvent $event): void
    {
        $configuredName = $event->getParameters()['data']['configuration']['name'] ?? '';
        // Do some complex business logic
        $event->setParameter('processedName', $configuredName);
    }
}
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Ropi\FrontendEditingExtensions\ContentEditor\Renderer\Subscriber\HelloElementRenderSubscriber">
            <tag name="kernel.event_subscriber"/>
        </service>
    </services>
</container>

< Document Context />

Haben Sie sich eigentlich schon gefragt, wie das Frontend Editing Plugin die Relation zwischen einer Seite und den Inhaltselementen herstellt? Dies geschieht über den sogenannten Document Context.

Navigieren Sie im Frontend Editor zu einer Kategorieseite. Inspizieren Sie innerhalb des Frontend Editors mit den Entwicklertools Ihrers Browsers das Body-Tag der Shopseite. Sie sehen dort das Attribut "data-ropi-document-context". Dieses Attribut enthält ein JSON, welches dem Frontend Editor mitteilt, für welche Seite die Inhaltselemente gespeichert werden sollen. Die JSON-Eigenschaften "salesChannelId", "bundle", "controller", "action" und "languageId" werden vom Frontend Editor Plugin standardmäßig immer gesetzt. Bei Kategorieseiten ist aber nicht nur die Controller-Action, der Verkausfkanal und die Sprache relevant, sondern auch welche konkrete Kategorie aktuell angezeigt wird. In dem Beispiel-JSON enthält die Eigenschaft "subcontext" die ID der aufgerufenen Kategorie.

data-ropi-document-context="{"salesChannelId":"9dde3e8a00f841f487aebdacc64dfe04","bundle":"ShopwareStorefront","controller":"Navigation","action":"index","languageId":"2fbb5fe2e29a4d70aa5854ce7ce3e20b","subcontext":"a515ae260223466f8e37471d279e6406"}"

Wenn Sie Frontend Editing auf einem weiteren bzw. eigenem Controller ermöglichen wollen, müssen Sie zwei Events registrieren. Im Beispielcode sind die Events für die Detailseite eines fiktiven News-Controller implementiert.

Beim DocumentContextBuildEvent wird der "subcontext" mit der ID der News definiert. Somit können für jede News individuelle Inhaltselemente angelegt werden. Würden wir dies nicht tun, wären die Inhalte nur global für alle News pflegbar, da der der DocumentContext für jede News identisch wäre.

Beim DocumentContextBuildUrlEvent wird die Route zu einer Newsdetailseite anhand eines DocumentContexts gebaut. Dies dient zur automatischen Cache-Invalidierung, wenn über den Frontend Editor Inhalte auf einer Newsdetailseite veröffentlicht werden. Würden wir das Event-Handling nicht einbauen, müsste nach Veröffentlichung der Inhalte der Cache immer manuell gelöscht werden, damit die Änderungen im Frontend sichtbar werden.

<?php declare(strict_types=1);

namespace Ropi\FrontendEditingExtensions\ContentEditor\DocumentContext\Subscriber;

use Ropi\FrontendEditing\ContentEditor\DocumentContext\Events\DocumentContextBuildEvent;
use Ropi\FrontendEditing\ContentEditor\DocumentContext\Events\DocumentContextBuildUrlEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class NewsShowDocumentContextSubscriber implements EventSubscriberInterface
{

    public static function getSubscribedEvents(): array
    {
        return [
            DocumentContextBuildEvent::getNameForControllerAction('BundleName', 'News', 'show') => 'onBuild',
            DocumentContextBuildUrlEvent::getNameForControllerAction('BundleName', 'News', 'show') => 'onBuildUrl'
        ];
    }

    public function onBuild(DocumentContextBuildEvent $event): void
    {
        $event->getDocumentContext()['subcontext'] = $event->getRequest()->get('newsId');
    }

    public function onBuildUrl(DocumentContextBuildUrlEvent $event): void
    {
        $event->setRoute('news.show.action');

        if (isset($event->getDocumentContext()['subcontext'])) {
            $subcontext = $event->getDocumentContext()['subcontext'];
            $event->setParameters(['newsId' => $subcontext]);
        }
    }
}

< Vorlagen />

Seit der Version 1.2.2 können Vorlagen direkt durch Erweiterung des Templates {ShopwarePath}/plugins/custom/RopiFrontendEditing/src/Resources/views/content-editor/content-editor.html.twig hinzugefügt werden. Hierzu kann man einfach entsprechende "ropi-content-preset"-Elemente in den Template-Block "content_presets" einfügen. Diese sollten mit dem Attribut "readonly" gekennzeichnet werden, damit diese von Nutzern nicht gelöscht werden können.

{% block content_presets %}

    {{ parent() }}

    <ropi-content-preset readonly name="My hardcodet Headline Preset" time="1612436532008" type="
{
   &quot;type&quot;:&quot;element&quot;,
   &quot;meta&quot;:{
      &quot;type&quot;:&quot;RopiFrontendEditing/headline&quot;
   },
   &quot;configuration&quot;:{
      &quot;level&quot;:&quot;1&quot;,
      &quot;padding&quot;:{
         &quot;b&quot;:{
            &quot;lg&quot;:&quot;inherit&quot;,
            &quot;md&quot;:&quot;inherit&quot;,
            &quot;sm&quot;:&quot;inherit&quot;,
            &quot;xl&quot;:&quot;default&quot;,
            &quot;xs&quot;:&quot;inherit&quot;
         },
         &quot;l&quot;:{
            &quot;lg&quot;:&quot;inherit&quot;,
            &quot;md&quot;:&quot;inherit&quot;,
            &quot;sm&quot;:&quot;inherit&quot;,
            &quot;xl&quot;:&quot;default&quot;,
            &quot;xs&quot;:&quot;inherit&quot;
         },
         &quot;r&quot;:{
            &quot;lg&quot;:&quot;inherit&quot;,
            &quot;md&quot;:&quot;inherit&quot;,
            &quot;sm&quot;:&quot;inherit&quot;,
            &quot;xl&quot;:&quot;default&quot;,
            &quot;xs&quot;:&quot;inherit&quot;
         },
         &quot;t&quot;:{
            &quot;lg&quot;:&quot;inherit&quot;,
            &quot;md&quot;:&quot;inherit&quot;,
            &quot;sm&quot;:&quot;inherit&quot;,
            &quot;xl&quot;:&quot;default&quot;,
            &quot;xs&quot;:&quot;inherit&quot;
         }
      },
      &quot;textColor&quot;:&quot;default&quot;,
      &quot;appearance&quot;:&quot;&quot;,
      &quot;visibility&quot;:{
         &quot;lg&quot;:&quot;&quot;,
         &quot;md&quot;:&quot;&quot;,
         &quot;sm&quot;:&quot;&quot;,
         &quot;xl&quot;:&quot;&quot;,
         &quot;xs&quot;:&quot;&quot;
      }
   },
   &quot;contents&quot;:{
      &quot;text&quot;:&quot;&lt;div style=\&quot;text-align: center;\&quot;&gt;About lorem ipsum&lt;/div&gt;&quot;
   },
   &quot;children&quot;:[
      
   ]
}"></ropi-content-preset>

{% endblock %}