2010-01-26 23:02:53 +08:00
|
|
|
/**
|
|
|
|
* Django admin inlines
|
|
|
|
*
|
|
|
|
* Based on jQuery Formset 1.1
|
|
|
|
* @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.
|
|
|
|
*
|
|
|
|
* Spiced up with Code from Zain Memon's GSoC project 2009
|
|
|
|
* and modified for Django by Jannis Leidel
|
|
|
|
*
|
|
|
|
* 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);
|
|
|
|
var updateElementIndex = function(el, prefix, ndx) {
|
|
|
|
var id_regex = new RegExp("(" + prefix + "-\\d+)");
|
|
|
|
var replacement = prefix + "-" + ndx;
|
2010-02-01 22:14:56 +08:00
|
|
|
if ($(el).attr("for")) {
|
|
|
|
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
|
|
|
|
}
|
|
|
|
if (el.id) {
|
|
|
|
el.id = el.id.replace(id_regex, replacement);
|
|
|
|
}
|
|
|
|
if (el.name) {
|
|
|
|
el.name = el.name.replace(id_regex, replacement);
|
|
|
|
}
|
2010-01-26 23:02:53 +08:00
|
|
|
};
|
2010-02-01 22:15:27 +08:00
|
|
|
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
|
2011-02-05 12:46:05 +08:00
|
|
|
var nextIndex = parseInt(totalForms.val());
|
2010-02-01 22:15:27 +08:00
|
|
|
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
|
2010-03-28 07:03:56 +08:00
|
|
|
// only show the add button if we are allowed to add more items,
|
|
|
|
// note that max_num = None translates to a blank string.
|
|
|
|
var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0;
|
2010-01-26 23:02:53 +08:00
|
|
|
$(this).each(function(i) {
|
|
|
|
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
|
|
|
});
|
|
|
|
if ($(this).length && showAddButton) {
|
|
|
|
var addButton;
|
|
|
|
if ($(this).attr("tagName") == "TR") {
|
|
|
|
// If forms are laid out as table rows, insert the
|
|
|
|
// "add" button in a new table row:
|
|
|
|
var numCols = this.eq(0).children().length;
|
|
|
|
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
|
|
|
addButton = $(this).parent().find("tr:last a");
|
|
|
|
} else {
|
|
|
|
// Otherwise, insert it immediately after the last form:
|
|
|
|
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
|
|
|
addButton = $(this).filter(":last").next().find("a");
|
|
|
|
}
|
|
|
|
addButton.click(function() {
|
2010-02-01 22:14:56 +08:00
|
|
|
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
2010-01-26 23:02:53 +08:00
|
|
|
var template = $("#" + options.prefix + "-empty");
|
2010-04-08 07:37:48 +08:00
|
|
|
var row = template.clone(true);
|
|
|
|
row.removeClass(options.emptyCssClass)
|
|
|
|
.addClass(options.formCssClass)
|
2011-02-05 12:11:39 +08:00
|
|
|
.attr("id", options.prefix + "-" + nextIndex);
|
2011-02-05 12:46:05 +08:00
|
|
|
nextIndex += 1;
|
2010-04-08 07:37:48 +08:00
|
|
|
row.find("*")
|
|
|
|
.filter(function() {
|
|
|
|
var el = $(this);
|
|
|
|
return el.attr("id") && el.attr("id").search(/__prefix__/) >= 0;
|
|
|
|
}).each(function() {
|
|
|
|
var el = $(this);
|
|
|
|
el.attr("id", el.attr("id").replace(/__prefix__/g, nextIndex));
|
|
|
|
})
|
|
|
|
.end()
|
|
|
|
.filter(function() {
|
|
|
|
var el = $(this);
|
|
|
|
return el.attr("name") && el.attr("name").search(/__prefix__/) >= 0;
|
|
|
|
}).each(function() {
|
|
|
|
var el = $(this);
|
|
|
|
el.attr("name", el.attr("name").replace(/__prefix__/g, nextIndex));
|
2011-02-06 14:20:27 +08:00
|
|
|
})
|
|
|
|
.end()
|
|
|
|
.filter(function() {
|
|
|
|
var el = $(this);
|
|
|
|
return el.attr("for") && el.attr("for").search(/__prefix__/) >= 0;
|
|
|
|
}).each(function() {
|
|
|
|
var el = $(this);
|
|
|
|
el.attr("for", el.attr("for").replace(/__prefix__/g, nextIndex));
|
2010-04-08 07:37:48 +08:00
|
|
|
});
|
|
|
|
if (row.is("tr")) {
|
2010-01-26 23:02:53 +08:00
|
|
|
// If the forms are laid out in table rows, insert
|
|
|
|
// the remove button into the last table cell:
|
2010-04-08 07:37:48 +08:00
|
|
|
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
|
|
|
} else if (row.is("ul") || row.is("ol")) {
|
2010-01-26 23:02:53 +08:00
|
|
|
// If they're laid out as an ordered/unordered list,
|
|
|
|
// insert an <li> after the last list item:
|
2010-04-08 07:37:48 +08:00
|
|
|
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
2010-01-26 23:02:53 +08:00
|
|
|
} else {
|
|
|
|
// Otherwise, just insert the remove button as the
|
|
|
|
// last child element of the form's container:
|
2010-04-08 07:37:48 +08:00
|
|
|
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
2010-01-26 23:02:53 +08:00
|
|
|
}
|
2010-04-08 07:37:48 +08:00
|
|
|
row.find("input,select,textarea,label,a").each(function() {
|
2010-02-01 22:14:56 +08:00
|
|
|
updateElementIndex(this, options.prefix, totalForms.val());
|
|
|
|
});
|
2011-02-05 12:11:39 +08:00
|
|
|
// Insert the new form when it has been fully edited
|
|
|
|
row.insertBefore($(template));
|
2010-01-26 23:02:53 +08:00
|
|
|
// Update number of total forms
|
2011-02-05 12:46:05 +08:00
|
|
|
$(totalForms).val(parseInt(totalForms.val()) + 1);
|
2010-02-01 22:14:56 +08:00
|
|
|
// Hide add button in case we've hit the max, except we want to add infinitely
|
2010-05-11 21:40:17 +08:00
|
|
|
if ((maxForms.val() != '') && (maxForms.val()-totalForms.val()) <= 0) {
|
2010-01-26 23:02:53 +08:00
|
|
|
addButton.parent().hide();
|
|
|
|
}
|
|
|
|
// The delete button of each row triggers a bunch of other things
|
2010-04-08 07:37:48 +08:00
|
|
|
row.find("a." + options.deleteCssClass).click(function() {
|
2010-01-26 23:02:53 +08:00
|
|
|
// Remove the parent form containing this button:
|
|
|
|
var row = $(this).parents("." + options.formCssClass);
|
|
|
|
row.remove();
|
|
|
|
// If a post-delete callback was provided, call it with the deleted form:
|
2010-02-01 22:14:56 +08:00
|
|
|
if (options.removed) {
|
|
|
|
options.removed(row);
|
|
|
|
}
|
2010-01-26 23:02:53 +08:00
|
|
|
// Update the TOTAL_FORMS form count.
|
|
|
|
var forms = $("." + options.formCssClass);
|
|
|
|
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
|
|
|
// Show add button again once we drop below max
|
2010-05-11 21:40:17 +08:00
|
|
|
if ((maxForms.val() == '') || (maxForms.val()-forms.length) > 0) {
|
2010-01-26 23:02:53 +08:00
|
|
|
addButton.parent().show();
|
|
|
|
}
|
|
|
|
// Also, update names and ids for all remaining form controls
|
|
|
|
// so they remain in sequence:
|
2010-02-01 22:14:56 +08:00
|
|
|
for (var i=0, formCount=forms.length; i<formCount; i++)
|
|
|
|
{
|
2010-03-15 20:15:44 +08:00
|
|
|
$(forms.get(i)).find("input,select,textarea,label,a").each(function() {
|
2010-01-26 23:02:53 +08:00
|
|
|
updateElementIndex(this, options.prefix, i);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
// If a post-add callback was supplied, call it with the added form:
|
2010-02-01 22:14:56 +08:00
|
|
|
if (options.added) {
|
2010-04-08 07:37:48 +08:00
|
|
|
options.added(row);
|
2010-02-01 22:14:56 +08:00
|
|
|
}
|
2010-01-26 23:02:53 +08:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
2010-02-01 22:14:56 +08:00
|
|
|
return this;
|
2010-01-26 23:02:53 +08:00
|
|
|
}
|
|
|
|
/* Setup plugin defaults */
|
|
|
|
$.fn.formset.defaults = {
|
2010-02-01 22:14:56 +08:00
|
|
|
prefix: "form", // The form prefix for your django formset
|
|
|
|
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
|
|
|
|
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
|
|
|
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
|
|
|
added: null, // Function called each time a new form is added
|
|
|
|
removed: null // Function called each time a form is deleted
|
2010-01-26 23:02:53 +08:00
|
|
|
}
|
2010-04-13 18:28:24 +08:00
|
|
|
})(django.jQuery);
|