Partage
  • Partager sur Facebook
  • Partager sur Twitter

Symfony 4 - Problème formulaire dynamique

    18 septembre 2019 à 21:22:23

    Bonjour ! Sur mon projet en Symfony, j'ai des utilisateurs qui peuvent poser des congés.

    Il y a plusieurs types de conégs ( congés annuels, maladie, RTT, heures sup' ... )

    J'ai une page où ma RH peut créditer des congés. Mais j'ai quelques problèmes au niveau de mon formulaire que j'ai essayé de dynamiser avec des listeners.

    Donc j'ai déjà ma classe :

    RHCredit.php:

    class RHCredit
    {
        /**
         * Type du congé
         *
         * @var TypeConge|null
         */
        private $typeConge;
    
        /**
         * Solde sous forme de nombres
         *
         * @var float|null
         */
        private $soldeFloat;
    
        /**
         * Solde sous forme d'heures
         *
         * @var Time
         */
        private $soldeTime;
    
    //getters and setters...


    Et j'ai créé le formType qui va avec :


    <?php
    
    namespace App\Form;
    
    use App\Entity\RHCredit;
    use App\Entity\TypeConge;
    use Symfony\Component\Form\FormEvents;
    use App\Repository\TypeCongeRepository;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Form\Extension\Core\Type\TimeType;
    use Symfony\Component\Form\Extension\Core\Type\NumberType;
    use Symfony\Component\Form\FormEvent;
    
    class RHCreditType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('typeConge', EntityType::class, [
                    'class' => TypeConge::class,
                    'label' => false,
                    'placeholder' => "Type d'absence",
                    'choice_label' => 'nom',
                    'choice_value' => 'nom',
                    'query_builder' => function (TypeCongeRepository $repoTypes) {
                        return $repoTypes->getTypesSaufMaladie();
                    }
                ]);
    
            $builder->addEventListener(
                FormEvents::POST_SET_DATA,
                function (FormEvent $event) {
                    $form = $event->getForm();
    
                    $data = $event->getData();
                    $typeConge = $data->getTypeConge();
    
                    if ($typeConge->getNom() == "Heures supp") {
                        $form->add('soldeTime', TimeType::class);
                    } else {
                        $form->add('soldeFloat', NumberType::class);
                    }
                }
            );
    
            // ->add('soldeFloat', NumberType::class)
            // ->add('soldeTime', TimeType::class);
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults([
                'data_class' => RHCredit::class,
            ]);
        }
    }



    Ce que je souhaite, c'est afficher par défaut le premier champs qui est la liste déroulante des types de congés. Donc ça c'est bon. Mais je veux que dès que je sélectionne un type de congé, si il s'agit du type "Heures supp", je veux afficher le champs "soldeTime"  car pour créditer des heures supp' à un utilisateur, ce sont des heures qu'on lui crédite en fait.

    Et si il choisit n'importe quoi d'autre, alors on lui affichera le "soldeFloat" qui permet d'entrer un nombre.

    Mais j'ai l'impression que ce que j'ai essayé de faire ne va pas du tout.

    Quand je lance ma page j'ai cette erreur, et donc ma page ne s'affiche pas :

    Call to a member function getNom() on null
    
    

    Sachant que ma classe TypeConge possède bien un attribut 'nom'. Mais je ne pense pas que le problème vienne de là

    Quelqu'un pourrait m'aider svp ?

    • Partager sur Facebook
    • Partager sur Twitter
      19 septembre 2019 à 10:30:54

      Salut,

      Je suppose qu'il s'agit de ton formulaire pour créer un nouveau RHCredit?
      Dans ce cas, c'est normal que $data->getTypeConge() soit null puisqu'il n'a pas encore été initialisé

      Il faut donc que tu ajoutes une condition pour vérifier s'il a été défini.

      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        19 septembre 2019 à 10:40:16

        Par "dynamique" je suppose que tu entends quelque chose comme ça https://jsfiddle.net/ay1q6u8z/

        Les événements de formulaire Symfony n'ont aucun impact côté client donc ils ne sont d'aucune utilité ici. Pour lier un formulaire à une telle interface il faut que tous les champs y soient présent. L'astuce est d'inclure les champs contextuels (soldeTime et soldeFloat) dans un fieldset qu'on peut désactiver via JavaScript. Une fois désactivé les contraintes de ses champs ne s'appliquent plus et Symfony récupérera null à la place.

        • Partager sur Facebook
        • Partager sur Twitter
          19 septembre 2019 à 10:45:21

          MatTheCat a écrit:

          Les événements de formulaire Symfony n'ont aucun impact côté client donc ils ne sont d'aucune utilité ici.

          Il faudra forcément du JS en plus, mais les FormEvents peuvent tout à fait servir à ajouter des champs en fonction d'autres champs.
          • Partager sur Facebook
          • Partager sur Twitter
          Anonyme
            19 septembre 2019 à 10:48:21

            Euh oui mais si tu n'inclues que soldeTime ou soldeFloat alors tu ne peux pas choisir l'autre dans ton interface, donc ça n'a aucun intérêt ici.

            • Partager sur Facebook
            • Partager sur Twitter
              19 septembre 2019 à 10:53:02

              en ajoutant un listener sur son champ typeConge, il pourra ajouter/enlever soldeTime ou soldeFloat en fonction du typeConge choisit.
              • Partager sur Facebook
              • Partager sur Twitter
              Anonyme
                19 septembre 2019 à 10:56:51

                Il faudrait que tu testes ce que tu proposes ; je pense que tu comprendras que ça ne servirait à rien.

                EDIT: au temps pour moi effectivement la documentation offre un moyen de résoudre le problème via PRE_SET_DATA, POST_SUBMIT et AJAX. C'est extrêmement lourd et compliqué mais ça peut marcher.

                Ici inclure les deux champs directement dans le formulaire est beaucoup plus simple.

                -
                Edité par Anonyme 19 septembre 2019 à 11:42:10

                • Partager sur Facebook
                • Partager sur Twitter
                  19 septembre 2019 à 12:38:22

                  Alors merci pour vos réponses ! J'avais effectivement pas pris en compte si le typeConge était null.

                  Et du coup j'aimerais quand même gérer tout ça grâce aux listener, PRE_SET_DATA, etc etc, pour me faire la main dessus et comprendre un peu plus comment ça marche, car j'aurai d'autres formulaires plus complexes à gérer de la même façon !

                  Alors le but c'est que quand la RH arrive sur la page avec le formulaire, on lui affiche que la liste déroulante. Et seulement après, selon son choix on affiche le soldeFloat ou le soldeTime.

                  J'ai modifié mon code :

                  $builder->addEventListener(
                              FormEvents::POST_SET_DATA,
                              function (FormEvent $event) {
                                  $form = $event->getForm();
                  
                                  $data = $event->getData();
                  
                                  $typeConge = $data->getTypeConge();
                  
                                  if ($typeConge != null) {
                  
                                      if ($typeConge->getNom() == "Heures supp") {
                                          $form->add('soldeTime', TimeType::class);
                                      } else {
                                          $form->add('soldeFloat', NumberType::class);
                                      }
                                  }
                              }
                          );

                  La page se charge bien, et j'ai bien la liste déroulante seulement. Mais quand je sélectionne un choix, rien ne se passe

                  • Partager sur Facebook
                  • Partager sur Twitter
                    19 septembre 2019 à 12:51:46

                    MatTheCat a écrit:

                    Ici inclure les deux champs directement dans le formulaire est beaucoup plus simple.

                    Là je suis d'accord avec toi =D (et ne connaissant pas le système avec les fieldset j'ai perdu pas mal de temps avec les FormEvents^^)

                    AgarioFR a écrit:

                    La page se charge bien, et j'ai bien la liste déroulante seulement. Mais quand je sélectionne un choix, rien ne se passe

                    il manque la partie JS pour déclencher l'évènement lors du changement:
                    https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms



                    • Partager sur Facebook
                    • Partager sur Twitter
                      19 septembre 2019 à 12:56:08

                      Ah mince, le JS c'est pas du tout mon truc :/ Je vois sur la doc' ce qu'il faut faire mais je n'y comprends rien du tout :o

                      EDIT : Alors j'ai fait ça :

                          <script>
                      
                              var $typeConge = $("#rh_credit_typeConge");
                      
                              $typeConge.change(function () {
                                  var $form = $(this).closest('form');
                      
                                  var data = {};
                                  data[$typeConge.attr('nom')] = $typeConge.val();
                      
                                  console.log(data[$typeConge.attr('nom')]);
                      
                                  $.ajax({
                                      url : $form.attr('action'),
                                      type : $form.attr('method'),
                                      data : data,
                                      success : function(html) {
                                          //...
                                      }
                                  })
                              })
                          </script>

                      Mais du coup je suis censé mettre quoi dans la function success ?

                      -
                      Edité par AgarioFR 19 septembre 2019 à 13:03:33

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Anonyme
                        19 septembre 2019 à 18:04:28

                        Le nom du champ est dans l'attribut name pas nom.

                        Le HTML que tu vas récupérer contiendra le champ soldeTime ou soldeFloat selon la valeur de typeConge ; il faudra que tu remplaces ledit champ par le nouveau, le cas échéant.

                        Ça me semble quand même casse-gueule leur solution. Ça se base sur le fait que le formulaire soumis via AJAX est invalide ; mais ça ne sera pas le cas si le champ est optionnel ??

                        -
                        Edité par Anonyme 19 septembre 2019 à 18:06:37

                        • Partager sur Facebook
                        • Partager sur Twitter
                          19 septembre 2019 à 19:14:06

                          Mais dans ce cas ça signifie que je suis obligé de remplacer un champs par un autre. Donc ça veut dire que par défaut je dois obligatoirement afficher au moins 2 champs. La liste déroulante du début et soit le champs soldeFloat soit soldeTime. Il y a pas une fonction append() ou quelque chose de ce genre ?
                          • Partager sur Facebook
                          • Partager sur Twitter
                          Anonyme
                            19 septembre 2019 à 19:35:12

                            Je suppose que pour être cohérent il faudrait afficher par défaut le champ qui correspond à la première valeur de la liste déroulante puisque c'est celui qui va être affiché.

                            • Partager sur Facebook
                            • Partager sur Twitter
                              19 septembre 2019 à 19:39:54

                              Mais il y a quelque chose que je comprends pas.

                              Pourquoi dans mon formType je m'embête à faire une condition :

                              "Si le type congé est heure supp on affiche le soldeTime. Sinon on affiche le soldeFloat".

                              Et que dans le twig je ne peux pas vraiment appliquer ma condition puisque c'est moi qui dit de remplacer tel champs par tel champs. Donc ma condition dans le formType elle sert à rien en fait ?

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Anonyme
                                19 septembre 2019 à 23:03:06

                                Si j'ai bien compris la condition de POST_SET_DATA sert pour l'affichage initial du formulaire et celle de POST_SUBMIT pour mettre à jour le formulaire après soumission pour qu'il soit valide pour Symfony.

                                Du coup quand tu le soumets via AJAX tu récupères le formulaire mis à jour en fonction du champ qui a changé.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  20 septembre 2019 à 15:11:25

                                  Normalement pour l'affichage initial c'est PRE_SET_DATA.

                                  Le POST_SET_DATA devrait être pour quand l'utilisateur inscrit des données

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  Anonyme
                                    20 septembre 2019 à 15:28:33

                                    Ah oui je me suis planté c'est PRE_SET_DATA qu'il faut utiliser ici. Par contre POST_SET_DATA intervient avant même que le formulaire soit affiché ; d'après la doc il sert à lire les données passées à l'initialisation du formulaire.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      20 septembre 2019 à 16:12:56

                                      Ah vraiment ? D'accord ! Mais du coup le problème reste le même finalement.

                                      J'ai mon event dans mon formulaire.

                                      Mais maintenant pour qu'il s'applique automatiquement je suis censé faire quoi ?

                                      J"ai bien essayé un truc de ce genre:

                                      {% block javascripts %}
                                      
                                          <script>
                                      
                                              var $typeConge = $("#rh_credit_typeConge");
                                      
                                              $typeConge.change(function () {
                                                  var $form = $(this).closest('form');
                                      
                                                  var data = {};
                                                  data[$typeConge.attr('name')] = $typeConge.val();
                                      
                                                  console.log(data[$typeConge.attr('name')]);
                                                  $valeur = data[$typeConge.attr('name')];
                                      
                                                  $.post($form.attr('action'), data).then(function (data) {
                                                      if ($valeur == "Heures supp") {
                                                          $('#rh_credit_soldeFloat').replaceWith('#rh_credit_soldeTime');
                                                      }
                                      
                                                  })
                                      
                                              })
                                          </script>
                                      
                                      {% endblock %}
                                      

                                      Mais d'une part, en faisant ça, mon event dans mon formulaire ne sert à rien du tout, et d'autre part, ça ne m'affiche pas le nouveau champs

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                      Anonyme
                                        20 septembre 2019 à 17:09:45

                                        Je pense que tu confonds les événements côté client et serveur. PRE_SET_DATA et POST_SUBMIT sont des événements de formulaire Symfony (donc côté serveur) et change est un événement survenant quand l'utilisateur sélectionne une valeur.

                                        Si tu n'écoutes que PRE_SET_DATA ça n'ira pas de toute façon ; la doc est là : https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          20 septembre 2019 à 17:16:08

                                          Ah okay je vois ... Du coup la seule façon pour moi d'ajouter dynamiquement ces champs en fonction de ce que l'utilisateur va sélectionner, c'est de faire du javascript et de zapper cette histoire d'events ?
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                          Anonyme
                                            20 septembre 2019 à 19:11:06

                                            Non. Lis la doc, je pense que c'est mieux expliqué ^^'

                                            • Partager sur Facebook
                                            • Partager sur Twitter

                                            Symfony 4 - Problème formulaire dynamique

                                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                            • Editeur
                                            • Markdown