/** * helper functions for sortable inlines (tabular and stacked) */ function reinitDateTimeFields(row) { row.find(".vDateField").datepicker({ //appendText: '(mm/dd/yyyy)', showOn: 'button', buttonImageOnly: false, buttonText: '', dateFormat: grappelli.getFormat('date'), }); row.find(".vTimeField").timepicker(); } function updateSelectFilter(row) { // If any SelectFilter widgets were added, instantiate a new instance. if (typeof SelectFilter != "undefined"){ row.find(".selectfilter").each(function(index, value){ var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}"); }); row.find(".selectfilterstacked").each(function(index, value){ var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}"); }); } } /** * reorder of inlines dom * works pretty similar to updateFormIndex() of the inline() widget * helper function for sortable */ function sortable_updateFormIndex(form, idx, prefix) { var re = /-\d+-/g; form.attr('id', prefix + idx); form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() { var node = django.jQuery(this), node_id = node.attr('id'), node_name = node.attr('name'), node_for = node.attr('for'), node_href = node.attr("href"); if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-")); if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-")); if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-")); if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-")); }); } /** * checks if inline form is filled * helper function for sortable */ function is_form_filled(form) { var input_tags = form.find("input"), input_tag, select_tags = form.find('select'), select_tag, select_template, re = /-\d+-/g; // flagged for deletion if (form.find("input[id$='DELETE']").attr("checked")) { return false; } // loop thru all inputs and check if anything is set for (var i = 0; i < input_tags.length; i++) { input_tag = django.jQuery(input_tags[i]); if (input_tag.val()) { // checkboxes and radio need to be checked too if (input_tag.attr("type") == "checkbox" || input_tag.attr("type") == "radio") { if (input_tag.attr("checked")) { return true; } // hidden fields are ignored } else if (input_tag.attr("type") != "hidden"){ return true; } } } for (var i = 0; i < select_tags.length; i++) { select_tag = django.jQuery(select_tags[i]); if (select_tag.val()) { // get the hidden empty form and compare against it's value (which is the default/empty value) select_template = form.find("#" + select_tag.attr('id').replace(re, "__prefix__")); // if they aren't equal the form is filled if (select_tag.val() != select_template.val()) { return true; } } } return false; } // updates label of inlines // to keep them in sequence (#1,#2,#3,...) function stacked_updateInlineLabel(row) { row.parent().find("div.module").find("h3:first").each(function(i) { var h3_node = django.jQuery(this); h3_node.html(h3_node.html().replace(/(#\d+)/g, "#" + ++i)); }); } // init tinymce for new inlines function reinitTinyMCE(row) { row.find("textarea.vLargeTextField").each(function() { tinyMCE.execCommand('mceAddControl', false, this.id); }); } // need to remove tinymce form removed inline function deleteTinyMCE(row) { row.find("textarea.vLargeTextField").each(function() { if (tinyMCE.getInstanceById(this.id)) { tinyMCE.execCommand('mceRemoveControl', false, this.id); } }); } function tabular_onAdded(row) { reinitDateTimeFields(row); updateSelectFilter(row); } function tabular_onRemoved(row) {} function stacked_onAdded(row) { reinitTinyMCE(row); reinitDateTimeFields(row); updateSelectFilter(row); stacked_updateInlineLabel(row); } function stacked_onRemoved(row) { stacked_updateInlineLabel(row); deleteTinyMCE(row); } (function($) { $.fn.inline = function(options) { var defaults = { 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-handler", // CSS class applied to the add link deleteCssClass: "delete-handler", // CSS class applied to the delete link removeCssClass: "remove-handler", // CSS class applied to the remove link emptyCssClass: "empty-form", // CSS class applied to the empty row formCssClass: "dynamic-form", // CSS class applied to each form in a formset predeleteCssClass: "predelete", onAdded: null, // Function called each time a new form is added onRemoved: null // Function called each time a form is deleted }; options = $.extend(defaults, options); return this.each(function() { var inline = $(this), // the current inline node totalForms = inline.find("#id_" + options.prefix + "-TOTAL_FORMS"), // current forms total maxForms = inline.find("#id_" + options.prefix + "-MAX_NUM_FORMS"), // max forms in this inline (0 if no limit) addButtons = inline.find("a." + options.addCssClass), template = inline.find("#" + options.prefix + "-empty"), // the hidden node we copy to create an additional form template_id = template.attr('id'), template_ready = false, /* updateElementIndex = function(el, prefix, ndx) { var id_regex = new RegExp("(" + prefix + "-\\d+)"); var replacement = prefix + "-" + ndx; 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); } }, */ initAddButtons = function() { addButtons.click(addButtonHandler); if (maxForms.val() != 0 && (maxForms.val() - totalForms.val()) == 0) { // hide add-buttons hideAddBottons(); } }, initFormIndex = function(form, nextIndex) { var re = /__prefix__/g; form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() { var node = $(this), node_id = node.attr('id'), node_name = node.attr('name'), node_for = node.attr('for'), node_href = node.attr("href"); if (node_id) node.attr('id', node_id.replace(re, nextIndex)); if (node_name) node.attr('name', node_name.replace(re, nextIndex)); if (node_for) node.attr('for', node_for.replace(re, nextIndex)); if (node_href) node.attr('href', node_href.replace(re, nextIndex)); }); }, updateFormIndex = function(form, idx) { // need to trigger onRemove and // onAdded (on bottom of function) to reinint rows if (options.onRemoved) { options.onRemoved(form); } var re = /-\d+-/g; form.attr('id', options.prefix + idx); form.find(':input,span,table,iframe,label,a,ul,p,img').each(function() { var node = $(this), node_id = node.attr('id'), node_name = node.attr('name'), node_for = node.attr('for'), node_href = node.attr("href"); if (node_id) node.attr('id', node_id.replace(re, "-" + idx + "-")); if (node_name) node.attr('name', node_name.replace(re, "-" + idx + "-")); if (node_for) node.attr('for', node_for.replace(re, "-" + idx + "-")); if (node_href) node.attr('href', node_href.replace(re, "-" + idx + "-")); }); if (options.onAdded) { options.onAdded(form); } }, addButtonHandler = function() { // FIXME wrong place to do this // choices: // 1) create a new event (beforAdded) and try to do this form outside :( // 2) add the "editor_deselector" class to the templates textarea // ... if (!options.template_ready) { if (typeof tinyMCE !== "undefined") { template.find('textarea').each(function(e) { if (tinyMCE.getInstanceById(this.id)) { tinyMCE.execCommand('mceRemoveControl', false, this.id); } }); } options.template_ready = true; } var nextIndex = parseInt(totalForms.val(), 10); // create new from (add it as last) var form = template.clone(true); form.removeClass(options.emptyCssClass) .attr("id", template_id.replace("-empty", nextIndex)) .insertBefore(template) .addClass(options.formCssClass); initFormIndex(form, nextIndex); totalForms.val(nextIndex + 1); // Hide add button in case we've hit the max, except we want to add infinitely if ((maxForms.val() != 0) && (maxForms.val() - totalForms.val()) <= 0) { // hide stuff hideAddBottons(); } // If a post-add callback was supplied, call it with the added form if (options.onAdded) { options.onAdded(form); } return false; }, hideAddBottons = function() { addButtons.hide().parents('div.add-item').hide(); }, showAddButtons = function() { addButtons.show().parents('div.add-item').show(); }, deleteHandler = function() { var deleteInput = $(this).prev(), form = deleteInput.parents("." + options.formCssClass).first(); if (form.hasClass("has_original")) { // toggle delete checkbox and delete css class form.toggleClass(options.predeleteCssClass); if (deleteInput.attr("checked")) { deleteInput.removeAttr("checked"); } else { deleteInput.attr("checked", 'checked'); } } return false; }, removeHandler = function() { var deleteInput = $(this).prev(), form = deleteInput.parents("." + options.formCssClass).first(); // last one stays // else if (totalForms.val() == 1) // alert("letztes bleibt da!"); // return false; // remove form // Remove the parent form containing this button: form.remove(); // If a post-delete callback was provided, call it with the deleted form: if (options.onRemoved) { options.onRemoved(form); } // Update the TOTAL_FORMS form count. var forms = inline.find("." + options.formCssClass); totalForms.val(parseInt(totalForms.val(), 10) - 1); // Show add button again once we drop below max if ((maxForms.val() == 0) || (maxForms.val() >= forms.length)) { showAddButtons(); } // Also, update names and ids for all remaining form controls // so they remain in sequence: var startReplaceAt = form.attr("id"); startReplaceAt = parseInt(startReplaceAt.replace(options.prefix, ""), 10); for (var i = startReplaceAt; i < forms.length; i++) { updateFormIndex($(forms[i]), i); } return false; }, initInlineForms = function() { var hasErrors = false; //inline.find("div.items div.module").each(function() { inline.find("div.module").each(function() { var form = $(this); // add the options.formCssClass to all forms in the inline if (form.attr('id') !== "") { form.not("." + options.emptyCssClass).addClass(options.formCssClass); } // open the form if it has errors if (form.find("ul.errorlist").length > 0) { form.removeClass('closed').addClass('open'); // to open the inline hasErrors = true; } }); // open the inline if it has forms with errors in it if (hasErrors) { inline.removeClass('closed').addClass('open'); } }; // set this to prevent the browser from keeping the current value after reload totalForms.attr("autocomplete", "off"); initInlineForms(); initAddButtons(); // delete button // toggle the delete-checkbox and add the predelete-class to the row inline.find("a." + options.deleteCssClass).click(deleteHandler); inline.find("a." + options.removeCssClass).click(removeHandler); // add options.predeleteCssClass to forms with the delete checkbox checked inline.find("li.delete-handler-container input").each(function() { var deleteInput = $(this); if (deleteInput.attr("checked")) { var form = $(deleteInput.parents("." + options.formCssClass).first()); if (form.hasClass("has_original")) { form.toggleClass(options.predeleteCssClass); } } }); }); }; })(django.jQuery);