首页 > 解决方案 > Symfony 3 带有子集合的表单集合

问题描述

我有一个名为“planes”的字段的表单。

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

子字段“engines”也是字段的集合: - rpm -fuel_type - weight

我想知道 Symfony 表单构建器会是什么样子,因为用户应该可以根据需要添加任意数量的平面。这同样适用于发动机。

标签: symfonysymfony-forms

解决方案


它看起来像这样:

具有平面集合的类中的生成器:

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 类中的生成器:

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 类中的生成器:

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

此外,您应该为动态添加/删除字段添加一些 javascript,在添加过程中您应该使用字段编号的 id 交换原型名称。

编辑: 一些带有 JQuery 的 js 代码使其在前端工作:

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'));
        }

    });
}

父集合应该有 class embeddedCollection,如果添加了新的子集合,则该事件'element-added'将被触发。如果添加了带有子引擎集合的新平面,您需要在添加的引擎集合上调用 initCollection 方法。

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

//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
); 

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

//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
    );
});

推荐阅读