- Author:
- halfnibble
- Posted:
- May 13, 2015
- Language:
- JavaScript
- Version:
- 1.7
- Score:
- 1 (after 1 ratings)
What It Is
This is a JavaScript-based solution to dynamically add and remove forms in formsets and inlineformsets. It requires jQuery.
Originally based on this Snippet: https://djangosnippets.org/snippets/1389/
I have done a lot of work to make it OO, and am using it in production on pages with multiple inlineformsets, and even nested inlineformsets (I call it, "Inlineformset Inception").
My hope is that the code and example are enough to show how it works.
Usage Details
In the example usage, I am using a CSS class, 'light', to make every other form have a light background color. My form placeholder is an element with an ID of 'formset-placeholder' (the default). And the form selector is a class name of 'dynamic-form' (the default).
When I have time, I will create a GitHub repository with the code and completed examples.
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | JavaScript
==========
/* For dynamically adding/deleting Django inline formsets
* Original snippet from: https://djangosnippets.org/snippets/1389/
* Original snippet by: elo80ka
* This version by: halfnibble (Josh Wedekind)
* Updated 5/13/2015
*/
var Formset = (function() {
var callbacks = {},
empty_form = {},
form_count = 0,
form_manager = {},
form_placeholder = $('#formset-placeholder')[0],
form_selector = '.dynamic-form',
prefix = 'formset';
function setup(context) {
/***************************************
* Common context attributes *
* prefix: 'formset' *
* form_selector: '.dynamic-form' *
* form_placeholder: DOM.element *
* callbacks: { funcs... } *
***************************************/
$.extend(this, context);
// Set a few more attributes
this.form_manager = $('#id_' + this.prefix + '-TOTAL_FORMS');
this.form_count = parseInt(this.form_manager.val());
if (this.form_placeholder === undefined || this.form_placeholder === [])
this.form_placeholder = $('#' + this.prefix + '-placeholder');
// Setup the empty form for add_form()
var empty_form = $(this.form_selector + ':first').clone(true).get(0);
// Clear all values except for hidden field values.
$(empty_form).find(':input')
.removeAttr('checked')
.removeAttr('selected')
.not(':button, :submit, :reset, [type="hidden"], :radio, :checkbox')
.val('')
.attr('value',''); // Handle UpdateView default values
this.empty_form = empty_form;
if (this.callbacks.setup)
this.callbacks.setup(this);
}
function add_form() {
var self = this;
// Assume new forms get last index counter
var index = this.form_count;
// Create form to add
var form = $(this.empty_form).clone(true).get(0);
this.update_index(form, index, false);
// Add form after last one, or after the placeholder if none exist.
if ($(this.form_selector + ':last').length > 0)
$(form).insertAfter(
$(this.form_selector + ':last')
);
else
$(form).insertAfter(this.form_placeholder);
// Update all pertinent form field indexes
$(form).children('.hidden').removeClass('hidden');
$(form).find('div, input, select, label, button').each( function() {
self.update_index(this, index, false);
});
// Update index counter
++this.form_count;
this.form_manager.val(this.form_count);
if (this.callbacks.add_form)
this.callbacks.add_form(this, form, index);
return false;
}
function delete_form(button) {
var self = this;
$(button).parents(this.form_selector).hide(400, function() {
var form = this;
$(form).remove();
--self.form_count;
self.form_manager.val(self.form_count);
// Delete anything else before updating
if (self.callbacks.delete_form)
self.callbacks.delete_form(self, form);
// Update all formset indexes, etc.
self.update_all();
});
return false;
}
function update_all() {
var self = this;
// Get list of all forms in formset
var forms = $(this.form_selector);
for (var index = 0; index < this.form_count; index++) {
/*jshint loopfunc: true */
var form = forms.get(index);
// Update form index, then all pertinent field indexes
this.update_index(form, index);
$(form).find('div, input, select, label, button')
.each( function() { self.update_index(this, index); });
if (this.callbacks.update_all)
this.callbacks.update_all(this, form, index);
}
}
function update_index(element, index, external_links) {
if (external_links === undefined)
external_links = true; // Default: update links to element ID
var regex = new RegExp('(' + this.prefix + '-\\d+)');
var replacement = this.prefix + '-' + index;
if ($(element).attr("for"))
$(element).attr(
"for",
$(element).attr("for")
.replace(regex, replacement)
);
if (element.id) {
// Update targets to element
if (external_links === true)
$('a[href="#'+element.id+'"]').attr('href', function (i, attr) {
return attr.replace(regex, replacement);
});
element.id = element.id.replace(regex, replacement);
}
if (element.name)
element.name = element.name.replace(regex, replacement);
if (element.getAttribute('data-prefix'))
element.setAttribute(
'data-prefix',
element.getAttribute('data-prefix')
.replace(regex, replacement)
);
// Use if you want to replace more field attributes
// E.g. element.className
if (this.callbacks.update_index)
this.callbacks.update_index(this, element, index,
regex, replacement);
}
return {
// Properties
callbacks: callbacks,
empty_form: empty_form,
form_count: form_count,
form_manager: form_manager,
form_placeholder: form_placeholder,
form_selector: form_selector,
prefix: prefix,
// Methods
setup: setup,
add_form: add_form,
delete_form: delete_form,
update_all: update_all,
update_index: update_index
};
});
Example Usage
=============
(Placed below formset in template)
<script type="text/javascript">
<!--
var callbacks = {
'setup': function(formset) {
$(formset.form_selector).find('.delete-form-row').click( function(e) {
e.preventDefault();
formset.delete_form(this);
});
$('.add-form-row').click( function(e) {
e.preventDefault();
formset.add_form();
});
},
'add_form': function(formset, form, index) {
$(form).find('.delete-form-row').click( function(e) {
e.preventDefault();
formset.delete_form(this);
});
if (index % 2 !== 0)
$(form).removeClass('light');
},
'update_all': function(formset, form, index) {
if (index % 2 === 0)
$(form).addClass('light');
else
$(form).removeClass('light');
}
};
var formset = new Formset();
formset.setup({
prefix: '{{ formset.prefix }}',
callbacks: callbacks
});
//-->
</script>
|
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
- 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
- Google v3 geocoding for Geodjango admin site by samhag 12 years ago
Comments
Please login first before commenting.