root / static / js / jquery.formset.js @ f1bd3b97
History | View | Annotate | Download (11.7 kB)
1 |
/**
|
---|---|
2 |
* jQuery Formset 1.3-pre
|
3 |
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
4 |
* @requires jQuery 1.2.6 or later
|
5 |
*
|
6 |
* Copyright (c) 2009, Stanislaus Madueke
|
7 |
* All rights reserved.
|
8 |
*
|
9 |
* Licensed under the New BSD License
|
10 |
* See: http://www.opensource.org/licenses/bsd-license.php
|
11 |
*/
|
12 |
;(function($) { |
13 |
$.fn.formset = function(opts) |
14 |
{ |
15 |
var options = $.extend({}, $.fn.formset.defaults, opts), |
16 |
flatExtraClasses = options.extraClasses.join(' '),
|
17 |
totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), |
18 |
maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), |
19 |
childElementSelector = 'input,select,textarea,label,div',
|
20 |
$$ = $(this), |
21 |
|
22 |
applyExtraClasses = function(row, ndx) { |
23 |
if (options.extraClasses) {
|
24 |
row.removeClass(flatExtraClasses); |
25 |
row.addClass(options.extraClasses[ndx % options.extraClasses.length]); |
26 |
} |
27 |
}, |
28 |
|
29 |
updateElementIndex = function(elem, prefix, ndx) { |
30 |
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), |
31 |
replacement = prefix + '-' + ndx + '-'; |
32 |
if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); |
33 |
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); |
34 |
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); |
35 |
}, |
36 |
|
37 |
hasChildElements = function(row) { |
38 |
return row.find(childElementSelector).length > 0; |
39 |
}, |
40 |
|
41 |
showAddButton = function() { |
42 |
return maxForms.length == 0 || // For Django versions pre 1.2 |
43 |
(maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)) |
44 |
}, |
45 |
|
46 |
insertDeleteLink = function(row) { |
47 |
if (row.is('TR')) { |
48 |
// If the forms are laid out in table rows, insert
|
49 |
// the remove button into the last table cell:
|
50 |
row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>'); |
51 |
} else if (row.is('UL') || row.is('OL')) { |
52 |
// If they're laid out as an ordered/unordered list,
|
53 |
// insert an <li> after the last list item:
|
54 |
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>'); |
55 |
} else {
|
56 |
// Otherwise, just insert the remove button as the
|
57 |
// last child element of the form's container:
|
58 |
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>'); |
59 |
} |
60 |
row.find('a.' + options.deleteCssClass).click(function() { |
61 |
var row = $(this).parents('.' + options.formCssClass), |
62 |
del = row.find('input:hidden[id $= "-DELETE"]'),
|
63 |
buttonRow = row.siblings("a." + options.addCssClass + ', .' + options.formCssClass + '-add'), |
64 |
forms; |
65 |
if (del.length) {
|
66 |
// We're dealing with an inline formset.
|
67 |
// Rather than remove this form from the DOM, we'll mark it as deleted
|
68 |
// and hide it, then let Django handle the deleting:
|
69 |
del.val('on');
|
70 |
row.hide(); |
71 |
forms = $('.' + options.formCssClass).not(':hidden'); |
72 |
} else {
|
73 |
row.remove(); |
74 |
// Update the TOTAL_FORMS count:
|
75 |
forms = $('.' + options.formCssClass).not('.formset-custom-template'); |
76 |
totalForms.val(forms.length); |
77 |
} |
78 |
for (var i=0, formCount=forms.length; i<formCount; i++) { |
79 |
// Apply `extraClasses` to form rows so they're nicely alternating:
|
80 |
applyExtraClasses(forms.eq(i), i); |
81 |
if (!del.length) {
|
82 |
// Also update names and IDs for all child controls (if this isn't
|
83 |
// a delete-able inline formset) so they remain in sequence:
|
84 |
forms.eq(i).find(childElementSelector).each(function() {
|
85 |
updateElementIndex($(this), options.prefix, i); |
86 |
}); |
87 |
} |
88 |
} |
89 |
// Check if we need to show the add button:
|
90 |
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show(); |
91 |
// If a post-delete callback was provided, call it with the deleted form:
|
92 |
if (options.removed) options.removed(row);
|
93 |
return false; |
94 |
}); |
95 |
}; |
96 |
|
97 |
$$.each(function(i) { |
98 |
var row = $(this), |
99 |
del = row.find('input:checkbox[id $= "-DELETE"]');
|
100 |
if (del.length) {
|
101 |
// If you specify "can_delete = True" when creating an inline formset,
|
102 |
// Django adds a checkbox to each form in the formset.
|
103 |
// Replace the default checkbox with a hidden field:
|
104 |
if (del.is(':checked')) { |
105 |
// If an inline formset containing deleted forms fails validation, make sure
|
106 |
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
|
107 |
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />'); |
108 |
row.hide(); |
109 |
} else {
|
110 |
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />'); |
111 |
} |
112 |
// Hide any labels associated with the DELETE checkbox:
|
113 |
$('label[for="' + del.attr('id') + '"]').hide(); |
114 |
del.remove(); |
115 |
} |
116 |
if (hasChildElements(row)) {
|
117 |
row.addClass(options.formCssClass); |
118 |
if (row.is(':visible')) { |
119 |
forms_tmp = $('.' + options.formCssClass).not('.formset-custom-template'); |
120 |
if(forms_tmp.length == 1) { |
121 |
forms_tmp.find('a.' + options.deleteCssClass).hide();
|
122 |
|
123 |
} else {
|
124 |
insertDeleteLink(row); |
125 |
} |
126 |
applyExtraClasses(row, i); |
127 |
} |
128 |
} |
129 |
}); |
130 |
|
131 |
if ($$.length) { |
132 |
var hideAddButton = !showAddButton(),
|
133 |
addButton, template; |
134 |
if (options.formTemplate) {
|
135 |
// If a form template was specified, we'll clone it to generate new form instances:
|
136 |
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate); |
137 |
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template'); |
138 |
template.find(childElementSelector).each(function() {
|
139 |
updateElementIndex($(this), options.prefix, '__prefix__'); |
140 |
}); |
141 |
insertDeleteLink(template); |
142 |
} else {
|
143 |
// Otherwise, use the last form in the formset; this works much better if you've got
|
144 |
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
145 |
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id'); |
146 |
template.find('input:hidden[id $= "-DELETE"]').remove();
|
147 |
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
148 |
template.find(childElementSelector).not(options.keepFieldValues).each(function() {
|
149 |
var elem = $(this); |
150 |
// If this is a checkbox or radiobutton, uncheck it.
|
151 |
// This fixes Issue 1, reported by Wilson.Andrew.J:
|
152 |
if (elem.is('input:checkbox') || elem.is('input:radio')) { |
153 |
elem.attr('checked', false); |
154 |
} else {
|
155 |
elem.val('');
|
156 |
} |
157 |
}); |
158 |
} |
159 |
// FIXME: Perhaps using $.data would be a better idea?
|
160 |
options.formTemplate = template; |
161 |
|
162 |
if ($$.attr('tagName') == 'TR') { |
163 |
// If forms are laid out as table rows, insert the
|
164 |
// "add" button in a new table row:
|
165 |
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :| |
166 |
buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>') |
167 |
.addClass(options.formCssClass + '-add');
|
168 |
$$.parent().append(buttonRow);
|
169 |
if (hideAddButton) buttonRow.hide();
|
170 |
addButton = buttonRow.find('a');
|
171 |
} else {
|
172 |
// Otherwise, insert it immediately after the last form:
|
173 |
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>'); |
174 |
addButton = $$.filter(':last').next(); |
175 |
if (hideAddButton) addButton.hide();
|
176 |
} |
177 |
addButton.click(function() {
|
178 |
var formCount = parseInt(totalForms.val()),
|
179 |
row = options.formTemplate.clone(true).removeClass('formset-custom-template'), |
180 |
buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this); |
181 |
applyExtraClasses(row, formCount); |
182 |
row.insertBefore(buttonRow).show(); |
183 |
forms_tmp = $('.' + options.formCssClass).not('.formset-custom-template'); |
184 |
if(forms_tmp.length == 1) { |
185 |
forms_tmp.find('a.' + options.deleteCssClass).hide();
|
186 |
|
187 |
} else {
|
188 |
insertDeleteLink(row); |
189 |
} |
190 |
row.find(childElementSelector).each(function() {
|
191 |
updateElementIndex($(this), options.prefix, formCount); |
192 |
}); |
193 |
totalForms.val(formCount + 1);
|
194 |
// Check if we've exceeded the maximum allowed number of forms:
|
195 |
if (!showAddButton()) buttonRow.hide();
|
196 |
// If a post-add callback was supplied, call it with the added form:
|
197 |
if (options.added) options.added(row);
|
198 |
return false; |
199 |
}); |
200 |
} |
201 |
|
202 |
return $$; |
203 |
} |
204 |
|
205 |
/* Setup plugin defaults */
|
206 |
$.fn.formset.defaults = {
|
207 |
prefix: 'form', // The form prefix for your django formset |
208 |
formTemplate: null, // The jQuery selection cloned to generate new form instances |
209 |
addText: 'add another', // Text for the add link |
210 |
deleteText: 'remove', // Text for the delete link |
211 |
addCssClass: 'add-row', // CSS class applied to the add link |
212 |
deleteCssClass: 'delete-row', // CSS class applied to the delete link |
213 |
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset |
214 |
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn |
215 |
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned |
216 |
added: null, // Function called each time a new form is added |
217 |
removed: null // Function called each time a form is deleted |
218 |
}; |
219 |
})(jQuery) |