Razvan B.
Razvan B.

Reputation: 6771

Symfony 3 - Edit form - populate fields with db data (array)

I needed a form with 3 dynamic select boxes (choicetypes), which, after form submission, are saved as a serialized array in the database.

I have managed to make it work in the end but now I am struggling to populate the dropdowns with the database values when editing a project.

Projects Entity

<?php

namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="projects")
 */
class Projects
{
    /**
     * @ORM\Column(type="string", length=255)
     */
    protected $projectName;
    /**
     * @ORM\Column(type="array")
     */
    protected $frequency;
    /**
     * No database fields for these.
     * They are used just to populate the form fileds
     * and the serialized values are stored in the frequency field
     */
     protected $update;
     protected $every;
     protected $on;

    /*
     * Project Name
     */
    public function getProjectName()
    {
        return $this->projectName;
    }
    public function setProjectName($projectName)
    {
        $this->projectName = $projectName;
    }
    /*
     * Frequency
     */
    public function getFrequency()
    {
        return $this->frequency;
    }
    public function setFrequency($frequency)
    {
        $this->frequency = $frequency;
    }
    /*
     * Update
     */  
    public function getUpdate()
    {
        return $this->update;
    }
    public function setUpdate($update)
    {
        $this->update = $update;
    }
    /*
     * Every
     */  
    public function getEvery()
    {
        return $this->every;
    }
    public function setEvery($every)
    {
        $this->every = $every;
    }
    /*
     * On
     */  
    public function getOn()
    {
        return $this->on;
    }
    public function setOn($on)
    {
        $this->on = $on;
    }
}

Projects Controller

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Projects;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
 * Projects controller.
 */
class ProjectsController extends Controller
{
    /**
     * @Route("/projects/new", name="projects_new")
     */
    public function newAction(Request $request)
    {
        $project = new Projects();
        $form = $this->createForm('AppBundle\Form\ProjectsType', $project);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            // This is where the data get's saved in the frequency field
            $frequency = array(
                "update" => $project->getUpdate(),
                "every" => $project->getEvery(),
                "on" => $project->getOn()
            );
            $project->setFrequency($frequency);

            $em->persist($project);
            $em->flush($project);

            return $this->redirectToRoute('projects_show', array('id' => $project->getId()));
        }

        return $this->render('projects/new.html.twig', array(
            'project' => $project,
            'form' => $form->createView(),
        ));
    }

    /**
     * @Route("/projects/edit/{id}", name="projects_edit", requirements={"id": "\d+"})
     * @ParamConverter("id", class="AppBundle:Projects")
     */
    public function editAction(Request $request, Projects $project)
    {
        $editForm = $this->createForm('AppBundle\Form\ProjectsType', $project);
        $editForm->handleRequest($request);

        if ($editForm->isSubmitted() && $editForm->isValid()) {
            // This is where the data get's saved in the frequency field
            $frequency = array(
                "update" => $project->getUpdate(),
                "every" => $project->getEvery(),
                "on" => $project->getOn()
            );
            $project->setFrequency($frequency);

            $this->getDoctrine()->getManager()->flush();

            return $this->redirectToRoute('projects_edit', array('id' => $project->getId()));
        }

        return $this->render('projects/edit.html.twig', array(
            'project' => $project,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }
}

ProjectsType Form

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use AppBundle\Entity\Projects;

class ProjectsType extends AbstractType
{
    private function setUpdateChoice(Projects $project)
    {
        return $project->getFrequency()['update'];
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('projectName', null, array());

        $builder->add('update', ChoiceType::class, array(
                'label'         => 'Update',
                'attr'          => array(
                    'class' => 'update_selector',
                ),
                'choices' => array(
                    'Daily' => 'Daily',
                    'Weekly' => 'Weekly',
                    'Monthly' => 'Monthly'
                ),
                'data' => $this->setUpdateChoice($builder->getData())
            )
        );

        $addFrequencyEveryField = function (FormInterface $form, $update_val) {
            $choices = array();
            switch ($update_val) {
                case 'Daily':
                    $choices = array('1' => '1', '2' => '2');
                    break;

                case 'Weekly':
                    $choices = array('Week' => 'Week', '2 Weeks' => '2 Weeks');
                    break;

                case 'Monthly':
                    $choices = array('Month' => 'Month', '2 Months' => '2 Months');
                    break;

                default:
                    $choices = array();
                    break;
            }
        };
  
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addFrequencyEveryField) {
                $update = $event->getData()->getUpdate();
                $update_val = $update ? $update->getUpdate() : null;
                $addFrequencyEveryField($event->getForm(), $update_val);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addFrequencyEveryField) {
                $data = $event->getData();
                $update_val = array_key_exists('update', $data) ? $data['update'] : null;
                $addFrequencyEveryField($event->getForm(), $update_val);
            }
        );

        $addFrequencyOnField = function (FormInterface $form, $every_val) {
            $choicesOn = array();
            switch ($every_val) {
                case 'Week':
                    $choicesOn = array(
                        'Monday' => 'Monday',
                        'Tuesday' => 'Tuesday',
                        'Wednesday' => 'Wednesday',
                        'Thursday' => 'Thursday',
                        'Friday' => 'Friday',
                        'Saturday' => 'Saturday',
                        'Sunday' => 'Sunday',
                    );
                break;

                case 'Month':
                    $choicesOn = array(
                        '1' => '1',
                        '2' => '2',
                        '3' => '3',
                        '4' => '4',
                        '5' => '5',
                        '6' => '6',
                        '7' => '7',
                    );
                break;

                default:
                    $choicesOn = array();
                break;
            }

            $form->add('on', ChoiceType::class, array(
                'choices'     => $choicesOn,
            ));

        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addFrequencyOnField) {
                $every = $event->getData()->getEvery();
                $every_val = $every ? $every->getEvery() : null;
                $addFrequencyOnField($event->getForm(), $every_val);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addFrequencyOnField) {
                $data = $event->getData();
                $every_val = array_key_exists('every', $data) ? $data['every'] : null;
                $addFrequencyOnField($event->getForm(), $every_val);
            }
        );
    }
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Projects'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_projects';
    }
}

and finally, the Edit Template containing ajax

{% extends 'base.html.twig' %}
{% block body %}
    {% block content %}
      <h1>Edit Project</h1>

      {{ form_start(edit_form) }}
          {{ form_widget(edit_form) }}
          <input type="submit" value="Edit" />
      {{ form_end(edit_form) }}
    {% endblock content %}


    {% block javascripts %}
        <script>
          var $update =       $('#appbundle_projects_update');
          var $every =        $('#appbundle_projects_every');
          var $on_selector =  $('#appbundle_projects_on');

          // When update gets selected ...
          $update.change(function() {
            $every.html('');
            // ... retrieve the corresponding form.
            var $form = $(this).closest('form');
            // Simulate form data, but only include the selected update value.
            var data = {};
            data[$update.attr('name')] = $update.val();
            // Submit data via AJAX to the form's action path.
            $.ajax({
              url : $form.attr('action'),
              type: $form.attr('method'),
              data : data,
              success: function(html) {
                var options = $(html).find('#appbundle_projects_every option');
                for (var i=0, total = options.length; i < total; i++) {
                    $every.append(options[i]);
                }
              }
            });
          });
          // // When every gets selected ...
          $every.change(function() {
            $on_selector.html('');
            // ... retrieve the corresponding form.
            var $form = $(this).closest('form');
            // Simulate form data, but only include the selected every value.
            var data = {};
            data[$every.attr('name')] = $every.val();
            // Submit data via AJAX to the form's action path.
            $.ajax({
              url : $form.attr('action'),
              type: $form.attr('method'),
              data : data,
              success: function(html) {
                  var options = $(html).find('#appbundle_projects_on option');
                  for (var i=0, total = options.length; i < total; i++) {
                      $on_selector.append(options[i]);
                  }
              }
            });
          });
        </script>
    {% endblock javascripts %}
{% endblock %}

I was able to set the selected value in the update field using setUpdateChoice() and assigning the return value to the ChoiceType::class data in ProjectsType. However, this doesn't have any effect on the every select or the other one, the PRE_SET_DATA listener returning null.

I've tried the same as above with the other two fields as well, but having the fields rendered in the listener resulted in an error no such field (or something similar)

I've also tried to get the value from the frequency in the controller and using setUpdate(), setEvery() and setOn() before rendering the form, resulting in You can't modify a form that has already been submitted.

This is how the form currently renders when editing a project:

initial render

And I would need it populated like this:

enter image description here

Any idea/advice/example of how I can tackle this would be greatly appreciated.

Thank you

PS. If I've omitted something, please let me know and I'll update my question.

Update

Thanks to Constantin's answer, I've managed to set the fields data in the controller doing the following:

  public function editAction(Request $request, Projects $project)
  {
      $project->setUpdate($project->getFrequency()['update']);
      $project->setEvery($project->getFrequency()['every']);
      $project->setOn($project->getFrequency()['on']);

      $editForm = $this->createForm('AppBundle\Form\ProjectsType', $project);
      $editForm->handleRequest($request);

      if ($editForm->isSubmitted() && $editForm->isValid()) {
          $frequency = array(
              "update" => $project->getUpdate(),
              "every" => $project->getEvery(),
              "on" => $project->getOn()
          );


          $project->setFrequency($frequency);
          $this->getDoctrine()->getManager()->flush();

          return $this->redirectToRoute('projects_edit', array('id' => $project->getId()));
      }

      return $this->render('projects/edit.html.twig', array(
          'project' => $project,
          'edit_form' => $editForm->createView(),
          'delete_form' => $deleteForm->createView(),
      ));
  }

Upvotes: 3

Views: 4898

Answers (1)

Constantin
Constantin

Reputation: 1304

Not sure that this will help you as I have not enough time to read all your post.

First of all (and just for your information), you can use in your form for your 3 fields (update, every and on) the option 'mapped' => false and remove from your entity these 3 field (this is a mapping with your entity, not DB).

You can then access them in your controller with

$editForm->get('update')->getData(); //or setData( $data );

But of course you can keep these attributes if you want and if it is more convenient for you. But if you do keep them, please make sure that they will always reflect the data in your frequency attribute.

Next, I see in your edit controller that before creating your form, you don't set the values of your 3 fields. As these fields are not mapped (with the database this time), they will be null when you fetch your entity (this is done with your paramConverter in your edit controller). So before passing your $project as a parameter in your creating form, you have to set them (unserialize or whatever).

Hope that this will help you!

Upvotes: 3

Related Questions