Django Dynamic Formsets with Jquery

If you have an existing project, it's quite easy to add
client-side support for adding and removing forms.

I'll assume you've already created your formset. You can create
formsets using any of the provided methods: both regular formsets
(created with the ``formset_factory``) and inline formsets
(created with the ``inlineformset_factory``) are supported.

1. First, copy ``jquery.formset.js`` to your ``MEDIA_ROOT``; don't
   forget to include the jQuery library too!

2. Include a reference to the script in your template; again, remember
   to reference the jQuery library, before including the script.

3. Render the formset as you would normally -- I usually use a table
   but you can use DIVs, Ps or whatever you desire. Let's use the
   example markup below::

<form id="myForm" method="post" action="">
           <table border="0" cellpadding="0" cellspacing="0">
               <tbody>
                   {% for form in formset.forms %}
                   <tr>
                      <td>{{ form.field1 }}</td>
                      <td>{{ form.field2 }}</td>
                      <td>{{ form.field3 }}</td>
                   </tr>
                   {% endfor %}
               </tbody>
           </table>
           {{ formset.management_form }}
       </form>

4. Add the following script to your template (before the closing
   ``BODY`` tag, or in your ``HEAD``, below the reference to
   ``jquery.formset.js``)::

       <script type="text/javascript">
           $(function() {
               $('#myForm tbody tr').formset();
           })
       </script>

   Notice that our jQuery selector targets the container for each
   form. We could have assigned a class to each ``TR`` and used that
   instead::

       $('.form-container').formset();

   Either way is fine, really :)

   If you used a non-inline formset, you're done. Fini. Save your
   template and navigate to the appropriate view in your application,
   and you should see an "add another" link. Clicking on it should add
   another instance of your form to the page. You can remove instances
   by clicking the "remove" link for an instance.

.. versionchanged:: 1.2

   In previous versions, if there was only one form in the formset,
   the remove link would be hidden. With the addition of form templates,
   this behaviour has been changed -- it is now possible to remove all
   forms in a formset, and have the `add` link still behave as expected.


.. _working-with-inline-formsets:

Working with Inline Formsets
============================

..versionchanged:: 1.2

In version 1.2, the behaviour of inline formsets was changed: clicking the
"remove" link for an instance in an inline formset will now cause Django
to delete that instance from the database when the form is POSTed.

To enable this behaviour, you'll need to do the following:

1. Create an inline formset, making sure to pass ``can_delete=True`` in the
   call to ``inlineformset_factory``. For more information, see the Django
   documentation on formsets.

2. Render the formset as you would normally. Be sure to include the ``DELETE``
   field generated by Django, for each form with an existing instance. Here's
   an example::

       <form id="myForm" method="post" action="">
           <table border="0" cellpadding="0" cellspacing="0">
               <tbody>
                   {% for form in formset.forms %}
                   <tr>
                      <td>
                         {% if form.instance.pk %}{{ form.DELETE }}{% endif %}
                         {{ form.field1 }}
                      </td>
                      <td>{{ form.field2 }}</td>
                      <td>{{ form.field3 }}</td>
                   </tr>
                   {% endfor %}
               </tbody>
           </table>
           {{ formset.management_form }}
       </form>

   Notice the ``{% if form.instance.pk %}...{% endif %}`` around
   ``{{ form.DELETE }}``? This is generally a good idea, since we only want
   Django to delete instances that already exist in the database.

3. Call ``formset`` in your template, making sure to set the ``prefix``
   option::

       <script type="text/javascript">
           $(function() {
               $('#myForm tbody tr').formset({
                   prefix: '{{ formset.prefix }}'
               });
           })
       </script>

4. Save your template and hit refresh in your browser. Try adding and
   removing a few rows, then submitting the page.


.. versionadded:: 1.1

.. _using-multiple-formsets:

Using multiple Formsets on the same page
========================================

What if you need to display more than one formset on a page? If you try
the above code with more than one formset, you'll notice it doesn't work
quite the way you'd expect. There are two things you need to do, in order
to use more than one formset on a single page:

1. Give each formset a unique prefix.

2. Tell the plugin which forms belong to which formset -- you do this
   using the ``formCssClass`` option.

For example, to use the plugin with ``FormSet1``, ``FormSet2`` and ``FormSet3``
on the same page, here's what you'd do:

1. In your view, when you instantiate each formset, pass a unique value for
   the ``prefix`` keyword argument::

       def my_view(request):
           if request.method == 'POST':
               formset1, formset2, formset3 = \
                  FormSet1(request.POST, prefix='fs1'), \
                  FormSet2(request.POST, prefix='fs2'), \
                  FormSet3(request.POST, prefix='fs3')
               if formset1.is_valid() and formset2.is_valid() \
                  and formset3.is_valid():
                   # Do something awesome with the forms.
           else:
               formset1, formset2, formset3 = \
                  FormSet1(prefix='fs1'), \
                  FormSet2(prefix='fs2'), \
                  FormSet3(prefix='fs3')
           ...

    Giving each formset a unique prefix ensures that they don't step on each
    other. For more information on ``prefix``, see the `Django documentation
    <http://docs.djangoproject.com/en/dev/topics/forms/formsets/#using-more-than-one-formset-in-a-view>`.

2. Render the formsets in your template::

       <form id="myFormsets" method="post" action="">
           <table id="myFormset1Table" border="0" cellpadding="0">
               <caption>Formset One</caption>
               <tbody>
                   {% for form in formset1.forms %}
                   <tr>
                      <td>{{ form.field1 }}</td>
                      <td>{{ form.field2 }}</td>
                   </tr>
                   {% endfor %}
               </tbody>
           </table>
           {{ formset1.management_form }}

           <table id="myFormset2Table" border="0" cellpadding="0">
               <caption>Formset Two</caption>
               <tbody>
                   {% for form in formset2.forms %}
                   <tr>
                      <td>{{ form.field1 }}</td>
                      <td>{{ form.field2 }}</td>
                   </tr>
                   {% endfor %}
               </tbody>
           </table>
           {{ formset2.management_form }}

           ...

       </form>

3. Add the code to initialize the plugin for the formsets, passing a
   unique CSS class name to ``formCssClass`` for each formset::

       <script type="text/javascript">
           $(function() {
               $('#myFormset1Table tbody tr').formset({
                   prefix: '{{ formset1.prefix }}',
                   formCssClass: 'dynamic-formset1'
               });
               $('#myFormset2Table tbody tr').formset({
                   prefix: '{{ formset2.prefix }}',
                   formCssClass: 'dynamic-formset2'
               });
               ...
           })
       </script>

   Save your template, hit refresh in your browser, et voila!


.. _formset-options:

Formset options
===============

You can customize this plugin's behavior by passing an options hash. A
complete list of available options is shown below::

    ``prefix``
        Use this to specify the prefix for your formset if it's anything
        other than the default ("form"). This option must be supplied for
        inline formsets.

    ``addText``
        Use this to set the text for the generated add link. The default
        text is "add another".

    ``deleteText``
        Use this to set the text for the generated delete links. The
        default text is "remove".

    ``addCssClass``
        Use this to change the default CSS class applied to the generated
        add link (possibly, to avoid CSS conflicts within your templates).
        The default class is "add-row".

    ``deleteCssClass``
        Use this to change the default CSS class applied to the generated
        delete links. The default class is "delete-row".

    ``added``
        If you set this to a function, that function will be called each
        time a new form is added. The function should take a single argument,
        ``row``; it will be passed a jQuery object, wrapping the form that
        was just added.

    ``removed``
        Set this to a function, and that function will be called each time
        a form is deleted. The function should take a single argument,
        ``row``; it will be passed a jQuery object, wrapping the form that
        was just removed.

.. versionadded:: 1.1

    ``formCssClass``
        Use this to set the CSS class applied to all forms within the same
        formset. Internally, all forms with the same class are assumed to
        belong to the same formset. If you have multiple formsets on a single
        HTML page, you MUST provide unique class names for each formset. If
        you don't provide a value, this defaults to "dynamic-form".

        For more information, see the section on :ref:`Using multiple Formsets
        on the same page <using-multiple-formsets>`, and check out the example
        in the demo project.

.. versionadded:: 1.2

    ``formTemplate``
        Use this to override the form that gets cloned, each time a new form
        instance is added. If specified, this should be a jQuery selector.

    ``extraClasses``
        Set this to an array of CSS class names (defaults to an empty array),
        and the classes will be applied to each form in the formset in turn.
        This can easily be used to acheive row-striping effects, which can
        make large formsets easier to deal with visually.
       
.. versionadded:: 1.3

    ``keepFieldValues``
        Set this to a jQuery selector, which should resolve to a list of elements
        whose values should be preserved when the form is cloned.
        Internally, this value is passed directly to the ``$.not(...)`` method.
        This means you can also pass in DOM elements, or a function (in newer
        versions of jQuery) as your selector.

.. note:: The ``addCssClass`` and ``deleteCssClass`` options must be unique.
   Internally, the plugin uses the class names to target the add and delete
   links. Any other elements with the same class applied to them will also
   have the add and delete behavior, which is almost certainly not what you
   want.


.. _provided-css-classes:

Provided CSS classes
====================

Each form's container will have the class specified by the ``formCssClass``
option (defaults to "dynamic-form") applied to it. You can use this to define
style rules targeting each of these forms.

Comments

Popular posts from this blog

Python mechanize For Browsing

Editor TinyMCE in Django admin