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

From Littledamien Wiki
Jump to navigation Jump to search
No edit summary
Line 2: Line 2:
== Goal ==
== Goal ==


'''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.
To display a list of records, select the records with checkboxes, then apply a batch update to the records when the form is submitted.<ref>[http://stackoverflow.com/questions/14638794/build-a-form-having-a-checkbox-for-each-entity-in-a-doctrine-collection#answer-14653394 Build a Form Having a Checkbox For Each Entity in a Doctrine Collection], Stackoverflow<br />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.</ref>
 
== Form class ==
 
<syntaxhighlight lang="php" highlight="9,15-19,27,33">
// 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';
    }
}
</syntaxhighlight>
 
* 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 ==
== Controller ==
Line 113: Line 161:


* [http://tutorials.dbarchowsky.com/app_dev.php/pending Pending Tutorials], Tutorial Database
* [http://tutorials.dbarchowsky.com/app_dev.php/pending Pending Tutorials], Tutorial Database
== Notes ==
<references />

Revision as of 14:23, 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

// ...
use Symfony\Component\HttpFoundation\Request;

//...
	/**
	 * @Route("/pending", name="_pending")
	 */
	public function pendingAction(Request $request)
	{
		// retrieve pending tutorials from database
		$groups = $this->getDoctrine()
			->getRepository('AppBundle:TutorialGroupTemp')
			->findAll();
		$tutorial_data = array();
		foreach($groups as $group) {
			$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
		$form->handleRequest($request);
		
		if ($form->isValid()) {
			
			// collect form data
			$data = $form->getData();
			
			/** @todo process form data */
		}

		// display listings view		
		return $this->render("tutorials/pending.html.twig", array(
			'groups' => $groups,
			'form' => $form->createView()
		));
	}
// ...
  1. Store all the record values in an array to be assigned to the checkbox inputs elements.
  2. Use a form builder object to define the form.
    1. Call the add() method for each checkbox to be added the form.
      1. Checkboxes representing an array of choices are created using choice as the form element
      2. The options 'multiple=true' and 'expanded=true' are what make it render as checkboxes as opposed to dropdowns.
      3. The values of the checkboxes are passed to the form builder with the choices option.
    2. 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.
      1. The form_widget() twig routine will only render an input once because generally they are all assigned id attributes.
      2. The submit button must still be added to the form builder in order to validate the form data.
  3. 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.)
  4. The form is validated with the isValid() call. If form data is not being submitted, this block is quietly skipped over.
  5. It's necessary to pass the form view to the template when rendering the view.

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.

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.