Reputation: 6771
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:
And I would need it populated like this:
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.
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
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