@@ -239,13 +239,141 @@ great, your user can't actually add any new todos yet.
239
239
Allowing "new" todos with the "prototype"
240
240
-----------------------------------------
241
241
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\T askBundle\F orm\T ype;
264
+
265
+ use Symfony\C omponent\F orm\A bstractType;
266
+ use Symfony\C omponent\F orm\F ormBuilder;
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\T askBundle\E ntity\T ask',
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="<div><label class=" required">$$name$$</label><div id="khepin_productbundle_producttype_tags_$$name$$"><div><label for="khepin_productbundle_producttype_tags_$$name$$_name" class=" required">Name</label><input type="text" id="khepin_productbundle_producttype_tags_$$name$$_name" name="khepin_productbundle_producttype[tags][$$name$$][name]" required="required" maxlength="255" /></div></div></div>" 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
+
244
372
245
373
.. _cookbook-form-collections-remove :
246
374
247
375
Allowing todos to be removed
248
376
----------------------------
249
377
250
378
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