1/** 2 * jQuery Formset 1.3-pre 3 * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) 4 * @requires jQuery 1.2.6 or later 5 * 6 * Copyright (c) 2009, Stanislaus Madueke 7 * All rights reserved. 8 * 9 * Licensed under the New BSD License 10 * See: http://www.opensource.org/licenses/bsd-license.php 11 */ 12;(function($) { 13 $.fn.formset = function(opts) 14 { 15 var options = $.extend({}, $.fn.formset.defaults, opts), 16 flatExtraClasses = options.extraClasses.join(' '), 17 totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), 18 maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), 19 minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'), 20 childElementSelector = 'input,select,textarea,label,div', 21 $$ = $(this), 22 23 applyExtraClasses = function(row, ndx) { 24 if (options.extraClasses) { 25 row.removeClass(flatExtraClasses); 26 row.addClass(options.extraClasses[ndx % options.extraClasses.length]); 27 } 28 }, 29 30 updateElementIndex = function(elem, prefix, ndx) { 31 var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), 32 replacement = prefix + '-' + ndx + '-'; 33 if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); 34 if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); 35 if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); 36 }, 37 38 hasChildElements = function(row) { 39 return row.find(childElementSelector).length > 0; 40 }, 41 42 showAddButton = function() { 43 return maxForms.length == 0 || // For Django versions pre 1.2 44 (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)); 45 }, 46 47 /** 48 * Indicates whether delete link(s) can be displayed - when total forms > min forms 49 */ 50 showDeleteLinks = function() { 51 return minForms.length == 0 || // For Django versions pre 1.7 52 (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)); 53 }, 54 55 insertDeleteLink = function(row) { 56 var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'), 57 addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.'); 58 if (row.is('TR')) { 59 // If the forms are laid out in table rows, insert 60 // the remove button into the last table cell: 61 row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>'); 62 } else if (row.is('UL') || row.is('OL')) { 63 // If they're laid out as an ordered/unordered list, 64 // insert an <li> after the last list item: 65 row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>'); 66 } else { 67 // Otherwise, just insert the remove button as the 68 // last child element of the form's container: 69 row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>'); 70 } 71 // Check if we're under the minimum number of forms - not to display delete link at rendering 72 if (!showDeleteLinks()){ 73 row.find('a.' + delCssSelector).hide(); 74 } 75 76 row.find('a.' + delCssSelector).click(function() { 77 78 var row = $(this).parents('.' + options.formCssClass), 79 del = row.find('input:hidden[id $= "-DELETE"]'), 80 buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'), 81 forms; 82 $(document).trigger('row-deleted', [row]); 83 if (del.length) { 84 // We're dealing with an inline formset. 85 // Rather than remove this form from the DOM, we'll mark it as deleted 86 // and hide it, then let Django handle the deleting: 87 del.val('on'); 88 row.hide(); 89 forms = $('.' + options.formCssClass).not(':hidden'); 90 } else { 91 forms = row.siblings('.' + options.formCssClass).not('.formset-custom-template'); 92 row.remove(); 93 // Update the TOTAL_FORMS count: 94 totalForms.val(forms.length); 95 } 96 for (var i=0, formCount=forms.length; i<formCount; i++) {
97 // Apply `extraClasses` to form rows so they're nicely alternating: 98 applyExtraClasses(forms.eq(i), i); 99 if (!del.length) { 100 // Also update names and IDs for all child controls (if this isn't 101 // a delete-able inline formset) so they remain in sequence: 102 forms.eq(i).find(childElementSelector).each(function() { 103 updateElementIndex($(this), options.prefix, i); 104 }); 105 } 106 } 107 // Check if we've reached the minimum number of forms - hide all delete link(s) 108 if (!showDeleteLinks()){ 109 $('a.' + delCssSelector).each(function(){$(this).hide();}); 110 } 111 // Check if we need to show the add button: 112 if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show(); 113 // If a post-delete callback was provided, call it with the deleted form: 114 if (options.removed) options.removed(row); 115 116 return false; 117 }); 118 }; 119 120 $$.each(function(i) { 121 var row = $(this), 122 del = row.find('input:checkbox[id $= "-DELETE"]'); 123 if (del.length) { 124 // If you specify "can_delete = True" when creating an inline formset, 125 // Django adds a checkbox to each form in the formset. 126 // Replace the default checkbox with a hidden field: 127 if (del.is(':checked')) { 128 // If an inline formset containing deleted forms fails validation, make sure 129 // we keep the forms hidden (thanks for the bug report and suggested fix Mike) 130 del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />'); 131 row.hide(); 132 } else { 133 del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />'); 134 } 135 // Hide any labels associated with the DELETE checkbox: 136 $('label[for="' + del.attr('id') + '"]').hide(); 137 del.remove(); 138 } 139 if (hasChildElements(row)) { 140 row.addClass(options.formCssClass); 141 if (row.is(':visible')) { 142 insertDeleteLink(row); 143 applyExtraClasses(row, i); 144 } 145 } 146 }); 147 148 if ($$.length) { 149 var hideAddButton = !showAddButton(), 150 addButton, template; 151 if (options.formTemplate) { 152 // If a form template was specified, we'll clone it to generate new form instances: 153 template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate); 154 template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template'); 155 template.find(childElementSelector).each(function() { 156 updateElementIndex($(this), options.prefix, '__prefix__'); 157 }); 158 insertDeleteLink(template); 159 } else { 160 // Otherwise, use the last form in the formset; this works much better if you've got 161 // extra (>= 1) forms (thnaks to justhamade for pointing this out): 162 template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id'); 163 template.find('input:hidden[id $= "-DELETE"]').remove(); 164 // Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion): 165 template.find(childElementSelector).not(options.keepFieldValues).each(function() { 166 var elem = $(this); 167 // If this is a checkbox or radiobutton, uncheck it. 168 // This fixes Issue 1, reported by Wilson.Andrew.J: 169 if (elem.is('input:checkbox') || elem.is('input:radio')) { 170 elem.attr('checked', false); 171 } else { 172 elem.val(''); 173 } 174 }); 175 } 176 // FIXME: Perhaps using $.data would be a better idea? 177 options.formTemplate = template; 178 179 if ($$.is('TR')) { 180 // If forms are laid out as table rows, insert the 181 // "add" button in a new table row: 182 var numCols = $$.eq(0).children().length, // This is a bit of an assumption :| 183 buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>') 184 .addClass(options.formCssClass + '-add'); 185 $$.parent().append(buttonRow); 186 if (hideAddButton) buttonRow.hide(); 187 addButton = buttonRow.find('a'); 188 } else { 189 // Otherwise, insert it immediately after the last form: 190 $$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
191 addButton = $$.filter(':last').next(); 192 if (hideAddButton) addButton.hide(); 193 } 194 addButton.click(function() { 195 var formCount = parseInt(totalForms.val()), 196 row = options.formTemplate.clone(true).removeClass('formset-custom-template'), 197 buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this) 198 delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'); 199 applyExtraClasses(row, formCount); 200 row.insertBefore(buttonRow).show(); 201 row.find(childElementSelector).each(function() { 202 updateElementIndex($(this), options.prefix, formCount); 203 }); 204 totalForms.val(formCount + 1); 205 // Check if we're above the minimum allowed number of forms -> show all delete link(s) 206 if (showDeleteLinks()){ 207 $('a.' + delCssSelector).each(function(){$(this).show();}); 208 } 209 // Check if we've exceeded the maximum allowed number of forms: 210 if (!showAddButton()) buttonRow.hide(); 211 // If a post-add callback was supplied, call it with the added form: 212 if (options.added) options.added(row); 213 return false; 214 }); 215 } 216 217 return $$; 218 }; 219 220 /* Setup plugin defaults */ 221 $.fn.formset.defaults = { 222 prefix: 'form', // The form prefix for your django formset 223 formTemplate: null, // The jQuery selection cloned to generate new form instances 224 addText: 'add another', // Text for the add link 225 deleteText: 'remove', // Text for the delete link 226 addCssClass: 'add-row', // CSS class applied to the add link 227 deleteCssClass: 'delete-row', // CSS class applied to the delete link 228 formCssClass: 'dynamic-form', // CSS class applied to each form in a formset 229 extraClasses: [], // Additional CSS classes, which will be applied to each form in turn 230 keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned 231 added: null, // Function called each time a new form is added 232 removed: null // Function called each time a form is deleted 233 }; 234})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.