Custom view model builders
Every EXT:form renderable that reaches
HBS_ is converted to a
view model before TypoScript processes it. The conversion is
handled by view model builders: classes that implement
\CPSIT\.
When no registered builder claims a renderable, a plain
Simple is
used as the fallback.
Implementing the interface
The
View interface declares two methods:
interface ViewModelBuilder
{
public function build(
RootRenderableInterface $renderable,
RenderingContext $renderingContext,
): ViewModel;
public function supports(RootRenderableInterface $renderable): bool;
}
supports is called first; return
true only for the renderable types
your builder handles.
build is then called to produce the view model.
The easiest starting point is to extend
\CPSIT\.
It takes care of the boilerplate:
- Wraps the rendering inside a simulated
<formvh:context so EXT:form's state is correct.render Renderable> - Applies grid-column classes when the renderable is inside a
Grid.Row - Provides
renderto resolveAdditional Attributes () fluidfrom the element's properties.Additional Attributes
Override
render to produce your custom view model; return
null
from it to fall back to the default
View that wraps
the full rendered output of the view helper.
Minimal example
use CPSIT\Typo3HandlebarsForms;
use TYPO3\CMS\Fluid;
use TYPO3\CMS\Form;
final class RatingViewModelBuilder extends Typo3HandlebarsForms\Domain\ViewModel\Builder\AbstractViewModelBuilder
{
protected array $supportedTypes = ['Rating'];
protected function renderRenderable(
Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
Fluid\Core\Rendering\RenderingContext $renderingContext,
): ?Typo3HandlebarsForms\Domain\ViewModel\ViewModel {
$result = $this->viewHelperInvoker->invoke(
$renderingContext,
Fluid\ViewHelpers\Form\TextfieldViewHelper::class,
[
'type' => 'number',
'property' => $renderable->getIdentifier(),
'id' => $renderable->getUniqueIdentifier(),
'additionalAttributes' => $this->renderAdditionalAttributes($renderable, $renderingContext),
],
);
return new Typo3HandlebarsForms\Domain\ViewModel\ViewHelperContainedViewModel($renderable, $result);
}
}
Registration
The
View interface carries a
# attribute. Any class that
implements the interface is therefore registered automatically via Symfony DI when
autowiring is enabled (the default for extensions that include a Configuration/
with autowire: true).
No explicit YAML service definition is needed.
Builder priority
When multiple builders claim the same renderable type via supports, the first
one in the service iterator wins. The iterator order is determined by the Symfony DI
priority
tag attribute. Built-in builders are registered without an explicit priority (i.e. priority 0).
To ensure your builder runs before a built-in one, set a higher priority:
# Configuration/Services.yaml
Vendor\MyExtension\Domain\ViewModel\Builder\RatingViewModelBuilder:
tags:
- name: handlebars_forms.view_model_builder
priority: 10
Choosing a view model type
Pick the view model type that best matches what your TypoScript configuration will consume:
| Type | When to use |
|---|---|
View | Wraps the rendered tag from a Fluid view helper. Use when
HBS_ and
HBS_ need to read from it. |
Standalone | Wraps a manually-built
Tag. Use for composite elements
such as
Fieldset where you construct the tag yourself. |
Form | Combines a label (as
View) with a child
view model. Use when
HBS_ should return the
view-helper-resolved label rather than the raw renderable label. |
View | Holds a named map of child view models. Use for multi-part elements
such as
Advanced or
File where
the template needs to access named children.
HBS_ iterates over these. |
Simple | Thin wrapper over a renderable with no extra state. Use as a fallback
or for elements whose properties are accessed entirely via
HBS_. |