tollwerk/tw-componentlibrary

Component library features for your TYPO3 project

v1.2.1 2021-03-18 16:46 UTC

README

Component library features for your TYPO3 project

About

This TYPO3 extension

  1. encourages and supports the development of self-contained, re-usable function and design modules ("components") along with your TYPO3 project and
  2. exposes these components via a JSON API so that component or pattern library, testing and styleguide tools like Fractal can extract and render your components individually and independently of your TYPO3 frontend.

Usage

Installation

Install the extension into your composer mode TYPO3 installation:

cd /path/to/site/root
composer require tollwerk/tw-componentlibrary

Alternatively, download the latest source package and extract its contents to typo3conf/ext/tw_componentlibrary.

Enable the extension via the TYPO3 extension manager.

Component types

The extension distiguishes 5 main types of components:

  • TypoScript components: Require a TypoScript path with an object definition to render (e.g. lib.menu, defined as HMENU).
  • Fluid template components: Require a Fluid template file (e.g. an Extbase / Fluid partial or standalone Fluid template) and an optional set of rendering parameters / variables.
  • Extbase plugin components: Require an Extbase controller, a controller action to call and possibly a list of parameters to pass to the controller action.
  • Content components: Convenient way to render existing TYPO3 content elements as components. Works with both default and custom content elements.
  • Form components: Hook into the TYPO3 Form Framework and treat standard and custom form elements as individual components.

The extension doesn't impose any requirements towards your TypoScript, Fluid templates or directory layout except that every component must be individually addressable. That is, you cannot expose e.g. just a part of a rendered Fluid template as a component. In that case, you'd have to outsource the desired part as a partial file of its own.

Declaring components

You can declare components either manually (described below) or using the command line component kickstarter .

  1. Create and install an empty TYPO3 extension that is going to hold your component definitions. Alternatively, pick an existing extension you've got write access to. It's possible to have multiple of these component provider extensions. If you're using and maintaining custom extensions anyway, I recommend using these for providing components on an per-extension basis.
  2. Create a Components directory inside the provider extension's root directory. In case you're running TYPO3 in composer mode, make sure this directory is properly mapped to the Component namespace. You might have to add something like this to your main composer.json file (replace the vendor name, extension key and paths with appropriate local values):
    {
        "autoload": {
            "psr-4": {
                "Vendor\\ExtKey\\Component\\": "web/typo3conf/ext/ext_key/Components/"
            }
        }
    }
  3. Especially if you're going to have a lot of components it's advisable to organise them in a hierarchical structure. Create a suitable directory layout below your Components folder to reflect this hierarchy. Use UpperCamelCase directory names without spaces, underscores and special characters — external systems can use the word boundaries for transforming these e.g. into a navigation tree. Your directory layout could look something like this:
    ext_key
    `-- Components
        |-- Composite
        |   `-- Form
        `-- Generic
            |-- Form
            `-- Typography
  4. Start creating component declarations by creating PHP class files at appropriate locations in your directory layout. Each file must declare exactly one class extending one of the main component type base classes (see below). The file and class names must be identical, should be in UpperCamelCase and must end with the suffix …Component (respectively …Component.php for the file name). The part before …Component will be used as the component by external tools. In addition to the base version of a component you may provide variants of that component by adding an underscore and appendage to the file and class name. System like Fractal can use this for a grouped display of component variants:
    ext_key
    `-- Components
        `-- Generic
            `-- Form
                |-- ButtonComponent.php
                |-- Button_IconLeftComponent.php
                |-- Button_IconRightComponent.php
                |-- Button_LinkComponent.php
                |-- Button_LinkIconLeftComponent.php
                `-- Button_LinkIconRightComponent.php
  5. Use the configure() method of your component declaration to specify the individual component properties. While each component type brings a small set of specific properties (see below), the majority of instructions is common to all component types.

TypoScript component

Use the setTypoScriptKey() method to specify the TypoScript object that renders the component output. The key will be evaluated for the page ID specified by the $page property (see common properties).

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\TypoScriptComponent;

/**
 * Example TypoScript component
 */
class ExampleTypoScriptComponent extends TypoScriptComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setTypoScriptKey('lib.example');
    }
}

Fluid template component

Use the setTemplate() method to specify the Fluid template file that renders the component output. Specify rendering parameters using the setParameter() method (works just like $this->view->assign('param', 'value') in Extbase controller actions).

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\FluidTemplateComponent;

/**
 * Example Fluid template component
 */
class ExampleFluidTemplateComponent extends FluidTemplateComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setTemplate('EXT:ext_kex/Resources/Private/Partials/Component.html');
        $this->setParameter('param', 'value');
    }
}

For convenience reasons, Fluid template components can read in external JSON files for setting parameters. JSON parameter files must be named after their associated component files and lie in the same directory:

|-- ButtonComponent.json
`-- ButtonComponent.php

The key / value pairs inside the JSON file will be used as input for the setParameter() method.

Extbase plugin component

To configure an Extbase plugin component, use the setExtbaseConfiguration() method to specify the plugin name, the controller class name and the controller action to be called. The output will be rendered using the Fluid template associated with the controller action. You can specify action arguments via setControllerActionArgument() and simulate controller controller settings via setControllerSettings().

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\ExtbaseComponent;

/**
 * Example Extbase plugin component
 */
class ExampleExtbaseComponent extends ExtbaseComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setExtbaseConfiguration('PluginName', MyCustomController::class, 'action');
        $this->setControllerActionArgument('param', [1, 2, 3]);
        
        $overrideExistingSettings = true;
        $this->setControllerSettings(['category' => 1], $overrideExistingSettings);
    }
}

Content component

Use the setContentRecordId() method to specify the UID of an existing content element (tt_content table) that you want to render as a component (please make sure the content element is accessible / isn't hidden by any means).

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\ContentComponent;

/**
 * Example content component
 */
class ExampleContentComponent extends ContentComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setContentRecordId(123);
    }
}

Form component

Inside your component definition, you must use the createElement() method to instantiate a renderable form element (TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable). This is basically the same object that you get via the Form Framework's Page::createElement() method during API form composition. You can further configure the form element using its native methods (as you would do in a form factory class).

To simulate a form validation error for your element, simply call addElementError($message) (the element must have been instantiated prior to adding errors). You can registrer multiple errors.

It is advised but not mandatory to register the form field's Fluid template using setTemplate() for a nicer display in your component library.

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\FormComponent;

/**
 * Example form component
 */
class ExampleTextComponent extends FormComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setTemplate('EXT:example/Resources/Private/Partials/Form/Text.html');
        
        $element = $this->createElement('Text', 'name');
        $element->setProperty(
            'fluidAdditionalAttributes',
            [
                'placeholder' => 'John Doe',
            ]
        );
        
        $this->addError('Please enter a name');
    }
}

Common properties

There's a bunch of component properties and methods that are common to all component types. Some of them are controlled via TypoScript constants, others by overriding component class properties or calling shared configuration methods.

TypoScript constants

Use the TypoScript constants to globally configure the HTML documents wrapped around your components when rendered by external systems. You can add base files, web fonts and libraries this way (global.css, jQuery, etc.). All resources can be referenced absolutely (starting with http:// or https://), relatively (/fileadmin/css/...) or using a TYPO3 extension prefix (EXT:ext_key/Resources/...).

TypoScript-Namensraum: plugin.tx_twcomponentlibrary.settings

Component properties

There are a couple of protected object properties you can override in your component classes to alter the default behaviour.

  • ¹ \TYPO3\CMS\Extbase\Mvc\Web\Request
  • ² \Tollwerk\TwComponentlibrary\Component\Preview\TemplateInterface
  • ³ \Tollwerk\TwComponentlibrary\Component\Preview\FluidTemplate

Example usage:

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\FluidTemplateComponent;

/**
 * Example Fluid template component
 */
class ExampleFluidTemplateComponent extends FluidTemplateComponent
{
    /**
     * Component status
     *
     * @var int
     */
    protected $status = self::STATUS_READY;
    
    /**
     * Label
     *
     * @var string
     */
    protected $label = 'Button with icon';
    
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setTemplate('EXT:ext_kex/Resources/Private/Partials/Button/Icon.html');
    }
}
Configuration methods

Use the following methods to further configure your components.

<?php

namespace Tollwerk\TwComponentlibrary\Component;

use \Tollwerk\TwComponentlibrary\Component\ComponentInterface;

/**
 * Abstract component
 */
abstract class AbstractComponent implements ComponentInterface
{
    /**
     * Add a notice
     * 
     * Fractal displays the notice in the "Notes" tab (only available for the default component variant)
     *
     * @param string $notice Notice
     */
    protected function addNotice($notice) {}
    
    /**
     * Set a custom preview template
     * 
     * Overrides the default preview template facilitating the `stylesheets`, `headerScript` and `footerScript` TypoScript constants
     *
     * @param TemplateInterface|string|null $preview Preview template
     */
    protected function setPreview($preview) {}
}
Preview templates

By default, the builtin FluidTemplate is used for rendering components for external systems. You can use your custom template as long as you implement the TemplateInterface. The default FluidTemplate supports a couple of configuration methods:

<?php

namespace Tollwerk\TwComponentlibrary\Component\Preview;

use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Basic preview template
 *
 * @package Tollwerk\TwComponentlibrary
 * @subpackage Tollwerk\TwComponentlibrary\Component
 */
class FluidTemplate implements TemplateInterface
{
    /**
     * Add a CSS stylesheet
     * 
     * Will be added in the `<head>` section of the preview template
     *
     * @param string $url CSS stylesheet URL
     */
    public function addStylesheet($url){}

    /**
     * Add a header JavaScript
     * 
     * Will be added in the `<head>` section of the preview template
     *
     * @param string $url Header JavaScript URL
     */
    public function addHeaderScript($url){}

    /**
     * Add a header inclusion resource
     * 
     * Path to a file to be included in the in the `<head>` section of
     * the preview template. Make sure to wrap the content e.g. in a    
     * `<script>` or `<style>` element. 
     *
     * @param string $path Header inclusion path
     */
    public function addHeaderInclude($path) {}

    /**
     * Add a footer JavaScript
     * 
     * Will be added just before the closing `</body>` element of the preview template
     *
     * @param string $path Footer JavaScript URL
     */
    public function addFooterScript($url) {}

    /**
     * Add a footer inclusion resource
     * 
     * Path to a file to be included in the in the `<head>` section of
     * the preview template. Make sure to wrap the content e.g. in a    
     * `<script>` or `<style>` element.
     *
     * @param string $path Footer inclusion path
     */
    public function addFooterInclude($path) {}
}

Example usage:

<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\FluidTemplateComponent;

/**
 * Example Fluid template component
 */
class ExampleFluidTemplateComponent extends FluidTemplateComponent
{
    /**
     * Configure the component
     */
    protected function configure()
    {
        $this->setTemplate('EXT:ext_kex/Resources/Private/Partials/Component.html');
        
        // Configure the preview template
        $this->preview->addHeaderInclude('fileadmin/js/icons-loader.html');
        $this->preview->addStylesheet('EXT:ext_key/Resources/Public/Css/example.min.css');
    }
}
Documentation

You can add documentation to your components in two ways:

  1. By using addNotice($str) inside the configure() method of a component.

  2. By creating a documentation directory named after the component (without variant suffix) and dropping documentation files in there. When the component is extracted,

    • it will first be checked if a file named index.md, readme.md or <component>.md exists inside that directory. If it does, this file will be used as the main documentation.
    • If there's no such documentation index, a simple Markdown listing will be generated, enumerating all the files in the directory. Valid image files will be embedded as images, otherwise the linked file name will be shown.

    Example:

    |-- Button
    |   |-- index.md
    |   `-- screenshot.jpg
    `-- ButtonComponent.php

    During component extraction, linked files in the documentation (including images) will be rewritten to their root relative path starting at your TYPO3 main directory.

Directory configuration

As of version 0.3.2, you can add directory specific configuration values to your component directory layout. When exporting a component, the values for each of its parent directories will be exported as well. Add configuration values to a directory by creating a file called local.json containing an arbitrary JSON data structure, e.g.

{
  "dirsort": 4,
  "label": 'Icons & images'
}

It's up to the consuming application to use these values for particular purposes. The TYPO3-Fractal-bridge for example uses the dirsort value for ordering the directories other than alphabetically and label for component folder names that couldn't be achieved via real file system directory names.

Command line component kickstarter

The extension provides a command line component kickstarter which let's you scaffold new components with ease. It's implemented as an extbase CLI command:

php vendor/bin/typo3 component:create Test/Button fluid tw_tollwerk Tollwerk

The command takes 4 arguments (in the following order; you can also enter it with explicit argument names):

  • --name: Directory path and name of the component within the Components directory of your provider extension.
  • --type: Component type, must be one of fluid, typoscript, extbase, content or form
  • --extension: Provider extension key
  • --vendor: Provider extension vendor name

The above command will kickstart a Fluid component named Button at this location inside the tw_tollwerk extension:

Components/
`-- Test
    `-- ButtonComponent.php

If you're mostly adding components to a particular provider extension, you can simplify the process by defining a default provider extension along with its corresponding default vendor name. To do so, please enter the extension configuration in the extension manager and provide these two settings:

Default provider extension settings

You can then omit the --extension and --vendor arguments when calling the CLI command:

php vendor/bin/typo3 component:create Test/Button fluid

Extracting components

The extension adds an Extbase CLI command that lets you discover the declared components in JSON format on the command line:

vendor/bin/typo3 component:discover

Sample result:

[
    {
         "status": "wip",
         "name": "My Widget",
         "variant": null,
         "label": "Alternative component label",
         "class": "Vendor\\ExtKey\\Component\\MyWidgetComponent",
         "type": "fluid",
         "valid": true,
         "parameters": [],
         "config": "EXT:ext_key/Resources/Private/Partials/Widget.html",
         "template": "<f:link.action action=\"...\">...</f:link.action>",
         "extension": "t3s",
         "preview": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>{{ _target.label }} \u2014 Preview Layout</title></head><body>{{{ yield }}}</body></html>",
         "request": {
             "method": "GET",
             "arguments": {
                 "L": 0,
                 "id": 1
             }
         },
         "path": [
             "Demo"
         ]
     }
 ]

Use the exposed JSON data in any way that makes sense for you. The Fractal-TYPO3 bridge, for instance, builds an explorable component library out of it.

Important note: As of version 1.0, the components declared by an extension will only be exported if the TypoScript setup of the extension features a features.exportComponents key with value 1. This is expected to be found at plugin.tx_myext in the overall TypoScript configuration, where tx_myext is built from the prefix tx_ and your extension key without underscores:

plugin.tx_myext {
    features {
        exportComponents = 1
    }
}

You can make this configurable via the extension constants. The component library extension comes with a language label you can use for that (in constants.typoscript):

# cat=plugin.tx_myext/enable/a; type=boolean; label=LLL:EXT:tw_componentlibrary/Resources/Private/Language/locallang_core.xlf:enable.export
exportComponents =

Rendering components

The extension introduces the new type parameter value 2400 which is used for calling TYPO3 as rendering engine for single components. The request

http://example.com/?type=2400&tx_twcomponentlibrary_component%5Bcomponent%5D=Vendor%5CExtKey%5CComponent%5CMyWidgetComponent

will exclusively render the component \Vendor\ExtKey\Component\MyWidgetComponent and return the generated source code without surrounding page level HTML.

Component dependency graphs

The extension introduces the new type parameter value 2401 which is used for dynamically rendering component dependency graphs. The request

http://example.com/?type=2401&tx_twcomponentlibrary_component%5Bcomponent%5D=Vendor%5CExtKey%5CComponent%5CMyWidgetComponent

will create a single component dependency graph like this one:

Single component dependency graph

To create an overview graph of all registered components, simply omit the component argument. The URL

http://example.com/?type=2401

will create a graph like this one:

Overview component dependency graph

You can use these graphs for documentation purposes or to display them in the Fractal component library tool.

Utilities

SvgIconUtility

The included class \Tollwerk\TwComponentlibrary\Utility\SvgIconUtility helps you with listing SVG graphics in backend forms and custom applications. Provide a comma-separated list of directories using the TypoScript constant plugin.tx_twcomponentlibrary.settings.iconDirectories. The following two methods will then find and return all *.svg files in these directories.

SvgIconUtility::getIcons(int $id = 1, int $typeNum = 0): array

Returns an array of SVG graphics, with the file base names as values and the absolute file paths as keys. Optionally provide a page ID and type for which the icon directories should be determined.

SvgIconUtility::getIconTcaSelectItems(int $id = 1, int $typeNum = 0): array

Similar to getIcons(), but returns the graphics ready for use in TCA select fields.

TYPO3 Backend integration

The extension provides a simple integration so that you can update your component library from within the TYPO3 backend. So far, only the Fractal component library is supported. To enable Fractal support, follow these simple steps:

  • Enter the extension configuration from the extension manager and enable the use of Fractal:

    Enable Fractal support

  • Provide the absolute path to a shell script that is able to run the necessary steps to update and restart your component library. You'll find an example file at Resources/Private/Script.

    Updating Fractal with a shell script is easy: Assuming you're using the TYPO3 Fractal bridge, simply cd into your Fractal instance directory and issue fractal update-typo3. It depends on your specific setup if there are some extra steps involved to re-initialize Fractal.

  • Make sure the shell script is executable for the user your web server runs under and use sudo inside the script where necessary (might require configuring appropriate sudo privileges which is beyond this documentation). Be careful — the shell script could harm your system if not crafted properly!

  • As soon as you enabled support for a component library and the shell script is in place, you should find an additional menu item in the cache menu pulldown:

    Cache menu pulldown extension

  • If done properly, you can now update and re-initialize your component library easily from within the TYPO3 backend. Depending on the number of components in your system the process of updating might take a while.

Contributing

Found a bug or have a feature request? Please have a look at the known issues first and open a new issue if necessary. Please see contributing and conduct for details.

Security

If you discover any security related issues, please email joschi@kuphal.net instead of using the issue tracker.

Credits

License

Copyright © 2018 Joschi Kuphal / joschi@kuphal.net. Licensed under the terms of the GPL v2 license.