Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[StimulusBundle] Adds Form Extension #2450

Open
wants to merge 2 commits into
base: 2.x
Choose a base branch
from

Conversation

19Gerhard85
Copy link

@19Gerhard85 19Gerhard85 commented Dec 18, 2024

Q A
Bug fix? no
New feature? yes
Issues Fix #737
License MIT

This PR adds a FromTypeExtension and adds the options stimulus_controller, stimulus_action and stimulus_target.

Supported syntax:

stimulus_controller

//Single controller
'stimulus_controller' => 'controllerName',

//Single controller with path
'stimulus_controller' => 'path/controllerName',

//Single controller with path, already in correct nomenclature
'stimulus_controller' => 'path--controllerName',

//Multiple controllers
'stimulus_controller' => ['controllerName1', 'controllerName2'],

//Single controller with values, classes and targets
'stimulus_controller' => [
    'controllerName' => [
        'values' => [
            'key' => 'value'
        ],
        'classes' => [
            'key' => 'value'
        ],
        'targets' => [
            'otherControllerName' => '.targetName'
        ]
    ]
]

stimulus_action

//Default event, already in correct nomenclature
'stimulus_action' => 'controllerName#actionName'

//Defined event, already in correct nomenclature
'stimulus_action' => 'eventName->controllerName#actionName'

//Default event in array notation
'stimulus_action' => ['controllerName' => 'actionName']

//Defined event in array notation
'stimulus_action' => ['controllerName' => ['eventName' => 'actionName']]

//Defined event in array notation with parameters
'stimulus_action' => ['controllerName' => ['eventName' => ['actionName' => ['param1' => 'value1']]]]

stimulus_target

//Single target in array notation
'stimulus_target' => ['controllerName' => 'targetName']

//Multiple targets of the same controller, already in correct nomenclature
'stimulus_target' => ['controllerName' => 'targetName1 targetName2']

//Multiple targets of same controller in array notation
'stimulus_target' => ['controllerName' => ['targetName1', 'targetName2']]

//Single/Multiple targets of different controllers
'stimulus_target' => ['controllerName' => ['targetName1', 'targetName2'], 'otherController' => 'otherTargetName']

Tests will follow when we finalized the syntax.

@carsonbot carsonbot added Feature New Feature Status: Needs Review Needs to be reviewed labels Dec 18, 2024
@19Gerhard85 19Gerhard85 force-pushed the feature/stimulus-form-extension branch 2 times, most recently from 8bf6482 to 281d55a Compare December 18, 2024 08:22
@19Gerhard85 19Gerhard85 force-pushed the feature/stimulus-form-extension branch from 281d55a to be4394f Compare December 18, 2024 08:26
@19Gerhard85 19Gerhard85 changed the title [Feature] StimulusBundle Form Extension [StimulusBundle] Adds Form Extension Dec 18, 2024
@smnandre
Copy link
Member

smnandre commented Dec 18, 2024

Continuing here the discussion from the issue #737 (comment)

Do we want only array_like structures ? What about some DTO ?

Arrays are usually a pain to deal with, and more in this case when you won't be able to use PHPDoc on them. It's like the Twig function stimulus_* we introduced a long time ago, because writting plain HTML attributes were painful.

Using a new kind of StimulusAttributes builder, as you suggested @smnandre, is a good idea to me.

The main question is "what is the end goal ?"

This first draft only handle stimulus_* on the form type, but i'm not sure if this is the expected behaviour.

How can i set an attribute on the row and another on the widget ?

Right now user can set attributes (classes but also data-**) on attr / row_attr etc..

@smnandre
Copy link
Member

Maybe simple static method could do most of the needs here?

Stimulus::controller(....)

Stimulus::target($controller, $target)

or builder-like

Stimulus::controller(name)
    ->value(name, value)
    ->class(name, value)
    ->...    

@19Gerhard85 19Gerhard85 force-pushed the feature/stimulus-form-extension branch from f8b426f to 2092187 Compare December 19, 2024 07:44
@19Gerhard85 19Gerhard85 force-pushed the feature/stimulus-form-extension branch from 2092187 to 92e2a5e Compare December 19, 2024 07:45
@19Gerhard85
Copy link
Author

Continuing here the discussion from the issue #737 (comment)

Do we want only array_like structures ? What about some DTO ?

Arrays are usually a pain to deal with, and more in this case when you won't be able to use PHPDoc on them. It's like the Twig function stimulus_* we introduced a long time ago, because writting plain HTML attributes were painful.
Using a new kind of StimulusAttributes builder, as you suggested @smnandre, is a good idea to me.

The main question is "what is the end goal ?"

This first draft only handle stimulus_* on the form type, but i'm not sure if this is the expected behaviour.

How can i set an attribute on the row and another on the widget ?

Right now user can set attributes (classes but also data-**) on attr / row_attr etc..

The FormTypeExtension works on forms and widgets, as long as the widget extends the AbstractType.

row_attr and choice_attr implemented now, unfortunately choice_attr isn't working at all, e.g.

        $builder->add('select', ChoiceType::class, [
            'expanded' => true,
            'choices'  => ['foo' => 'foo', 'bar' => 'bar'],
            'row_attr' => [
                'class' => 'rowClass',
                'stimulus_controller' => ['row/Controller']
            ],
            'choice_attr' => [
                'class' => 'choiceClass',
                'stimulus_controller' => ['widget/Controller']
            ],
        ]);

choiceClass isn't added to the radio button.

<div class="rowClass form-group" data-controller="row--Controller">
    <label class="control-label">Select</label>
    <div id="test_form_select">
        <div class="radio">
            <label for="test_form_select_placeholder" class="">
                <input type="radio" id="test_form_select_placeholder" name="test_form[select]" value="" checked="checked"> None
            </label>
        </div>
        <div class="radio">
            <label for="test_form_select_0" class="">
                <input type="radio" id="test_form_select_0" name="test_form[select]" value="foo"> foo
            </label>
        </div>
        <div class="radio">
            <label for="test_form_select_1" class="">
                <input type="radio" id="test_form_select_1" name="test_form[select]" value="bar"> bar
            </label>
        </div>
    </div>
</div>

@smnandre
Copy link
Member

row_attr and choice_attr implemented now, unfortunately choice_attr isn't working at all, e.g.

In fact, choice_attr works "per choice".

Examples from the doc:

    'choice_attr' => [
        'Apple' => ['data-color' => 'Red'],
        'Banana' => ['data-color' => 'Yellow'],
        'Durian' => ['data-color' => 'Green'],
    ],
    'choice_attr' => function ($choice, string $key, mixed $value) {
        // adds a class like attending_yes, attending_no, etc
        return ['class' => 'attending_'.strtolower($key)];
    },

And there is a static helper to defer the computation

    'choice_attr' => ChoiceList::attr($this, function (?Category $category): array {
        return $category ? ['data-uuid' => $category->getUuid()] : [];
    }),

The FormTypeExtension works on forms and widgets, as long as the widget extends the AbstractType.

Yep.. Do we want this is my point :) Or should this be configured / dynamic in any way ?

@smnandre
Copy link
Member

@19Gerhard85 This extension is a very good idea, thanks for following up and taking charge of the topic!

And let's keep the motivation high, even if it takes a few iterations! We're aiming for solid choices for the future, so let's stay the course—it’ll be worth it!

Stimulus logic / object

I don't think we should have the parsing "logic" coded in here.. using a small dedicated object will simplify the code & its maintenance, and allow us to is elsewhere. This really is the occasion.

Name

Let's rename the extension StimulusFormExtension, ok for you ? (or anything with Stimulus in it :) )

I let some other comments in the code :)


class FormTypeExtension extends AbstractTypeExtension
{
private StimulusAttributes $stimulusAttributes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is temporary right ?

I think we won't need it and use a new object but if we don't, you can inject the StimulusHelper here

Comment on lines +32 to +34
public function buildView(FormView $view, FormInterface $form, array $options): void
{
if (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this method you repeat two times the same code, this can be reduce a lot i think

Comment on lines +53 to +54
$attributes = array_merge($view->vars['attr'], $this->stimulusAttributes->toArray());
$view->vars['attr'] = $attributes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$attributes = array_merge($view->vars['attr'], $this->stimulusAttributes->toArray());
$view->vars['attr'] = $attributes;
$view->vars['attr'] = [...$view->vars['attr'], ...$this->stimulusAttributes->toArray()];

Avoid unnecessary var

Comment on lines +41 to +43
if (isset($options['stimulus_controller'])) {
$this->handleController($options['stimulus_controller']);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isset($options['stimulus_controller'])) {
$this->handleController($options['stimulus_controller']);
}
if ($options['stimulus_controller']) {
$this->handleController($options['stimulus_controller']);
}
// same for the other ones

Comment on lines +34 to +38
if (
isset($options['stimulus_controller'])
|| !isset($options['stimulus_target'])
|| !isset($options['stimulus_action'])
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (
isset($options['stimulus_controller'])
|| !isset($options['stimulus_target'])
|| !isset($options['stimulus_action'])
) {
if ($options['stimulus_controller'] || $options['stimulus_target'] || $options['stimulus_action']) {

Comment on lines +104 to +109
private function handleTarget(array $targets): void
{
foreach ($targets as $controllerName => $target) {
$this->stimulusAttributes->addTarget($controllerName, \is_array($target) ? implode(' ', $target) : $target);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed / inline when it is called

private function handleController(string|array $controllers): void
{
if (\is_string($controllers)) {
$controllers = [$controllers];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be handle outside this method i suppose (will call $this->stimulusAttributes->addController(...) every case)

Comment on lines +113 to +115
// 'stimulus_action' => 'controllerName#actionName'
// 'stimulus_action' => 'eventName->controllerName#actionName'
if (\is_string($actions) && str_contains($actions, '#')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason i want another object, we should not have to duplicate this in multiple place. Will open a draft later if i can

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(i cannot 😅 🕐 )

@19Gerhard85
Copy link
Author

row_attr and choice_attr implemented now, unfortunately choice_attr isn't working at all, e.g.

In fact, choice_attr works "per choice".

Examples from the doc:

    'choice_attr' => [
        'Apple' => ['data-color' => 'Red'],
        'Banana' => ['data-color' => 'Yellow'],
        'Durian' => ['data-color' => 'Green'],
    ],
    'choice_attr' => function ($choice, string $key, mixed $value) {
        // adds a class like attending_yes, attending_no, etc
        return ['class' => 'attending_'.strtolower($key)];
    },

And there is a static helper to defer the computation

    'choice_attr' => ChoiceList::attr($this, function (?Category $category): array {
        return $category ? ['data-uuid' => $category->getUuid()] : [];
    }),

The FormTypeExtension works on forms and widgets, as long as the widget extends the AbstractType.

Yep.. Do we want this is my point :) Or should this be configured / dynamic in any way ?

😬 Sorry my fault > RTFM 😅

@19Gerhard85
Copy link
Author

@19Gerhard85 This extension is a very good idea, thanks for following up and taking charge of the topic!

And let's keep the motivation high, even if it takes a few iterations! We're aiming for solid choices for the future, so let's stay the course—it’ll be worth it!

Stimulus logic / object

I don't think we should have the parsing "logic" coded in here.. using a small dedicated object will simplify the code & its maintenance, and allow us to is elsewhere. This really is the occasion.

Name

Let's rename the extension StimulusFormExtension, ok for you ? (or anything with Stimulus in it :) )

I let some other comments in the code :)

An object is definitely a better choice and easier to maintain. We can also enforce a syntax for the parameters.

I have the next weeks off and will work on a nice object in "Builder style".

Stimulus::controller(name)
    ->value(name, value)
    ->class(name, value)
    ->...    

@smnandre smnandre added Status: Reviewing Review is ongoing, refining with author and removed Status: Needs Review Needs to be reviewed labels Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New Feature Status: Reviewing Review is ongoing, refining with author
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PHP functions to add Stimulus JS attributes
3 participants