Search code examples
formssymfonyuser-interfacesymfony-formsforum

Symfony : How can i display and handle many edit form on sigle page


I'm stuck with the following problem.

I'm using Symfony 4.2.3 to build a forum ( a piece of a bigger project )

I've made a ForumController who handle :

  • Forum Home page ( show forumCategories with their forumSubCategories )
  • Forum Category page ( show selected forumCategory with her forumSubCategories )
  • Forum Sub Category page ( show selected forumSubCategory with her forumTopics )
  • Forum Topic page ( show selected forumTopic with her forumMessage( i.e. reaction ) )

What i want is to implement an edit button who not redirect to an edit page but $(this).slidedown an edit form in a div. This button is display only if the ForumMessage author is the current login user. So if this user have reply many time to the topic, I need just as many buttons ( and form ). After click on edit, the page can be reload and entity update.

For create / edit with redirection i've understood how to.

The problem was, how to handle an unknown number of edit MessageType form ( and so unknown form id ).

I've try to create an array of ForumMessage link to an array of MessageType Form. But when i have to give the .createView() of each form to twig, my brain glitch.

So please, how can i have many edit form on sigle page( each link to the refered entity ) and handle them in my controller to .flush modification ?

I already implement JS function for the diplaying of the button and linked div.

The showTopic method of my ForumController.php :

/**
 * @Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
 * @ParamConverter("topic", options={"id" = "idTopic"})
 * @param $idCategory
 * @param $idSubCategory
 * @param $idTopic
 * @param Request $request
 * @param ObjectManager $manager
 * @param UserInterface $user
 * @return \Symfony\Component\HttpFoundation\Response
 * @throws \Exception
 */
public function showTopic($idCategory, $idSubCategory, $idTopic, Request $request, ObjectManager $manager, UserInterface $user = null) {
    $topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
    $userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
        'author' => "Kaarie",
        'forumTopic' => $topic
    ]);

    // Nouveau message sur un topic
    $message = new ForumMessage();
    $form = $this->createForm(ForumMessageType::class, $message);
    $form->handleRequest($request);
    if($form->isSubmitted() && $form->isValid()) {
        $message->setAuthor($user->getUsername())
                ->setCreatedAt(new \DateTime())
                ->setForumTopic($topic);
        $manager->persist($message);
        $manager->flush();

        return $this->redirectToRoute('topic', [
            'idCategory' => $idCategory,
            'idSubCategory' => $idSubCategory,
            'idTopic' => $topic->getId(),
        ]);
    }

    // Editer un message
    $editMessage = new ForumMessage();
    $editForm = $this->createForm(ForumMessageType::class, $editMessage);
    $editForm->handleRequest($request);
    if($editForm->isSubmitted() && $editForm->isValid()) {
        $manager->persist($editMessage);
        $manager->flush();

        return $this->redirectToRoute('topic', [
            'idCategory' => $idCategory,
            'idSubCategory' => $idSubCategory,
            'idTopic' => $topic->getId(),
        ]);
    }

    return $this->render('forum/showTopic.html.twig',[
        'idCategory' => $idCategory,
        'idSubCategory' => $idSubCategory,
        'topic' => $topic,
        'messageForm' => $form->createView(),
        'editForm' => $editForm->createView(),
        'userMessage' => $userMessages,
    ]);
}

The class MessageType in MessageType.php

class ForumMessageType extends AbstractType
{
   public function buildForm(FormBuilderInterface $builder, array $options)
   {
      $builder
        ->add('content')
        ->add('submit', SubmitType::class)
    ;
   }

   public function configureOptions(OptionsResolver $resolver)
   {
      $resolver->setDefaults([
        'data_class' => ForumMessage::class,
      ]);
   }
}

The twig part who display Message from showTopic.html.twig

    <ul>
        {% for message in topic.ForumMessages %}
            <li>
                {{ message.author }},</br>
                {{ message.content }}
                {% if app.user %}
                    {% if is_granted("ROLE_MODERATOR") %}

                        <button>Moderate</button> {# TODO: moderation d'un message #}
                    {% endif %}
                    {% if app.user.username == message.author %}
                        <div  class="alert alert-danger" style="margin: 1em; display: none">
                            <h3>Etidé votre réponse :</h3>
                            {{ form_start(editForm) }}
                            {{ form_row(editForm.content) }}
                            {{ form_row(editForm.submit, {'label': 'Editer'}) }}
                            {#<button type="submit" class="btn btn-primary">Editer</button>#}
                            {{ form_end(editForm) }}
                        </div>
                        <button id="buton_EditTopic">Modifier</button>
                    {% endif %}
                {% endif %}
            </li>
        {% endfor %}
    </ul>

For any other ressources please ask me !


Solution

  • My approach would be (maybe some tweaking is necessary, didnt test it)

    Short Hand explanation: The list will only contain containers. When you edit one message, you load only the form and push that to this specific container. If you edit this and press save, it would send the form per ajax request to the controller. If the form is valid, it will return then a json repsonse instead of html ...

    Controller:

    /**
     * @Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
     * @ParamConverter("topic", options={"id" = "idTopic"})
     * @param $idCategory
     * @param $idSubCategory
     * @param $idTopic
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \Exception
     */
    public function showTopic(
        $idCategory, 
        $idSubCategory, 
        $idTopic, 
        Request $request, 
        ObjectManager $manager, 
        UserInterface $user = null
    )
    {
        $topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
        $userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
            'author' => "Kaarie",
            'forumTopic' => $topic
        ]);
    
        return $this->render('forum/showTopic.html.twig',[
            'idCategory' => $idCategory,
            'idSubCategory' => $idSubCategory,
            'topic' => $topic,
            'userMessage' => $userMessages,
        ]);
    }
    
    /**
     * With this, you can create and mod Topics
     * @Route("/forum/messages/{forumMessage}/mod-message", name="message.mod", defaults={"forumMessage":0})
     * @IsGranted("ROLE_USER")
     * @param Request $request
     * @param ForumMessage $forumMessage
     * @return mixed
     */
    public function modTopic(
        Request $request, 
        Objectmanager $manager, 
        ForumMessage $forumMessage=null
    )
    {
    
        if($formMessage == null) {
            $forumMessage = new ForumMessage();
            /* set Additional Info here, maybe User, IP Adress or whatever */
        }
        $editForm = $this->createForm(ForumMessageType::class, $forumMessage);
        $editForm->handleRequest($request);
        if($editForm->isSubmitted() && $editForm->isValid()) {
            $manager->persist($forumMessage);
            $manager->flush();
    
            return new JsonRepsonse(['status'=>true, 'message' => "ForumMessage save successfull"]);
        }
    
    
        return $this->render('mod.message.html.twig',[
            'messageForm' => $editForm->createView(),
        ]);
    }
    

    FormType:

    class ForumMessageType extends AbstractType
    {
       public function buildForm(FormBuilderInterface $builder, array $options)
       {
          $builder
            ->add('content')
        ;
       }
    
       public function configureOptions(OptionsResolver $resolver)
       {
          $resolver->setDefaults([
            'data_class' => ForumMessage::class,
          ]);
       }
    }
    

    list.html.twig

    <ul>
        {% for message in topic.ForumMessages %}
            <li>
                {{ message.author }},</br>
                {{ message.content }}
                {% if app.user %}
                    {% if is_granted("ROLE_MODERATOR") %}
    
                        <button>Moderate</button> {# TODO: moderation d'un message #}
                    {% endif %}
                    {% if app.user.username == message.author %}
                        <div id="modMessageContainer{{ message.id }}"  class="alert alert-danger" style="margin: 1em; display: none">
                        </div>
                        <button onclick="modMessage(this);" 
                             data-attr-url="{{ path('message.mod'.{'forumMessage':message.id}) }}" 
                             data-attr-container="#modMessageContainer{{ message.id }}"
                        >Modifier</button>
                    {% endif %}
                {% endif %}
            </li>
        {% endfor %}
    </ul>
    <script>
        function modMessage(element)
        {
            $.ajax({
                url: $(element).attr('data-attr-url'),
                success: function(data) {
                    $($(element).attr('data-attr-container')).html(data).show();
                }
            });
        }
    
        function saveMessage(element)
        {
            var container = $(element).attr('data-attr-container');
            $.ajax({
                url: $(element).attr('data-attr-url'),
                type:'POST',
                data: $(container +' form').serialize(),
                success: function(data) {
                    if(typeof data == 'object' && data instanceof Object && !(data instanceof Array)) {
                        if(data.status) {
                            location.reload()
                        } else {
                            alert(data.message);
                        }
                    } else {
                        $(container).show();
                        $('#modMessage').replaceWith($(data).find('#modMessage'));
                    }
                }
            });
        }
    </script>
    

    mod.html.twig

    <div>
        <div id="modMessage">
            <h3>Etidé votre réponse :</h3>
            {{ form_start(editForm) }}
            {{ form_row(editForm.content) }}
            {{ form_row(editForm.submit, {'label': 'Editer'}) }}
            {#<button type="submit" class="btn btn-primary">Editer</button>#}
            {{ form_end(editForm) }}
            <div style="text-align:right">
                <button onclick="saveMessage(this);" 
                     type="button" 
                     class="btn btn-success" 
                     data-attr-container="modMessageContainer{{ message.id }}" 
                     data-attr-url="{{ path('message.mod', {'forumMessage':message.id}) }}"
                 >Save</button>
            </div>
        </div>
    </div>