带有子集合的 Symfony 3 表单集合

Symfony 3 form collection with sub-collection

我有一个表单,其中包含一个名为 "planes" 的字段。

该字段可以是多个(集合),具有以下子字段: - 速度 - 重量 - 颜色 - 引擎

子字段"engines"也是字段的集合: - 转速 - fuel_type - 体重

我想知道 Symfony 表单生成器会是什么样子,因为用户应该可以根据需要添加尽可能多的平面。这同样适用于引擎。

看起来像这样:

class 的建造者有飞机 collection:

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('planes', CollectionType::class, array(
            'entry_type' => PlaneType::class,
            'allow_add'    => true,
            'allow_delete' => true, 
            'by_reference' => false,// this will call get/set of your entity
            'prototype' => true, // is needed coz there will be 2 prototypes 
            'prototype_name'=> '__planes__'//1-st prototype name
            'attr' => [
                'class' => 'embeddedCollection'
            ]

        ));
    }

PlaneType 中的建造者 class:

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('speed');
        $builder->add('weight');
        $builder->add('color');
        $builder->add('engines', CollectionType::class, array(
            'entry_type' => EngineType::class,
            'allow_add'    => true,
            'allow_delete' => true, 
            'by_reference' => false,// this will call get/set of your entity
            'prototype' => true, // is needed coz there will be 2 prototypes 
            'prototype_name'=> '__engines__',//2 -nd prototype
            'attr' => [
                'class' => 'subEmbeddedCollection'
            ]
        ));
    }

EngineType 中的构建器 class:

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('rpm');
        $builder->add('fuel_type');
        $builder->add('weight');
    }

您还应该为动态 adding/removing 字段添加一些 javascript,在添加过程中您应该将原型名称与字段编号的 id 交换。

编辑: 一些带有 JQuery 的 js 代码使其在 front-end:

上工作
function initCollection(collectionHolder, addItemButton, deleteItemButton, prototypePattern) {
    let $addItemButton = $(addItemButton);
    // Get the ul that holds the collection of tags
    let $collectionHolder = $(collectionHolder);
    // add a delete link to all of the existing tag form li elements
    $collectionHolder.children('.form-group').each(function () {
        addFormDeleteLink($(this), deleteItemButton);
    });
    // add the "add a tag" anchor and li to the tags ul
    $collectionHolder.append($addItemButton);
    // 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);
    //when new element is added then trigger event on collection
    let addNewElementEvent = jQuery.Event ('element-added');
    let addNewChildCollection = jQuery.Event ('new-child-collection');
    $addItemButton.on('click', function (e)  {
        // add a new tag form (see next code block)
        lastAddedElement = addForm($collectionHolder, $addItemButton, deleteItemButton, prototypePattern);
        if($collectionHolder.hasClass('embeddedCollection')) {
            $collectionHolder.trigger(addNewElementEvent);
        }else{
            $collectionHolder.trigger(addNewChildCollection);
        }
    });
}

function addForm($collectionHolder, $addItemButton, deleteItemButton, prototypePattern) {
    // Get the data-prototype explained earlier
    let prototype = $collectionHolder.data('prototype');
    // get the new index
    let index = $collectionHolder.data('index');
    let newForm = prototype;
    newForm = newForm.replace(prototypePattern, 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
    let $newFormLi = $(newForm);
    $addItemButton.before($newFormLi);
    addFormDeleteLink($newFormLi, deleteItemButton);
    return $newFormLi;
}

function addFormDeleteLink($tagFormLi, deleteItemButton) {
    let $removeFormButton = $(deleteItemButton);
    $tagFormLi.append($removeFormButton);

    $removeFormButton.on('click', function (e) {
        // remove the li for the tag form
        let $parent = $tagFormLi.parent();
        $tagFormLi.remove();

        if($removeFormButton.hasClass('labels-rewrite'))
        {
            $parent.trigger(jQuery.Event('labels-rewrite'));
        }

    });
}

parent collection 应该有 class embeddedCollection,然后如果添加了新的 child 将触发事件 'element-added' .如果添加了带有 child collection 引擎的新飞机,您需要在添加的引擎 collection.

上调用 initCollection 方法

所以首先你需要初始化现有的 collections,例如:

//init all sub collections of engines of the planes 
$('.subEmbeddedCollection').each(function () {
    initCollection($(this),
    '<button type="button">Add engine</button>',
    '<button type="button" >Delete engine</button>',
        /__engines__/g
    );
});
//init main collection of planes
let $collectionHolder = $('.embeddedCollection');
initCollection($collectionHolder,
    '<button type="button">Add plane</button>',
    '<button type="button" >Delete plane</button>',
    /__planes__/g
); 

然后当事件 element-added 在主要 collection 平面中被触发时,您应该初始化所有动态添加的引擎子集合:

//init collection in added plane
$collectionHolder.on('element-added', function () {

    let $lastChildCollection = $(this).find('.engines').last();
    initCollection($lastChildCollection,
    '<button type="button">Add engine</button>',
    '<button type="button" >Delete engine</button>',
        /__exam_type_lab_request__/g
    );
});