diff --git a/docs/src/docs/features/theming.md b/docs/src/docs/features/theming.md index ccd0e792..61db4746 100644 --- a/docs/src/docs/features/theming.md +++ b/docs/src/docs/features/theming.md @@ -82,12 +82,33 @@ class ProductController extends AbstractController } ``` -Last but not least, you can also overwrite the themes of the data table inside a template: +## Applying themes in Twig + +Similar to forms, you can set the data table themes directly in the Twig template, by using the `data_table_theme` tag: ```twig -
- {{ data_table(products, { themes: ['@KreyuDataTable/themes/bootstrap_5.html.twig'] }) }} -
+{% data_table_theme products 'themes/data_table.html.twig' %} + +{{ data_table(products) }} +``` + +If you wish to use multiple themes, pass an array using the `with` keyword: + +```twig +{% data_table_theme products with [ + 'themes/data_table.html.twig', + '@KreyuDataTable/themes/bootstrap_5.html.twig', +] %} + +{{ data_table(products) }} +``` + +If you wish to disable currently configured themes for the data table and **only** use given ones, add the `only` keyword after the list of data table themes: + +```twig +{% data_table_theme products with ['themes/data_table.html.twig'] only %} + +{{ data_table(products) }} ``` ## Customizing existing theme diff --git a/src/Twig/DataTableExtension.php b/src/Twig/DataTableExtension.php index 0a7ba6b0..6168bf2b 100755 --- a/src/Twig/DataTableExtension.php +++ b/src/Twig/DataTableExtension.php @@ -64,6 +64,22 @@ public function getFunctions(): array return $functions; } + public function getTokenParsers(): array + { + return [ + new DataTableThemeTokenParser(), + ]; + } + + public function setDataTableThemes(DataTableView $view, array $themes, bool $only = false): void + { + if ($only) { + $view->vars['themes'] = $themes; + } else { + array_push($view->vars['themes'], ...$themes); + } + } + /** * @param array $variables * diff --git a/src/Twig/DataTableThemeNode.php b/src/Twig/DataTableThemeNode.php new file mode 100644 index 00000000..fddfbc7f --- /dev/null +++ b/src/Twig/DataTableThemeNode.php @@ -0,0 +1,28 @@ + $dataTable, 'themes' => $themes], ['only' => $only], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf('$this->env->getExtension("%s")->setDataTableThemes(', DataTableExtension::class)) + ->subcompile($this->getNode('data_table')) + ->raw(', ') + ->subcompile($this->getNode('themes')) + ->raw(sprintf(", %s);\n", $this->getAttribute('only') ? 'true' : 'false')) + ; + } +} diff --git a/src/Twig/DataTableThemeTokenParser.php b/src/Twig/DataTableThemeTokenParser.php new file mode 100644 index 00000000..eebfcaa3 --- /dev/null +++ b/src/Twig/DataTableThemeTokenParser.php @@ -0,0 +1,46 @@ +getLine(); + $stream = $this->parser->getStream(); + + $dataTable = $this->parser->getExpressionParser()->parseExpression(); + $only = false; + + if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) { + $this->parser->getStream()->next(); + + $themes = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->nextIf(Token::NAME_TYPE, 'only')) { + $only = true; + } + } else { + $themes = new ArrayExpression([], $stream->getCurrent()->getLine()); + + do { + $themes->addElement($this->parser->getExpressionParser()->parseExpression()); + } while (!$stream->test(Token::BLOCK_END_TYPE)); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new DataTableThemeNode($dataTable, $themes, $lineno, $this->getTag(), $only); + } + + public function getTag(): string + { + return 'data_table_theme'; + } +} diff --git a/tests/Unit/Twig/DataTableExtensionTest.php b/tests/Unit/Twig/DataTableExtensionTest.php new file mode 100644 index 00000000..2e7e5af1 --- /dev/null +++ b/tests/Unit/Twig/DataTableExtensionTest.php @@ -0,0 +1,42 @@ +vars['themes'] = ['foo']; + + $this->createExtension()->setDataTableThemes($view, ['bar']); + + $this->assertEquals(['foo', 'bar'], $view->vars['themes']); + } + + public function testSetDataTableThemesWithOnly(): void + { + $view = new DataTableView(); + $view->vars['themes'] = ['foo']; + + $this->createExtension()->setDataTableThemes($view, ['bar'], true); + + $this->assertEquals(['bar'], $view->vars['themes']); + } + + private function createExtension(): DataTableExtension + { + return new DataTableExtension( + $this->createStub(ColumnSortUrlGeneratorInterface::class), + $this->createStub(FilterClearUrlGeneratorInterface::class), + ); + } +} diff --git a/tests/Unit/Twig/DataTableThemeNodeTest.php b/tests/Unit/Twig/DataTableThemeNodeTest.php new file mode 100644 index 00000000..fc607611 --- /dev/null +++ b/tests/Unit/Twig/DataTableThemeNodeTest.php @@ -0,0 +1,111 @@ +assertEquals($dataTable, $node->getNode('data_table')); + $this->assertEquals($themes, $node->getNode('themes')); + $this->assertFalse($node->getAttribute('only')); + } + + #[DataProvider('provideCompileCases')] + public function testCompile(DataTableThemeNode $node, array $expected) + { + $environment = new Environment($this->createMock(LoaderInterface::class)); + + $extension = $this->createMock(DataTableExtension::class); + + $environment->addExtension($extension); + + $compiler = new Compiler($environment); + + // In Twig 2, the ["foo", "bar"] is parsed with as [0 => "foo", 1 => "bar"]. + // In Twig 3, the ["foo", "bar"] is parsed with as ["foo", "bar"]. + // For compatibility, we are checking whether the compiled value is equal to one of the expected values. + $this->assertThat(trim($compiler->compile($node)->getSource()), $this->logicalOr( + $this->equalTo($expected[0]), + $this->equalTo($expected[1]), + )); + } + + public static function provideCompileCases(): iterable + { + yield 'single theme' => [ + new DataTableThemeNode( + new NameExpression('data_table', 0), + new ArrayExpression([ + new ConstantExpression(0, 0), + new ConstantExpression('foo', 0), + ], 0), + 0, + 'data_table_theme', + false, + ), [ + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), ["foo"], false);', + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), [0 => "foo"], false);', + ], + ]; + + yield 'multiple themes without only' => [ + new DataTableThemeNode( + new NameExpression('data_table', 0), + new ArrayExpression([ + new ConstantExpression(0, 0), + new ConstantExpression('foo', 0), + new ConstantExpression(1, 0), + new ConstantExpression('bar', 0), + ], 0), + 0, + 'data_table_theme', + false, + ), [ + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), ["foo", "bar"], false);', + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), [0 => "foo", 1 => "bar"], false);', + ], + ]; + + yield 'multiple themes with only' => [ + new DataTableThemeNode( + new NameExpression('data_table', 0), + new ArrayExpression([ + new ConstantExpression(0, 0), + new ConstantExpression('foo', 0), + new ConstantExpression(1, 0), + new ConstantExpression('bar', 0), + ], 0), + 0, + 'data_table_theme', + true, + ), [ + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), ["foo", "bar"], true);', + '$this->env->getExtension("Kreyu\\Bundle\\DataTableBundle\\Twig\\DataTableExtension")->setDataTableThemes(($context["data_table"] ?? null), [0 => "foo", 1 => "bar"], true);', + ], + ]; + } +} diff --git a/tests/Unit/Twig/DataTableThemeTokenParserTest.php b/tests/Unit/Twig/DataTableThemeTokenParserTest.php new file mode 100644 index 00000000..63b4acee --- /dev/null +++ b/tests/Unit/Twig/DataTableThemeTokenParserTest.php @@ -0,0 +1,84 @@ +createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); + $env->addTokenParser(new DataTableThemeTokenParser()); + + $source = new Source($source, ''); + $stream = $env->tokenize($source); + $parser = new Parser($env); + + $expected->setSourceContext($source); + + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode('0')); + } + + public static function provideCompileCases(): iterable + { + yield 'single theme' => [ + '{% data_table_theme data_table "foo" %}', + new DataTableThemeNode( + new NameExpression('data_table', 1), + new ArrayExpression([ + new ConstantExpression(0, 1), + new ConstantExpression('foo', 1), + ], 1), + 1, + 'data_table_theme', + false, + ), + ]; + + yield 'multiple themes without only' => [ + '{% data_table_theme data_table with ["foo", "bar"] %}', + new DataTableThemeNode( + new NameExpression('data_table', 1), + new ArrayExpression([ + new ConstantExpression(0, 1), + new ConstantExpression('foo', 1), + new ConstantExpression(1, 1), + new ConstantExpression('bar', 1), + ], 1), + 1, + 'data_table_theme', + false, + ), + ]; + + yield 'multiple themes with only' => [ + '{% data_table_theme data_table with ["foo", "bar"] only %}', + new DataTableThemeNode( + new NameExpression('data_table', 1), + new ArrayExpression([ + new ConstantExpression(0, 1), + new ConstantExpression('foo', 1), + new ConstantExpression(1, 1), + new ConstantExpression('bar', 1), + ], 1), + 1, + 'data_table_theme', + true, + ), + ]; + } +}