Skip to content

Commit

Permalink
feature #191 Handle Stimulus CSS Classes (jmsche)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the main branch.

Discussion
----------

Handle Stimulus CSS Classes

Closes #123.

This PR aims to add support for Stimulus CSS Classes (see https://stimulus.hotwired.dev/reference/css-classes).

Commits
-------

6a3b1d4 Handle Stimulus CSS Classes
  • Loading branch information
weaverryan committed Oct 18, 2022
2 parents 2a4889b + 6a3b1d4 commit 3cbefbf
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 18 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ class ScriptNonceSubscriber implements EventSubscriberInterface
### stimulus_controller

This bundle also ships with a special `stimulus_controller()` Twig function
that can be used to render [Stimulus Controllers & Values](https://stimulus.hotwired.dev/reference/values).
that can be used to render [Stimulus Controllers & Values](https://stimulus.hotwired.dev/reference/values)
and [CSS Classes](https://stimulus.hotwired.dev/reference/css-classes).
See [stimulus-bridge](https://github.com/symfony/stimulus-bridge) for more details.

For example:
Expand All @@ -224,6 +225,29 @@ For example:
</div>
```

If you want to set CSS classes:

```twig
<div {{ stimulus_controller('chart', { 'name': 'Likes', 'data': [1, 2, 3, 4] }, { 'loading': 'spinner' }) }}>
Hello
</div>
<!-- would render -->
<div
data-controller="chart"
data-chart-name-value="Likes"
data-chart-data-value="&#x5B;1,2,3,4&#x5D;"
data-chart-loading-class="spinner"
>
Hello
</div>
<!-- or without values -->
<div {{ stimulus_controller('chart', controllerClasses: { 'loading': 'spinner' }) }}>
Hello
</div>
```

Any non-scalar values (like `data: [1, 2, 3, 4]`) are JSON-encoded. And all
values are properly escaped (the string `&#x5B;` is an escaped
`[` character, so the attribute is really `[1,2,3,4]`).
Expand Down
23 changes: 18 additions & 5 deletions src/Dto/StimulusControllersDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ final class StimulusControllersDto extends AbstractStimulusDto
{
private $controllers = [];
private $values = [];
private $classes = [];

public function addController(string $controllerName, array $controllerValues = []): void
public function addController(string $controllerName, array $controllerValues = [], array $controllerClasses = []): void
{
$controllerName = $this->getFormattedControllerName($controllerName);
$this->controllers[] = $controllerName;
Expand All @@ -31,6 +32,12 @@ public function addController(string $controllerName, array $controllerValues =

$this->values['data-'.$controllerName.'-'.$key.'-value'] = $value;
}

foreach ($controllerClasses as $key => $class) {
$key = $this->escapeAsHtmlAttr($this->normalizeKeyName($key));

$this->values['data-'.$controllerName.'-'.$key.'-class'] = $class;
}
}

public function __toString(): string
Expand All @@ -39,9 +46,15 @@ public function __toString(): string
return '';
}

return rtrim('data-controller="'.implode(' ', $this->controllers).'" '.implode(' ', array_map(static function (string $attribute, string $value): string {
return $attribute.'="'.$value.'"';
}, array_keys($this->values), $this->values)));
return rtrim(
'data-controller="'.implode(' ', $this->controllers).'" '.
implode(' ', array_map(static function (string $attribute, string $value): string {
return $attribute.'="'.$value.'"';
}, array_keys($this->values), $this->values)).' '.
implode(' ', array_map(static function (string $attribute, string $value): string {
return $attribute.'="'.$value.'"';
}, array_keys($this->classes), $this->classes))
);
}

public function toArray(): array
Expand All @@ -52,7 +65,7 @@ public function toArray(): array

return [
'data-controller' => implode(' ', $this->controllers),
] + $this->values;
] + $this->values + $this->classes;
}

/**
Expand Down
17 changes: 9 additions & 8 deletions src/Twig/StimulusTwigExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ public function getFilters(): array
}

/**
* @param string $controllerName the Stimulus controller name
* @param array $controllerValues array of controller values
* @param string $controllerName the Stimulus controller name
* @param array $controllerValues array of controller values
* @param array $controllerClasses array of controller CSS classes
*/
public function renderStimulusController(Environment $env, $controllerName, array $controllerValues = []): StimulusControllersDto
public function renderStimulusController(Environment $env, $controllerName, array $controllerValues = [], array $controllerClasses = []): StimulusControllersDto
{
$dto = new StimulusControllersDto($env);

if (\is_array($controllerName)) {
trigger_deprecation('symfony/webpack-encore-bundle', 'v1.15.0', 'Passing an array as first argument of stimulus_controller() is deprecated.');

if ($controllerValues) {
throw new \InvalidArgumentException('You cannot pass an array to the first and second argument of stimulus_controller(): check the documentation.');
if ($controllerValues || $controllerClasses) {
throw new \InvalidArgumentException('You cannot pass an array to the first and second/third argument of stimulus_controller(): check the documentation.');
}

$data = $controllerName;
Expand All @@ -61,7 +62,7 @@ public function renderStimulusController(Environment $env, $controllerName, arra
return $dto;
}

$dto->addController($controllerName, $controllerValues);
$dto->addController($controllerName, $controllerValues, $controllerClasses);

return $dto;
}
Expand Down Expand Up @@ -107,9 +108,9 @@ public function renderStimulusAction(Environment $env, $controllerName, string $
return $dto;
}

public function appendStimulusController(StimulusControllersDto $dto, string $controllerName, array $controllerValues = []): StimulusControllersDto
public function appendStimulusController(StimulusControllersDto $dto, string $controllerName, array $controllerValues = [], array $controllerClasses = []): StimulusControllersDto
{
$dto->addController($controllerName, $controllerValues);
$dto->addController($controllerName, $controllerValues, $controllerClasses);

return $dto;
}
Expand Down
24 changes: 20 additions & 4 deletions tests/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,57 +209,73 @@ public function provideRenderStimulusController()
'controllerValues' => [
'my"Key"' => true,
],
'expectedString' => 'data-controller="symfony--ux-dropzone--dropzone" data-symfony--ux-dropzone--dropzone-my-key-value="true"',
'expectedArray' => ['data-controller' => 'symfony--ux-dropzone--dropzone', 'data-symfony--ux-dropzone--dropzone-my-key-value' => 'true'],
'controllerClasses' => [
'second"Key"' => 'loading',
],
'expectedString' => 'data-controller="symfony--ux-dropzone--dropzone" data-symfony--ux-dropzone--dropzone-my-key-value="true" data-symfony--ux-dropzone--dropzone-second-key-class="loading"',
'expectedArray' => ['data-controller' => 'symfony--ux-dropzone--dropzone', 'data-symfony--ux-dropzone--dropzone-my-key-value' => 'true', 'data-symfony--ux-dropzone--dropzone-second-key-class' => 'loading'],
];

yield 'short-single-controller-no-data' => [
'dataOrControllerName' => 'my-controller',
'controllerValues' => [],
'controllerClasses' => [],
'expectedString' => 'data-controller="my-controller"',
'expectedArray' => ['data-controller' => 'my-controller'],
];

yield 'short-single-controller-with-data' => [
'dataOrControllerName' => 'my-controller',
'controllerValues' => ['myValue' => 'scalar-value'],
'controllerClasses' => [],
'expectedString' => 'data-controller="my-controller" data-my-controller-my-value-value="scalar-value"',
'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-my-value-value' => 'scalar-value'],
];

yield 'false-attribute-value-renders-false' => [
'dataOrControllerName' => 'false-controller',
'controllerValues' => ['isEnabled' => false],
'controllerClasses' => [],
'expectedString' => 'data-controller="false-controller" data-false-controller-is-enabled-value="false"',
'expectedArray' => ['data-controller' => 'false-controller', 'data-false-controller-is-enabled-value' => 'false'],
];

yield 'true-attribute-value-renders-true' => [
'dataOrControllerName' => 'true-controller',
'controllerValues' => ['isEnabled' => true],
'controllerClasses' => [],
'expectedString' => 'data-controller="true-controller" data-true-controller-is-enabled-value="true"',
'expectedArray' => ['data-controller' => 'true-controller', 'data-true-controller-is-enabled-value' => 'true'],
];

yield 'null-attribute-value-does-not-render' => [
'dataOrControllerName' => 'null-controller',
'controllerValues' => ['firstName' => null],
'controllerClasses' => [],
'expectedString' => 'data-controller="null-controller"',
'expectedArray' => ['data-controller' => 'null-controller'],
];

yield 'short-single-controller-no-data-with-class' => [
'dataOrControllerName' => 'my-controller',
'controllerValues' => [],
'controllerClasses' => ['loading' => 'spinner'],
'expectedString' => 'data-controller="my-controller" data-my-controller-loading-class="spinner"',
'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-loading-class' => 'spinner'],
];
}

/**
* @dataProvider provideRenderStimulusController
*/
public function testRenderStimulusController($dataOrControllerName, array $controllerValues, string $expectedString, array $expectedArray)
public function testRenderStimulusController($dataOrControllerName, array $controllerValues, array $controllerClasses, string $expectedString, array $expectedArray)
{
$kernel = new WebpackEncoreIntegrationTestKernel(true);
$kernel->boot();
$twig = $this->getTwigEnvironmentFromBootedKernel($kernel);

$extension = new StimulusTwigExtension();
$dto = $extension->renderStimulusController($twig, $dataOrControllerName, $controllerValues);
$dto = $extension->renderStimulusController($twig, $dataOrControllerName, $controllerValues, $controllerClasses);
$this->assertSame($expectedString, (string) $dto);
$this->assertSame($expectedArray, $dto->toArray());
}
Expand Down

0 comments on commit 3cbefbf

Please sign in to comment.