简体   繁体   中英

Embedding multiple levels collection of forms in Symfony2

I have the following situation: the CVCFormType is a collection of BenefiItemsFormType. Each BenefitItemFormType has one field that is a collection of BenefitGroupFormType.

I want to be able to dynamically add and remove elements.

I followed the instructions here . Of course they must be tweaked as we talk about nested collections.

On the "fixed" side everything is ok. On the dynamic side (to add and remove elements) so far I've implemented only the inner side (adding BenefitGroups) and only for adding fields.

Here is what I get (which is not right). I have a double link on the top Benefit Item (I should have only one), plus the two group of links (of the first benefit item and of the second one) are not independent (I click on the second of the one above and it adds a field to the one below). I think I'll have to dynamically change the ul class name.

Any help?

Here is a screenshot:

在此输入图像描述

And here is the code:

{% extends "internal.html.twig" %}

{% block content %}

{{ form_start(form) }}
<br><b>CVC</b>
{% for benefititem in form.benefititems %}
<br><b>Benefit Item</b>
{{ form_row(benefititem.comment) }}

<br><b>Benefit Groups</b>
    {# <ul class="benefitgroups"> #}
    <ul class="benefitgroups" data-prototype="{{ form_widget(benefititem.benefitgroups.vars.prototype)|e }}">
        {% for benefitgroup in benefititem.benefitgroups %}
            <li>{{ form_row(benefitgroup.name) }}</li>
        {% endfor %}
    </ul>
{% endfor %}

{{ form_end(form) }}
{% block javascripts %}
<script>
var $collectionHolder;

// setup an "add a benefitgroup" link
var $addBenefitGroupLink = $('<a href="#" class="add_benefitgroup_link">Add a Group</a>');
var $newLinkLi = $('<li></li>').append($addBenefitGroupLink);

jQuery(document).ready(function() {
// Get the ul that holds the collection of benefit groups
$collectionHolder = $('ul.benefitgroups');

// add the "add a benefitgroup" anchor and li to the benefitgroups ul
$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);

$addBenefitGroupLink.on('click', function(e) {
    // prevent the link from creating a "#" on the URL
    e.preventDefault();

    // add a new tag form (see next code block)
    addBenefitGroupForm($collectionHolder, $newLinkLi);
});
});

function addBenefitGroupForm($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 BenefitGroup" link li
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}

</script>
{% endblock %}
{% endblock content %}

If it can help here is the generated HTML:

<html>
<head>
    <meta charset="UTF-8" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <title>Welcome!</title>
            <link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
        <h1>Here you are inside</h1>

<form name="CVC" method="post" action="">
<br><b>CVC</b>
<br><b>Benefit Item</b>
<div>                <label for="CVC_benefititems_0_comment" class="required">Comment</label>    <input type="text" id="CVC_benefititems_0_comment" name="CVC[benefititems][0][comment]" required="required" maxlength="400" value="b1" /></div>

<br><b>Benefit Groups</b>
            <ul class="benefitgroups" data-prototype="&lt;div id=&quot;CVC_benefititems_0_benefitgroups___name__&quot;&gt;&lt;div&gt;                &lt;label for=&quot;CVC_benefititems_0_benefitgroups___name___name&quot; class=&quot;required&quot;&gt;Name&lt;/label&gt;    &lt;input type=&quot;text&quot; id=&quot;CVC_benefititems_0_benefitgroups___name___name&quot; name=&quot;CVC[benefititems][0][benefitgroups][__name__][name]&quot; required=&quot;required&quot; maxlength=&quot;100&quot; /&gt;&lt;/div&gt;&lt;/div&gt;">
                        <li><div>                <label for="CVC_benefititems_0_benefitgroups_0_name" class="required">Name</label>    <input type="text" id="CVC_benefititems_0_benefitgroups_0_name" name="CVC[benefititems][0][benefitgroups][0][name]" required="required" maxlength="100" value="c1b1" /></div></li>
                        <li><div>                <label for="CVC_benefititems_0_benefitgroups_1_name" class="required">Name</label>    <input type="text" id="CVC_benefititems_0_benefitgroups_1_name" name="CVC[benefititems][0][benefitgroups][3][name]" required="required" maxlength="100" value="c2b1" /></div></li>
                </ul>
<br><b>Benefit Item</b>
<div>                <label for="CVC_benefititems_1_comment" class="required">Comment</label>    <input type="text" id="CVC_benefititems_1_comment" name="CVC[benefititems][4][comment]" required="required" maxlength="400" value="b2" /></div>

<br><b>Benefit Groups</b>
            <ul class="benefitgroups" data-prototype="&lt;div id=&quot;CVC_benefititems_1_benefitgroups___name__&quot;&gt;&lt;div&gt;                &lt;label for=&quot;CVC_benefititems_1_benefitgroups___name___name&quot; class=&quot;required&quot;&gt;Name&lt;/label&gt;    &lt;input type=&quot;text&quot; id=&quot;CVC_benefititems_1_benefitgroups___name___name&quot; name=&quot;CVC[benefititems][5][benefitgroups][__name__][name]&quot; required=&quot;required&quot; maxlength=&quot;100&quot; /&gt;&lt;/div&gt;&lt;/div&gt;">
                        <li><div>                <label for="CVC_benefititems_1_benefitgroups_0_name" class="required">Name</label>    <input type="text" id="CVC_benefititems_1_benefitgroups_0_name" name="CVC[benefititems][6][benefitgroups][0][name]" required="required" maxlength="100" value="c2b2" /></div></li>
                </ul>

<div><button type="submit" id="CVC_submit" name="CVC[submit]">Do Something</button></div><input type="hidden" id="CVC__token" name="CVC[_token]" value="MEUAU3VawkCDJ5jTHo5hSTGrgrWS6XUm-UXeEI9onT8" /></form>

Instead of a list I wish to do all this with tables (so adding and removing rows from the table).

The final goal (adding an additional layer) will be the following: 在此输入图像描述

Well, as you say, your problem is on the dynamic side, aka, Client Side.

I gonna post my shot on what you're trying to do here .

But before, a pro-tip: NEVER EVER print html on an attribute, use another technique, like the one I'm using for example, templates (to replace your prototype attr). There are a ton more techniques to do this, I'll just explain mine.

var $outerTemplate;
var $innerTemplate;
var $outerContainer;

jQuery(document).ready(function($) {
    $outerTemplate = $('#top-form-template');
    $innerTemplate = $('#inner-form-template');
    $outerContainer = $("#row-container");
    $("#addRow").on('click', function(e){
        addOuterForm();
    });

    $outerContainer.on('click', '.addItem', function (e) {
        addInnerForm(e.target.dataset.rowId);
    })

    $outerContainer.on('click', '.destroy-row', function (e) {
        destroyRow(e.target.dataset.rowId);
    });

    $outerContainer.on('click', '.destroy-item', function (e) {
        destroyItem(e.target.dataset.itemId);
    });
});

function addOuterForm () {
    var compiled = _.template($outerTemplate.html());
    var html = compiled({
        outerId: _.uniqueId(),
        innerId: _.uniqueId()
    });
    $outerContainer.append(html);
}

function addInnerForm (outerId) {
    var compiled = _.template($innerTemplate.html());

    var html = compiled({
        outerId: outerId,
        innerId: _.uniqueId()
    });

    $outerContainer.find('#row-'+outerId).find('.benefitgroups').append(html);
}

function destroyRow(id){
    $("#row-"+id).remove();
}
function destroyItem(id){
    $("#item-"+id).remove();
}

What I did is create 2 templates, one for the outer form (the one with benefit item & group) and other with an inner form (the extra benefit items). Then attach & removing them using some buttons. I encorage you to take a look at templating engines on client side (I notice you know how to use Twig templating engine maybe Handlebars will be easy to catch for you).

I'm using lodash templating engine, because its quite simple and lodash tools are very powerful and useful.

To embed multiple collection of forms usually i use self-widget to control templating. For example:

{{form.name}}
<ul id="benefit-items" data-prototype="{{_self.widget_prototype(form.benefitItems.vars.prototype}|e}}">
    {% for benefitItem in form.benefitItems %}
        {{_self.widget_prototype(benefitItem)}}
    {% endfor %}
    <li id="add-benefit-item" onclick="addBenefitItem(this);">add benefit</li>
</ul>
{% macro widget_prototype(form) %}
    <li class="benefitItem">
        {{form.title}}
        <ul class="benefit-group" data-prototype="{{form_widget(form.benefitGroups.vars.prototype)|e}}">
        </ul> 
    </li>
{% endmacro %}

It's how i'm used to create multiple collection forms.

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