Symfony 2:如何在表单中处理嵌套集合

8

我正在尝试创建一个包含嵌套集合的表单。 我不知道如何处理JS部分来显示子集合。有人知道我该怎么做吗?

这是我的表单代码:

class ParentFormType extends AbstractType 
{

    public function buildForm(FormBuilderInterface $builder, array $options) 
    {   
        $builder
              ->add('case', 'choice', array(
                        'choices'   => array(
                            'case1'   =>  'case1',
                            'case2'   =>  'case2',
                            'case3'   =>  'case3',
                )))
            ->add ('subForm1', 'collection', array (
             'type' => new Sub1FormType(),
             'allow_add' => true,
             'allow_delete' => true,
             'by_reference' => false,
             'prototype' => true,
            ))

    ;

        $builder->add('save',"submit") ;
    }


    public function setDefaultOptions(OptionsResolverInterface $resolver) {
    }

    public function getName() {
        return 'formtestparenttype';
    }
}

class Sub1FormType extends AbstractType 
{

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

        $builder
          ->add('fieldSub1',"text" )
          ->add ('childForm1', 'collection', array (
              'type' => new Sub2FormType,
              'allow_add' => true,
              'allow_delete' => true,
              'by_reference' => false,
              'prototype' => true,
          ))
          ;
    }


    public function setDefaultOptions(OptionsResolverInterface $resolver) {

    }

    public function getName() {
        return 'formtestsub1type';
    }
}

class Sub2FormType extends AbstractType 
{

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

        $builder
               ->add('fieldSub2',"text" )
          ;
    }


    public function setDefaultOptions(OptionsResolverInterface $resolver) {

    }

    public function getName() {
        return 'formtesttype';
    }
}

控制器:

$form = $this->createForm(new ParentFormType() ) ;
return $this->render('MyBundle:Test:test.html.twig', array(
    'form' => $form->createView()
  ));

这里是Twig和JS相关的部分:

{% extends '::base.html.twig' %}
{% block content %}

{{ form_start(form) }} 
    <h3>Tags</h3>
    <ul class="collectionHolder" data-prototype="{{ form_widget(form.subForm1.vars.prototype)|e }}">
        {# iterate over each existing tag and render its only field: name #}
        {% for subForm1 in form.subForm1 %}
            <li>{{ form_row(subForm1) }} </li>
             <ul class="collectionHolder" data-prototype="{{ form_widget(subForm2.vars.prototype)|e }}">
            {%for subForm2 in subForm1.subForm2 %}
                <li>{{ form_row(subForm2) }}</li>
            {% endfor %}

        {% endfor %}
    </ul>
{{ form_end(form) }}



<script>
var $collectionHolder;

// setup an "add a tag" linkd
var $addTagLink = $('<a href="#" class="add_tag_link">Add</a>');
var $newLinkLi = $('<li></li>').append($addTagLink);

jQuery(document).ready(function() {

    function addTagForm($collectionHolder, $newLinkLi) 
    { 
        // Get the data-prototype explained earlier
        var prototype = $collectionHolder.data('prototype');
        // get the new index
        var index = $collectionHolder.data('index');
        // Replace '__name__' in the prototype's HTML to
        // instead be a number based on how many items we have
        var newForm = prototype.replace(/__name__/g, index);

        // increase the index with one for the next item
        $collectionHolder.data('index', index + 1);

        // Display the form in the page in an li, before the "Add a tag" link li
        var $newFormLi = $('<li></li>').append(newForm);
        $newLinkLi.before($newFormLi);
     }


    $collectionHolder = $('.collectionHolder');

    $collectionHolder.append($newLinkLi);

    // count the current form inputs we have (e.g. 2), use that as the new
    // index when inserting a new item (e.g. 2)
    $collectionHolder.data('index', $collectionHolder.find(':input').length);

    $addTagLink.on('click', function(e) {
        e.preventDefault();
        addTagForm($collectionHolder, $newLinkLi);
    });
});
</script>
{% endblock content %}

你能解释一下你的问题具体是什么吗?从你的代码来看,你已经访问了Symfony文档中的Forms Collection页面。 - ecc
2个回答

4

您的问题在于示例JavaScript未编写以处理多个集合。

我编写了一个单独的JavaScript文件,每当处理这些表单集合时,我都会包含它:

// js/form.collection.js
function FormCollection(div_id)
{
    // keep reference to self in all child functions
    var self=this;

    self.construct = function () {
        // set some shortcuts
        self.div = $('#'+div_id);
        self.div.data('index', self.div.find(':input').length);

        // add delete link to existing children
        self.div.children().each(function() {
            self.addDeleteLink($(this));
        });

        // add click event to the Add new button
        self.div.next().on('click', function(e) {
            // prevent the link from creating a "#" on the URL
            e.preventDefault();

            // add a new tag form (see next code block)
            self.addNew();
        });
    };

    /**
     * onClick event handler -- adds a new input
     */
    self.addNew = function () {
        // Get the data-prototype explained earlier
        var prototype = self.div.data('prototype');

        // get the new index
        var index = self.div.data('index');

        // Replace '__name__' in the prototype's HTML to
        // instead be a number based on how many items we have
        var newForm = prototype.replace(/__name__/g, index);

        // increase the index with one for the next item
        self.div.data('index', index + 1);

        // Display the form in the page in an li, before the "Add a tag" link li
        self.div.append($(newForm));

        // add a delete link to the new form
        self.addDeleteLink( $(self.div.children(':last-child')[0]) );

        // not a very nice intergration.. but when creating stuff that has help icons, 
        // the popovers will not automatically be instantiated
        //initHelpPopovers();

        return $(newForm);
    };

    /**
     * add Delete icon after input
     * @param Element row
     */
    self.addDeleteLink = function (row) {
        var $removeFormA = $('<a href="#" class="btn btn-danger" tabindex="-1"><i class="entypo-trash"></i></a>');
        $(row).find('select').after($removeFormA);
        row.append($removeFormA);
        $removeFormA.on('click', function(e) {
            // prevent the link from creating a "#" on the URL
            e.preventDefault();

            // remove the li for the tag form
            row.remove();
        });
    };

    self.construct();
}

在所需的模板中,我只需通过ID定位到集合,并通过ID实例化FormCollection,如下所示:

{% extends '::base.html.twig' %}
{% block content %}

{{ form_start(form) }} 
    <h3>Tags</h3>
    <ul id="col-subform1" data-prototype="{{ form_widget(form.subForm1.vars.prototype)|e }}">
        {# iterate over each existing tag and render its only field: name #}
        {% for subForm1 in form.subForm1 %}
            <li>{{ form_row(subForm1) }} </li>
             <ul id="col-subform2" data-prototype="{{ form_widget(subForm2.vars.prototype)|e }}">
            {%for subForm2 in subForm1.subForm2 %}
                <li>{{ form_row(subForm2) }}</li>
            {% endfor %}

        {% endfor %}
    </ul>
{{ form_end(form) }}

<script type="text/javascript" src="/js/form.collection.js"></script>
<script type="text/javascript">
  new FormCollection('col-subform1');
  new FormCollection('col-subform2');
</script>

这看起来不错!我一定会实现这段代码。 - Lex Hartman
嗨!当每个表单A可以嵌套多个表单B时,它无法解决。 - NorthmaN

0

以防其他人遇到同样的问题。我遇到了相同的困扰,以下是我认为导致这种行为的原因。

如果我的情况不是正常的Symfony行为,而是由于我的错误,请随时纠正我。

感谢Rein Baarsma,我发现我的问题出在原型过程中。

Symfony为什么在集合中不创建第2、第3...第n个子项,是因为第二层集合的原型不像第一层的那样是"空白"的。

第一层集合的"空白"原型看起来是这样的(对于输入部分):someObject_collection___name___,其中___name___将被替换为集合索引,因此在文档的JavaScript中使用了replace(/__name__/g, index)

但是对于第二层集合,原型并不是"空白"的,而是与第一层集合中相关元素的索引生成的,例如:someObject_collection_1__otherCollection_1_,而不是我所认为的someObject_collection_1__otherCollection__name__

所以,当第二级集合调用replace函数时,找不到匹配的内容来替换___name___为新的子索引。

解决方法是改变第二级集合的replace调用,将第一级集合元素的索引替换为当前第二级集合元素的索引。

类似于:newForm.replace(/collection_\d/g, 'collection_' + index);,对于label标签的for属性和input标签的id属性。

还有类似于:newForm.replace(/\[ligneCPackProds\]\[\d\]/g, '[collection][' + index + ']');,对于input标签的name属性。

通过这样做,我成功地获得了我的第二级集合中的所有子元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接