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 InvokeRenderFunctionInterface $renderFunction;
protected $object; protected $object;
public string $Persistence_Object_Identifier = ''; public ?string $Persistence_Object_Identifier = null;
public function __construct(InvokeRenderFunctionInterface $renderFunction, $object) 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 # DigiComp.FluidRenderFunctions
## Quickstart
This Package provides you with the possibility to register render functions, created from them, to use them dynamically else where. 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: Let me show you the idea:
@ -15,3 +17,9 @@ FluidRenderFunctions to the rescue:
</rf:registerRenderFunction> </rf:registerRenderFunction>
<f:form.select options="{books -> rf:applyRenderFunction(function: 'renderBook')}" /> <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; namespace DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Controller;
use DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Domain\Post;
use Neos\Flow\Mvc\Controller\ActionController; use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Flow\Persistence\Doctrine\Query; use Neos\Flow\Persistence\Doctrine\Query;
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag; 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()); $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; namespace DigiComp\FluidRenderFunctions\Tests\Functional;
use DigiComp\FluidRenderFunctions\Tests\Functional\Fixtures\Domain\Post;
use Neos\Flow\Mvc\Routing\Route; use Neos\Flow\Mvc\Routing\Route;
use Neos\Flow\Tests\FunctionalTestCase; use Neos\Flow\Tests\FunctionalTestCase;
use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag; use Neos\FluidAdaptor\Tests\Functional\Form\Fixtures\Domain\Model\Tag;
use Symfony\Component\DomCrawler\Crawler;
class RenderFunctionsTest extends FunctionalTestCase class RenderFunctionsTest extends FunctionalTestCase
{ {
@ -51,8 +53,46 @@ class RenderFunctionsTest extends FunctionalTestCase
$this->persistenceManager->add($post2); $this->persistenceManager->add($post2);
$this->persistenceManager->persistAll(); $this->persistenceManager->persistAll();
$response = $this->browser->request('http://localhost/test/fluidrenderfunctions/test/select'); $this->browser->request('http://localhost/test/fluidrenderfunctions/test/select');
static::assertStringContainsString('hallo is cool', (string)$response->getBody()); $options = $this->browser->getCrawler()
static::assertStringContainsString('hallo 2 is cool', (string)$response->getBody()); ->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);
} }
} }