You too can become a hero and support this Blog at GitHub Sponsors or Patreon.

More about support

This tutorial is based on my TYPO3 extension fluid_styled_slider that can be found on github. It was not uploaded to TER (yet), as I consider it to be more a proof of concept kind of extension but please feel free to use it for your projects anyways. So let's see what is necessary to get our own content element up and running.

Components of a content element based on FSC

First of all we are going to introduce a new content type (CType). Lets call it fs_slider. If you make your own elements you may want to prefix your custom CTypes somehow to prevent naming conflicts. The CType is stored in the tt_content table and - guess what - determines the type of a content element.

Now that we have a name, lets add it to TYPO3.

Top

PageTSconfig

To make our content element appear in the wizard for new content elements, we add it via PageTSconfig. We can configure a few things here:

  • iconIdentifier: The icon API of TYPO3 works with identifiers. There are quite a lot already registered but if you want to use a custom icon you can of course register your own icons. How this is done is described in the documentation or in my post TYPO3 Icon API.
  • title and description: Not much to say here. Pretty obvious.
  • tt_content_defValues: You can set certain fields of the tt_content row to default values.

How does this look for our fs_slider?

mod.wizards.newContentElement.wizardItems.common {
    elements {
        fs_slider {
            iconIdentifier = content-image
            title = LLL:EXT:fluid_styled_slider/Resources/Private/Language/locallang_be.xlf:wizard.title
            description = LLL:EXT:fluid_styled_slider/Resources/Private/Language/locallang_be.xlf:wizard.description
            tt_content_defValues {
                CType = fs_slider
            }
        }
    }
    show := addToList(fs_slider)
}

Our content element is now visible in the wizard for new content elements. But we didn't configure the fields that the editor should see. Let's head over to the TCA and do it.

Top

TCA

We have to extend the tt_content TCA configuration. This stuff is preferably done in the folder Configuration/TCA/Overrides. There we create a file with the same name as the table we want to override stuff of. In our case it's tt_content. So we create Configuration/TCA/Overrides/tt_content.php.

Let's add our new CType first:

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'tt_content',
    'CType',
    [
        'LLL:EXT:fluid_styled_slider/Resources/Private/Language/locallang_be.xlf:wizard.title',
        'fs_slider',
        'content-image'
    ],
    'textmedia',
    'after'
);

Now we configure what fields to show for our CType:

$GLOBALS['TCA']['tt_content']['types']['fs_slider'] = [
    'showitem'         => 
            '--palette--;' . $frontendLanguageFilePrefix . 'palette.general;general,'
            . '--palette--;' . $languageFilePrefix . 'tt_content.palette.mediaAdjustments;mediaAdjustments,'
            . 'pi_flexform,'
            . '--div--;' . $customLanguageFilePrefix . 'tca.tab.sliderElements,'
            . 'assets'
];

So far we can choose the new content element in the wizard and we have a form based on our TCA where we can edit some fields. But there is no output in the frontend. That is because there is no rendering definition for our CType yet. So next stop is our beloved TypoScript.

Top

TypoScript

We just need a few lines to tell TYPO3 how to render our content element. Thanks to fluid_styled_content we just use the lib.contentElement which is not much more than the initialization of a FLUIDCONTENT object. All we have to do is adding our folders to the rootPaths for templates, partials and layouts and then specify a template name.

Note that the lib.contentElement is only available in TYPO3 8LTS and higher. It was first called lib.fluidContent in TYPO3 7 LTS but was renamed in this patch. In TYPO3 8 LTS both libs are available to be compatible with 7LTS but lib.fluidContent will be removed in TYPO3 9 LTS.

OK, let's do it.

lib.contentElement {
  templateRootPaths {
    100 = EXT:fluid_styled_slider/Resources/Private/Templates
  }
  partialRootPaths {
    100 = EXT:fluid_styled_slider/Resources/Private/Partials
  }
  layoutRootPaths {
    100 = EXT:fluid_styled_slider/Resources/Private/Layouts
  }
}

tt_content {
    fs_slider < lib.contentElement
    fs_slider {
        templateName = FluidStyledSlider
        dataProcessing {
            10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
            10 {
                references.fieldName = assets
            }
            20 = DanielGoerz\FluidStyledSlider\DataProcessing\FluidStyledSliderProcessor
        }
    }
}

Additionally we specify some DataProcessors our content should go through before rendering. Oh right, DataProcessors.

Top

DataProcessors

Those are PHP classes that get the data before it is passed to the fluidtemplate. They can manipulate the data or add stuff to be present in the template. The TYPO3\CMS\Frontend\DataProcessing\FilesProcessor for instance resolves all attached media elements for us, so we can access the FileReference objects in the view. DanielGoerz\FluidStyledSlider\DataProcessing\FluidStyledSliderProcessor is a custom processor class to illustrate how this work:

class FluidStyledSliderProcessor implements DataProcessorInterface
{
    /**
     * Process data for the CType "fs_slider"
     *
     * @param ContentObjectRenderer $cObj The content object renderer, which contains data of the content element
     * @param array $contentObjectConfiguration The configuration of Content Object
     * @param array $processorConfiguration The configuration of this processor
     * @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
     * @return array the processed data as key/value store
     * @throws ContentRenderingException
     */
    public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
    {
        // Content of $processedData will be available in the template
        // It can be processed here to your needs.
        $processedData['slider']['width'] = 1000;
        return $processedData;
    }
}

However, DataProcessors are optional. Without any DataProcessors you will always have the content of the database row (tt_content) availabe in your fluid template. And thats where we go next. The fluid template.

Top

The Fluid Template

The last piece in the puzzle is the actual template that receives the data after it was processed by all specified DataProcessors. This is plain fluid as we know (and love) it:

<html xmlns:f="http://xsd.helhum.io/ns/typo3/cms-fluid/master/ViewHelpers">
<div class="fluid-styled-slider" style="width: {slider.width}px; margin: auto">
    <f:for each="{files}" as="file">
        <figure class="fluid-styled-slider-item">
            <f:image image="{file}" height="{data.imageheight}" width="{data.imagewidth}" alt="{file.properties.alt}" title="{file.properties.title}"/>
            <f:if condition="{file.properties.description}">
                <figcaption>{file.properties.description}</figcaption>
            </f:if>
        </figure>
    </f:for>
</div>
</html>

Of course we can use Layouts and Partials here as well. Note how {data} contains the tt_content row from the rendered content element. {files} is added by the TYPO3\CMS\Frontend\DataProcessing\FilesProcessor and contains the attached media as proper objects. {slider.width} is added by our own DataProcessor.

Now everything is put together. But there is still one thing we could do: enhance the preview of our content element in the backend page module.

Top

Optional: Preview in Page Module

There are two notable possibilities to achieve this:

Fluid template via PageTSconfig

We can simply specify a fluid template to be rendered as preview in PageTSconfig:

mod.web_layout.tt_content.preview.fs_slider = EXT:fluid_styled_slider/Resources/Private/Templates/Preview/FluidStyledSlider.html

This template will receive all fields of the tt_content row directly. So {header} contains the header, {bodytext} contains the bodytext and so on.

tt_content_drawItem Hook

If we want to do more sophisticated stuff like getting child records resolved, we can register to the tt_content_drawItem hook like this:

// Register for hook to show preview of tt_content element of CType="fluid_styled_slider" in page module
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['fluid_styled_slider']
    = \DanielGoerz\FluidStyledSlider\Hooks\FsSliderPreviewRenderer::class;

Our class has to implement the \TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface.

namespace DanielGoerz\FluidStyledSlider\Hooks;

class FsSliderPreviewRenderer implements PageLayoutViewDrawItemHookInterface
{
    /**
     * Preprocesses the preview rendering of a content element of type "fs_slider"
     *
     * @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
     * @param bool $drawItem Whether to draw the item using the default functionality
     * @param string $headerContent Header content
     * @param string $itemContent Item content
     * @param array $row Record row of tt_content
     * @return void
     */
    public function preProcess(PageLayoutView &$parentObject, &$drawItem, &$headerContent, &$itemContent, array &$row)
    {
        if ($row['CType'] === 'fs_slider') {
            $itemContent .= '<h3>Fluid Styled Slider</h3>';
            if ($row['assets']) {
                $itemContent .= $parentObject->thumbCode($row, 'tt_content', 'assets') . '<br />';
            }
            $drawItem = false;
        }
    }
}

Whatever we write into $itemContent will be rendered in the page module inside our content element.

Top