我想动态地向Django表单集添加新表单,这样当用户点击“添加”按钮时,它就会运行JavaScript,向页面添加新表单(这是表单集的一部分)。


当前回答

克隆莫尔函数有一个小问题。因为它也会清除django自动生成的隐藏字段的值,如果你试图保存一个包含多个空表单的表单集,它会导致django报错。

这里有一个解决方案:

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);
}

其他回答

模拟和模仿:

Create a formset which corresponds to the situation before clicking the "add" button. Load the page, view the source and take a note of all <input> fields. Modify the formset to correspond to the situation after clicking the "add" button (change the number of extra fields). Load the page, view the source and take a note of how the <input> fields changed. Create some JavaScript which modifies the DOM in a suitable way to move it from the before state to the after state. Attach that JavaScript to the "add" button.

虽然我知道表单集使用特殊的隐藏<input>字段,并且大约知道脚本必须做什么,但我不记得我头顶上的细节。我上面所描述的就是我在你的情况下会做的事情。

Paolo的建议很好用,但有一点需要注意——浏览器的后退/前进按钮。

如果用户使用后退/前进按钮返回到表单集,那么用Paolo的脚本创建的动态元素将不会被呈现。对一些人来说,这个问题可能是交易的破坏者。

例子:

1)用户使用“add-more”按钮向表单集中添加两个新表单

2)用户填充表单并提交表单集

3)用户在浏览器中点击后退按钮

4)形式集现在减少到原来的形式,所有动态添加的形式不存在

这根本不是保罗剧本的缺陷;但是dom操作和浏览器缓存是一个现实。

我认为可以在会话中存储表单的值,并在表单集加载时再次创建元素并从会话中重新加载值;但这取决于你想要的相同用户和表单的多个实例,这可能会变得非常复杂。

有人有好的建议吗?

谢谢!

是的,如果你有有限数量的条目,我也建议在html中渲染它们。(如果你不这样做,你将不得不使用另一个方法)。

你可以像这样隐藏它们:

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

然后js真的很简单:

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();
        };
    };
}

因为上面所有的答案都使用jQuery,使一些事情有点复杂,我写了下面的脚本:

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
    }
};

首先,您应该将auto_id设置为false,从而禁用id和name的复制。因为输入名称在表单中必须是唯一的,所以所有的标识都是用它们来完成的,而不是用id。 您还必须替换表单集的表单、类型和容器。(在上述选择的例子中)

我可以向每个正在寻找开箱即用解决方案的人推荐django-dynamic-formsets。

它取代了我从提出的解决方案中派生的所有代码,并提供了一些额外的功能,例如删除表单或对相关按钮进行风格化。