Developer's Guide
This guide describes how to make technical enhancements to the Frontend Editing Plugin.
Before your start, make sure that the Frontend Editing Plugin is correctly installed and activated in your Shopware.
This guide describes how to make technical enhancements to the Frontend Editing Plugin.
Before your start, make sure that the Frontend Editing Plugin is correctly installed and activated in your Shopware.
Since content areas are simple DIV elements, you only need to extend the corresponding Shopware templates to create your own content areas.
Create the template file "RopiFrontendEditingExtensions/src/Resources/views/storefront/page/product-detail/index.html.twig"
and insert the example code. Then delete the Shopware cache and navigate via the Frontend Editor to any product detail page. You will now see the new content area above the product title.
The attribute "data-ropi-content-area" marks an HTML element as content area. This allows the HTML element to be filled later with content elements. Each content area must have a unique identifier within a page (in this example the identifier is "myArea").
Within the HTML element the Twig function "ropi_frontend_editing_render_content_area" is called. This function renders later the content elements of the content area in the frontend. Please note that the first parameter must always have the same identifier as the attribute "data-ropi-content-area". The Twig variable ropiFrontendEditingContentDocument is automatically defined by the Frontend Editing Plugin and contains the structure of the content elements that should be rendered in the content area.
Attention: All HTML elements with the attribute "data-ropi-content-area", must be block elements and should not change the CSS properties "margin", "padding", "background", "outline" and "position", because for usability reasons these will be overwritten by the Frontend Editor in edit mode.
{% 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 %}
In this example we create a content element called "Hello". First, create a Twig template under the file path "RopiFrontendEditingExtensions/src/Resources/views/content-editor/elements/hello/element.html.twig"
.
Please note that templates for content elements always correspond to the file path "{PluginName}/src/Resources/views/content-editor/elements/{element-name}/element.html.twig"
. At the root level, content elements must consist of only one HTML element and must have various special attributes defined in order to be recognized correctly by the Frontend Editor. These attributes include, for example, the ID, name, and configurations of the content element. You do not have to define the attributes yourself, you can simply include them using the file "editor-attributes.html.twig". The "general-classes.html.twig" includes some helper classes, which for example are creating the configuration-dependent padding.
The content element should display a greeting and a name, each of which can be configured in the Frontend Editor. Furthermore, the content element contains an editable text with the identifier "someEditableText". If you have several editable texts within a content element, they must have different identifiers.
<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>
Hint: By default, DIV tags are created for paragraphs in editable texts. Since version 1.1.4, the attribute "data-ropi-content-editable-defaultParagraphSeparator" can be used to define which tag should be created for paragraphs (e.g. defaultParagraphSeparator="p" to use P tags instead of DIV tags).
<div data-ropi-content-editable-defaultParagraphSeparator="p" data-ropi-content-editable="someEditableText">{{ data.contents.text|raw }}</div>
Finally, the new content element must be registered in the Frontend Editor. You also need to define the configuration options for this content element.
Since the Frontend Editor is completely based on web components, it can easily be extended via HTML. Therefore, we extend the template file of the Frontend Editor itself. Create the file "RopiFrontendEditingExtensions/src/Resources/views/content-editor/content-editor.html.twig"
and fill it with the example source code.
For a better overview, we store the definition of the content element in the file "definition.html.twig" and include it via 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 %}
<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>
<?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>
Have you ever wondered how the Frontend Editing Plugin creates the relation between a page and the content elements? This is done via the so-called Document Context.
Navigate to a category page in the Frontend Editor. In the Frontend Editor, use the developer tools of your browser to inspect the body tag of the shop page. You will see the attribute "data-ropi-document-context" there. This attribute contains a JSON that tells the Frontend Editor for which page the content elements are to be saved. The JSON properties "salesChannelId", "bundle", "controller", "action" and "languageId" are always set by the Frontend Editor Plugin by default. For category pages not only the controller action, the sales channel and the language is relevant, but also which concrete category is currently displayed. In the example JSON the property "subcontext" contains the ID of the called category.
data-ropi-document-context="{"salesChannelId":"9dde3e8a00f841f487aebdacc64dfe04","bundle":"ShopwareStorefront","controller":"Navigation","action":"index","languageId":"2fbb5fe2e29a4d70aa5854ce7ce3e20b","subcontext":"a515ae260223466f8e37471d279e6406"}"
If you want to enable frontend editing on an additional or custom controller, you must register a subscriber for two events. In the example code, events have been implemented for the detail page of a fictitious news controller.
In the DocumentContextBuildEvent the "subcontext" is defined with the ID of the news. Thus, individual content elements can be created for each news. If we would not do this, the content would only be maintainable globally for all news, because the DocumentContext would be identical for each news.
With the DocumentContextBuildUrlEvent the route to a news detail page is built using a DocumentContext. This is used for automatic cache invalidation when content is published on a news detail page using the frontend editor.If we did not implement the event handling, the cache would always have to be deleted manually after publishing the content to make the changes visible in the frontend.
<?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]); } } }
Since version 1.2.2 presets can be added directly by extending the template {ShopwarePath}/plugins/custom/RopiFrontendEditing/src/Resources/views/content-editor/content-editor.html.twig. To do this, you can simply add corresponding "ropi-content-preset" elements to the "content_presets" template block. These should be marked with the attribute "readonly" so that they cannot be deleted by users.
{% block content_presets %} {{ parent() }} <ropi-content-preset readonly name="My hardcodet Headline Preset" time="1612436532008" type=" { "type":"element", "meta":{ "type":"RopiFrontendEditing/headline" }, "configuration":{ "level":"1", "padding":{ "b":{ "lg":"inherit", "md":"inherit", "sm":"inherit", "xl":"default", "xs":"inherit" }, "l":{ "lg":"inherit", "md":"inherit", "sm":"inherit", "xl":"default", "xs":"inherit" }, "r":{ "lg":"inherit", "md":"inherit", "sm":"inherit", "xl":"default", "xs":"inherit" }, "t":{ "lg":"inherit", "md":"inherit", "sm":"inherit", "xl":"default", "xs":"inherit" } }, "textColor":"default", "appearance":"", "visibility":{ "lg":"", "md":"", "sm":"", "xl":"", "xs":"" } }, "contents":{ "text":"<div style=\"text-align: center;\">About lorem ipsum</div>" }, "children":[ ] }"></ropi-content-preset> {% endblock %}