JavaScript
==========

/* For dynamically adding/deleting Django inline formsets
 * Original snippet from: https://djangosnippets.org/snippets/1389/
 * Original snippet by: elo80ka
 * This version by: halfnibble (Josh Wedekind)
 * Updated 5/13/2015
 */

var Formset = (function() {
    var callbacks = {},
        empty_form = {},
        form_count = 0,
        form_manager = {},
        form_placeholder = $('#formset-placeholder')[0],
        form_selector = '.dynamic-form',
        prefix = 'formset';        

    function setup(context) {
        /***************************************
         * Common context attributes           *
         *   prefix:           'formset'       *
         *   form_selector:    '.dynamic-form' *
         *   form_placeholder: DOM.element     *
         *   callbacks:        { funcs... }    *
         ***************************************/
        $.extend(this, context);

        // Set a few more attributes
        this.form_manager = $('#id_' + this.prefix + '-TOTAL_FORMS');
        this.form_count = parseInt(this.form_manager.val());        
        if (this.form_placeholder === undefined || this.form_placeholder === [])
            this.form_placeholder = $('#' + this.prefix + '-placeholder');

        // Setup the empty form for add_form()
        var empty_form = $(this.form_selector + ':first').clone(true).get(0);
        // Clear all values except for hidden field values.
        $(empty_form).find(':input')
            .removeAttr('checked')
            .removeAttr('selected')
            .not(':button, :submit, :reset, [type="hidden"], :radio, :checkbox')
            .val('')
            .attr('value',''); // Handle UpdateView default values
        this.empty_form = empty_form;

        if (this.callbacks.setup)
            this.callbacks.setup(this);
    }

    function add_form() {
        var self = this;

        // Assume new forms get last index counter
        var index = this.form_count;

        // Create form to add
        var form = $(this.empty_form).clone(true).get(0);
        this.update_index(form, index, false);
        
        // Add form after last one, or after the placeholder if none exist.
        if ($(this.form_selector + ':last').length > 0)
            $(form).insertAfter(
                $(this.form_selector + ':last')
            );
        else
            $(form).insertAfter(this.form_placeholder);

        // Update all pertinent form field indexes
        $(form).children('.hidden').removeClass('hidden');
        $(form).find('div, input, select, label, button').each( function() {
            self.update_index(this, index, false);
        });

        // Update index counter
        ++this.form_count;
        this.form_manager.val(this.form_count);

        if (this.callbacks.add_form)
            this.callbacks.add_form(this, form, index);

        return false;
    }

    function delete_form(button) {
        var self = this;
        $(button).parents(this.form_selector).hide(400, function() {
            var form = this;
            $(form).remove();
            --self.form_count;
            self.form_manager.val(self.form_count);

            // Delete anything else before updating
            if (self.callbacks.delete_form)
                self.callbacks.delete_form(self, form);

            // Update all formset indexes, etc.
            self.update_all();
        });

        return false;
    }

    function update_all() {
        var self = this;
        // Get list of all forms in formset
        var forms = $(this.form_selector);

        for (var index = 0; index < this.form_count; index++) {
            /*jshint loopfunc: true */
            var form = forms.get(index);

            // Update form index, then all pertinent field indexes
            this.update_index(form, index);
            $(form).find('div, input, select, label, button')
                .each( function() { self.update_index(this, index); });

            if (this.callbacks.update_all)
                this.callbacks.update_all(this, form, index);
        }
    }

    function update_index(element, index, external_links) { 
        if (external_links === undefined)
            external_links = true; // Default: update links to element ID
        var regex = new RegExp('(' + this.prefix + '-\\d+)');
        var replacement = this.prefix + '-' + index;

        if ($(element).attr("for"))
            $(element).attr(
                "for",
                $(element).attr("for")
                .replace(regex, replacement)
            );
        if (element.id) {
            // Update targets to element
            if (external_links === true)
                $('a[href="#'+element.id+'"]').attr('href', function (i, attr) {
                    return attr.replace(regex, replacement);
                });
            element.id = element.id.replace(regex, replacement);
        }
        if (element.name)
            element.name = element.name.replace(regex, replacement);
        if (element.getAttribute('data-prefix'))
            element.setAttribute(
                'data-prefix',
                element.getAttribute('data-prefix')
                .replace(regex, replacement)
            );

        // Use if you want to replace more field attributes
        // E.g. element.className
        if (this.callbacks.update_index)
            this.callbacks.update_index(this, element, index,
                                        regex, replacement);
    }

    return {
        // Properties
        callbacks: callbacks,
        empty_form: empty_form,
        form_count: form_count,
        form_manager: form_manager,
        form_placeholder: form_placeholder,
        form_selector: form_selector,
        prefix: prefix,
        // Methods
        setup: setup,
        add_form: add_form,
        delete_form: delete_form,
        update_all: update_all,
        update_index: update_index
    };
});



Example Usage
=============

(Placed below formset in template)

<script type="text/javascript">
    <!--
    var callbacks = {
        'setup': function(formset) {
            $(formset.form_selector).find('.delete-form-row').click( function(e) {
                e.preventDefault();
                formset.delete_form(this);
            });
            $('.add-form-row').click( function(e) {
                e.preventDefault();
                formset.add_form();
            });
        }, 

        'add_form': function(formset, form, index) {
            $(form).find('.delete-form-row').click( function(e) {
                e.preventDefault();
                formset.delete_form(this);
            });
            if (index % 2 !== 0)
                $(form).removeClass('light');
        },

        'update_all': function(formset, form, index) {
            if (index % 2 === 0)
                $(form).addClass('light');
            else
                $(form).removeClass('light');
        }
    };

    var formset = new Formset();

    formset.setup({
        prefix: '{{ formset.prefix }}',
        callbacks: callbacks
    });
    //-->
</script>