Dynamically adding a form to a Django formset with Ajax

后端 未结 15 1922
不思量自难忘°
不思量自难忘° 2020-11-22 03:09

I want to automatically add new forms to a Django formset using Ajax, so that when the user clicks an \"add\" button it runs JavaScript that adds a new form (which is part o

相关标签:
15条回答
  • 2020-11-22 03:19

    I think this is a much better solution.

    How would you make a dynamic formset in Django?

    Does things clone doesn't:

    • Add form when no initial forms exists
    • Handles javascript in the form better, for example django-ckeditor
    • Keep initial data
    0 讨论(0)
  • 2020-11-22 03:20

    Yea I'd also recommend just rendering them out in the html if you have a finite number of entries. (If you don't you'll have to user another method).

    You can hide them like this:

    {% for form in spokenLanguageFormset %}
        <fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">
    

    Then the js is really simple:

    addItem: function(e){
        e.preventDefault();
        var maxForms = parseInt($(this).closest("fieldset").find("[name*='MAX_NUM_FORMS']").val(), 10);
        var initialForms = parseInt($(this).closest("fieldset").find("[name*='INITIAL_FORMS']").val(), 10);
        // check if we can add
        if (initialForms < maxForms) {
            $(this).closest("fieldset").find("fieldset:hidden").first().show();
            if ($(this).closest("fieldset").find("fieldset:visible").length == maxForms ){
                // here I'm just hiding my 'add' link
                $(this).closest(".control-group").hide();
            };
        };
    }
    
    0 讨论(0)
  • 2020-11-22 03:20

    Because all answers above use jQuery and make some things a bit complex I wrote following script:

    function $(selector, element) {
        if (!element) {
            element = document
        }
        return element.querySelector(selector)
    }
    
    function $$(selector, element) {
        if (!element) {
            element = document
        }
        return element.querySelectorAll(selector)
    }
    
    function hasReachedMaxNum(type, form) {
        var total = parseInt(form.elements[type + "-TOTAL_FORMS"].value);
        var max = parseInt(form.elements[type + "-MAX_NUM_FORMS"].value);
        return total >= max
    }
    
    function cloneMore(element, type, form) {
        var totalElement = form.elements[type + "-TOTAL_FORMS"];
        total = parseInt(totalElement.value);
        newElement = element.cloneNode(true);
        for (var input of $$("input", newElement)) {
            input.name = input.name.replace("-" + (total - 1) + "-", "-" + total + "-");
            input.value = null
        }
        total++;
        element.parentNode.insertBefore(newElement, element.nextSibling);
        totalElement.value = total;
        return newElement
    }
    var addChoiceButton = $("#add-choice");
    addChoiceButton.onclick = function() {
        var choices = $("#choices");
        var createForm = $("#create");
        cloneMore(choices.lastElementChild, "choice_set", createForm);
        if (hasReachedMaxNum("choice_set", createForm)) {
            this.disabled = true
        }
    };
    

    First you should set auto_id to false and so disable the duplication of id and name. Because the input names have to be unique in there form, all identification is done with them and not with id's. You also have to replace the form, type and the container of the formset. (In the example above choices)

    0 讨论(0)
  • 2020-11-22 03:21

    I've posted a snippet from an app I worked on a while back. Similar to Paolo's, but also allows you delete forms.

    0 讨论(0)
  • 2020-11-22 03:26

    @Paolo Bergantino

    to clone all the handlers attached just modify the line

    var newElement = $(selector).clone();
    

    for

    var newElement = $(selector).clone(true);
    

    to prevent this problem.

    0 讨论(0)
  • 2020-11-22 03:30

    There is a small issue with the cloneMore function. Since it's also cleaning the value of the django auto-generated hidden fields, it causes django to complain if you try to save a formset with more than one empty form.

    Here is a fix:

    function cloneMore(selector, type) {
        var newElement = $(selector).clone(true);
        var total = $('#id_' + type + '-TOTAL_FORMS').val();
        newElement.find(':input').each(function() {
            var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
            var id = 'id_' + name;
    
            if ($(this).attr('type') != 'hidden') {
                $(this).val('');
            }
            $(this).attr({'name': name, 'id': id}).removeAttr('checked');
        });
        newElement.find('label').each(function() {
            var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
            $(this).attr('for', newFor);
        });
        total++;
        $('#id_' + type + '-TOTAL_FORMS').val(total);
        $(selector).after(newElement);
    }
    
    0 讨论(0)
提交回复
热议问题