0
|
1 |
/** |
|
2 |
* Django admin inlines |
|
3 |
* |
|
4 |
* Based on jQuery Formset 1.1 |
|
5 |
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) |
|
6 |
* @requires jQuery 1.2.6 or later |
|
7 |
* |
|
8 |
* Copyright (c) 2009, Stanislaus Madueke |
|
9 |
* All rights reserved. |
|
10 |
* |
|
11 |
* Spiced up with Code from Zain Memon's GSoC project 2009 |
|
12 |
* and modified for Django by Jannis Leidel |
|
13 |
* |
|
14 |
* Licensed under the New BSD License |
|
15 |
* See: http://www.opensource.org/licenses/bsd-license.php |
|
16 |
*/ |
|
17 |
(function($) { |
|
18 |
$.fn.formset = function(opts) { |
|
19 |
var options = $.extend({}, $.fn.formset.defaults, opts); |
|
20 |
var updateElementIndex = function(el, prefix, ndx) { |
|
21 |
var id_regex = new RegExp("(" + prefix + "-\\d+)"); |
|
22 |
var replacement = prefix + "-" + ndx; |
|
23 |
if ($(el).attr("for")) { |
|
24 |
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); |
|
25 |
} |
|
26 |
if (el.id) { |
|
27 |
el.id = el.id.replace(id_regex, replacement); |
|
28 |
} |
|
29 |
if (el.name) { |
|
30 |
el.name = el.name.replace(id_regex, replacement); |
|
31 |
} |
|
32 |
}; |
|
33 |
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); |
|
34 |
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); |
|
35 |
// only show the add button if we are allowed to add more items, |
|
36 |
// note that max_num = None translates to a blank string. |
|
37 |
var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0; |
|
38 |
$(this).each(function(i) { |
|
39 |
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass); |
|
40 |
}); |
|
41 |
if ($(this).length && showAddButton) { |
|
42 |
var addButton; |
|
43 |
if ($(this).attr("tagName") == "TR") { |
|
44 |
// If forms are laid out as table rows, insert the |
|
45 |
// "add" button in a new table row: |
|
46 |
var numCols = this.eq(0).children().length; |
|
47 |
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>"); |
|
48 |
addButton = $(this).parent().find("tr:last a"); |
|
49 |
} else { |
|
50 |
// Otherwise, insert it immediately after the last form: |
|
51 |
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>"); |
|
52 |
addButton = $(this).filter(":last").next().find("a"); |
|
53 |
} |
|
54 |
addButton.click(function() { |
|
55 |
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); |
|
56 |
var nextIndex = parseInt(totalForms.val()); |
|
57 |
var template = $("#" + options.prefix + "-empty"); |
|
58 |
var row = template.clone(true); |
|
59 |
row.removeClass(options.emptyCssClass) |
|
60 |
.addClass(options.formCssClass) |
|
61 |
.attr("id", options.prefix + "-" + nextIndex) |
|
62 |
.insertBefore($(template)); |
|
63 |
row.find("*") |
|
64 |
.filter(function() { |
|
65 |
var el = $(this); |
|
66 |
return el.attr("id") && el.attr("id").search(/__prefix__/) >= 0; |
|
67 |
}).each(function() { |
|
68 |
var el = $(this); |
|
69 |
el.attr("id", el.attr("id").replace(/__prefix__/g, nextIndex)); |
|
70 |
}) |
|
71 |
.end() |
|
72 |
.filter(function() { |
|
73 |
var el = $(this); |
|
74 |
return el.attr("name") && el.attr("name").search(/__prefix__/) >= 0; |
|
75 |
}).each(function() { |
|
76 |
var el = $(this); |
|
77 |
el.attr("name", el.attr("name").replace(/__prefix__/g, nextIndex)); |
|
78 |
}); |
|
79 |
if (row.is("tr")) { |
|
80 |
// If the forms are laid out in table rows, insert |
|
81 |
// the remove button into the last table cell: |
|
82 |
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>"); |
|
83 |
} else if (row.is("ul") || row.is("ol")) { |
|
84 |
// If they're laid out as an ordered/unordered list, |
|
85 |
// insert an <li> after the last list item: |
|
86 |
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>"); |
|
87 |
} else { |
|
88 |
// Otherwise, just insert the remove button as the |
|
89 |
// last child element of the form's container: |
|
90 |
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>"); |
|
91 |
} |
|
92 |
row.find("input,select,textarea,label,a").each(function() { |
|
93 |
updateElementIndex(this, options.prefix, totalForms.val()); |
|
94 |
}); |
|
95 |
// Update number of total forms |
|
96 |
$(totalForms).val(nextIndex + 1); |
|
97 |
// Hide add button in case we've hit the max, except we want to add infinitely |
|
98 |
if ((maxForms.val() != '') && (maxForms.val()-totalForms.val()) <= 0) { |
|
99 |
addButton.parent().hide(); |
|
100 |
} |
|
101 |
// The delete button of each row triggers a bunch of other things |
|
102 |
row.find("a." + options.deleteCssClass).click(function() { |
|
103 |
// Remove the parent form containing this button: |
|
104 |
var row = $(this).parents("." + options.formCssClass); |
|
105 |
row.remove(); |
|
106 |
// If a post-delete callback was provided, call it with the deleted form: |
|
107 |
if (options.removed) { |
|
108 |
options.removed(row); |
|
109 |
} |
|
110 |
// Update the TOTAL_FORMS form count. |
|
111 |
var forms = $("." + options.formCssClass); |
|
112 |
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); |
|
113 |
// Show add button again once we drop below max |
|
114 |
if ((maxForms.val() == '') || (maxForms.val()-forms.length) > 0) { |
|
115 |
addButton.parent().show(); |
|
116 |
} |
|
117 |
// Also, update names and ids for all remaining form controls |
|
118 |
// so they remain in sequence: |
|
119 |
for (var i=0, formCount=forms.length; i<formCount; i++) |
|
120 |
{ |
|
121 |
$(forms.get(i)).find("input,select,textarea,label,a").each(function() { |
|
122 |
updateElementIndex(this, options.prefix, i); |
|
123 |
}); |
|
124 |
} |
|
125 |
return false; |
|
126 |
}); |
|
127 |
// If a post-add callback was supplied, call it with the added form: |
|
128 |
if (options.added) { |
|
129 |
options.added(row); |
|
130 |
} |
|
131 |
return false; |
|
132 |
}); |
|
133 |
} |
|
134 |
return this; |
|
135 |
} |
|
136 |
/* Setup plugin defaults */ |
|
137 |
$.fn.formset.defaults = { |
|
138 |
prefix: "form", // The form prefix for your django formset |
|
139 |
addText: "add another", // Text for the add link |
|
140 |
deleteText: "remove", // Text for the delete link |
|
141 |
addCssClass: "add-row", // CSS class applied to the add link |
|
142 |
deleteCssClass: "delete-row", // CSS class applied to the delete link |
|
143 |
emptyCssClass: "empty-row", // CSS class applied to the empty row |
|
144 |
formCssClass: "dynamic-form", // CSS class applied to each form in a formset |
|
145 |
added: null, // Function called each time a new form is added |
|
146 |
removed: null // Function called each time a form is deleted |
|
147 |
} |
|
148 |
})(django.jQuery); |