jquery.formset.js
8.96 KB
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
/**
* jQuery Formset 1.2
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
* @requires jQuery 1.2.6 or later
*
* Copyright (c) 2009, Stanislaus Madueke
* All rights reserved.
*
* Licensed under the New BSD License
* See: http://www.opensource.org/licenses/bsd-license.php
*/
;(function($) {
$.fn.formset = function(opts)
{
var options = $.extend({}, $.fn.formset.defaults, opts),
flatExtraClasses = options.extraClasses.join(' '),
$$ = $(this),
applyExtraClasses = function(row, ndx) {
if (options.extraClasses) {
row.removeClass(flatExtraClasses);
row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
}
},
updateElementIndex = function(elem, prefix, ndx) {
var idRegex = new RegExp('(' + prefix + '-\\d+-)|(^)'),
replacement = prefix + '-' + ndx + '-';
if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
},
hasChildElements = function(row) {
return row.find('input,select,textarea,label').length > 0;
},
insertDeleteLink = function(row) {
if (row.is('TR')) {
// If the forms are laid out in table rows, insert
// the remove button into the last table cell:
row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');
} else if (row.is('UL') || row.is('OL')) {
// If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');
} else {
// Otherwise, just insert the remove button as the
// last child element of the form's container:
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');
}
row.find('a.' + options.deleteCssClass).click(function() {
var row = $(this).parents('.' + options.formCssClass),
del = row.find('input:hidden[id $= "-DELETE"]');
if (del.length) {
// We're dealing with an inline formset; rather than remove
// this form from the DOM, we'll mark it as deleted and hide
// it, then let Django handle the deleting:
del.val('on');
row.hide();
} else {
row.remove();
// Update the TOTAL_FORMS form count.
// Also update names and IDs for all remaining form controls so they remain in sequence:
var forms = $('.' + options.formCssClass).not('.formset-custom-template');
$('#id_' + options.prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
applyExtraClasses(forms.eq(i), i);
forms.eq(i).find('input,select,textarea,label').each(function() {
updateElementIndex($(this), options.prefix, i);
});
}
}
// If a post-delete callback was provided, call it with the deleted form:
if (options.removed) options.removed(row);
return false;
});
};
$$.each(function(i) {
var row = $(this),
del = row.find('input:checkbox[id $= "-DELETE"]');
if (del.length) {
// If you specify "can_delete = True" when creating an inline formset,
// Django adds a checkbox to each form in the formset.
// Replace the default checkbox with a hidden field:
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
del.remove();
}
if (hasChildElements(row)) {
insertDeleteLink(row);
row.addClass(options.formCssClass);
applyExtraClasses(row, i);
}
});
if ($$.length) {
var addButton, template;
if (options.formTemplate) {
// If a form template was specified, we'll clone it to generate new form instances:
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
template.removeAttr('id').addClass(options.formCssClass).addClass('formset-custom-template');
template.find('input,select,textarea,label').each(function() {
updateElementIndex($(this), options.prefix, 2012);
});
insertDeleteLink(template);
} else {
// Otherwise, use the last form in the formset; this works much better if you've got
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
template.find('input:hidden[id $= "-DELETE"]').remove();
template.find('input,select,textarea,label').each(function() {
var elem = $(this);
// If this is a checkbox or radiobutton, uncheck it.
// This fixes Issue 1, reported by Wilson.Andrew.J:
if (elem.is('input:checkbox') || elem.is('input:radio')) {
elem.attr('checked', false);
} else {
elem.val('');
}
});
}
// FIXME: Perhaps using $.data would be a better idea?
options.formTemplate = template;
if ($$.attr('tagName') == 'TR') {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
var numCols = $$.eq(0).children().length;
$$.parent().append('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>');
addButton = $$.parent().find('tr:last a');
addButton.parents('tr').addClass(options.formCssClass + '-add');
} else {
// Otherwise, insert it immediately after the last form:
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
addButton = $$.filter(':last').next();
}
addButton.click(function() {
var formCount = parseInt($('#id_' + options.prefix + '-TOTAL_FORMS').val()),
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
buttonRow = $(this).parents('tr.' + options.formCssClass + '-add').get(0) || this;
applyExtraClasses(row, formCount);
row.insertBefore($(buttonRow)).show();
row.find('input,select,textarea,label').each(function() {
updateElementIndex($(this), options.prefix, formCount);
});
$('#id_' + options.prefix + '-TOTAL_FORMS').val(formCount + 1);
// If a post-add callback was supplied, call it with the added form:
if (options.added) options.added(row);
return false;
});
}
return $$;
}
/* Setup plugin defaults */
$.fn.formset.defaults = {
prefix: 'form', // The form prefix for your django formset
formTemplate: null, // The jQuery selection cloned to generate new form instances
addText: 'add another', // Text for the add link
deleteText: 'remove', // Text for the delete link
addCssClass: 'add-row', // CSS class applied to the add link
deleteCssClass: 'delete-row', // CSS class applied to the delete link
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
added: null, // Function called each time a new form is added
removed: null // Function called each time a form is deleted
};
})(jQuery)