0

What I'm trying to do is "Embed a Collection of Forms" as described here : http://symfony.com/doc/2.3/cookbook/form/form_collections.html

I have managed to get it set up correctly and up to the stage where I add JavaScript/JQuery it works fine. However now I have added the JQuery section I can't get it to work quite right.

ScreenShot Image showing issue2 Initial Loaded Form ScreenShot Image showing issue1 Form after clicking add product once.

As you can see above when I click add product it is adding 2 sets of the add product form. This in it's self is not right. To make matters worse, the first of the 2 new rows added doesn't persist to the database. The same thing happens each time I 'add new product'.

Now the documentation only deals with one filed 'tags' but I assumed just adapting the tutorial to my needs would be enough. I have fiddled everywhere inside my Entities/Mappings/Controllers and Views trying to sort this. Think I have tinkered with everything trying to get it right but cannot. Except for the JQuery code itself as I have no idea about JQuery or Javascript (Having enough of a job learning PHP at the moment without complicating things though I fully intend on learning it as soon as I've got a grasp of Symfony).

//Recipe Form

class RecipeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('recipename') ->add('recipedesc')
        ->add('recipeyeild') ->add('recipecost');
    $builder->add('product', 'collection', array(
    'type' => new ProductRecipeType(),
    'allow_add'    => true,
    'by_reference' => false,
    'allow_delete' => true,));
}

//ProductRecipe Form

class ProductRecipeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
->add('amount') ->add('product');
}

//Recipe view new.twig

<h1>Recipes creation</h1>
{{ form_start(form) }}
{{ form_row(form.recipename) }} {{ form_row(form.recipedesc) }}
{{ form_row(form.recipeyeild) }} {{ form_row(form.recipecost) }}

<h3>Products</h3>
<ul class="product" data-prototype="{{ form_widget(form.product.vars.prototype)|e }}">       

{% for products in form.product %}
    {{ form_row(products.amount) }}
    {{ form_row(products.product) }}
{% endfor %}
</ul>
{{ form_end(form) }}

//JQuery for the recipe form (Currently in the Twig file however I do intent on moving it to a separate file once working properly)

<script>
var $collectionHolder;
// setup an "add a product" link
var $addProductsLink = $('<a href="#" class="add_product_link">Add a product</a>');
var $newLinkLi = $('<li></li>').append($addProductsLink);

jQuery(document).ready(function() {
    // Get the ul that holds the collection of tags

    $collectionHolder = $('ul.product');

    // add a delete link to all of the existing tag form li elements
    $collectionHolder.find('li').each(function() {
        addProductsFormDeleteLink($(this));
    });



    // add the "add a tag" anchor and li to the tags 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);

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

        // add a new tag form (see next code block)
        addProductsForm($collectionHolder, $newLinkLi);
    });
});
function addProductsForm($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(/__product__/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);

    addProductsFormDeleteLink($newFormLi);
}
function addProductsFormDeleteLink($productsFormLi) {
    var $removeFormA = $('<a href="#">Delete</a>');
    $productsFormLi.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
        $productsFormLi.remove();
    });
}

Now what I think is causing the issue is this line :

$collectionHolder.data('index', $collectionHolder.find(':input').length);

Specifically the .find(':input') section as I'm guessing it is counting the input fields but this is just pure guess work.

Also while I'm asking this question I also would prefer it if the 'Add products' link didn't have a delete button as when clicked it completely removes the 'Add products' link from the form, and the only way to get it back is by refreshing the page. Think I've covered everything.

EDIT

This is the prototype stuff created from 'view source' (Which looks disgusting to be honest lol)

     <ul class="product" data-prototype="&lt;div
 id=&quot;bc_inventorybundle_recipe_product___name__&quot;&gt;&lt;div&gt;&lt;
 label for=&quot;bc_inventorybundle_recipe_product___name___amount&quot;
 class=&quot;required&quot;&gt;Amount&lt;/label&gt;&lt;input type=&quot;
 text&quot; id=&quot;bc_inventorybundle_recipe_product___name___amount&quot;
 name=&quot;bc_inventorybundle_recipe[product][__name__][amount]&quot; 
 required=&quot;required&quot; /&gt;&lt;/div&gt;&lt;div&gt;&lt;label
 for=&quot;bc_inventorybundle_recipe_product___name___product&quot;&gt;Product&lt;
 /label&gt;&lt;select id=&quot;bc_inventorybundle_recipe_product___name___product&quot;
 name=&quot;bc_inventorybundle_recipe[product][__name__][product]&quot;&gt;&lt;option  
 value=&quot;&quot;&gt;&lt;/option&gt;&lt;option 
 value=&quot;66&quot;&gt;Butter&lt;/option&gt;&lt;option 
 value=&quot;67&quot;&gt;Beef&lt;/option&gt;&lt;option 
 value=&quot;68&quot;&gt;Jam&lt;/option&gt;&lt;option value=&quot;69&quot;&gt;Xanthan
 Gum&lt;/option&gt;&lt;option value=&quot;70&quot;&gt;Test
 Product&lt;/option&gt;&lt;option
 value=&quot;71&quot;&gt;test&lt;/option&gt;&lt;option 
 value=&quot;72&quot;&gt;test&lt;/option&gt;&lt;option 
 value=&quot;73&quot;&gt;Beef&lt;/option&gt;&lt;option  
 value=&quot;74&quot;&gt;Beef&lt;/option&gt;&lt;/select&gt;&lt;/div&gt;&lt;/div&gt;"> 

1 Answer 1

1

Ok then, so I think I've pretty much cracked it.

Issue 1: Add product button resulted in 3 'forms' however only 2 would be persisted.

Answer 1: Add < li > tags to all rows in form.

 {% for products in form.product %}
   <li>{{ form_row(products.amount) }}</li>
   <li>{{ form_row(products.product) }}</li>
 {% endfor %}

This fixes persistence but...

Issue 2: The reason I had initially removed the < li > tags was because It would add 'Delete' buttons to everything ...

Fixed Image Example

Answer 2: I fixed this by adding a class to the < li > for one field.

  <li class="formrowprod">{{ form_row(products.product) }}</li>

and then selected it by modifying this in the JQuery:

$collectionHolder.find('li').each(function()

To this:

$collectionHolder.find('li.formrowprod').each(function()

Issue 3: Now almost everything is fixed, everything except there is still "DeleteDelete" (Two Delete buttons next to each other on the starting row which is both ugly and unnecessary (Every recipe will have at least one product so no one will want to remove all)

Answer 3:

Remove the li. completely from .find():

    $collectionHolder.find('formrowprod').each(function() {
    addProductsFormDeleteLink($(this));
    });

So it now all works, pretty well however it still loads 2 rows instead of 1 when I click 'Add Product' but it's not the end of the world but would love to know why if anyone can help.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.