Dynamically adding forms to a formset with jQuery

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Javascript (dynamic-formset.js):

	function updateElementIndex(el, prefix, ndx) {
		var id_regex = new RegExp('(' + prefix + '-\\d+)');
		var replacement = prefix + '-' + ndx;
		if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
		if (el.id) el.id = el.id.replace(id_regex, replacement);
		if (el.name) el.name = el.name.replace(id_regex, replacement);
	}

    function addForm(btn, prefix) {
        var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
        var row = $('.dynamic-form:first').clone(true).get(0);
        $(row).removeAttr('id').insertAfter($('.dynamic-form:last')).children('.hidden').removeClass('hidden');
        $(row).children().not(':last').children().each(function() {
    	    updateElementIndex(this, prefix, formCount);
    	    $(this).val('');
        });
        $(row).find('.delete-row').click(function() {
    	    deleteForm(this, prefix);
        });
        $('#id_' + prefix + '-TOTAL_FORMS').val(formCount + 1);
        return false;
    }

    function deleteForm(btn, prefix) {
        $(btn).parents('.dynamic-form').remove();
        var forms = $('.dynamic-form');
        $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
        for (var i=0, formCount=forms.length; i<formCount; i++) {
    	    $(forms.get(i)).children().not(':last').children().each(function() {
    	        updateElementIndex(this, prefix, i);
    	    });
        }
        return false;
    }

Template (form.html):

    <script type="text/javascript">
    <!--
    $(function () {
        $('.add-row').click(function() {
    	    return addForm(this, 'form');
        });
        $('.delete-row').click(function() {
    	    return deleteForm(this, 'form');
        })
    })
    //-->
    </script>
    <table id="id_forms_table" border="0" cellpadding="0" cellspacing="5">
        <thead>
    	    <tr>
    	        <th scope="col">&nbsp;</th>
    	        <th scope="col">Property name</th>
    	        <th scope="col">&nbsp;</th>
    	        <th scope="col">&nbsp;</th>
    	    </tr>
        </thead>
        <tbody>
            {% for form in property_formset.forms %}
    	    <tr id="{{ form.prefix }}-row" class="dynamic-form">
    	        <td{% if forloop.first %} class="hidden"{% endif %}>{{ form.operand }}</td>
    	        <td>{{ form.property }}</td>
    	        <td>contains {{ form.value }}</td>
    	        <td{% if forloop.first %} class="hidden"{% endif %}>
    	            <a id="remove-{{ form.prefix }}-row" href="javascript:void(0)" class="delete-row"></a>
    	        </td>
            </tr>
    	    {% endfor %}
            <tr>
    	        <td colspan="4"><a href="javascript:void(0)" class="add-row">add property</a></td>
    	    </tr>
        </tbody>
    </table>
    {{ property_form.management_form }}
    <div>
        <input type="submit" value=" Find &raquo; " />
    </div>

More like this

  1. Complex Formsets, Redux by smagala 3 years, 2 months ago
  2. Complex Formsets by smagala 4 years, 4 months ago
  3. Dynamic tabular inlines with optional drag-n-drop sorting by Aneon 4 years ago
  4. Collapsed stacked inlines by Aneon 4 years ago
  5. Add delete buttons to admin changelist by kylefox 6 years ago

Comments

nubela (on March 28, 2009):

there is an error with updateElementIndex, since it assumes that every child element contains .name and .id, and also it doesn't fix labels.

heres a corrected one:

function updateElementIndex(el, prefix, ndx){ var id_regex = new RegExp('(' + prefix + '-\d+)'); var replacement = prefix + '-' + ndx; if ($(el).attr("for")) $(el).attr("for",$(el).attr("for").replace(id_regex, replacement)); if (el.id) el.id = el.id.replace(id_regex, replacement); if (el.name) el.name = el.name.replace(id_regex, replacement); }

#

elo80ka (on June 4, 2009):

Thanks for catching that nubela - I've updated the snippet.

#

martync (on June 23, 2009):

nice snippet many thanks. I'll post an update for make this working with inlineformset soon

#

christian.oudard (on September 23, 2009):

This is a great piece of code! Very useful.

I ran into a problem while using it: It assumes too much about the structure of each row. I, for example, am using fieldsets. I had to change line 15 like so:

$(row).find('input, select, label').each(function() {
...

Does this alter too many elements? Does this leave out any important elements?

#

stormlifter (on October 1, 2009):

Wish we could see your formset in this example. Also did you ever get inlineformset to work?

#

stormlifter (on October 1, 2009):

Also is it just me or does the "delete" function call not have any text and thus is unclickable?

I'm still looking at how it is designed because it seems as though you'd want the delete button to only show if there is more than one row up, but the code doesn't seem to be accomplishing that.

Maybe I'm misunderstanding the code, it's possible.

#

elo80ka (on October 10, 2009):

@stormlifter: to use this with inline formsets, you need to change the prefix in the script on your template. Something like this:

$(function () {
    $('.add-row').click(function() {
        return addForm(this, '{{ formset.prefix }}');
    });
    $('.delete-row').click(function() {
        return deleteForm(this, '{{ formset.prefix }}');
    })
})

Assuming your formset is named "formset" in the template context.

#

elo80ka (on October 10, 2009):

I've packaged this snippet as a jQuery plugin, hosted on google code. See: http://code.google.com/p/django-dynamic-formset/

#

justhamade (on January 8, 2010):

I have simplified this as a drop in replacement template to overried the admin change_form.html template http://www.djangosnippets.org/snippets/1859/

#

cronosa (on May 31, 2010):

Added the view part to dynamically alter a defined formset/modelformset to handle multiple additions to the formset via javascript: http://djangosnippets.org/snippets/2043/

#

(Forgotten your password?)