|
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); |