< SHOPWARE FRONTEND EDITING />

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.

If you would like to become familiar with the Frontend Editor, please read the User Guide.

< Create plugin />

In the first step, create a new Shopware plugin in which you want to make the extensions.

For the examples within this tutorial, a plugin named "RopiCmsExtensions" was created. The complete example plugin can be downloaded here .

< Create content areas />

Since content areas are simple DIV elements, you only need to extend the corresponding Shopware templates to create your own content areas.

To do this, register a new template directory via your plugin, as shown in the example code.

<?php
namespace RopiCmsExtensions;

class RopiCmsExtensions extends \Shopware\Components\Plugin
{

    public static function getSubscribedEvents()
    {
        return [
            'Enlight_Controller_Action_PreDispatch_Frontend' => ['onPreDispatch', -100],
            'Enlight_Controller_Action_PreDispatch_Backend' => ['onPreDispatch', -100],
            'Enlight_Controller_Action_PreDispatch_Widgets' => ['onPreDispatch', -100]
        ];
    }

    public function onPreDispatch()
    {
        $this->container->get('Template')->addTemplateDir(
            $this->getPath() . '/Resources/Views/'
        );
    }
}

Create the template file "RopiCmsExtensions/Resources/Views/frontend/detail/content/header.tpl" 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 the example the identifier is "myArea").

Within the HTML element the Smarty function "ropicmsareacontent" is called. This function renders later the content elements of the content area in the frontend. Please note that the parameter "area" must always have the same identifier as the attribute "data-ropi-content-area". The Smarty variable $ropiCmsContentDocumentStructure is automatically defined by the Frontend Editing Plugin and contains the structure of the content elements that should be rendered in the content area.

{extends file="parent:frontend/detail/content/header.tpl"}

{block name='frontend_detail_index_product_info' prepend}
<div data-ropi-content-area="myArea">
{ropicmsareacontent area="myArea" contentAreas=$ropiCmsContentDocumentStructure.children}
</div>
{/block}
The new content area

< Create content elements />

Content elements are conventional Shopware widgets. In this example, we'll create a content element called "Hello". First, create the corresponding widget controller under the file path "RopiCmsExtensions/Controllers/Widgets/RopiCmsExtensionsHello.php" . Note that the widget controller must inherit from the AbstractContentElementController class. This class already implements the indexAction, so no additional code is needed.

<?php
use RopiCms\Controller\AbstractContentElementController;

class Shopware_Controllers_Widgets_RopiCmsExtensionsHello extends AbstractContentElementController
{
}

Next, create the appropriate template file "RopiCmsExtensions/Resources/widgets/ropi_cms_extensions_hello/index.tpl" . At the root level, content elements must consist of only one HTML element and must have various special attributes defined. 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.tpl". The "general_classes.tpl" includes a few helper classes which, for example, generate the element's margin bottom depending on the configuration.

If no name is configured, the content element should display the text "Hello Guest". If a name is configured via the Frontend Editor, the configured name should be displayed instead of the word Guest. Furthermore, the content element contains an editable text with the identifier "someEditableText". If you have several editable texts within a widget, they must have different identifiers.

<div
  {include file='widgets/_ropi_cms_includes/editor_attributes.tpl'}
  class="{include file='widgets/_ropi_cms_includes/general_classes.tpl'}">
  Hello {if $data.configuration.name|strip}{$data.configuration.name}{else}Guest{/if}
  <div data-ropi-content-editable="someEditableText">{$data.contents.someEditableText|unescape}</div>
</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 "RopiCmsExtensions/Resources/backend/ropi_cms/index.tpl" and fill it with the example source code.

We register our new content element by appending a new "ropi-content-element" tag to the smarty block "backend_ropi_cms_content_elements". The attribute "type" must contain the controller name of the widget. In the attribute "icon", you can define a Material Icon . To do this, enter the identifier of the desired icon. You can enter any value for the "group" attribute. This attribute defines under which group the new content element should be listed in the Frontend Editor. The "name" attribute defines the speaking name of the content element. The "src" attribute must include the widget URL. This URL is called in the Frontend Editor via Ajax to render the content element.

Within the "ropi-content-element" tag, there must be a "template" tag as a child node. This template tag can be filled with any HTML and defines what the content element configuration menu should look like. It is important that, as with conventional HTML forms, the attribute "name" is set and defined for all input fields, because with the defined name, you can access the values in the widget template. In the example code, the "name" attribute of our text input field has the value "name" and thus the entered value can be accessed in the widget template via the template variable "$data.configuration.name".
As you can see, the example code uses some custom tags, that make the job easier. Of course, you can also use conventional HTML elements. However, if you need complex HTML elements that use JavaScript, you should include them as Web Components. To learn more about Web Components, click here .

{extends file="parent:backend/ropi_cms/index.tpl"}

{block name='backend_ropi_cms_content_elements' append}
<ropi-content-element
  type="RopiCmsExtensionsHello"
  icon="extension"
  group="{s name='basis' namespace='backend/ropi_cms/content_element_settings'}Basis{/s}"
  name="Hello"
  src="widgets/RopiCmsExtensionsHello/index">
  <template>
    <ropi-tabs scrollable tablistposition="bottom">
      <ropi-touchable slot="tab">General</ropi-touchable>
      <div slot="tabpanel">
        <ropi-subheader>Basic</ropi-subheader>
        <div class="ropi-cms-margin-h">
          <ropi-textfield name="name">
            <div slot="placeholder">Name</div>
          </ropi-textfield>
        </div>
      </div>
      {foreach from=$breakpoints item=breakpoint}
          <ropi-touchable slot="tab">{$breakpoint.name}</ropi-touchable>
          <div slot="tabpanel">
            {include file="backend/ropi_cms/content_elements/common/general_settings.tpl"}
          </div>
      {/foreach}
    </ropi-tabs>
  </template>
</ropi-content-element>
{/block}

Delete the Shopware cache and reload the Frontend Editor. You should now see the new Hello element.

In the directory "RopiCms/Resources/backend/ropi_cms/content_elements/" you will find the definitions for the standard content elements. Use them as a reference when creating your own content elements. Furthermore, you will see there how the custom tags can be used.

Hello-Element

< Document Context />

Have you ever wondered how the Frontend Editing Plugin makes the relation between a page and the content elements? This is done via the so-called Document Context.

In the Frontend Editor, navigate to a shop page (for example, the privacy policy page). Within the Frontend Editor, use the developer tools of your browser to inspect the body tag of the shop page. You will see the "data-ropi-document-context" attribute there. This attribute contains a JSON, which tells the Frontend Editor, for which page the content elements should be persisted. The JSON properties "scope" (=module), "controller", "action" and "site" (=shop ID) are always set by the Frontend Editor Plugin by default.In the case of shop pages, not only the controller action and the shop itself are relevant, but also which specific shop page is currently displayed (e. g. terms of service, privacy policy, etc.). In the example JSON, this is defined by the property "staticPageId", which contains the ID of the current shop page. Such properties are called subcontext.

data-ropi-document-context='{"scope":"frontend","controller":"custom","action":"index","site":1,"staticPageId":"7"}'

< Subcontext for custom controller actions />

As an example, create a new news controller in your plugin under the path "RopiCmsExtensions/Controllers/Frontend/RopiCmsExtensionsNews.php" . The showAction is responsible for displaying a single news. The GET parameter "newsId" controls which news should be displayed.

<?php
class Shopware_Controllers_Frontend_RopiCmsExtensionsNews extends Enlight_Controller_Action
{

    public function showAction() {
        $news = [];

        if (isset($_GET['newsId'])) {
            if ($_GET['newsId'] === '1') {
                $news = ['title' => 'News 1', 'body' => 'My first news'];
            } else if ($_GET['newsId'] === '2') {
                $news = ['title' => 'News 2', 'body' => 'My second news'];
            }
        }

        $this->View()->assign('news', $news);
    }
}

Create the template file for the showAction under the path "RopiCmsExtensions/Resources/Views/frontend/ropi_cms_extensions_news/show.tpl" . and fill it with the example source code. If we were to create content in these content areas now, the same content would be displayed for every news, because no subcontext has yet been defined.

{extends file="frontend/index/index.tpl"}

{block name="frontend_index_content"}
<div>
  {if $news}
    <h1>{$news.title}</h1>
    <div data-ropi-content-area="top">
    {ropicmsareacontent area="top" contentAreas=$ropiCmsContentDocumentStructure.children}
    </div>
    <div>{$news.body}</div>
    <div data-ropi-content-area="bottom">
    {ropicmsareacontent area="bottom" contentAreas=$ropiCmsContentDocumentStructure.children}
    </div>
  {else}
    NEWS NOT FOUND
  {/if}
</div>
{/block}

To make content dependent on each news, you must subscribe to the event "RopiCms_BuildSubcontext_RopiCmsExtensionsNews" (where "RopiCmsExtensionsNews" has to be replaced by the name of the controller for which you want to define a subcontext). To do this, extend the file "RopiCmsExtensions/RopiCmsExtensions.php" as shown in the example code. In the new method "onBuildSubcontext" the current "newsId" is defined as subcontext for the showAction. Now call the URL https://yourshopdomain.example/frontend/RopiCmsExtensionsNews/show?newsId=1 in the Frontend Editor, where yourshopdomain.example must be replaced with your shop domain.

<?php
namespace RopiCmsExtensions;

class RopiCmsExtensions extends \Shopware\Components\Plugin
{

    public static function getSubscribedEvents()
    {
        return [
            'Enlight_Controller_Action_PreDispatch_Frontend' => ['onPreDispatch', -100],
            'Enlight_Controller_Action_PreDispatch_Backend' => ['onPreDispatch', -100],
            'Enlight_Controller_Action_PreDispatch_Widgets' => ['onPreDispatch', -100],
            'RopiCms_BuildSubcontext_RopiCmsExtensionsNews' => 'onBuildSubcontext'
        ];
    }

    public function onPreDispatch()
    {
        $this->container->get('Template')->addTemplateDir(
            $this->getPath() . '/Resources/Views/'
        );
    }

    public function onBuildSubcontext(\Enlight_Event_EventArgs $args)
    {
        $request = $args->getController()->Request();

        if ($request->getActionName() === 'show') {
            if (!isset($_GET['newsId']) || $_GET['newsId'] <= 0) {
                return [];
            }

            return ['newsId' => $_GET['newsId']];
        }

        return [];
    }
}

Now inspect the body tag of the news page in the Frontend Editor. You can see that in the JSON of the "data-ropi-document-context" attribute, the "newsId" property is specified and filled with the value of the passed "newsId" parameter. Now every news page can be individually filled with content elements.

data-ropi-document-context="{"newsId":"1","scope":"frontend","controller":"RopiCmsExtensionsNews","action":"show","site":1}"