Skip to content

[Form] Twig example of personalized embed collection #6321

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

Closed
wants to merge 1 commit into from
Closed

[Form] Twig example of personalized embed collection #6321

wants to merge 1 commit into from

Conversation

Th3Mouk
Copy link
Contributor

@Th3Mouk Th3Mouk commented Mar 2, 2016

Q A
Doc fix? no
New docs? yes
Applies to all
Fixed tickets none

@@ -1574,6 +1574,15 @@ are 4 possible *parts* of a form that can be rendered:

.. note::

The portion corresponding to ``collection`` type is ``entry``.
In the case of embed collections, for exemple,
Copy link
Member

Choose a reason for hiding this comment

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

embed collections -> embedded collections

Copy link
Member

Choose a reason for hiding this comment

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

for exemple -> for example

@javiereguiluz
Copy link
Member

@Th3Mouk there are some minor typos to fix, but I like your proposal. Thanks for contributing to Symfony!

@Th3Mouk
Copy link
Contributor Author

Th3Mouk commented Mar 11, 2016

@javiereguiluz thanks for support !

@Th3Mouk Th3Mouk changed the title [ADD] Form twig example of personalized embed collection [Form] Twig example of personalized embed collection Mar 11, 2016
in a form which you have a year which contains months, and months contains days.
You want to personalize each collection render, where the form prefix is ``_my_form_year``,
you need to write two blocks, ``_my_form_year_months_entry_row`` to customize month render, and
``_my_form_year_months_entry_days_entry_row`` for days inputs.
Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I do not really understand this addition. To which entry do we refer here? And what do we understand by "a year which contains months, and months contains days"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its an example on multiple embedded collections, a form which contains a collectiontype, which contains himself an other collectiontype. If you have an idea for a better explanation, it would help.

Copy link
Member

Choose a reason for hiding this comment

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

Do you have the code of the example you have in mind? If I completely understand what you are thinking about I can hopefully suggest something that is (as I think) a bit easier to understand (maybe it's sufficient to provide some more details).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CalendrierExerciceType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('periodeSemaines', CollectionType::class, [
                'label' => false,
                'entry_type' => PeriodeSemaineType::class,
                'entry_options' => ['saison' => $options['saison']],
                'by_reference' => false,
                'allow_add' => true,
                'prototype_name' => '__periode__',
            ])
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'saison' => null,
            'data_class' => 'Entities\ElcaBundle\Entity\CalendrierExercice',
        ));
    }
}
namespace AppBundle\Form\Type;

use Entities\ElcaBundle\Repository\PeriodeRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PeriodeSemaineType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('periode', EntityType::class, [
                'class' => 'Entities\ElcaBundle\Entity\Periode',
                'choice_label' => 'libelle',
                'query_builder' => function (PeriodeRepository $em) use ($options) {
                    return $em
                        ->createQueryBuilder('p')
                        ->join('p.saison', 's')
                        ->where('s.id = :id_saison')
                        ->setParameter('id_saison', $options['saison']->getId())
                    ;
                },
                'attr' => [
                    'data-select2-perso' => true,
                ],
            ])
            ->add('semaines', CollectionType::class, [
                'entry_type' => SemaineZoneType::class,
                'by_reference' => false,
                'allow_add' => true,
                'prototype_name' => '__semaine__',
            ])
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Entities\ElcaBundle\Entity\PeriodeSemaine',
            'saison' => null,
        ));
    }
}
namespace AppBundle\Form\Type;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class SemaineZoneType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('startDate', DateType::class, [
                'label' => 'Date de début',
                'widget' => 'single_text',
                'format' => 'dd/MM/yyyy',
            ])
            ->add('endDate', DateType::class, [
                'label' => 'Date de fin',
                'widget' => 'single_text',
                'format' => 'dd/MM/yyyy',
            ])
            ->add('zones', EntityType::class, [
                'class' => 'Entities\ElcaBundle\Entity\Zone',
                'choice_label' => 'libelle',
                'multiple' => true,
                'required' => false,
                'attr' => [
                    'data-select2-perso' => true,
                ],
            ])
        ;
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Entities\ElcaBundle\Entity\Semaine',
        ));
    }
}
{% block _calendrier_exercice_periodeSemaines_entry_row %}
    <div class="row periode">
        <div class="col-xs-2">
        {{ form_row(form.periode) }}
        </div>
        <div class="col-xs-10">
        {{ form_errors(form.semaines) }}
        {{ form_label(form.semaines) }}<a href="#" data-add-week style="color: green;"> <i class="fa
        fa-plus-circle"></i></a>
        {{ form_widget(form.semaines) }}
        </div>
    </div>
{% endblock %}

{% block _calendrier_exercice_periodeSemaines_entry_semaines_entry_row %}
    <div class="row semaine">
        <div class="col-xs-4">
            {{ form_row(form.startDate) }}
        </div>
        <div class="col-xs-4">
            {{ form_row(form.endDate) }}
        </div>
        <div class="col-xs-4">
            {{ form_row(form.zones) }}
        </div>
    </div>
{% endblock %}

Copy link
Contributor

@HeahDude HeahDude Mar 16, 2016

Choose a reason for hiding this comment

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

@Th3Mouk Those are nice examples! However I suggest minor things if they should go in the docs:

  • utiliser des noms de classes et d'options en anglais "saison => season",
  • actually PeriodeSemaineType may break
    • This is a good opportunity to show some option resolving as default for "saison" option is null but you call $options['saison']->getId()
      You should have in configureOptions

      $resolver->setDefaults(array(
          'data_class' => 'Entities\ElcaBundle\Entity\PeriodeSemaine',
          'saison' => 'null',
          'query_builder' => function (Options $options) { // make it lazy
              $saison = $options['saison'];
              if (null  === $saison || !$saison instanceof Saison) {
                  return null;
              }
      
              return function (PeriodeRepository $em) use ($saison) {
                  return $em
                      ->createQueryBuilder('p')
                      ->leftJoin('p.saison', 's')
                      ->select('p, s')
                      ->where('s.id = :id_saison')
                      ->setParameter('id_saison', $saison->getId())
                  ;
              };
          },
      ));
      • Btw CalendrierExerciceType only needs to have $resolver-setDefined('saison') as a "child" option it needs no default value, only to be passed to the child which will resolve it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if the whole code must be added to documentation, but it's an example of what i've didn't find in the customization doc.

@elpiel
Copy link

elpiel commented Jun 1, 2016

I am totally going nuts with this collection type! I renamed my parent form to mainForm, and when I try to use form in the block it gives me Variable "form" does not exist:

{% set bookingSealNumbersForm = mainForm.bookingSealNumbers %}
{% form_theme bookingSealNumbersForm _self %}

{% block _mainForm_bookingSealNumbers_entry_row %}
    {{ form.count }}
    {#{{ form_label(form.sealNumber, null, {'label_attr': {'class': 'col-sm-2 control-label'} }) }}#}
    {#<div class="form-group {% if not form.sealNumber.vars.valid %}has-error{% endif %}">
        {{ form_label(form.sealNumber, null, {'label_attr': {'class': 'col-sm-2 control-label'} }) }}
        <div class="col-sm-4">
            {{ form_widget(form.sealNumber, { 'attr': { 'class': 'form-control' }}) }}
            {{ form_errors(form.sealNumber) }}
        </div>
    </div>#}
{% endblock %}

I am showing the comment lines too because I want you to see what I've tried, the local variable inside the block form doesn't exists and I've tried lots and lots of things, but no luck. I also tried to use the fully qualified names, e.g. mainForm.bookingSealNumbers.sealNumber with no luck, tried all sort of things...

@stof
Copy link
Member

stof commented Jun 1, 2016

@twify93 your issue is that you use _self as form theme, but your current template does not extend anything. this means that the {% block _mainForm_bookingSealNumbers_entry_row %} is rendered as part of the normal template rendering too, not just when using the form theming.
Actually, this is why I always store form theming blocks in a separate template rather than in _self. Mixing 2 different rendering contexts in the same template creates far too much opportunities to break things compared to having 2 templates

@elpiel
Copy link

elpiel commented Jun 1, 2016

OK, so simple example which can work? I've moved the theme block to another file, but now it's just ignoring it... It's like I am not getting the name of the row I want to override..

@stof
Copy link
Member

stof commented Jun 1, 2016

Well, have you updated your {% form_theme %} tag to point to this other template rather than to _self after moving the theme ?

@javiereguiluz
Copy link
Member

I'm closing this because we couldn't merge it on time and meanwhile, the docs about this "fragment naming" have been improved a lot. See https://symfony.com/doc/current/form/form_themes.html#form-fragment-naming Thank you all for the discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants