简体   繁体   中英

Why is symfony only rendering one nested form collection element here?

Edit: I have since figured out the issue. Explanation below and my work-around.

I have an app I'm working on for my HR department which will be used to perform employee annual evaluations. The way they've requested it, there are pages of bullet points grouped by subject (Business Sense, Skills, etc), and each bullet point is assigned a score by the reviewer. Every position in the company will have the same three pages of bullet points, but each department may elect to add more for the department as a whole, or for each position.

I have my entity for this designed and working properly.

The issue I'm running into is that I'm having some trouble getting the form to render. Right now, for testing purposes, I'm just rendering the form with {{ form(evaluation) }}. It's built with forms nested in a collection.

I've checked the builders with var_dump, and the EvaluationType (the outer form) shows the pages registred in the form, and if I var_dump the EvaluationPage object, it displays the bullet points registered to the object, but when the form renders, only the LAST bullet point renders. None of the other points from any other page display, only the last one added. I've checked and rechecked, and I'm sure it's something simple, but I can't find the issue.

Here are my code snippets:

EvaluationController (getModernEvaluationLocker just uses dummy data:

namespace Company\PerformanceBundle\Controller;

use Company\PerformanceBundle\Entity\Noncommitable\EvaluationLocker;
use Company\PerformanceBundle\Form\Builders\EvaluationType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class EvaluationController extends Controller
{
    /**
     * @Route("/strive/evaluation", name="eval")
     * @Template("CompanyPerformanceBundle:Evaluation:Evaluation.html.twig")
     */
    function evaluationEntryAction()
    {

        $locker = $this->getModernEvaluationLocker();

        $em = $this->getDoctrine()->getManager();
        $form = $this->createForm(new EvaluationType($em, $locker));



        return array(
            'sidebarTitle' => 'Strive Evaluations Manager',
            'sidebar' => null,
            'function' => 'test',
            'title' => 'test',
            'evaluation' => $form->createView(),
        );
    }

    private function getModernEvaluationLocker()
    {
        $locker = new EvaluationLocker();

        $locker ->addPage('Brand Pillars and Core Values')
                    ->addBullet('Inspires a passion for the industry and ensures that the needs of all customers (internal and external) are met, benefitting both the customer and organization.')
                    ->addBullet('Completes work to high standards and continually looks for ways to improve performance.')
                    ->addBullet('Delivers remarkable customer service 100% of the time, each and everytime to both our internal and external customers.')
                ->addPage('Business Acumen')
                    ->addBullet('Clear understanding of The Company’s mission, initiatives and direction.')
                    ->addBullet('Has a broad and deep understanding of the issues that affect the organization, industry and the business environment.');

        return $locker;
    }  
}

EvaluationType (The outer form):

namespace Company\PerformanceBundle\Form\Builders;

use Doctrine\ORM\EntityManager;
use Company\PerformanceBundle\Entity\Noncommitable\EvaluationLocker;
use Company\PerformanceBundle\Entity\Noncommitable\EvaluationLockerPage;
use Company\PerformanceBundle\Form\EventListener\EvaluationPageSubscriber;
use Company\PerformanceBundle\Form\Fields\EvaluationPage;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class EvaluationType extends AbstractType {
    private $entityManager;
    private $evalLocker;

    public function __construct(EntityManager $em, EvaluationLocker $locker)
    {
        $this->entityManager = $em;
        $this->evalLocker = $locker;
    }

    public function getName() {

        return 'evaluation';

    }
    public function buildForm(FormBuilderInterface $builder, array $options) {

         $builder->addEventSubscriber(new EvaluationPageSubscriber($this->evalLocker));

         $builder
                ->add('employeeID', 'text', array(
                    'label' => 'Employee ID:',
                    'required' => true,
                    'trim' => true,
                    'error_bubbling' => true,
                    ))
                ->add('managerLogin', 'text', array(
                    'label' => 'Manager Login:',
                    'required' => true,
                    'trim' => true,
                    'error_bubbling' => true,
                    'mapped' => false,
                    ))
                ->add('managerPassword', 'password', array(
                    'label' => 'Manager Password:',
                    'required' => true,
                    'trim' => true,
                    'error_bubbling' => true,
                    'mapped' => false,
                    ))
                ->add('appraisalType', 'choice', array(
                    'choices' => array('0' => '90 Day', '1' => 'Annual'),
                    'required' => true,
                    'expanded' => true,
                    'multiple' => false,
                    'label' => 'Appraisal Type:',
                    'error_bubbling' => true,
                ))
                ->add('pages', 'collection');

         for($i =0; $i<=$this->evalLocker->getPageCount(); $i++)
         {
             $page = ($i==0 ? $this->evalLocker->rewind() : $this->evalLocker->next());
             if ($page)
             {  
                $builder->get('pages')->add('page_' . $i, new EvaluationPage($page));
             }
         }

    }    

}

EvaluationPage (The inner form):

namespace Company\PerformanceBundle\Form\Fields;

use Company\PerformanceBundle\Entity\Noncommitable\EvaluationLockerPage;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class EvaluationPage extends AbstractType {

    private $page;

    public function __construct( EvaluationLockerPage $page)
    {
        $this->page = $page;
    }

    public function getName() {

        return 'evaluationPage';

    }

    public function buildForm(FormBuilderInterface $builder, array $options) 
    {

        $builder->add('bullets', 'collection');

        $i =0;
        foreach ($this->page as $bullet => $value)
        {
            if ($bullet)
            {   
                $builder->get('bullets') -> add('bullet_'.$i, 'text', array(
                    'label' => $bullet,
                    'data'  => $value,
                    'required' => true,
                    'trim' => true,
                    ));
            }

            $i++;    
        }
    }
}

EvaluationLocker and EvaluationLockerPage are simply objects with the ArrayAccess and Iterator interfaces, and a few simple functions such as getPageCount and similar ops.

Think of EvaluationLocker as an array of EvaluationLockerPage arrays. Each EvaluationLockerPage entry is a key/value pair where the key is the bullet point text, and the value is the score it's been assigned.

So, when the page renders, the only bullet point that displays is 'Has a broad and deep understanding of the issues that affect the organization, industry and the business environment.' along with its text box. The rest of the form displays fine, but only that one bullet point is present.

I'm still a little new to Symfony, so if I've done something in a weird way or done something dumb, that's probably why.

What am I missing?

The Answer

The issue was twofold, caused by the way collection field types were being handled, as well as a mistake in my event subscriber.

The symfony engine does not render collection entries unless they have an actual value assigned to them. Since these text boxes were empty, symfony was ignoring them, expecting the boxes to be added to the form via javascript and prototypes.

That caused the form lines not to display. However, remember that ONE line was displaying.

That was caused by my event subscriber. It was a relic of a way I had previously tried to get this working, and forgot to remove it. It was looping over the locker entries and reassigning a "pages_" text field with the information for each iteration because I forgot to append an iterator to the end of the field. ( pages_1, pages_2, etc) Adding the iterator to the field and eliminating the foreach loop in the EvaluationType file got all the fields successfully rendering.

I added a data-grouping attribute to the field assignment in the subscriber to set which page the question should appear on, rendered the bullet points in a hidden div, then used javascript to move the dom nodes into their proper place in the page divs by looping over

document.getElementById('page' + pageNum + '_div').appendChild(
    document.querySelectorAll('[data-grouping="page' + pageNum + '"]').parentNode);

where pageNum is the page number I'm currently building.

A bit of a clunky work-around, but it works, and I still have all my hair.

The updated event subscriber:

<?php

namespace Company\PerformanceBundle\Form\EventListener;

use Company\PerformanceBundle\Entity\Noncommitable\EvaluationLocker;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class EvaluationPageSubscriber implements EventSubscriberInterface{

    private $EvaluationLocker;

    public function __construct(EvaluationLocker $locker)
    {
        $this->EvaluationLocker = $locker;
    }


    public static function getSubscribedEvents() {

        return array(FormEvents::PRE_SET_DATA => 'preSetData');

    }

    public function preSetData(FormEvent $event)
    {
        $form = $event->getForm();

        list($i,$j) = array(0,0);

        foreach ( $this->EvaluationLocker as $contents )
        {
            $i++;

            foreach ($contents as $bullet => $value)
            {
                $j++;
                $form->add('page_' . $i . '-bullet_' . $j, 'text', array(
                    'label' => $bullet,
                    'trim' => true,
                    'data' => $value,
                    'attr' => array(
                        'data-grouping' => 'page' . $i,
                        )
                    ));
            }

        }

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM