The extension provides a full rendering environment for Handlebars templates
within TYPO3 CMS. All core features of
Handlebars.js are supported by the usage of the third-party library
PHP Handlebars.
Its main use is to seamlessly integrate Handlebars templates into TYPO3 without
the need to modify these templates again for output in TYPO3.
Features
Full rendering environment for Handlebars templates
Native support for custom Handlebars helpers
Easy to extend and customize
Built on dependency injection for better performance and maintainability
Integration with TYPO3's cache framework for compiled templates
Support
There are several ways to get support for this extension:
Otherwise, template paths are not evaluated in the right order and might get
overridden.
Configuration
Much of the extension configuration takes place via the service
configuration using Services.yaml. You should be familiar
with the service configuration and
Dependency injection in
general to be able to implement most of the necessary configurations
correctly.
Learn here which configurations are necessary and learn more about
how to use the extension:
The Handlebars cache is called handlebars and is registered by default
when installing and activating the extension. Its cache backend is not
configured explicitly and therefore uses the default setting (database
cache).
You can specify a different cache backend as follows:
The HandlebarsExtension
takes care of all template paths. They will be merged and then added to the
service container resulting in the following parameters:
%handlebars.templateRootPaths%
%handlebars.partialRootPaths%
You can reference those parameters in your custom configuration to use the
resolved template paths in your services.
The drawback of this configuration is that it is applied to the whole TYPO3
instance since there exists only one service container for the whole system.
In case you need different template paths for specific parts of your
installation, take a look at the following configuration method that uses
TypoScript.
A more flexible configuration method is the usage of TypoScript. This way you
can override the configuration from the service container (as described above)
which allows you to define different template root paths and partial root
paths for specific parts of the system.
Configuration of template paths in multiple extensions
If template paths are defined multiple times (e.g. within various
extensions that provide Handlebars templates), the ones with the
same numeric key will be overridden by the last defined one.
Default data
It may happen that several (or all) templates require recurring, consistent
data. This can be, for example, paths to assets or other firmly defined
content such as e-mail addresses or names/labels/etc.
The standard HandlebarsRenderer provides the possibility to specify an
array $rootContext for this purpose. This data is merged with the
concrete render data during each rendering and passed on to the Renderer.
Configuration
In your Services.yaml file, add the following lines:
All data will then be available as service parameter %handlebars.variables%
within the service container. So you can use it everywhere you need it in
your Services.yaml file.
Overwrite default data
If in certain cases it is necessary to overwrite a value from the default data,
it can simply be passed as an additional value in the Presenter:
Rendering of Handlebars templates can be done with additional debugging. This
results in individual tags being provided with debug output, which can be
used to better localize errors, especially during development, and thus fix
them more efficiently.
Warning
Note that debugging only applies in the default Renderer. If a custom
Renderer is implemented and used, then this functionality is not available
out of the box.
This section describes basic and advanced functionalities and their
possible applications. Here you can learn how to use the individual
components of the extension optimally and how to reuse them in your
own components.
This page shows how to create a single module and which components are
necessary to process data from TYPO3, prepare it and finally output it
in the frontend using a Handlebars template.
The CType header serves as an example.
Note
This example only describes the standard process for creating a new
module. Further possibilities are described in the section
Extended usage.
Example
Note
All examples are written in PHP 7.4.
Preparations
Before the actual components are created, some preliminary work is
necessary.
Add the extension as dependency to your extension as described
here.
Services.yaml
Create a basic Services.yaml file in your extension. Make sure
to read and follow the guidelines described at
Dependency injection.
TypoScript configuration
Create a TypoScript configuration file and include it in your site's root
template. Make sure to include static TypoScript from
EXT:fluid_styled_content.
Create a new DataProcessor
Each DataProcessor must implement the
CPSIT\Typo3Handlebars\DataProcessing\DataProcessor interface.
There's already a default DataProcessor in place that provides some
basic logic and is required in case you want to develop components
like described on this page. Just extend your DataProcessor from
CPSIT\Typo3Handlebars\DataProcessing\AbstractDataProcessor and
implement the abstract method render():
Next, a DataProvider must be created that prepares the module's data and makes
it available to the DataProcessor again. Each DataProvider must implement the
CPSIT\Typo3Handlebars\Data\DataProvider interface.
As you can see, the DataProvider returns an instance of a so-called
ProviderResponse object. This holds the prepared data for higher-level transfer
within the rendering process. Create it in the associated namespace:
To complete the rendering process, a new Presenter called HeaderPresenter
must be created. It must implement the CPSIT\Typo3Handlebars\Presenter\Presenter
interface; furthermore, an CPSIT\Typo3Handlebars\Presenter\AbstractPresenter is
already available with the default Renderer already specified as a dependency.
# Classes/Presenter/HeaderPresenter.phpnamespaceVendor\Extension\Presenter;
useCPSIT\Typo3Handlebars\Data\Response\ProviderResponse;
useCPSIT\Typo3Handlebars\Exception\UnableToPresentException;
useCPSIT\Typo3Handlebars\Presenter\AbstractPresenter;
classHeaderPresenterextendsAbstractPresenter{
publicfunctionpresent(ProviderResponse $data): string{
if (!($data instanceof HeaderProviderResponse)) {
thrownew UnableToPresentException(
'Received unexpected response from provider.',
1613552315
);
}
// Use data from ProviderResponse or implement custom logic
$renderData = $data->toArray();
return$this->renderer->render(
'Extensions/FluidStyledContent/Header',
$renderData
);
}
}
Copied!
Set up TypoScript configuration
Finally, you have to configure via TypoScript that instead of the default Fluid
rendering the special Handlebars rendering should be executed for all content
elements of the CType header.
For this purpose, each DataProcessor provides a method
process(string $content, array $configuration) as entry point.
# Configuration/TypoScript/setup.typoscript
tt_content.header = USER
tt_content.header.userFunc = Vendor\Extension\DataProcessing\HeaderProcessor->process
Copied!
Optional: Create custom Helpers
If your templates use custom Helpers, you will need to create them
additionally. Read Create a custom Helper to learn what options are
available for creating your own Helpers.
Flush caches
Changes were made to the service configuration and also the rendering was
overwritten using TypoScript. Therefore, it is now necessary to ensure that
the caches are flushed and the service container is reconfigured.
Take a look at the chapter Basic usage to get an insight into
the basic applicability of the individual components. This chapter only
covers advanced use cases, which require a basic understanding of all
components.
Several advanced use cases are explained on the following pages. These are
primarily aimed at specifically extending the standard components.
If it is not necessary to further process the data transferred
by TypoScript, a SimpleProcessor is available. This passes the
transferred data directly to the Renderer without any further
interaction.
As you can see, the SimpleProcessor is directly addressed as
userFunc. It already provides the necessary functionality,
but can of course also be extended with your own requirements.
Furthermore it is necessary to indicate a template path with (this normally
happens in the Presenter). The SimpleProcessor throws an exception, if
the template path is not set or invalid.
In some cases, the presence of the current ContentObjectRenderer may
be necessary in the DataProvider. For this case a
CPSIT\Typo3Handlebars\ContentObjectRendererAware interface is
provided, which can be used in combination with the trait
CPSIT\Typo3Handlebars\Traits\ContentObjectRendererAwareTrait.
Usage
Transfer of the ContentObjectRenderer
If the rendering process is triggered via TypoScript, the
DataProcessor is automatically assigned the current instance of
the ContentObjectRenderer (via the cObj property). It can
then pass this to the DataProvider:
It is not always necessary or desired to automatically register all
related components based on the registration of a DataProcessor.
In some cases, for example, it may be necessary to use a component
more than once, e.g. if several modules use the same template and
thus one Presenter can be used for all those modules.
To be able to cover this special case, it is possible to specify
a concrete DataProvider or Presenter for individual DataProcessors
in the Services.yaml file.
Warning
Use of theAbstractDataProcessorrequired
The following examples are only applicable to DataProcessors that
extend the AbstractDataProcessor, since it provides the necessary
methods. These are not part of the DataProcessor interface.
Example 1: Shared Presenter
Assume that there are two modules Highlight Box and Highlight Text,
which are both rendered using the same Handlebars template. The data
provision is still done via two separate DataProviders.
In the Services.yaml file, we register both DataProcessors, but
specify a concrete method call setPresenter(). This is normally
called automatically if it is not set manually.
Both DataProcessors are now injected with the same Presenter, while all
other components continue to act independently.
Example 2: Shared DataProvider
The same procedure can be used if a common DataProvider is to be used instead
of a common Presenter. In this case the method call must be setProvider():
All components are described using interfaces. This makes it easy to
exchange individual components. The following illustrates how such a
use case can look.
Custom Renderer
The CPSIT\Typo3Handlebars\Renderer\Renderer interface describes a
Renderer. A distinction must be made as to whether the custom Renderer
is to be used for all components or only for individual variants.
Important
If you want your custom Renderer to be autoconfigured with all globally
registered Helpers, make sure to tag it with handlebars.renderer and
implement the interface CPSIT\Typo3Handlebars\Renderer\HelperAwareInterface.
Global replacement
If the custom Renderer is to be used equally for all components, it can
simply be registered as a global replacement for the default Renderer in
the Services.yaml file.
Note that if you use your own Renderer, you are responsible for providing
it with the necessary dependencies. These include the cache, TemplateResolver,
and the default data (if needed). This is already configured with the default
Renderer.
Single replacement
A custom Renderer can also be used only for specific modules. In this case,
it replaces the default Renderer for the concrete Presenters.
Warning
Use of theAbstractPresenterrequired
The following example is only applicable to Presenters that extend the
AbstractPresenter, since it holds the required dependency in its constructor.
This is not part of the Presenter interface.
A standard TemplateResolver exists for resolving template paths for templates
and partials. This is used in the default Renderer, but a custom
TemplateResolver can also be used for specific purposes.
To use a custom TemplateResolver, a corresponding class is created that
implements the CPSIT\Typo3Handlebars\Renderer\Template\TemplateResolver
interface:
Any Helper implemented in the frontend via JavaScript and used in Handlebars
templates must also be replicated in PHP. For this purpose, the extension
provides a CPSIT\Typo3Handlebars\Renderer\Helper\Helper interface.
Note
In the following examples, a Helper is created with the identifier greet.
The associated class name is Vendor\Extension\Renderer\Helper\GreetHelper.
Implementation options
There are several ways to implement Helpers. To understand how Helpers are
resolved in the Renderer, it is worth taking a look at the responsible
Trait.
Basically every registered Helper must be
callable. This means
that both globally defined functions and invokable classes as well as class
methods are possible. See what options are available in the following examples.
Global function
Any globally registered function can be used as a Helper, provided that it
is recognized by the registered PHP autoloader.
The most convenient variant is the implementation of a class and an
associated method. This also allows, for example, the use of dependency
injection, provided that a corresponding registration of the Helper
takes place via the service container.
Helpers can be registered either via configuration in the Services.yaml
file or directly via the Renderer (if the default Renderer is used).
Automatic registration via the service container (recommended)
The recommended way to register Helpers is to use the global service container.
This ensures that the Helpers are always available in the Renderer. To achieve
this, add the following lines to your Services.yaml file:
Note that registration using a tag is available only for the implementation as
Class method (recommended). For all other implementations, a direct method call must
currently still be registered:
# Configuration/Services.yamlservices:handlebars.renderer_extended:parent:handlebars.renderercalls:# Global function-registerHelper:['greet','greet']# or invokable class-registerHelper:['greet','@Vendor\Extension\Renderer\Helper\GreetHelper']CPSIT\Typo3Handlebars\Renderer\Renderer:alias:'handlebars.renderer_extended'
Copied!
The identifier configuration specifies how the Helper should be named and
referenced. It will then be used in Handlebars templates when calling the
registered Helper. An example template could look like this:
{{ greet id=1 }}
Copied!
It will result in: Hello, Bob!
method defines the class method to be used as a callback in combination with
the configured class name. The above example leads to the registration of a
new Helper named greet which provides a callback
Vendor\Extension\Renderer\Helper\GreetHelper::greetById.
Manual registration
In addition to automatic registration, Helpers can also be registered
manually at any time. For this purpose it is necessary to initialize the
Renderer beforehand. Then a Helper can be registered with the
registerHelper() method and thus made available in the Renderer:
There are several events available that allow to influence the rendering.
Event listeners must be registered via the service configuration. More
information can be found in the official TYPO3 documentation.
BeforeRenderingEvent
This event is triggered directly before the compiled template is rendered along
with the provided data. This allows the data to be manipulated once again before
it is passed to the Renderer.
After the Renderer has completely rendered the template using the provided data,
the AfterRenderingEvent is triggered. This can be used to subsequently
influence the rendering result.
The Handlebars extension integrates with TYPO3's modern AssetCollector API to manage
JavaScript and CSS assets in your frontend rendering. This feature allows you to
register external files and inline code directly through TypoScript configuration.
TYPO3 13+ provides the AssetCollector service as the recommended way to register
frontend assets. The Handlebars extension fully supports this API through the
assets configuration in HANDLEBARSTEMPLATE content objects.
Benefits
Deduplication: Assets with the same identifier are automatically merged across the page
Priority Control: Control rendering order with priority options
CSP Support: Automatic nonce injection for Content Security Policy compliance
Modern API: Uses TYPO3's recommended approach (not deprecated PageRenderer methods)
Asset Types
The AssetCollector API supports four distinct asset types, all fully supported by this extension:
External JavaScript Files - Link to external .js files
Inline JavaScript Code - Embed JavaScript directly in the page
External CSS Files - Link to external .css files
Inline CSS Code - Embed styles directly in the page
JavaScript Files
Register external JavaScript files using the javaScript configuration:
Automatic asset deduplication across multiple content elements
Better control over rendering order via priority
Content Security Policy nonce support
Type-safe attribute handling
Future-proof implementation using TYPO3's recommended API
Note
Legacy methods (headerAssets and footerAssets)
remain fully supported for backward compatibility. You can use both modern
and legacy methods in the same content object.
Best Practices
Unique Identifiers
Always use unique, namespaced identifiers across your entire page to avoid conflicts:
DataProviders are used to provide relevant data for processing by a
DataProcessor. The data source is irrelevant: data can be provided both
from local sources such as the TYPO3 database and from external sources
such as APIs.
Thus, DataProviders fulfill the part of the Model in the MVC pattern.
The data supplied is not necessarily only applicable to a specific template,
but serves the general usability of all components involved in the rendering
process of a parent module.
DataProcessors are the entry point into the entire rendering process.
They fetch the data from the DataProvider, process it and pass it on to
the Presenter.
This is where the entire processing logic takes place. Thus, DataProcessors
fulfill the part of the Controller in the MVC pattern. They are usually
addressed directly via TypoScript.
In Presenter, the supplied data is prepared for rendering a specific
Handlebars template. Dependent templates can also be selected based on
the supplied data if multiple template variants are possible.
In the MVC pattern, the Presenter takes on a transitional role
between the DataProcessor (Controller) and the Renderer (View).
The template is finally rendered in the Renderer. For this purpose,
the template is compiled and filled with data from the Presenter. The
resulting output is returned and completes the rendering process.
The Renderer is thus responsible for the View in the context of the
MVC pattern. The compiled templates used for this are usually cached.
Helpers describe a simple way to bring custom PHP functionality to
Handlebars templates. They are similar to ViewHelpers used in Fluid
templates.
The default Renderer is able to handle various Helpers. There are
few limitations to the successful use of Helpers:
The associated callable (class method/function) must be publicly
callable
If the callable is a class method, it must be loadable by the
registered PHP class autoloader
Helpers play a rather subordinate role in the MVC pattern, since
they are not explicitly involved. However, since they are implicitly
involved in the output of a template, they most likely take the role
of the View.
Whenever a template is rendered by the Renderer, it must first be
resolved, e.g. by looking it up in all defined template root paths. It
is necessary to define a TemplateResolver for each Renderer, because
the Renderer itself is not able to resolve template paths.
The TemplateResolver is also used for resolving partials. However,
since partials do not necessarily have to be used, defining a
TemplateResolver for them is optional.
Compatibility
This section shows which possibilities the extension provides
for compatibility with other TYPO3 components.
In order to increase compatibility with standard extbase plugins,
the extension supports the rendering of extbase controller actions.
For this purpose, a compatibility layer was introduced, with which
own DataProcessors can be triggered using the HandlebarsViewResolver.
Configuration
Tag each DataProcessor with handlebars.compatibility_layer within
your Services.yaml file and provide additional information
about the target extbase controller and actions supported by it.
The action configuration can be either empty (= NULL) or set
to a comma-separated list of action names that are supported by the
configured DataProcessor. If you leave it empty, the DataProcessor
is used for all controller actions.
Important
Only DataProcessors that are additionally tagged with
handlebars.processor are respected as component for additional
compatibility layers.
Usage
Once the HandlebarsViewResolver is triggered to render
a specific "view", it creates an array of information and passes
it to the configured DataProcessor. You can then take further
steps based on the provided configuration.
When accessing the $configuration property inside your
DataProcessor, you should see the following properties:
This compatibility method is only applicable to DataProcessors
that extend the AbstractDataProcessor, since it provides the
necessary method. It is not part of the DataProcessor interface.
When Extbase repositories are used to fetch data via the DataProvider,
it may be necessary to perform the necessary bootstrapping for Extbase
repositories. This is the case whenever the rendering process is executed
outside the Extbase context and fields such as tt_content.pages or
tt_content.recursive are to be accessed in the repository to determine
the storage PIDs.
To execute the necessary bootstrapping or to reset the underlying
ConfigurationManager and to fill it with the current
ContentObjectRenderer, the method
initializeConfigurationManager() must be executed in the
DataProcessor.
Usage
# Classes/DataProcessing/HeaderProcessor.php
namespace Vendor\Extension\DataProcessing;
use CPSIT\Typo3Handlebars\DataProcessing\AbstractDataProcessor;
class HeaderProcessor extends AbstractDataProcessor
{
protected function render(): string
{
+ $this->initializeConfigurationManager();
$data = $this->provider->get($this->cObj->data);
return $this->presenter->present($data);
}
}
Thanks for considering contributing to this extension! Since it is
an open source product, its successful further development depends
largely on improving and optimizing it together.
The development of this extension follows the official
TYPO3 coding standards.
To ensure the stability and cleanliness of the code, various code
quality tools are used and most components are covered with test
cases. In addition, we use DDEV
for local development. Make sure to set it up as described below. For
continuous integration, we use GitHub Actions.
git clone https://github.com/CPS-IT/handlebars.git
cd handlebars
Copied!
Now install all Composer dependencies:
composer install
Copied!
# All analyzers
composer analyze
# Specific analyzers
composer analyze:dependencies
Copied!
Check code quality
# Run all linters
composer lint
# Run specific linters
composer lint:composer
composer lint:editorconfig
composer lint:php
composer lint:typoscript
# Fix all CGL issues
composer fix
# Fix specific CGL issues
composer fix:composer
composer fix:editorconfig
composer fix:php
# Run all static code analyzers
composer sca
# Run specific static code analyzers
composer sca:php
Copied!
Run tests
# All tests
composer test# Specific tests
composer test:functional
composer test:unit
# All tests with code coverage
composer test:coverage
# Specific tests with code coverage
composer test:coverage:functional
composer test:coverage:unit
# Merge code coverage of all test suites
composer test:coverage:merge
Copied!
Code coverage reports are written to .Build/coverage. You can
open the last merged HTML report like follows:
open .Build/coverage/html/_merged/index.html
Copied!
Build documentation
# Rebuild and open documentation
composer docs
# Build documentation (from cache)
composer docs:build
# Open rendered documentation
composer docs:open
Ideally, your PR references an issue describing the problem
you're trying to solve. All described code quality tools are automatically
executed on each pull request for all currently supported PHP versions and TYPO3
versions.
Sitemap
Index
Reference to the headline
Copy and freely share the link
This link target has no permanent anchor assigned.The link below can be used, but is prone to change if the page gets moved.