TagBuilder ViewHelpers
With the new ViewHelperResolver delegates, it should be possible to build a new collection of ViewHelpers that helps with creating dynamic HTML tags. This could look something like this:
{namespace tb=TYPO3Fluid\Fluid\TagBuilders}
<tb:div
id="myId"
class="{
0: 'someClass',
1: 'anotherClass',
}"
class:myClass="{someVariable} === 'foo'"
data="{
foo: 'bar',
}"
if="{shouldTagBeRendered}"
>
</tb:div>
Some things we might add:
- various ways to add classes
-
dataandariaas array - conditional tags (
if) - skipping empty attributes (syntax tbd)
- switching the tag name, e. g. button/a (syntax tbd)
i think we should have a close look at syntax and restrictions. we may also want to see if such a system could be an opportunity to have "valid" html when a full attribute+value should be optional, which is currently this evil inline 'if' construct that breaks validation?
I think we should have a close look at syntax and restrictions. We may also want to see if such a system could be an opportunity to have "valid" HTML when a full attribute+value should be optional, which is currently this evil inline 'if' construct that breaks validation?
I would suggest the following:
<tb:div foo> => <div foo>
<tb:div foo="bar"> => <div foo="bar">
<tb:div foo=bar> => <div foo=1>
<tb:div foo="{null}"> => <div>
The only missing thing is now if you want to have <div foo> and "foo" should be optional. But in this case it would also be valid in HTML to render <div foo="foo">
And I think the class syntax is too fancy... Just build the string of classes outside of the tag and then use the variable.
And the "if" is also not clear. Is only the tag not rendered or also the tag content? It should be only the tag but that’s IMO not clear enough.
I'll just let my Tag-Builder Viewhelper here for inspiration. It does also allow to create dynamic Tags but only if they are in the "allowed" list
<?php
declare(strict_types = 1);
namespace Flowd\XXX\ViewHelper;
use SMS\FluidComponents\Exception\InvalidArgumentException;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
/**
* Trivial ViewHelper to build an HTML tag, to avoid invalid tags in templates
*
* Example:
*
* <flowd:tag name="h{type}" class="{class}" allowed="{0: 'h1', 1: 'h2', 2: 'h3', 3: 'h4', 4: 'h5', 5: 'h6'}">
* {content}
* </flowd:tag>
*/
class TagViewHelper extends AbstractTagBasedViewHelper
{
/**
* Initialize arguments.
*
* @throws Exception
*/
#[\Override]
public function initializeArguments(): void
{
parent::initializeArguments();
$this->registerArgument(
'name',
'string',
'The name of the tag that should be rendered',
true
);
$this->registerArgument(
'allowed',
'array',
'Array of allowed resulting tags',
true
);
$this->registerArgument(
'additionalAttributes',
'array',
'Array of additional attributes that should be added to the tag and could not be set in the template as arguments',
false,
[]
);
$this->registerArgument(
'optionalContainer',
'boolean',
'When the tag is an optional container and the tag has no attributes, only the children are rendered.',
false,
false
);
}
#[\Override]
public function render(): string
{
if (!in_array($this->arguments['name'], $this->arguments['allowed'], true)) {
throw new InvalidArgumentException(sprintf(
'The given tag name "%s" is invalid. Possible tag names are: %s',
var_export($this->arguments['name'], true),
implode(
', ',
array_map(static fn ($item): string => var_export($item, true), $this->arguments['allowed'])
)
), 1670603848);
}
$children = $this->renderChildren();
$this->tag->setTagName($this->arguments['name']);
$this->tag->setContent($children);
$this->tag->ignoreEmptyAttributes(true);
$additionalAttributes = (array)($this->arguments['additionalAttributes'] ?? []);
foreach ($additionalAttributes as $additionalAttribute => $additionalAttributeValue) {
// add a special handling for data-controller and data-action to merge the values
if (in_array($additionalAttribute, ['data-controller', 'data-action'], true)) {
$existingValue = explode(' ', (string)$this->tag->getAttribute($additionalAttribute));
$existingValue[] = $additionalAttributeValue;
$additionalAttributeValue = implode(' ', array_unique($existingValue));
}
$this->tag->addAttribute($additionalAttribute, $additionalAttributeValue);
}
$optionalContainer = (bool)$this->arguments['optionalContainer'];
return !$optionalContainer || $this->tag->getAttributes() !== [] ? $this->tag->render() : $children;
}
}
I think we should have a close look at syntax and restrictions. We may also want to see if such a system could be an opportunity to have "valid" HTML when a full attribute+value should be optional, which is currently this evil inline 'if' construct that breaks validation?
I would suggest the following:
<tb:div foo>=><div foo><tb:div foo="bar">=><div foo="bar"><tb:div foo=bar>=><div foo=1><tb:div foo="{null}">=><div>
I think @lolli42 wasn't primarily thinking about boolean attributes (we've got those covered pretty well now) but of something like this:
<img {f:if(conditon: title, then: ' title="{title}"')} />
To solve this, we would probably need to extend the syntax, something like:
<tb:img ?title="{title}" />