From 098d5c09a62978d9f4ee2f312e2edcefba4416a9 Mon Sep 17 00:00:00 2001 From: Ferdinand Kuhl Date: Sun, 2 Jun 2024 15:36:25 +0200 Subject: [PATCH] adding augmentations for select and textfield ViewHelpers --- Classes/FormExtensions/SelectAspect.php | 69 +++++++++++++++++++ Classes/FormExtensions/TextfieldAspect.php | 59 ++++++++++++++++ Classes/Utils/RenderableProxy.php | 2 +- Configuration/Settings.yaml | 5 ++ README.md | 8 +++ .../Fixtures/Test/ExtendedSelect.html | 6 ++ .../Fixtures/Test/ExtendedTextfield.html | 11 +++ .../Fixtures/Controller/TestController.php | 12 ++++ Tests/Functional/Fixtures/Domain/Post.php | 43 ++++++++++++ Tests/Functional/RenderFunctionsTest.php | 46 ++++++++++++- 10 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 Classes/FormExtensions/SelectAspect.php create mode 100644 Classes/FormExtensions/TextfieldAspect.php create mode 100644 Configuration/Settings.yaml create mode 100644 Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedSelect.html create mode 100644 Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedTextfield.html create mode 100644 Tests/Functional/Fixtures/Domain/Post.php diff --git a/Classes/FormExtensions/SelectAspect.php b/Classes/FormExtensions/SelectAspect.php new file mode 100644 index 0000000..43b87e6 --- /dev/null +++ b/Classes/FormExtensions/SelectAspect.php @@ -0,0 +1,69 @@ +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); + } + } + ); + } +} diff --git a/Classes/FormExtensions/TextfieldAspect.php b/Classes/FormExtensions/TextfieldAspect.php new file mode 100644 index 0000000..a116e64 --- /dev/null +++ b/Classes/FormExtensions/TextfieldAspect.php @@ -0,0 +1,59 @@ +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; + } +} diff --git a/Classes/Utils/RenderableProxy.php b/Classes/Utils/RenderableProxy.php index 2ab78de..1540dcc 100644 --- a/Classes/Utils/RenderableProxy.php +++ b/Classes/Utils/RenderableProxy.php @@ -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) { diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml new file mode 100644 index 0000000..763eab0 --- /dev/null +++ b/Configuration/Settings.yaml @@ -0,0 +1,5 @@ +DigiComp: + FluidRenderFunctions: + enableAspects: + select: true + textfield: true diff --git a/README.md b/README.md index f5f553a..5cdbaf6 100644 --- a/README.md +++ b/README.md @@ -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: ``` + +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`. diff --git a/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedSelect.html b/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedSelect.html new file mode 100644 index 0000000..185c717 --- /dev/null +++ b/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedSelect.html @@ -0,0 +1,6 @@ +{namespace rf=DigiComp\FluidRenderFunctions\ViewHelpers} + + + {subject.name} is cool + + diff --git a/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedTextfield.html b/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedTextfield.html new file mode 100644 index 0000000..548c86e --- /dev/null +++ b/Resources/Private/Templates/Tests/Functional/Fixtures/Test/ExtendedTextfield.html @@ -0,0 +1,11 @@ +{namespace rf=DigiComp\FluidRenderFunctions\ViewHelpers} + + + {subject -> f:format.date(format: 'd.m.Y')} + + + + + + + diff --git a/Tests/Functional/Fixtures/Controller/TestController.php b/Tests/Functional/Fixtures/Controller/TestController.php index 64f75b0..16238ae 100644 --- a/Tests/Functional/Fixtures/Controller/TestController.php +++ b/Tests/Functional/Fixtures/Controller/TestController.php @@ -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()); + } } diff --git a/Tests/Functional/Fixtures/Domain/Post.php b/Tests/Functional/Fixtures/Domain/Post.php new file mode 100644 index 0000000..6d18f51 --- /dev/null +++ b/Tests/Functional/Fixtures/Domain/Post.php @@ -0,0 +1,43 @@ +title = $title; + $this->publishedAt = $publishedAt; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function getPublishedAt(): ?\DateTimeImmutable + { + return $this->publishedAt; + } +} diff --git a/Tests/Functional/RenderFunctionsTest.php b/Tests/Functional/RenderFunctionsTest.php index 6452834..65f633c 100644 --- a/Tests/Functional/RenderFunctionsTest.php +++ b/Tests/Functional/RenderFunctionsTest.php @@ -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); } }