adding augmentations for select and textfield ViewHelpers

This commit is contained in:
Ferdinand Kuhl 2024-06-02 15:36:25 +02:00
parent 050e6e119f
commit 098d5c09a6
10 changed files with 257 additions and 4 deletions

View file

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace DigiComp\FluidRenderFunctions\FormExtensions;
use DigiComp\FluidRenderFunctions\InvokeRenderFunctionInterface;
use DigiComp\FluidRenderFunctions\Utils\GeneratorClosureIterator;
use DigiComp\FluidRenderFunctions\Utils\RenderableProxy;
use DigiComp\FluidRenderFunctions\ViewHelpers\RegisterRenderFunctionViewHelper;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Aop\JoinPointInterface;
use Neos\FluidAdaptor\ViewHelpers\Form\SelectViewHelper;
/**
* @Flow\Aspect
*/
class SelectAspect extends SelectViewHelper
{
/**
* @Flow\After("setting(DigiComp.FluidRenderFunctions.enableAspects.select) && method(Neos\FluidAdaptor\ViewHelpers\Form\SelectViewHelper->initializeArguments())")
*/
public function introduceRenderFuncArgument(JoinPointInterface $joinPoint): void
{
$proxy = $joinPoint->getProxy();
if (!($proxy instanceof SelectViewHelper)) {
return;
}
$proxy->registerArgument('renderFunction', 'string', 'callabe to use to render single object');
}
/**
* @Flow\After("setting(DigiComp.FluidRenderFunctions.enableAspects.select) && method(Neos\FluidAdaptor\ViewHelpers\Form\SelectViewHelper->validateArguments())")
*/
public function validateRenderFunction(JoinPointInterface $joinPoint): void
{
$proxy = $joinPoint->getProxy();
if (!($proxy instanceof SelectViewHelper)) {
return;
}
if (!isset($proxy->arguments['renderFunction'])) {
return;
}
$renderFunction = $proxy->viewHelperVariableContainer->get(
RegisterRenderFunctionViewHelper::class,
$proxy->arguments['renderFunction']
);
if (!($renderFunction instanceof InvokeRenderFunctionInterface)) {
throw new \InvalidArgumentException(
'render function with name "' . $proxy->arguments['renderFunction'] . '" has not been registered.',
1717293038
);
}
$proxy->arguments['renderFunction'] = $renderFunction;
$originalOptions = $proxy->arguments['options'];
if (!\is_iterable($originalOptions)) {
// Validation is left to the original view helper
return;
}
$proxy->arguments['options'] = new GeneratorClosureIterator(
static function () use ($originalOptions, $renderFunction) {
foreach ($originalOptions as $option) {
yield new RenderableProxy($renderFunction, $option);
}
}
);
}
}

View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace DigiComp\FluidRenderFunctions\FormExtensions;
use DigiComp\FluidRenderFunctions\InvokeRenderFunctionInterface;
use DigiComp\FluidRenderFunctions\Utils\RenderableProxy;
use DigiComp\FluidRenderFunctions\ViewHelpers\RegisterRenderFunctionViewHelper;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Aop\JoinPointInterface;
use Neos\FluidAdaptor\ViewHelpers\Form\TextfieldViewHelper;
/**
* @Flow\Aspect
*/
class TextfieldAspect extends TextfieldViewHelper
{
/**
* @Flow\After("setting(DigiComp.FluidRenderFunctions.enableAspects.textfield) && method(Neos\FluidAdaptor\ViewHelpers\Form\TextfieldViewHelper->initializeArguments())")
*/
public function introduceRenderFuncArgument(JoinPointInterface $joinPoint): void
{
$proxy = $joinPoint->getProxy();
if (!($proxy instanceof TextfieldViewHelper)) {
return;
}
$proxy->registerArgument('renderFunction', 'string', 'callabe to use to render single object');
}
/**
* @Flow\Around("setting(DigiComp.FluidRenderFunctions.enableAspects.textfield) && method(Neos\FluidAdaptor\ViewHelpers\Form\TextfieldViewHelper->getValueAttribute())")
*/
public function applyRenderFunctionToValue(JoinPointInterface $joinPoint)
{
$proxy = $joinPoint->getProxy();
if (!($proxy instanceof TextfieldViewHelper)) {
return;
}
if (!isset($proxy->arguments['renderFunction'])) {
return $joinPoint->getAdviceChain()->proceed($joinPoint);
}
$renderFunction = $proxy->viewHelperVariableContainer->get(
RegisterRenderFunctionViewHelper::class,
$proxy->arguments['renderFunction']
);
if (!($renderFunction instanceof InvokeRenderFunctionInterface)) {
throw new \InvalidArgumentException(
'render function with name "' . $proxy->arguments['renderFunction'] . '" has not been registered.',
1717293038
);
}
$originalObject = $joinPoint->getAdviceChain()->proceed($joinPoint);
if (\is_object($originalObject)) {
return new RenderableProxy($renderFunction, $originalObject);
}
return $originalObject;
}
}

View file

@ -17,7 +17,7 @@ class RenderableProxy
{
protected InvokeRenderFunctionInterface $renderFunction;
protected $object;
public string $Persistence_Object_Identifier = '';
public ?string $Persistence_Object_Identifier = null;
public function __construct(InvokeRenderFunctionInterface $renderFunction, $object)
{

View file

@ -0,0 +1,5 @@
DigiComp:
FluidRenderFunctions:
enableAspects:
select: true
textfield: true

View file

@ -1,5 +1,7 @@
# DigiComp.FluidRenderFunctions
## Quickstart
This Package provides you with the possibility to register render functions, created from them, to use them dynamically else where.
Let me show you the idea:
@ -15,3 +17,9 @@ FluidRenderFunctions to the rescue:
</rf:registerRenderFunction>
<f:form.select options="{books -> rf:applyRenderFunction(function: 'renderBook')}" />
```
To make your live easier, FluidRenderFunctions augments the original `SelectViewHelper` and the `TextfieldViewHelper` with an optional `renderFunction` argument. That way, you can even use the usual Textfield to display formatted Datetime objects. Neat!
## Configuration
If - for whatever reason - you do not want FluidRenderFunctions to augment the original ViewHelpers you can opt out by setting `DigiComp.FluidRenderFunctions.enableAspects.select` or `DigiComp.FluidRenderFunctions.enableAspects.textfield` to `false`.

View file

@ -0,0 +1,6 @@
{namespace rf=DigiComp\FluidRenderFunctions\ViewHelpers}
<rf:registerRenderFunction as="renderTag">
{subject.name} is cool
</rf:registerRenderFunction>
<f:form.select options="{tags}" renderFunction="renderTag" />

View file

@ -0,0 +1,11 @@
{namespace rf=DigiComp\FluidRenderFunctions\ViewHelpers}
<rf:registerRenderFunction as="renderDate">
{subject -> f:format.date(format: 'd.m.Y')}
</rf:registerRenderFunction>
<f:form action="extendedTextField">
<f:form.textfield value="{now}" renderFunction="renderDate" />
</f:form>
<f:form object="{post}" objectName="post" action="extendedTextfield">
<f:form.textfield property="publishedAt" renderFunction="renderDate" />
</f:form>

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Controller;
use DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Domain\Post;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Flow\Persistence\Doctrine\Query;
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag;
@ -19,4 +20,15 @@ class TestController extends ActionController
{
$this->view->assign('testEntities', (new Query(Tag::class))->execute());
}
public function extendedSelectAction()
{
$this->view->assign('tags', (new Query(Tag::class))->execute());
}
public function extendedTextfieldAction()
{
$this->view->assign('now', new \DateTimeImmutable('2024-06-02T15:03:00Z'));
$this->view->assign('post', (new Query(Post::class))->execute()->getFirst());
}
}

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Domain;
use Neos\Flow\Annotations as Flow;
/**
* @Flow\Entity
*/
class Post
{
/**
* @var string|null
*/
protected ?string $title = null;
/**
* @var \DateTimeImmutable|null
*/
protected ?\DateTimeImmutable $publishedAt = null;
/**
* @param string|null $title
* @param \DateTimeImmutable|null $publishedAt
*/
public function __construct(?string $title, ?\DateTimeImmutable $publishedAt)
{
$this->title = $title;
$this->publishedAt = $publishedAt;
}
public function getTitle(): ?string
{
return $this->title;
}
public function getPublishedAt(): ?\DateTimeImmutable
{
return $this->publishedAt;
}
}

View file

@ -4,9 +4,11 @@ declare(strict_types=1);
namespace DigiComp\FluidRenderFunctions\Tests\Functional;
use DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Domain\Post;
use Neos\Flow\Mvc\Routing\Route;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag;
use Symfony\Component\DomCrawler\Crawler;
class RenderFunctionsTest extends FunctionalTestCase
{
@ -51,8 +53,46 @@ class RenderFunctionsTest extends FunctionalTestCase
$this->persistenceManager->add($post2);
$this->persistenceManager->persistAll();
$response = $this->browser->request('http://localhost/test/fluidrenderfunctions/test/select');
static::assertStringContainsString('hallo is cool', (string)$response->getBody());
static::assertStringContainsString('hallo 2 is cool', (string)$response->getBody());
$this->browser->request('http://localhost/test/fluidrenderfunctions/test/select');
$options = $this->browser->getCrawler()
->filterXPath('//select[1]/option')
->each(fn (Crawler $node) => $node->text());
static::assertEquals(['hallo is cool', 'hallo 2 is cool'], $options);
}
/**
* @test
*/
public function itAllowsRenderFunctionOnStandardSelect(): void
{
$post1 = new Tag('hallo');
$this->persistenceManager->add($post1);
$post2 = new Tag('hallo 2');
$this->persistenceManager->add($post2);
$this->persistenceManager->persistAll();
$this->browser->request('http://localhost/test/fluidrenderfunctions/test/extendedselect');
$options = $this->browser->getCrawler()
->filterXPath('//select[1]/option')
->each(fn (Crawler $node) => $node->text());
static::assertEquals(['hallo is cool', 'hallo 2 is cool'], $options);
}
/**
* @test
*/
public function itAllowsRenderFunctionOnStandardTextField(): void
{
$testDate = new \DateTimeImmutable('2024-06-02T15:03:00Z');
$post1 = new Post('hallo', $testDate);
$this->persistenceManager->add($post1);
$this->persistenceManager->persistAll();
$this->browser->request('http://localhost/test/fluidrenderfunctions/test/extendedtextfield');
$input1 = $this->browser->getCrawler()->filterXPath('//input[@type="text"][1]/@value')->text('input not found');
static::assertEquals('02.06.2024', $input1);
$input2 = $this->browser->getCrawler()->filterXPath('//input[@type="text"][2]/@value')->text('input not found');
static::assertEquals('02.06.2024', $input2);
}
}