Forms With Checkboxes For Each Entity in Symfony: Difference between revisions

From Littledamien Wiki
Jump to navigation Jump to search
No edit summary
Line 54: Line 54:
== Controller ==
== Controller ==


<syntaxhighlight lang="php" highlight="18,23-34,37,39-42,50">
<syntaxhighlight lang="php" highlight="">
// ...
// src\AppBundle\Controller\DefaultController.php
use Symfony\Component\HttpFoundation\Request;


//...
//...
/**
* @Route("/pending", name="_pending")
*/
public function pendingAction(Request $request)
public function pendingAction(Request $request)
{
{
// retrieve pending tutorials from database
// get all the available options
$groups = $this->getDoctrine()
$pending_options = $this->get('app.pending_tutorial_options')
->getRepository('AppBundle:TutorialGroupTemp')
->getTutorialOptionsList();
->findAll();
 
$tutorial_data = array();
// get a form object, passing it the available options
foreach($groups as $group) {
$form = $this->createForm(new PendingTutorialOptionType(), $pending_options);
$group->getTutorials();
foreach ($group->tutorials as $tutorial) {
$tutorial_data[$tutorial->getId()] = $tutorial->getName();
}
}


// define form inputs
$fb = $this->createFormBuilder();
foreach($tutorial_data as $key => $label) {
$fb->add('tutorialId', 'choice', array(
'label' => 'Tutorials',
'multiple' => true,
'expanded' => true,
'choices' => $tutorial_data,
'required' => false
));
}
$fb->add('update', 'submit', array('label' => 'save selected'));
$form = $fb->getForm();
// process form submission
// process form submission
$form->handleRequest($request);
$form->handleRequest($request);
 
// check if form data was submitted
if ($form->isValid()) {
if ($form->isValid()) {
// collect form data
// collect selected entities from form data
$data = $form->getData();
$data = $form['tutorialId']->getData();
 
/** @todo process form data */
$selected_ids = array();
}
foreach($data as $tutorial) {
// $data is a collection of PendingTutorial objects with all their data
array_push($selected_ids, $tutorial->getId();
}


// display listings view
// do something with the matching records...
return $this->render("tutorials/pending.html.twig", array(
'groups' => $groups,
'form' => $form->createView()
));
}
// ...
// ...
</syntaxhighlight>
</syntaxhighlight>


# Store all the record values in an array to be assigned to the checkbox inputs elements.
`$form->getData()` returns a collection of all the available options in the form. `$form['tutorialId']->getData()` returns the selected options as entity objects.
# Use a form builder object to define the form.
 
## Call the `add()` method for each checkbox to be added the form.
There is a
### Checkboxes representing an array of choices are created using `choice` as the form element
### The options 'multiple=true' and 'expanded=true' are what make it render as checkboxes as opposed to dropdowns.
### The values of the checkboxes are passed to the form builder with the `choices` option.
## In this case submit buttons are repeated throughout the form for the convenience of not having to scroll through all the listings to find them.  
### The `form_widget()` twig routine will only render an input once because generally they are all assigned `id` attributes.
### The submit button must still be added to the form builder in order to validate the form data.
# The form data is collected and processed with the `handleRequest()` and `getData()` calls. (`getData()` wouldn't be used if the form data was being assigned to an entity class.)
# The form is validated with the `isValid()` call. If form data is not being submitted, this block is quietly skipped over.
# It's necessary to pass the form view to the template when rendering the view.


<span style="color:orange;">'''N.B.''' There are probably other things that I could do (form class? services?) that would be better practices. As documented here it works. Update with any new practices.</span>
'''''N.B.''' I was having difficulty with `isValid()` because I was manually inserting the submit buttons in the form, with a name value of `form[update]`. The form class returns `pending_options` as its name; the correct value for the submit button's name should have been `pending_options[update]`.''


== Template ==
== Template ==

Revision as of 15:49, 8 February 2015

Goal

To display a list of records, select the records with checkboxes, then apply a batch update to the records when the form is submitted.[1]

Form class

// src/AppBundle/Form/Type/PendingTutorialOptionType.php

// ...

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PendingTutorialOptionType extends AbstractType
{
	public function buildForm(FormBuilderInterface $builder, array $options)
	{
		$builder->add('tutorialId', 'entity', array(
			'required' => false,
			'class' => 'AppBundle:TutorialTemp',
			'property' => 'id',
			'property_path' => '[id]',
			'multiple' => true,
			'expanded' => true
		));
		$builder->add('update', 'submit', array('label' => 'save selected'));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null
        ));
    }

    public function getName()
    {
        return 'pending_tutorials';
    }
}
  • In the buildForm() routine,
    • Use entity as the field type.
    • Assign the entity type with the class option.
    • The property and property_path options assign the id field of the entity to the checkbox elements.
    • multiple and expanded options set to true cause the form to render with a series of checkboxes as opposed to text fields (or dropdowns if the field type was choice).
  • Important to set data_class to null in setDefaultOptions().
  • Value returned by getName() is used to identify the form in the Twig template and to collect the form data after it is submitted.

Controller

// src\AppBundle\Controller\DefaultController.php

//...
	public function pendingAction(Request $request)
	{
		// get all the available options
		$pending_options = $this->get('app.pending_tutorial_options')
			->getTutorialOptionsList();

		// get a form object, passing it the available options
		$form = $this->createForm(new PendingTutorialOptionType(), $pending_options);

		// process form submission
		$form->handleRequest($request);

		// check if form data was submitted
		if ($form->isValid()) {
			
			// collect selected entities from form data
			$data = $form['tutorialId']->getData();

			$selected_ids = array();
			foreach($data as $tutorial) {
				// $data is a collection of PendingTutorial objects with all their data
				array_push($selected_ids, $tutorial->getId();
			}

			// do something with the matching records...
// ...

$form->getData() returns a collection of all the available options in the form. $form['tutorialId']->getData() returns the selected options as entity objects.

There is a

N.B. I was having difficulty with isValid() because I was manually inserting the submit buttons in the form, with a name value of form[update]. The form class returns pending_options as its name; the correct value for the submit button's name should have been pending_options[update].

Template

{{ form_start(form) }}
{{ form_errors(form) }}
<button type="submit" class="btn btn-default" name="form[update]">save selected</button>
{% for tutorial in tutorials %}
{{ form_widget(form.tutorialId[loop.index0] ) }}
{% endfor %}
<button type="submit" class="btn btn-default" name="form[update]">save selected</button>
{% do form.update.setRendered %}{# compensates for manually inserting this multiple times in the form #}
{{ form_end(form) }}
  1. The checkboxes are rendered with form_widget().
    1. tutorialId corresponds to the index of the checkbox collection added to the form builder object.
    2. The individual checkboxes in the collection are accessed through a numeric index. Twig provides the loop.index0 helper.
  2. The html for the submit buttons are is manually inserted into the template. The Symfony helper functions will only render a form element once to avoid duplicate id values.
  3. form_end() will render any fields that have not explicitly rendered in the form already. form.update.setRendered prevents this.

Processing the form data

After the call to $form->getData(), the submitted form data will be accessible through an array:

array (size=1)
  'tutorialID' => 
    array (size=3)
      0 => int 3
      1 => int 6
      2 => int 9

Examples

Notes

  1. Build a Form Having a Checkbox For Each Entity in a Doctrine Collection, Stackoverflow
    Take note of the following answer that points out how to correctly access the collection in the form data with the current version of Symfony.