- Author:
- elo80ka
- Posted:
- March 22, 2009
- Language:
- JavaScript
- Version:
- Not specified
- Score:
- 18 (after 18 ratings)
I recently worked on an application, where I had to provide a way for users to search for objects based on user-defined properties attached to these objects. I decided to model the search form using a formset, and I thought it'd be a good idea to allow users dynamically add and remove search criteria.
The script (dynamic-formset.js) should be re-usable as-is:
- Include it in your template (don't forget to include jquery.js first!).
- Apply the 'dynamic-form' class to the container for each form instance (in this example, the 'tr').
- Handle the 'click' event for your
add
anddelete
buttons. Call theaddForm
anddeleteForm
functions respectively, passing each function a reference to the button raising the event, and the formset prefix.
That's about it. In your view, you can instantiate the formset, and access your forms as usual.
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"> </th>
<th scope="col">Property name</th>
<th scope="col"> </th>
<th scope="col"> </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 » " />
</div>
|
More like this
- Django Collapsed Stacked Inlines by applecat 1 year, 9 months ago
- Django Collapsed Stacked Inlines by mkarajohn 3 years, 10 months ago
- Dynamically adding forms to a formset. OOP version. by halfnibble 9 years, 6 months ago
- Convert multiple select for m2m to multiple checkboxes in django admin form by abidibo 11 years, 7 months ago
- Django admin inline ordering - javascript only implementation by ojhilt 11 years, 11 months ago
Comments
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); }
#
Thanks for catching that nubela - I've updated the snippet.
#
nice snippet many thanks. I'll post an update for make this working with inlineformset soon
#
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:
Does this alter too many elements? Does this leave out any important elements?
#
Wish we could see your formset in this example. Also did you ever get inlineformset to work?
#
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.
#
@stormlifter: to use this with inline formsets, you need to change the prefix in the script on your template. Something like this:
Assuming your formset is named "formset" in the template context.
#
This snippet evolved into a jQuery plug-in by the same author which you can find here: https://github.com/elo80ka/django-dynamic-formset
#
it works like a charm, only one doubt after 5 years, mi delete button renders the id for added forms like:
<button id="remove-form-0-row" class="w3-btn w3-gray delete-row" type="button">Delete form</button>
being the form id:
form-(number diferent than 0)-row
and i am not shure what problem could bring me this, although so far so good#
Please login first before commenting.