Is this article helpful?
 
(optional) let us know why:

How to build your first ShopBuilder widget plugin

This tutorial will show you how to build a plugin that provides a widget for the plentymarkets ShopBuilder. In the case of this tutorial, the widget will implement Google Maps in the ShopBuilder. The Google Maps widget described here is version 1.0.0.
You can download the Google Maps widget from GitHub:

Further reading

Tutorial contents

Widget plugin folder structure

    MyWidget/
        ├── resources/
        │   ├── images/
        │   │   └── MyWidget.svg // Preview image of the widget
        │   │
        │   ├── lang/
        │   │   └── de/en
        │   │       └── MyWidget.properties // Contains the widget texts, such as labels and tooltips
        │   │
        │   └── views/
        │       └── Widgets/
        │           └── MyWidget.twig // Content to be injected into the ShopBuilder template container
        ├── src/
        │   ├── Widgets/
        │   │   └── MyWidget.php // The widget's PHP class
        │   │
        │   └── Providers/
        │       └── MyWidgetServiceProvider.php
        │
        ├── plugin.json // Contains the plugin information
        │
        └── contentWidgets.json // Contains the widget information

    

Description of the contentWidgets.json

The contentWidgets.json file serves to make a widget available to the ShopBuilder. The contentWidgets.json file holds all information that is necessary in order to configure the widget in the ShopBuilder interface, including its icon and settings. The same plugin can provide multiple widgets as objects in the contentWidgets.json array. The contentWidgets.json file needs to be located in the root directory of the plugin, i.e. on the same level as the plugin.json.

Below you find the content of the contentWidgets.json and the table explaining the individual key-value pairs listed in the JSON file.

contentWidgets.json
      [
        {
          "identifier": "GoogleMapsWidget::MapsWidget",
          "label": "Widget.mapsLabel",
          "previewImageURL": "/images/google-maps-widget.svg",
          "type": "default",
          "position": 1050,
          "widgetClass": "GoogleMapsWidget\\Widgets\\MapsWidget",
          "settings": {
              "apiKey": {
                  "type": "text",
                  "required": true,
                  "defaultValue": "",
                  "options": {
                      "name": "Widget.mapsApiKeyLabel",
                      "tooltip": "Widget.mapsApiKeyTooltip"
                  }
              },
              "address": {
                  "type": "textarea",
                  "required": true,
                  "defaultValue": "",
                  "options": {
                      "name": "Widget.mapsAddressLabel",
                      "tooltip": "Widget.mapsAddressTooltip"
                  }
              },
              "zoom": {
                  "type": "number",
                  "required": false,
                  "defaultValue": 16,
                  "options": {
                      "name": "Widget.mapsZoomLabel",
                      "tooltip": "Widget.mapsZoomTooltip"
                  }
              }
          }
      }
    ]
    
Property Description
identifier The unique identifier of the widget. It is sensible for this identifier to include the namespace of the plugin.
label Contains the label of the widget that is used to display the widget's name in the ShopBuilder user interface. The label provided here refers to the translation key stored in the widgets.properties files under \resources\lang\de and \resources\lang\en.
previewImageURL Contains the icon that is displayed in the widget list of the ShopBuilder interface. The path provided here refers to an image stored under \resources\images. The usual image formats are applicable (SVG, JPG, PNG), but we recommend that you use an image in SVG format with a width of 350px and a height of 120px. The icon can also be provided via a URL.
type There are four types of widgets: default, structure, header and footer. The widget type determines where the widget can be implemented on a page. Widgets of the type header can only be integrated into the header section of a ShopBuilder page; widgets of the types default and structure can be integrated into the body and footer sections of a ShopBuilder page; widgets of the type footer can only be integrated into the footer section of a ShopBuilder page. The widget type is also relevant for the allowedNestingTypes detailed below.
position Contains the position of the widget in the widget list of the ShopBuilder user interface. The positions of widgets provided by Ceres are numbered in steps of 100. Setting the position of a widget to 150, for instance, places it in the second position of the widget list between two Ceres widgets.
allowedNestingTypes Contains the widget types that can be nested in the widget. This is only applicable to widgets of the type structure, such as a four-column grid. Here, you can determine that your widget may, for instance, only nest widgets of the type footer.
widgetClass This is the path of the widget's PHP class. In our case, the class' label is MapsWidget and is located at src/Widgets/MapsWidget. In the widget's class, you define the location of the TWIG template and determine which data is handed over to the TIWG template.
settings The settings provide the configuration options of the widget in the ShopBuilder. The settings are stored in a JSON object. Each item in the settings object needs to have a unique key, which is used in the code to refer to it. In the case of the Google Maps widget, the three setting keys are apiKey, address and zoom. You can provide as many settings as necessary for your widget.
  • type: Specifies the input type of the widget setting. Please find a detailed explanation of the various input types further down the page.
  • required: A Boolean that determines whether this widget setting is mandatory for the user.
  • defaultValue: Determines the default value for a setting. The type of value is contingent on the input type. Please find a detailed description of applicable default values for each input type further below.
  • options: The options are a JSON object that includes the name of the setting and the tooltip. If the setting's input type is a select, i.e. a drop-down list, the options also include the listBoxValues, meaning the various entries in the drop-down list.
    • name: The key for the setting's name. The key is used to display the text stored in the widgets.properties file.
    • tooltip: The key which is used to display a tooltip when hovering above the setting. The text is stored in the widgets.properties file.
  • isVisible: Determines whether the setting is visible. If nothing else is specified, the default value is "true". You can define a JavaScript expression, like an if-condition, that dynamically changes the value of isVisible. An example of how this setting is implemented is described further below.
  • isList: Determines whether the setting can be duplicated. This setting can be used, for instance, to add further slides to the image carousel or add additional entries to the list widget. Please find a detailed explanation of how to implement this setting further below.

Input types

You specify the input type in the settings section of the contentWidgets.json. The following table details the different input types that are available for widget developers.

Input type Explanation
text Provides a one-line text input field into which users can enter any combination of letters and numbers. This input type is used for the title bar and list widgets, for example.
textarea Provides a larger text input field in which users can enter any combination of letters and numbers. This text field is not limited to one line.
number Provides an input field that only registers numbers. Users can also enter decimal numbers and negative numbers.
date Provides an input field for dates. The format of the entered date can be MM.DD.YYYY, MM-DD-YYYY or YYYY-MM-DD.
noteEditor Provides a text editor. This input type is similar to the textarea input, but also includes basic markup options, such as bold, italic and lists.
codeEditor Provides a code editor in which users can enter HTML. This input type is used for the text widget, for example.
checkbox Provides a checkbox. Activating the checkbox sets the value to true; deactivating the checkbox sets the value to false. It's magic.
file Provides a file picker. Clicking the Select file button gives the user access to files stored in the plentymarkets webspace. The file picker is, for instance, used for the ShopBuilder image box and image carousel.
category Provides a category picker via which users can select a category from their pool of item and content categories.
select Provides a drop-down list. If this input type is used for the widgets, the different entries of the drop-down list have to be specified in the options section of the settings. There, the JSON file needs to include listBoxValues underneath name and tooltip, which provides an array with objects that include the keys value, caption and position. The value key specifies the label that is used in the code; the caption retrieves the translation entry from the widgets.properties file and provides the label users see in the ShopBuilder interface; the position determines the order of entries in the drop-down list. If no position is specified for the entries of the drop-down list, the entries are displayed according to their order in the JSON file.
horizontal Provides a horizontal container that allows the display of multiple input fields next to one another. It is used to group multiple settings under one headline. The grouped entries are displayed in the same line and are thereby identifiable as belonging together. It is advised that no more than two settings should be displayed next to each other, since a larger number of entries might negatively impact the layout. The horizontal container is most often used for settings that are expandable by the user via the isList property. The use of the horizontal container necessitates an additional JSON object on the level of the input type, namely the children object. This children object includes the settings you want to group within the container. See the implementation of the isList property below for an illustration of how to use containers.
vertical Provides a vertical container that allows the display of multiple input fields above one another. It is used to group multiple settings under one headline. The grouped entries are indented and are thereby identifiable as belonging together. The vertical container is most often used for settings that are expandable by the user via the isList property. The use of the vertical container necessitates an additional JSON object on the level of the input type, namely the children object. This children object includes the settings you want to group within the container. See the implementation of the isList property below for an illustration of how to use containers.

The isVisible property

In the settings of a widget, you can define the property isVisible. By default, the Boolean value of this property is true, so that the setting is always visible. You can, however, specify a JavaScript expression that makes the setting visible if certain conditions are met. Take a look at an excerpt from the item list widget of the ShopBuilder:

Example of a dynamic use of the isVisible property
      "headlineStyle": {
                  "type": "select",
                  "required": true,
                  "defaultValue": "default-caption",
                  "options": {
                      "name": "Widget.itemListCaptionLabel",
                      "tooltip": "Widget.itemListCaptionTooltip",
                      "listBoxValues": [
                          {
                              "value": "default-caption",
                              "caption": "Widget.itemListCaptionDefault",
                              "position": 0
                          },
                          {
                              "value": "custom-caption",
                              "caption": "Widget.itemListCaptionCustom",
                              "position": 1
                          },
                          {
                              "value": "no-caption",
                              "caption": "Widget.itemListCaptionNoCaption",
                              "position": 2
                          }
                      ]
                  }
              },
      "headline": {
                  "type": "text",
                  "required": false,
                  "isVisible": "headlineStyle === 'custom-caption'",
                  "defaultValue": "",
                  "options": {
                      "name": "Widget.itemListHeadlineLabel",
                      "tooltip": "Widget.itemListHeadlineTooltip"
                  }
              },

The isList property

In the settings of a widget, you can define the property isList. This property allows users to add additional entries of the setting to the interface, e.g. additional list entries for the list widget or additional slides for the image carousel. If the isList property is active, the interface automatically displays add and delete buttons. You can specify a minimum and maximum number of entries, e.g. "isList": "[1, 3]",. The add and delete buttons are greyed out if the specified minimum/maximum number of entries is reached. If you do not specify a minimum/maximum number of entries and instead set the isList value to true, no entry will initially be displayed in the ShopBuilder interface; there will, however, still be an add button for adding entries.
Take a look at an excerpt from the list widget of the ShopBuilder:

Example of the isList property
  "texts": {
            "type": "text",
            "required": false,
            "defaultValue": "",
            "isList": "[1,]",
            "options": {
                "name": "Widget.listTextLabel",
                "tooltip": "Widget.listTextTooltip"
            }
        }

Here, this simple isList property of the texts setting provides the user with the possibility of adding further text input fields, as shown in the following screenshot:


Since no maximum number of entries has been specified, a user can add any number of additional list entries.


You can also use the isList property to add more complex entries, which consist of more than one setting. To do so, you need to include a children JSON object on the level of the isList property. This children object includes the settings you want users to duplicate. We use this functionality of the isList property for the link list widget of the ShopBuilder:

Example of the isList property with multiple settings
  "entries": {
                "type": "vertical",
                "isList": "[1,]",
                "options": {
                    "name": "Widget.linkListEntryLabel"
                },
                "children": {
                    "text": {
                        "type": "text",
                        "required": false,
                        "options": {
                            "name": "Widget.linkListEntryNameLabel",
                            "tooltip": "Widget.linkListEntryNameTooltip"
                        }
                    },
                    "url": {
                        "type": "text",
                        "required": false,
                        "options": {
                            "name": "Widget.linkListEntryUrlLabel",
                            "tooltip": "Widget.linkListEntryUrlTooltip"
                        }
                    }
                }

Default values for input types

The following table specifies which default values can be set for each input type. The defaultValue key is part of the settings of a widget and is located at the same level as type, required and options.

Input type Possible default value
text Any string is valid.
textarea Any string is valid.
checkbox Boolean
date Any string that is formatted as MM.DD.YYYY, MM-DD-YYYY or YYYY-MM-DD.
file Default value is not applicable.
category Default value is not applicable.
number Integer or float.
select The default value is one of the values specified in the listBoxValues of the drop-down list.

Widget PHP class

Ceres already supplies a widget base class that provides the necessary logic for ShopBuilder widgets. This base widget is located in the Ceres file structure under src\Widgets\Helper\BaseWidget.php. You can use and extend the base widget so that you do not have to write the entire logic of the ShopBuilder widget yourself. Every widget PHP class needs to include the functions getPreview and render. The getPreview function serves to render the TWIG for the ShopBuilder user interface; the render function serves to make the contents saved by the user available in the frontend. Take a look at how the Google Maps widget implements the Ceres base widget:

src/Widgets/MapsWidget.php
<?php

  namespace GoogleMapsWidget\Widgets;

  use Ceres\Widgets\Helper\BaseWidget;

  class MapsWidget extends BaseWidget
  {
      protected $template = "GoogleMapsWidget::Widgets.MapsWidget";

      protected function getTemplateData($widgetSettings, $isPreview)
  }

Take a look at the array returned in the Maps widget:

src/Widgets/MapsWidget.php
  if ($lat && $lng && $formatted_address)
        {
            return [
                "geocoding_data" => [
                    "lat" => $lat,
                    "lng" => $lng,
                    "address" => $formatted_address,
                    "apiKey" => $apiKey
                ]
            ];
        }

        return [
            "geocoding_data" => false
        ];

The Widget interface

You do not need to use the Ceres base widget if you want to develop a widget. If you build the widget's logic yourself, you can instead implement the Widget contract, i.e. the Widget interface. The integration of the Widget interface is imperative. The Widget interface is located under Plenty\Modules\ContentBuilder\Contracts\Widget. You integrate the Widget interface by using the Widget interface (Plenty\Modules\ContentBuilder\Contracts\Widget) and including class MyWidgetClass implements Widget in your widget PHP class. Take a look at how the base widget refers to the Widget interface:

src/Widgets/Helper/BaseWidget.php
      <?php

      namespace Ceres\Widgets\Helper;

      use Plenty\Modules\ShopBuilder\Contracts\Widget;
      use Plenty\Plugin\Templates\Twig;

      class BaseWidget implements Widget
    

The Widget Twig

The TWIG template of your widget is located under MyWidget/resources/views/Widgets/MyWidget.twig. Here, data entered by the customer is read and subsequently rendered for the display of the HTML in the frontend. The following code is the TWIG template of the Maps widget:

GoogleMapsWidget/resources/views/Widgets/MapsWidget.twig
    {% if geocoding_data is not empty %}
        <div>
            <google-maps-widget class="widget widget-proportional" google-api-key="{{ geocoding_data.apiKey }}" address="{{ geocoding_data.address }}" :lat="{{ geocoding_data.lat }}" :lng="{{ geocoding_data.lng }}" :zoom="{{ widget.settings.zoom.mobile | default(16) }}"></google-maps-widget>
        </div>
    {% endif %}