TasticFieldHandler

The standard data sources in Frontastic are contained in what we call dataSourceTypes. For example, product-list and content are both dataSourceTypes. If a Frontastic component is defined to retrieve a data source of a certain dataSourceType, it'll be provided with the corresponding data automatically at the time of rendering. You can extend this mechanism with a TasticFieldHandler to provide a custom dataSourceType to a Frontastic component in a similar way.

📘

A TasticFieldHandler has previously been known as a custom field handler or a custom stream handler, but the class has always been TasticFieldHandler. A Tastic is the short name for a Frontastic component in code.

To provide a custom dataSourceType, you'll need to follow the steps below to register the TasticFieldHandler in the Frontastic PHP stack:

  1. Provide an implementation
    In order to handle the custom dataSourceType, you'll need to provide an implementation of Frontastic\Catwalk\FrontendBundle\Domain\TasticFieldHandlerV3 which is the generalized interface for handling all field types and contains 2 methods:
namespace Frontastic\Catwalk\FrontendBundle\Domain;

use Frontastic\Catwalk\ApiCoreBundle\Domain\Context;

abstract class TasticFieldHandlerV3
{
    /**
     * @return string
     */
    abstract public function getType(): string;

    /**
     * @param mixed $fieldValue
     * @return mixed Handled value
     */
    abstract public function handle(Context $context, Node $node, Page $page, Tastic $tastic, $fieldValue);
}

The getType() method should return the type identifier for your dataSourceType.

You must prefix dataSourceTypes with your project ID in the form<project-id>-<dataSourceType>, for example, awesomeshop-stores if your project is awesomeshop and your dataSourceType will provide information about local stores.

The 2nd method handle() performs the actual work of handling your dataSourceType. It's called by Frontastic as soon as a Frontastic component requires a data source of your dataSourceType.

As parameters, it receives the current application Context, Node, Page, Tastic, and the $fieldValue which was configured in the Frontastic studio.

🚧

We're still working on this feature. It's not yet possible to assign values to custom data source fields in the Frontastic studio. But even without this possibility, a custom dataSourceType can be useful to provide data to a Frontastic component.

The handle() method is supposed to return the value of the data source that will then be given to the Frontastic component that required it. It can be an arbitrary value type except for 1 constraint: It must be serializable to JSON (that excludes, for example, resource types). Ideally, you'll return either:

  • A simple data object
  • An array of scalar values of simple array objects
  • Null
  • A combination of the above

You might notice that this is also called TasticFieldHandlerV3. If you previously implemented the TasticFieldHandler or TasticFieldHandlerV2 and it's not using V3, you can use the TasticFieldHandlerAdapter if you don't want to update to using V3.

  1. Register your TasticFieldHandler
    Now that you've implemented your TasticFieldHandler for your dataSourceType, you need to register it in the Symfony service container and apply a tag to it, so it knows what it is.

To do this, go to your services.xml in one of your project bundles (if there isn't a bundle yet, you can create one using, for example, bin/console frontastic:create:bundle MyDecorators, see the Creating a backend bundle article for more information) and then add the tag frontend.tasticFieldHandler to it in a similar way to the below:

<service id="Frontastic\Customer\StorefinderBundle\Domain\StreamHandler">
     <argument type="service" id="Frontastic\Customer\StorefinderBundle\Domain\StorefinderService" />

     <tag name="frontend.tasticFieldHandler"/>
</service>

In this example, a TasticFieldHandler is registered for implementing a store finder. The implementation itself is rather slim and dispatches all work to a service. This gives you the possibility to unleash the full power of Frontastic, Symfony, and your own microservice APIs within a TasticFieldHandler.

  1. Use the dataSourceType
    Now you need to add your TasticFieldHandler to a Frontastic component schema. You must set the type to dataSource and then set the dataSourceType to the type that you created in the first step, for example:
{
    "tasticType": "awesomeshop-store-finder",
    "name": "Storefinder",
    "category": "Awesome Shop",
    "icon": "store",
    "schema": [
	      {
	         "name": "General",
	         "fields": [
		           {
		               "label": "Stores",
		               "field": "dataSource",
		               "type": "dataSource",
		               "dataSourceType": "awesomeshop-stores",
		               "default": null,
		               "required": false
		           }
	         ]
	      }
    ]
}

Configure custom data sources

You can enable custom data sources to be configurable in the Frontastic studio if you've had this feature enabled (contact our Support team if you'd like the customStreamConfig enabled).

To do this, follow the steps below.

For this example, we'll be implementing custom dataSourceType of frontastic/weather, so our Frontastic component schema would look like the below:

{
    //...  
        "label": "Weather data source",
        "field": "weather",
        "type": "dataSource",
        "dataSourceType": "frontastic/weather",
        "required": true
   //...   
}
  1. Open the Frontastic studio, click Developer, and then TasticFieldHandler

  2. Click Create schema, this will open the schema editor

  1. Input your JSON schema, for example, we'll copy the below into the schema editor:

🚧

The field value of customStreamType must match the dataSourceType field from the Frontastic component schema.

{
    "customStreamType": "frontastic/weather",
    "name": "Weather",
    "category": "Content",
    "icon": "wb_sunny",
    "schema": [
        {
            "name": "Location",
            "fields": [
                {
                    "label": "Default city",
                    "field": "city",
                    "type": "string",
                    "default": "",
                    "translatable": false
                },
                {
                    "label": "Default country code",
                    "field": "country",
                    "type": "string",
                    "default": "DE",
                    "translatable": false
                }
            ]
        }
    ]
}

You'll see that we've added 2 fields (Default city and Default country code), these are the fields that are configurable when a Frontastic component is added to a page and would look like the below:

The values configured here will be passed as the $fieldValue to the TasticFieldHandler.

  1. Click Validate to check for errors
  2. Click Publish

You can edit your schema once published if you need to. Click the name of your custom stream to open the right-hand drawer for options:

If you make any changes to your schema, ensure it's backwards compatible before saving.

Accessing the request

❗️

We generally discourage accessing the request from a service, however, until we can offer a better way to access such information it can be used as a workaround.

Since a field handler is a normal Symfony service, you can get any service injected to dispatch work to. This also holds true for the RequestStack which allows you to get your hands on the currently processed request to access URL parameters and much more:

namespace Frontastic\Customer\StorefinderBundle\Domain;

use Symfony\Component\HttpFoundation\RequestStack;

use Frontastic\Catwalk\FrontendBundle\Domain\TasticFieldHandler;
use Frontastic\Catwalk\FrontendBundle\Domain\Stream;
use Frontastic\Catwalk\ApiCoreBundle\Domain\DataRepository;
use Frontastic\Catwalk\ApiCoreBundle\Domain\Context;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi\Locale;

class StoresFieldHandler 
extends TasticFieldHandlerV3
{
    private $service;

    private $requestStack;

    public function __construct(StorefinderService $service, RequestStack $requestStack)
    {
        $this->service = $service;
        $this->requestStack = $requestStack;
    }

    public function getType(): string
    {
        return 'awesomeshop-stores';
    }

    public function handle(Context $context, Node $node, Page $page, Tastic $tastic, $fieldValue)
    {
        $locationId = $this->requestStack->getCurrentRequest()->get('location', null);
        if (!$locationId) {
            return null;
        }

        $locale = Locale::createFromPosix($context->locale);
        return $this->service->getStores($locale->territory, $locationId);
     }
}

In this example, the RequestStack is asked for the current request to fetch the URL parameter location. If this isn't set, the dataSourceType contains nothing. Otherwise, the corresponding domain service is used to determine stores for the dataSourceType.