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::
4. Add the following script to your template (before the closing
``BODY`` tag, or in your ``HEAD``, below the reference to
``jquery.formset.js``)::
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::
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::
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::
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::
3. Add the code to initialize the plugin for the formsets, passing a
unique CSS class name to ``formCssClass`` for each formset::
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.
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
Post a Comment