Skip to content

Commit

Permalink
feature #942 Stimulus controllers: allow to define outlets (jmsche)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

Stimulus controllers: allow to define outlets

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Tickets       | N/A
| License       | MIT

This PR allows to define outlets to Stimulus controllers. See https://stimulus.hotwired.dev/reference/outlets

Also updated .gitignore as running PHPUnit locally generated a `.phpunit.result.cache` file.

Commits
-------

21dfabd Add changelog entry
2b632f6 Stimulus outlets
  • Loading branch information
weaverryan committed Jun 29, 2023
2 parents 922a422 + 21dfabd commit 029be0c
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/StimulusBundle/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.php-cs-fixer.cache
.phpunit.cache
.phpunit.result.cache
composer.lock
vendor/
tests/fixtures/var
4 changes: 4 additions & 0 deletions src/StimulusBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.10.0

- Handle Stimulus outlets

## 2.9.0

- Introduce the bundle
26 changes: 26 additions & 0 deletions src/StimulusBundle/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ stimulus_controller

This bundle ships with a special ``stimulus_controller()`` Twig function
that can be used to render `Stimulus Controllers & Values`_ and `CSS Classes`_.
Stimulus Controllers can also reference other controllers by using `Outlets`_.

For example:

Expand Down Expand Up @@ -203,6 +204,30 @@ If you want to set CSS classes:
Hello
</div>

And with outlets:

.. code-block:: html+twig

<div {{ stimulus_controller('chart', { 'name': 'Likes', 'data': [1, 2, 3, 4] }, { 'loading': 'spinner' }, { 'other': '.target' ) }}>
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"
data-chart-other-outlet=".target"
>
Hello
</div>

<!-- or without values/classes -->
<div {{ stimulus_controller('chart', controllerOutlets = { 'other': '.target' }) }}>
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 Expand Up @@ -478,6 +503,7 @@ it will normalize it:
.. _`AssetMapper`: https://symfony.com/doc/current/frontend/asset-mapper.html
.. _`Stimulus Controllers & Values`: https://stimulus.hotwired.dev/reference/values
.. _`CSS Classes`: https://stimulus.hotwired.dev/reference/css-classes
.. _`Outlets`: https://stimulus.hotwired.dev/reference/outlets
.. _`Stimulus Actions`: https://stimulus.hotwired.dev/reference/actions
.. _`parameters`: https://stimulus.hotwired.dev/reference/actions#action-parameters
.. _`Stimulus Targets`: https://stimulus.hotwired.dev/reference/targets
Expand Down
8 changes: 7 additions & 1 deletion src/StimulusBundle/src/Dto/StimulusAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function getIterator(): \Traversable
return new \ArrayIterator($this->toArray());
}

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

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

foreach ($controllerOutlets as $outlet => $selector) {
$outlet = $this->normalizeKeyName($outlet);

$this->attributes['data-'.$controllerName.'-'.$outlet.'-outlet'] = $selector;
}
}

/**
Expand Down
9 changes: 5 additions & 4 deletions src/StimulusBundle/src/Twig/StimulusTwigExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,19 @@ public function getFilters(): array
* @param string $controllerName the Stimulus controller name
* @param array $controllerValues array of controller values
* @param array $controllerClasses array of controller CSS classes
* @param array $controllerOutlets array of controller outlets
*/
public function renderStimulusController(string $controllerName, array $controllerValues = [], array $controllerClasses = []): StimulusAttributes
public function renderStimulusController(string $controllerName, array $controllerValues = [], array $controllerClasses = [], array $controllerOutlets = []): StimulusAttributes
{
$stimulusAttributes = $this->stimulusHelper->createStimulusAttributes();
$stimulusAttributes->addController($controllerName, $controllerValues, $controllerClasses);
$stimulusAttributes->addController($controllerName, $controllerValues, $controllerClasses, $controllerOutlets);

return $stimulusAttributes;
}

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

return $stimulusAttributes;
}
Expand Down
26 changes: 22 additions & 4 deletions src/StimulusBundle/tests/Twig/StimulusTwigExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ protected function setUp(): void
/**
* @dataProvider provideRenderStimulusController
*/
public function testRenderStimulusController(string $controllerName, array $controllerValues, array $controllerClasses, string $expectedString, array $expectedArray): void
public function testRenderStimulusController(string $controllerName, array $controllerValues, array $controllerClasses, array $controllerOutlets, string $expectedString, array $expectedArray): void
{
$extension = new StimulusTwigExtension(new StimulusHelper($this->twig));
$dto = $extension->renderStimulusController($controllerName, $controllerValues, $controllerClasses);
$dto = $extension->renderStimulusController($controllerName, $controllerValues, $controllerClasses, $controllerOutlets);
$this->assertSame($expectedString, (string) $dto);
$this->assertSame($expectedArray, $dto->toArray());
}
Expand All @@ -50,14 +50,18 @@ public static function provideRenderStimulusController(): iterable
'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'],
'controllerOutlets' => [
'other' => '.test',
],
'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" data-symfony--ux-dropzone--dropzone-other-outlet=".test"',
'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', 'data-symfony--ux-dropzone--dropzone-other-outlet' => '.test'],
];

yield 'short-single-controller-no-data' => [
'controllerName' => 'my-controller',
'controllerValues' => [],
'controllerClasses' => [],
'controllerOutlets' => [],
'expectedString' => 'data-controller="my-controller"',
'expectedArray' => ['data-controller' => 'my-controller'],
];
Expand All @@ -66,6 +70,7 @@ public static function provideRenderStimulusController(): iterable
'controllerName' => 'my-controller',
'controllerValues' => ['myValue' => 'scalar-value'],
'controllerClasses' => [],
'controllerOutlets' => [],
'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'],
];
Expand All @@ -74,6 +79,7 @@ public static function provideRenderStimulusController(): iterable
'controllerName' => 'false-controller',
'controllerValues' => ['isEnabled' => false],
'controllerClasses' => [],
'controllerOutlets' => [],
'expectedString' => 'data-controller="false-controller" data-false-controller-is-enabled-value="false"',
'expectedArray' => ['data-controller' => 'false-controller', 'data-false-controller-is-enabled-value' => 'false'],
];
Expand All @@ -82,6 +88,7 @@ public static function provideRenderStimulusController(): iterable
'controllerName' => 'true-controller',
'controllerValues' => ['isEnabled' => true],
'controllerClasses' => [],
'controllerOutlets' => [],
'expectedString' => 'data-controller="true-controller" data-true-controller-is-enabled-value="true"',
'expectedArray' => ['data-controller' => 'true-controller', 'data-true-controller-is-enabled-value' => 'true'],
];
Expand All @@ -90,6 +97,7 @@ public static function provideRenderStimulusController(): iterable
'controllerName' => 'null-controller',
'controllerValues' => ['firstName' => null],
'controllerClasses' => [],
'controllerOutlets' => [],
'expectedString' => 'data-controller="null-controller"',
'expectedArray' => ['data-controller' => 'null-controller'],
];
Expand All @@ -98,9 +106,19 @@ public static function provideRenderStimulusController(): iterable
'controllerName' => 'my-controller',
'controllerValues' => [],
'controllerClasses' => ['loading' => 'spinner'],
'controllerOutlets' => [],
'expectedString' => 'data-controller="my-controller" data-my-controller-loading-class="spinner"',
'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-loading-class' => 'spinner'],
];

yield 'short-single-controller-no-data-with-outlet' => [
'controllerName' => 'my-controller',
'controllerValues' => [],
'controllerClasses' => [],
'controllerOutlets' => ['other-controller' => '.target'],
'expectedString' => 'data-controller="my-controller" data-my-controller-other-controller-outlet=".target"',
'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-other-controller-outlet' => '.target'],
];
}

public function testAppendStimulusController(): void
Expand Down

0 comments on commit 029be0c

Please sign in to comment.