diff --git a/README.md b/README.md index a0abb01..5f987e2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ UX Collection JS requires PHP 7.4+ and Symfony 4.4+. Install this bundle using Composer and Symfony Flex: ```sh -composer require tienvx/ux-collection-js +composer require tienvx/ux-collection-js:^1.0@alpha # Don't forget to install the JavaScript dependencies as well and compile yarn add --dev '@symfony/stimulus-bridge@^2.0.0' @@ -19,26 +19,27 @@ yarn encore dev ## Usage +### Symfony + Use the new CollectionType class defined by this bundle: ```php // ... use Tienvx\UX\CollectionJs\Form\CollectionJsType; -class TaskType extends AbstractType +class PostType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder // ... - ->add('tags', CollectionJsType::class, [ - 'entry_type' => TextType::class, + ->add('attachments', CollectionJsType::class, [ + 'entry_type' => FileType::class, 'allow_add' => true, - 'allow_remove' => true, + 'allow_delete' => true, 'allow_move_up' => true, 'allow_move_down' => true, 'call_post_add_on_init' => true, - 'prototype' => true, ]) // ... ; @@ -48,13 +49,9 @@ class TaskType extends AbstractType } ``` -Then you need to set the form theme: -```yaml -# config/packages/twig.yaml -twig: - # For bootstrap for example - form_themes: ['@CollectionJs/bootstrap_5_layout.html.twig'] -``` +### Easyadmin + +TBD ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/composer.json b/composer.json index 88fb11f..a9b3771 100644 --- a/composer.json +++ b/composer.json @@ -30,13 +30,16 @@ }, "require": { "php": "^7.4|^8.0", + "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4.17|^5.0", "symfony/form": "^4.4.17|^5.0", - "symfony/http-kernel": "^4.4.17|^5.0" + "symfony/http-kernel": "^4.4.17|^5.0", + "twig/twig": "^2.12|^3.0" }, "require-dev": { "phpunit/phpunit": "^9.5", - "symfony/framework-bundle": "^4.4.17|^5.0" + "symfony/framework-bundle": "^4.4.17|^5.0", + "symfony/twig-bundle": "^4.4.17|^5.0" }, "conflict": { "symfony/flex": "<1.13" diff --git a/src/DependencyInjection/CollectionJsExtension.php b/src/DependencyInjection/CollectionJsExtension.php new file mode 100644 index 0000000..5edab72 --- /dev/null +++ b/src/DependencyInjection/CollectionJsExtension.php @@ -0,0 +1,30 @@ +getParameter('kernel.bundles'); + + if (!isset($bundles['TwigBundle'])) { + return; + } + + $container->prependExtensionConfig('twig', ['form_themes' => ['@CollectionJs/bootstrap_5_layout.html.twig']]); + } + + public function load(array $configs, ContainerBuilder $container) + { + $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('services.php'); + } +} diff --git a/src/Form/CollectionJsType.php b/src/Form/CollectionJsType.php index c3ae97a..558e315 100644 --- a/src/Form/CollectionJsType.php +++ b/src/Form/CollectionJsType.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Tienvx\UX\CollectionJs\Form; use Symfony\Component\Form\AbstractType; diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 9b33324..b64bfc9 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -3,10 +3,13 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Tienvx\UX\CollectionJs\Form\CollectionJsType; +use Tienvx\UX\CollectionJs\Twig\CollectionJsTwigExtension; return static function (ContainerConfigurator $container): void { $container->services() ->set(CollectionJsType::class) ->tag('form.type') + ->set(CollectionJsTwigExtension::class) + ->tag('twig.extension') ; }; diff --git a/src/Resources/views/bootstrap_5_layout.html.twig b/src/Resources/views/bootstrap_5_layout.html.twig index b8fcc24..57d67e8 100644 --- a/src/Resources/views/bootstrap_5_layout.html.twig +++ b/src/Resources/views/bootstrap_5_layout.html.twig @@ -38,11 +38,7 @@

{% apply spaceless %} diff --git a/src/Twig/CollectionJsTwigExtension.php b/src/Twig/CollectionJsTwigExtension.php new file mode 100644 index 0000000..edf1274 --- /dev/null +++ b/src/Twig/CollectionJsTwigExtension.php @@ -0,0 +1,34 @@ + true]), + ]; + } + + public function representAsString(Environment $environment, $value): string + { + if ($filter = $environment->getFilter('ea_as_string')) { + return $filter->getCallable()($value); + } + + if (\is_string($value)) { + return $value; + } + + if (\is_object($value) && method_exists($value, '__toString')) { + return (string) $value; + } + + return ''; + } +} diff --git a/tests/CollectionJsBundleTest.php b/tests/CollectionJsBundleTest.php index a2a1435..91b1fd5 100644 --- a/tests/CollectionJsBundleTest.php +++ b/tests/CollectionJsBundleTest.php @@ -6,6 +6,7 @@ use Symfony\Component\HttpKernel\Kernel; use Tienvx\UX\CollectionJs\Tests\Kernel\EmptyAppKernel; use Tienvx\UX\CollectionJs\Tests\Kernel\FrameworkAppKernel; +use Tienvx\UX\CollectionJs\Tests\Kernel\TwigAppKernel; class CollectionJsBundleTest extends TestCase { @@ -13,6 +14,7 @@ public function provideKernels() { yield 'empty' => [new EmptyAppKernel('test', true)]; yield 'framework' => [new FrameworkAppKernel('test', true)]; + yield 'twig' => [new TwigAppKernel('test', true)]; } /** @@ -23,4 +25,15 @@ public function testBootKernel(Kernel $kernel) $kernel->boot(); $this->assertArrayHasKey('CollectionJsBundle', $kernel->getBundles()); } + + public function testFormThemeMerging() + { + $kernel = new TwigAppKernel('test', true); + $kernel->boot(); + $this->assertEquals([ + 'form_div_layout.html.twig', + '@CollectionJs/bootstrap_5_layout.html.twig', + 'form_theme.html.twig', + ], $kernel->getContainer()->getParameter('twig.form.resources')); + } } diff --git a/tests/Kernel/TwigAppKernel.php b/tests/Kernel/TwigAppKernel.php new file mode 100644 index 0000000..62db606 --- /dev/null +++ b/tests/Kernel/TwigAppKernel.php @@ -0,0 +1,39 @@ +load(function (ContainerBuilder $container) { + $container->loadFromExtension('framework', ['secret' => '$ecret', 'test' => true]); + $container->loadFromExtension('twig', [ + 'default_path' => __DIR__ . '/templates', + 'strict_variables' => true, + 'form_themes' => [ + 'form_theme.html.twig', + ], + 'exception_controller' => null, + 'debug' => '%kernel.debug%', + ]); + + $container->setAlias('test.collection_js.twig_extension', CollectionJsTwigExtension::class)->setPublic(true); + }); + } +} diff --git a/tests/Kernel/templates/form_theme.html.twig b/tests/Kernel/templates/form_theme.html.twig new file mode 100644 index 0000000..e4ede06 --- /dev/null +++ b/tests/Kernel/templates/form_theme.html.twig @@ -0,0 +1 @@ +{# dummy file #} diff --git a/tests/Twig/CollectionJsTwigExtensionTest.php b/tests/Twig/CollectionJsTwigExtensionTest.php new file mode 100644 index 0000000..e01e18e --- /dev/null +++ b/tests/Twig/CollectionJsTwigExtensionTest.php @@ -0,0 +1,69 @@ +boot(); + $container = $kernel->getContainer()->get('test.service_container'); + + if ($filter) { + $container->get(Environment::class)->addFilter($filter); + } + + $rendered = $container->get('test.collection_js.twig_extension')->representAsString( + $container->get(Environment::class), + $value + ); + + $this->assertSame($expected, $rendered); + } + + public function valueDataProvider(): array + { + $text = 'some text'; + $textFromFilter = 'some text from filter'; + $object = new class($text) { + private string $text; + + public function __construct(string $text) + { + $this->text = $text; + } + + public function __toString(): string + { + return $this->text; + } + }; + $filter = new TwigFilter('ea_as_string', fn () => $textFromFilter); + + return [ + [$filter, $text, $textFromFilter], + [$filter, $object, $textFromFilter], + [$filter, 123, $textFromFilter], + [$filter, [$text], $textFromFilter], + [$filter, true, $textFromFilter], + [$filter, false, $textFromFilter], + [$filter, new \stdClass(), $textFromFilter], + [null, $text, $text], + [null, $object, $text], + [null, 123, ''], + [null, [$text], ''], + [null, true, ''], + [null, false, ''], + [null, new \stdClass(), ''], + ]; + } +}