Skip to content

Commit fab2805

Browse files
khepinweaverryan
authored andcommitted
Add cookbook doc for dynamically adding to a collection field
1 parent de2ccc4 commit fab2805

File tree

1 file changed

+131
-3
lines changed

1 file changed

+131
-3
lines changed

cookbook/form/form_collections.rst

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,141 @@ great, your user can't actually add any new todos yet.
239239
Allowing "new" todos with the "prototype"
240240
-----------------------------------------
241241

242-
This section has not been written yet, but will soon. If you're interested
243-
in writing this entry, see :doc:`/contributing/documentation/overview`.
242+
.. note::
243+
244+
Allowing the user to dynamically add new todos means that we'll need to
245+
use some javascript. Previously we added two tags to our form in the
246+
controller. Now we need to let the user add as many tag forms as he
247+
needs to directly in the browser. This will be done through a bit of
248+
javascripting.
249+
250+
.. tip::
251+
252+
If you want to see a working example of how this can be done, you can
253+
check khepin/ProductBundle on github. The basic idea is similar although
254+
it's products and their tags instead of todos.
255+
256+
The first thing we need to do is to let our form collection know that it will
257+
receive an unknown number of tags. So far we added two and the form type
258+
expects to receive two as well otherwise an error will be thrown:
259+
``This form should not contain extra fields``. For this we add the ``'allow_add'``
260+
option to our collection field.
261+
262+
// src/Acme/TaskBundle/Form/Type/TaskType.php
263+
namespace Acme\TaskBundle\Form\Type;
264+
265+
use Symfony\Component\Form\AbstractType;
266+
use Symfony\Component\Form\FormBuilder;
267+
268+
class TaskType extends AbstractType
269+
{
270+
public function buildForm(FormBuilder $builder, array $options)
271+
{
272+
$builder->add('description');
273+
274+
$builder->add('tags', 'collection', array(
275+
'type' => new TagType(),
276+
'allow_add' => true,
277+
'by_reference' => false,
278+
));
279+
}
280+
281+
public function getDefaultOptions(array $options)
282+
{
283+
return array(
284+
'data_class' => 'Acme\TaskBundle\Entity\Task',
285+
);
286+
}
287+
288+
public function getName()
289+
{
290+
return 'task';
291+
}
292+
}
293+
294+
Note that we also added ``'by_reference' => false``. This is because
295+
we are not sending a reference to an existing tag but rather creating
296+
a new tag at the time we save the todo and its tags together.
297+
298+
The ``allow_add`` option also does one more thing. It will add a ``data-prototype``
299+
property to the ``div`` containing the tag collection. This property
300+
contains html to add a Tag form element to our page like this:
301+
302+
<div data-prototype="&lt;div&gt;&lt;label class=&quot; required&quot;&gt;$$name$$&lt;/label&gt;&lt;div id=&quot;khepin_productbundle_producttype_tags_$$name$$&quot;&gt;&lt;div&gt;&lt;label for=&quot;khepin_productbundle_producttype_tags_$$name$$_name&quot; class=&quot; required&quot;&gt;Name&lt;/label&gt;&lt;input type=&quot;text&quot; id=&quot;khepin_productbundle_producttype_tags_$$name$$_name&quot; name=&quot;khepin_productbundle_producttype[tags][$$name$$][name]&quot; required=&quot;required&quot; maxlength=&quot;255&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;" id="khepin_productbundle_producttype_tags">
303+
</div>
304+
305+
We will get this property from our javascript and use it to display
306+
new Tag forms. To make things simple, we will embed jQuery in our page
307+
as it allows for more simple cross-browser manipulation of the page.
308+
309+
First let's add a link on the ``new`` form with a class ``add_tag_link``.
310+
Everytime this is clicked by the user, we will add an empty tag for him.
311+
312+
{% extends "::base.html.twig" %}
313+
{% block body %}
314+
<h1>Product creation</h1>
315+
316+
<form action="{{ path('product_create') }}" method="post" {{ form_enctype(form) }}>
317+
{{ form_widget(form) }}
318+
<p>
319+
<button type="submit">Create</button>
320+
</p>
321+
</form>
322+
323+
<ul class="record_actions">
324+
<li>
325+
<a href="{{ path('product') }}">
326+
Back to the list
327+
</a>
328+
</li>
329+
<li>
330+
<a href="#" class="add_tag_link">
331+
Add a tag
332+
</a>
333+
</li>
334+
</ul>
335+
{% include "AcmeTodoBundle:Todo:js.html.twig" %}
336+
{% endblock %}
337+
338+
We also include a template containing the javascript needed to add
339+
the form elements when the link is clicked.
340+
341+
.. note:
342+
343+
It is better to separate your javascript in real js files than
344+
to write it inside the HTML as we are doing here.
345+
346+
Our script can be as simple as this:
347+
348+
<script>
349+
function addTagForm() {
350+
// Get the div that holds the collection of tags
351+
var collectionHolder = $('#task_tags');
352+
// Get the data-prototype we explained earlier
353+
var prototype = collectionHolder.attr('data-prototype');
354+
// Replace '$$name$$' in the prototype's HTML to
355+
// instead be a number based on the current collection's length.
356+
form = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);
357+
// Display the form in the page
358+
collectionHolder.append(form);
359+
}
360+
361+
$('a.jslink').click(function(event){
362+
addTagForm();
363+
});
364+
</script>
365+
366+
Now everytime a user clicks the ``Add a tag`` link, a new sub form
367+
will appear on the page. The server side form component is aware
368+
it should not expect any specific size for the ``Tag`` collection.
369+
And all the tags we add while creating the new ``Todo`` will be
370+
saved together with it.
371+
244372

245373
.. _cookbook-form-collections-remove:
246374

247375
Allowing todos to be removed
248376
----------------------------
249377

250378
This section has not been written yet, but will soon. If you're interested
251-
in writing this entry, see :doc:`/contributing/documentation/overview`.
379+
in writing this entry, see :doc:`/contributing/documentation/overview`.

0 commit comments

Comments
 (0)