Skip to content

Commit

Permalink
Add "data_table_theme" Twig node (#102)
Browse files Browse the repository at this point in the history
* Add "data_table_theme" Twig node
  • Loading branch information
Kreyu authored Jun 28, 2024
1 parent 980f1d0 commit b4341d5
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 4 deletions.
29 changes: 25 additions & 4 deletions docs/src/docs/features/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<div class="card">
{{ data_table(products, { themes: ['@KreyuDataTable/themes/bootstrap_5.html.twig'] }) }}
</div>
{% 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
Expand Down
16 changes: 16 additions & 0 deletions src/Twig/DataTableExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, mixed> $variables
*
Expand Down
28 changes: 28 additions & 0 deletions src/Twig/DataTableThemeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Twig;

use Twig\Compiler;
use Twig\Node\Node;

class DataTableThemeNode extends Node
{
public function __construct(Node $dataTable, Node $themes, int $lineno, ?string $tag = null, bool $only = false)
{
parent::__construct(['data_table' => $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'))
;
}
}
46 changes: 46 additions & 0 deletions src/Twig/DataTableThemeTokenParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Twig;

use Twig\Node\Expression\ArrayExpression;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;

class DataTableThemeTokenParser extends AbstractTokenParser
{
public function parse(Token $token): DataTableThemeNode
{
$lineno = $token->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';
}
}
42 changes: 42 additions & 0 deletions tests/Unit/Twig/DataTableExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Tests\Unit\Twig;

use Kreyu\Bundle\DataTableBundle\Column\ColumnSortUrlGeneratorInterface;
use Kreyu\Bundle\DataTableBundle\DataTableView;
use Kreyu\Bundle\DataTableBundle\Filter\FilterClearUrlGeneratorInterface;
use Kreyu\Bundle\DataTableBundle\Twig\DataTableExtension;
use PHPUnit\Framework\TestCase;

class DataTableExtensionTest extends TestCase
{
public function testSetDataTableThemes(): void
{
$view = new DataTableView();
$view->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),
);
}
}
111 changes: 111 additions & 0 deletions tests/Unit/Twig/DataTableThemeNodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Tests\Unit\Twig;

use Kreyu\Bundle\DataTableBundle\Twig\DataTableExtension;
use Kreyu\Bundle\DataTableBundle\Twig\DataTableThemeNode;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Twig\Compiler;
use Twig\Environment;
use Twig\Loader\LoaderInterface;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Node;

class DataTableThemeNodeTest extends TestCase
{
public function testConstructor()
{
$dataTable = new NameExpression('data_table', 0);

$themes = new Node([
new ConstantExpression('foo', 0),
new ConstantExpression('bar', 0),
]);

$node = new DataTableThemeNode($dataTable, $themes, 0);

$this->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);',
],
];
}
}
84 changes: 84 additions & 0 deletions tests/Unit/Twig/DataTableThemeTokenParserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\Tests\Unit\Twig;

use Kreyu\Bundle\DataTableBundle\Twig\DataTableThemeNode;
use Kreyu\Bundle\DataTableBundle\Twig\DataTableThemeTokenParser;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Loader\LoaderInterface;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Parser;
use Twig\Source;

class DataTableThemeTokenParserTest extends TestCase
{
#[DataProvider('provideCompileCases')]
public function testCompile($source, $expected)
{
$env = new Environment($this->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,
),
];
}
}

0 comments on commit b4341d5

Please sign in to comment.