sonata-project / SonataAdminBundle

The missing Symfony Admin Generator
https://docs.sonata-project.org/projects/SonataAdminBundle
MIT License
2.11k stars 1.26k forks source link

[RFC] TemplateType Integration #5863

Closed silasjoisten closed 2 years ago

silasjoisten commented 4 years ago

Feature Request

I would like to provide a new FormType called TemplateType. This allows you to add your custom template into the SonataAdmin configureFormFields without overriding any sonata template.

Example

Admin:

protected function configureFormFields(FormMapper $formMapper): void
{
      $formMapper
          ->with('default')
               ->add('example', TemplateType::class, [
                   'template' => 'block/example.html.twig',
                    'label' => false,
                    'options' => [
                        'object' => $this->getSubject(),
                    ],
                ])
         ->end();
}

Template:

{#block/example.html.twig#}
<div class="col-md-3">
    <div class="info-box bg-green">
        <span class="info-box-icon"><i class="fa fa-thumbs-o-up"></i></span>

        <div class="info-box-content">
            <span class="info-box-text">Likes</span>
            <span class="info-box-number">41,410</span>

            <div class="progress">
                <div class="progress-bar" style="width: 70%"></div>
            </div>
            <span class="progress-description">
                    70% Increase in 30 Days
                  </span>
        </div>
    </div>
</div>

Result:

Bildschirmfoto 2020-02-01 um 11 17 20

Without overriding the template.

OskarStark commented 4 years ago

Sounds interesting 😊

core23 commented 4 years ago

I'm not sure how you define the alternative templates? Is this some kind of ChoiceType with predefined options?

silasjoisten commented 4 years ago

No its just a FormType where the User can define its own template and pass twig variables into it. The code looks like the following:

<?php

declare(strict_types=1);

namespace Sonata\AdminBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TemplateType extends AbstractType
{
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['template'] = $options['template'];
        $view->vars['options'] = $options['options'];
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'template' => null,
            'options' => [],
            'mapped' => false,
            'required' => false,
            'label' => false,
        ]);
    }
}
{# src/Resources/views/Form/Type/template.html.twig #}
{% block template_widget %}
    {% include form.vars.template with form.vars.options %}
{% endblock  %}
<!-- src/Resources/config/form_types.xml -->
    <services>
        <!-- ... -->
        <service id="sonata.admin.form.type.template" class="Sonata\AdminBundle\Form\Type\TemplateType" public="true">
            <tag name="form.type" alias="sonata_type_template"/>
        </service>
    </services>

The User needs todo:

# config/twig.yaml
twig:
    form_themes:
        - '@SonataAdmin/Form/Type/template.html.twig'

and use the Type like in issue description mentioned.

greg0ire commented 4 years ago

:thinking: Sorry, but I don't think I like it very much… I mean, what's wrong with overriding a template? Is it too complicated? If yes, maybe that's what we should work on IMO.

silasjoisten commented 4 years ago

Nothings wrong about overriding a template. Was just an idea. I thought in future when there is a redesign of Sonata is it easier to upgrade if there less overritten templates in the world.

greg0ire commented 4 years ago

I've not been using Sonata for a while, so my question about overriding a template is genuine… if we can make it easier somehow, please consider working on that, but keep in mind that configureFormFields should be about exactly that: configuring form fields.

VincentLanglet commented 4 years ago

I've not been using Sonata for a while, so my question about overriding a template is genuine… if we can make it easier somehow, please consider working on that, but week in mind that configureFormFields should be about exactly that: configuring form fields.

In our team, we had to do this a long time ago multiple times. Our strategy was to do a custom Type:

->add('fooCustom', CustomType::class, [
    'disabled' => true,
])

The value was provided by a custom getter in the entity

public function fooCustom()
{
    return something
}
<?php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

class CustomType extends AbstractType
{
    /**
     * @return mixed
     */
    public function getParent()
    {
        return TextType::class;
    }
}

Adding the formTheme

/**
     * @return array
     */
    public function getFormTheme()
    {
        return array_merge(
            parent::getFormTheme(),
            [
                'admin/sonata/form/custom.html.twig',
            ]
        );
    }
{% block custom_label %}
{% endblock %}

{% block custom_widget %}
    Hello world. {{ value }}
{% endblock %}

It's clearly not the best strategy but we were beginners and since it works we always do like this now. If we want to move this custom field on another Admin, or change the order of the field, it feels so much easier in the configure form.

In 4 year using Sonata, we almost never overriding templates. Don't really know why.

@silasjoisten I do like the templateType, this seems similar to our strategy but in a so much better way !

greg0ire commented 4 years ago

:thinking: :thinking: :thinking: if I try to recall my very old memories of this, I think most of the pain comes from finding the exact name of the block you need to override to achieve your goals… I'd like somebody to show us how overrides are usually done, and if that's cumbersome and can't easily be improved, then maybe we should go with the TemplateType, especially if it makes the life of everyone easier.

VincentLanglet commented 4 years ago

🤔 🤔 🤔 if I try to recall my very old memories of this, I think most of the pain comes from finding the exact name of the block you need to override to achieve your goals… I'd like somebody to show us how overrides are usually done, and if that's cumbersome and can't easily be improved, then maybe we should go with the TemplateType, especially if it makes the life of everyone easier.

When we want to override/make something tricky/discover a feature/fix a bug we got deep in the vendor code. We never read the doc because we don't find what we're looking for or we don't know how to find what we're looking for. So in our case, most of the pain is the documentation. I do like the symfony documentation but I don't like the Sonata documentation.

For example, if I want something about the configureForm. I have this https://sonata-project.org/bundles/admin/3-x/doc/getting_started/the_form_view.html this https://sonata-project.org/bundles/admin/3-x/doc/reference/form_types.html this https://sonata-project.org/bundles/admin/3-x/doc/reference/form_help_message.html and many more.

If I want something about the symfony form, i just go here https://symfony.com/doc/current/forms.html

greg0ire commented 4 years ago

Okay so the docs are shitty, which is not news… is that really the root of the problem here? That no one overrides template because they can't figure out how to do it? Or is it just not possible? Also, the example of @silasjoisten is not a form control, what's the use case for embedding something else than a form inside a form? Did you have similar use cases or were you trying to embed custom form controls?

OskarStark commented 4 years ago

What about adding a new method to the form builder? ->renderTemplate(template.html.twig, [arguments....])?

I am currently on a phone 📲

VincentLanglet commented 4 years ago

Okay so the docs are shitty, which is not news…

I want to be sure that I said that in a constructive way. The Sonata projects are a big help for us. I didn't know that is was an already known fact that the doc was not the best. But making a new one would take a lot of work.

is that really the root of the problem here? That no one overrides template because they can't figure out how to do it? Or is it just not possible?

Can't really help about this point. I would need to see how to do it in order to say which is my preferred way.

what's the use case for embedding something else than a form inside a form? Did you have similar use cases or were you trying to embed custom form controls?

In our society, we created form with some show block inside. This display some data you can't edit but you have to see.

For exemple, we have 3 entities: Clients has Contracts which has Vouchers. When I edit a Contract, I want to see all the Vouchers of the Clients in order to check I dont give similar Voucher or too much Voucher.

Plus we have so custom workflow where we are redirect from Entity1 editaction to Entity2 editaction to Entity3 editaction etc.

greg0ire commented 4 years ago

I want to be sure that I said that in a constructive way. I didn't know that is was an already known fact that the doc was not the best.

Yeah don't worry, I didn't write them ;) I think writing good docs are hard, and I think these docs were not written in one go, but just piled on by many different people, which might explain the feeling of "all over the place" they can give.

Can't really help about this point. I would need to see how to do it in order to say which is my preferred way.

My memories are far, but I think creating a Sonata form results in Twig template blocks, and that those can be overriden. Thanks to the Symfony profiler, you can get a view of the template rendering tree, and find what to override. Might not be as convenient as @silasjoisten 's method.

What about adding a new method to the form builder? ->renderTemplate(template.html.twig, [arguments....])?

That sounds better, because it feels a bit less like twisting a form. In Symfony, forms contain other forms which contain other forms and so on… everything is a form. If we are to add something else, it would be great to make the distinction, and find a name for that something else. So what do you call something that is in a form but is not a form control (some sort of input)?

wokenlex commented 4 years ago

is that really the root of the problem here? That no one overrides template because they can't figure out how to do it?

Actually, yes, right now its very hard to find the place.

Or is it just not possible? Also, the example of @silasjoisten is not a form control, what's the use case for embedding something else than a form inside a form? Did you have similar use cases or were you trying to embed custom form controls?

For example, i have some forms and the widgets that i need to place between them, that showing the related information for editors.

wokenlex commented 4 years ago

If we are to add something else, it would be great to make the distinction, and find a name for that something else. So what do you call something that is in a form but is not a form control (some sort of input)?

->appendWidget(string $widgetClass, ?array $options) or you can use your admin block's logic: ->appendBlock(string $blockClass)

it will force people to write widgets/blocks outside of the controllers or admin configuration.

->appendWidget(ArticlesWithClosestNames::class, ['template' => 'adminShortTemplate.html.twig', widget_options => ['limit' => 10]]);

VincentLanglet commented 4 years ago

If we are to add something else, it would be great to make the distinction, and find a name for that something else. So what do you call something that is in a form but is not a form control (some sort of input)?

->appendWidget(string $widgetClass, ?array $options) or you can use your admin block's logic: ->appendBlock(string $blockClass)

it will force people to write widgets/blocks outside of the controllers or admin configuration.

->appendWidget(ArticlesWithClosestNames::class, ['template' => 'adminShortTemplate.html.twig', widget_options => ['limit' => 10]]);

Personnaly I would prefer a more generic function, like the proposed renderTemplate since it can be use for something else than widget/block.

VincentLanglet commented 4 years ago

@silasjoisten Do you want to implement this feature ?

I think it's agreed that the PR will be merged. Maybe there will still a little debate about the name, but that's all. Correct me if I'm wrong @OskarStark @greg0ire

silasjoisten commented 4 years ago

@VincentLanglet sure i will do that but first i finish webpack integration

silasjoisten commented 4 years ago

If we are to add something else, it would be great to make the distinction, and find a name for that something else. So what do you call something that is in a form but is not a form control (some sort of input)?

->appendWidget(string $widgetClass, ?array $options) or you can use your admin block's logic: ->appendBlock(string $blockClass) it will force people to write widgets/blocks outside of the controllers or admin configuration. ->appendWidget(ArticlesWithClosestNames::class, ['template' => 'adminShortTemplate.html.twig', widget_options => ['limit' => 10]]);

Personnaly I would prefer a more generic function, like the proposed renderTemplate since it can be use for something else than widget/block.

How about addTemplate? so it would be general.

VincentLanglet commented 4 years ago

What about adding a new method to the form builder? ->renderTemplate(template.html.twig, [arguments....])?

That sounds better, because it feels a bit less like twisting a form. In Symfony, forms contain other forms which contain other forms and so on… everything is a form. If we are to add something else, it would be great to make the distinction, and find a name for that something else. So what do you call something that is in a form but is not a form control (some sort of input)?

I discovered virtual_field : https://symfony.com/doc/master/bundles/SonataAdminBundle/cookbook/recipe_virtual_field.html

With the showMapper, you can write

$showMapper->add('my_custom_field', null, [
    'virtual_field' => true,
    'template' => 'admin/CRUD/show_my_custom_field.html.twig',
])

Adding the support of this option would be less weird since it would be consistent with other mappers.

github-actions[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

OskarStark commented 3 years ago

I am all the way for a renderTemplate() method which gets a path and parameters

VincentLanglet commented 3 years ago

Hi @silasjoisten, this feature would be awesome. Are you still interested to make a PR ? :)

silasjoisten commented 3 years ago

@VincentLanglet yea sure! so wich solution shall i do?

  1. ->add('sometemplate', TemplateType:class, ...)
  2. ->renderTemplate('path/to/template.html.twig', [] )
  3. virtual_field
VincentLanglet commented 3 years ago

After some thoughts

  1. ->add('sometemplate', TemplateType:class, ...)

This can be confusing

  1. virtual_field

This still need to use a Type (->add('foo', Type::class, ['virtual_field' => true])

So I think the option 2 ->renderTemplate('path/to/template.html.twig', [] ) is finally the best one.

OskarStark commented 3 years ago

Yes 👍🏻😃

VincentLanglet commented 3 years ago

Friendly ping @silasjoisten, did you have time to take a look for this feature and how to do it because it would be awesome.