web/static/admin/js/inlines.js
changeset 0 ecdfc63274bf
equal deleted inserted replaced
-1:000000000000 0:ecdfc63274bf
       
     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);