首页 > 解决方案 > 如何从 symfony 4 形式的嵌入式集合中获取添加的项目数据?allow_add 似乎不起作用

问题描述

我已经完成了一个表单,其中嵌入了一个实体(作物生产)及其子项(事件),类似于我们可以在此处找到的表单,即使用 jQuery 进行动态添加/删除项目的工作。

一切正常,子实体的编辑/删除,除了表单中添加的项目未保存在请求中。

似乎当我使用 jQuery 在模板中添加新项目时,表单原型/标签无法识别/映射/与表单链接,但我无法弄清楚为什么原型是由 symfony 自动生成的,即动态添加的输入的名称是好的。

edit.html.twig(带有 jQ​​uery 的模板)

{% extends picture is not defined ? 'base.html.twig' : 'blank.html.twig' %}




{% block main %}

<div class="card card-body">
{{ form_start(form) }}
     {{ form_row(form.Crop_production) }}
     {# render each row and field in the embedded form, in this example it's rendering the experience (Exp) fiels as rows #}
</div>
     <div class="col-sm-12 col-sm-offset-2">

<legend class="col-form-label required mt-3">Crop production events</legend>

       <div class="row" id="event_list" data-prototype="{{ form_widget(form.cropProductionEvents.vars.prototype)|e }}">

         {% for row in form.cropProductionEvents %}
             <div class="panel panel-warning">
             <div class="col-md-12 col-xl-12">
                 <div class="card mb-3 mt-3 widget-content">
                 <div class="widget-content-outer">
                     {{ form_row(row) }}
                 </div>
                 </div>
             </div>
             </div>

         {% endfor %}
       </div>
     </div>
 {{ form_end(form) }}
<br>


<script>
// this variable is the list in the dom, it's initiliazed when the document is ready
console.log("init");
var $collectionHolder;
// the link which we click on to add new items
var $addNewItem = $('<a href="#" class="btn btn-info mb-3 ml-3">Add new event</a>');
// when the page is loaded and ready
$(document).ready(function () {
 console.log("ready");

   // get the collectionHolder, initilize the var by getting the list;
   $collectionHolder = $('#event_list');
   // append the add new item link to the collectionHolder
   $collectionHolder.parent().append($addNewItem);
   // add an index property to the collectionHolder which helps track the count of forms we have in the list
   $collectionHolder.data('index', $collectionHolder.find('.panel').length)
   // finds all the panels in the list and foreach one of them we add a remove button to it
   // add remove button to existing items
   $collectionHolder.find('.panel').each(function () {
       // $(this) means the current panel that we are at
       // which means we pass the panel to the addRemoveButton function
       // inside the function we create a footer and remove link and append them to the panel
       // more informations in the function inside
       addRemoveButton($(this));
   });
   // handle the click event for addNewItem
   $addNewItem.click(function (e) {
       // preventDefault() is your  homework if you don't know what it is
       // also look up preventPropagation both are usefull
       e.preventDefault();
       // create a new form and append it to the collectionHolder
       // and by form we mean a new panel which contains the form
       addNewForm();
   })
});
/*
* creates a new form and appends it to the collectionHolder
*/
function addNewForm() {
   console.log("add new form");

   // getting the prototype
   // the prototype is the form itself, plain html
   var prototype = $collectionHolder.data('prototype');
   // get the index
   // this is the index we set when the document was ready, look above for more info
   var index = $collectionHolder.data('index');
   // create the form
   var newForm = prototype;
   // replace the __name__ string in the html using a regular expression with the index value
   newForm = newForm.replace(/__name__/g, index);
   // incrementing the index data and setting it again to the collectionHolder
   $collectionHolder.data('index', index+1);
   // create the panel
   // this is the panel that will be appending to the collectionHolder
   var $panel = $('<div class="panel panel-warning"></div>');
   // create the panel-body and append the form to it
   var $panelBody = $('<div class="col-md-12 col-xl-12"></div>').append($('<div class="card mb-3 mt-3 widget-content"></div>').append($('<div class="widget-content-outer"></div>').append($('<fieldset class="form-group"></fieldset>').append(newForm))));
   // append the body to the panel
   $panel.append($panelBody);
   // append the removebutton to the new panel
   addRemoveButton($panel);
   // append the panel to the addNewItem
   // we are doing it this way to that the link is always at the bottom of the collectionHolder
   $panel.hide();
   $("#event_list").append($panel);
   $panel.slideDown(200);

   console.log("selection holder");
   console.log($collectionHolder.data('index'));
}

/**
* adds a remove button to the panel that is passed in the parameter
* @param $panel
*/
function addRemoveButton ($panel) {
   console.log("add remove button");

   // create remove button
   var $removeButton = $('<a href="#" class="btn btn-danger">Remove</a>');
   // appending the removebutton to the panel footer
   var $panelFooter = $('<div class="panel-footer"></div>').append($removeButton);
   // handle the click event of the remove button
   $removeButton.click(function (e) {
       e.preventDefault();
       // gets the parent of the button that we clicked on "the panel" and animates it
       // after the animation is done the element (the panel) is removed from the html
       $(e.target).parents('.panel').slideUp(200, function () {
           $(this).remove();
       })
   });
   // append the footer to the panel
   var $form = $panel.find(".widget-content-outer");
   console.log($form);
   $form.append($panelFooter);
}
</script>

{% endblock %}

CropProductionType.php(父表单类型)

<?php

namespace App\Form;

use App\Entity\CropProduction;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type as Type;

class CropProductionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

      $units = [
        "money" => [
          'currency' => 'USD',
          'help' => 'value in $',
          'required' => false,
          "attr" => ["step" => '0.01']
        ],
        "surface" => [
          'help' => 'value in ha',
          "attr" => ["step" => '0.01']
        ]
      ];

     $builder->add(
        $builder->create('Crop_production', Type\FormType::class, [
                  'inherit_data' => true,
                  'row_attr' => [
                    'class' => 'form-col-2'
                  ],
                ])
                ->add('type', Type\ChoiceType::class, ["label" => "Type harvested", "required" => true, "choices" => [
                      "Grain" => "Grain",
                      "Fruit" => "Fruit",
                      "Tuber" => "Tuber",
                      "Stem" => "Stem"
                    ]
                  ])

                  ->add('dateHarvested', Type\DateType::class, [
                      'label' => 'Date Harvested',
                      'widget' => 'single_text',
                      // this is actually the default format for single_text
                      'format' => 'yyyy-MM-dd',
                  ])

                ->add('prodFarmerStatement', Type\NumberType::class, ["label" => "Production farmer statement", 'help' => 'in kg dry/ha (grain) or in ton fresh/ha (other)'])

                ->add('prodFieldMeasure', Type\NumberType::class, ["label" => "Production field measure", 'help' => 'in kg dry/ha (grain) or in ton fresh/ha (other)'])
      );



        $builder->add('cropProductionEvents', Type\CollectionType::class, [
                   'entry_type' => CropProductionEventType::class,
                   'entry_options' => [
                       'label' => false
                   ],
                   // this allows the creation of new forms and the prototype too
                   'allow_add' => true,
                   // self explanatory, this one allows the form to be removed
                   'allow_delete' => true,
                   'delete_empty' => true,
                   'prototype' => true,
                   'by_reference' => false,

               ]);




      $builder->add('save', Type\SubmitType::class, ['label' => 'Save']);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => CropProduction::class,
            'label' => 'fdg',
        ]);
    }


}

CropProductionController.php 编辑函数(父控制器)

  /**
     * @Route("{id}/delete", name="_delete")
     */
    public function delete_production($id)
    {
      $em = $this->getDoctrine()->getManager();
      $crop_production = $em->getRepository(CropProduction::class)->find($id);
      $crop = $crop_production->getCrop();
      $em->remove($crop_production);
      $em->flush();
      return $this->redirectToRoute("crop_details", ["id" => $crop->getId(), '_fragment' => 'crop_production']);
    }

    /**
     * @Route("{id}/edit", name="_edit")
     */
    public function edit(Request $request, $id)
    {
      $em = $this->getDoctrine()->getManager();
      $form = NULL;

      $crop_production = $em->getRepository(CropProduction::class)->find($id);

      // $events = new ArrayCollection();
      // foreach ($crop_production->getCropProductionEvents() as $event) {
      //   $events->add($event);
      // }

      if(!$crop_production) return $this->render('error/blank.html.twig', []);
      $form = $this->createForm(CropProductionType::class, $crop_production);

      $form->handleRequest($request);
      if ($form->isSubmitted() && $form->isValid()) {

          

          $em->persist($crop_production);
          $em->flush();
          return $this->redirectToRoute("crop_details", ["id" => $crop_production->getCrop()->getId()]);
      }



      return $this->render('crop_production/edit.html.twig', [
        "crop_prod" => $crop_production,
        'form' => $form->createView(),
      ]);
    }

知道在模板上添加元素可能会错过什么吗?谢谢

标签: phpformssymfonydoctrine-ormsymfony4

解决方案


data-index=""您的<div class="row" id="event_list" >. 你必须把当前数量的孩子(集合类型),所以 addNewForm()可以指望......

像这样:

<div class="row" id="event_list" data-index="{{ form.cropProductionEvents|length }}" data-prototype="{{ form_widget(form.cropProductionEvents.vars.prototype)|e }}">

推荐阅读