我想使用Ajax自动将新表单添加到Django表单集中,这样当用户单击“添加”按钮时,它会运行JavaScript,向页面添加一个新表单(它是表单集的一部分)。
答案 0 :(得分:209)
我就是这样做的,使用jQuery:
我的模板:
<h3>My Services</h3>
{{ serviceFormset.management_form }}
{% for form in serviceFormset.forms %}
<div class='table'>
<table class='no_error'>
{{ form.as_table }}
</table>
</div>
{% endfor %}
<input type="button" value="Add More" id="add_more">
<script>
$('#add_more').click(function() {
cloneMore('div.table:last', 'service');
});
</script>
在javascript文件中:
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;
$(this).attr({'name': name, 'id': id}).val('').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);
}
它的作用:
cloneMore
接受selector
作为第一个参数,并将formset的type
作为第二个参数。 selector
应该做的是将它应该复制的内容传递给它。在这种情况下,我传递它div.table:last
,以便jQuery查找具有类table
的最后一个表。 :last
部分很重要,因为selector
也用于确定之后插入新表单的内容。很可能你会在其余表格结束时想要它。 type
参数是为了让我们可以更新management_form
字段,尤其是TOTAL_FORMS
字段,以及实际的表单字段。如果您的表单集中包含Client
个模型,则管理字段的ID为id_clients-TOTAL_FORMS
和id_clients-INITIAL_FORMS
,而表单字段的格式为id_clients-N-fieldname
} N
是表单编号,以0
开头。因此,使用type
参数,cloneMore
函数会查看当前有多少表单,并遍历新表单中的每个输入和标签,从id_clients-(N)-name
替换所有字段名称/ ID到id_clients-(N+1)-name
等等。完成后,它会更新TOTAL_FORMS
字段以反映新表单并将其添加到集合的末尾。
此功能对我特别有用,因为它的设置方式允许我在整个应用程序中使用它,当我想在formset中提供更多表单时,并不会让我需要一个隐藏的“模板”只要我传递formset名称和表单的格式,就可以复制表单。希望它有所帮助。
答案 1 :(得分:101)
使用empty_form
作为模板的简化版Paolo答案。
<h3>My Services</h3>
{{ serviceFormset.management_form }}
<div id="form_set">
{% for form in serviceFormset.forms %}
<table class='no_error'>
{{ form.as_table }}
</table>
{% endfor %}
</div>
<input type="button" value="Add More" id="add_more">
<div id="empty_form" style="display:none">
<table class='no_error'>
{{ serviceFormset.empty_form.as_table }}
</table>
</div>
<script>
$('#add_more').click(function() {
var form_idx = $('#id_form-TOTAL_FORMS').val();
$('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});
</script>
答案 2 :(得分:24)
我来自我前一段时间工作过的应用posted a snippet。与Paolo相似,但也允许您删除表格。
答案 3 :(得分:18)
Paolo的建议很好地解决了一个问题 - 浏览器的后退/前进按钮。
如果用户使用后退/前进按钮返回到formset,则不会呈现使用Paolo脚本创建的动态元素。对某些人来说可能是一个交易破坏者的问题。
示例:
1)用户使用“添加更多”按钮
向表单集添加两个新表单2)用户填充表单并提交formset
3)用户点击浏览器中的后退按钮
4)Formset现在缩减为原始形式,所有动态添加的表单都不存在
这根本不是Paolo脚本的缺陷;但这是dom操纵和浏览器缓存的生活现实。
我想有人可以在会话中存储表单的值,并且当formset加载再次创建元素并从会话重新加载值时,会有一些ajax魔法;但是根据你想要关于同一个用户和表单的多个实例的肛门,这可能会变得非常复杂。
任何人都有一个很好的建议来解决这个问题吗?
谢谢!
答案 4 :(得分:13)
查看以下动态django表单的解决方案:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
他们都使用jQuery并且是特定于django的。第一个似乎更精致,并提供了一个非常好的演示下载。
答案 5 :(得分:11)
模拟和模仿:
<input>
字段。<input>
字段的更改方式。虽然我知道formsets使用特殊的隐藏<input>
字段并且大致知道脚本必须做什么,但我不记得我头脑中的详细信息。我上面描述的是我在你的情况下会做的事情。
答案 6 :(得分:5)
有一个jquery plugin for this,我在Django 1.3中设置了inline_form,它运行得很好,包括预填充,客户端表单添加,删除和多个inline_formsets。
答案 7 :(得分:4)
一种选择是创建一个包含所有可能形式的formset,但最初将不需要的表单设置为隐藏 - 即display: none;
。当需要显示表单时,将其css显示设置为block
或任何适当的。
如果不了解您的“Ajax”正在做什么的更多细节,很难给出更详细的回复。
答案 8 :(得分:4)
另一个cloneMore版本,允许对字段进行选择性清理。当您需要防止删除多个字段时使用它。
$('table tr.add-row a').click(function() {
toSanitize = new Array('id', 'product', 'price', 'type', 'valid_from', 'valid_until');
cloneMore('div.formtable table tr.form-row:last', 'form', toSanitize);
});
function cloneMore(selector, type, sanitize) {
var newElement = $(selector).clone(true);
var total = $('#id_' + type + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var namePure = $(this).attr('name').replace(type + '-' + (total-1) + '-', '');
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).removeAttr('checked');
if ($.inArray(namePure, sanitize) != -1) {
$(this).val('');
}
});
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);
}
答案 9 :(得分:2)
cloneMore函数存在一个小问题。由于它还清除了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);
}
答案 10 :(得分:2)
我认为这是一个更好的解决方案。
How would you make a dynamic formset in Django?
克隆没有:
答案 11 :(得分:1)
@Paolo Bergantino
克隆所有附加的处理程序只需修改行
var newElement = $(selector).clone();
代表
var newElement = $(selector).clone(true);
答案 12 :(得分:1)
是的,如果您的条目数量有限,我还建议您只在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();
};
};
}
答案 13 :(得分:1)
因为上面的所有答案都使用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和名称。因为输入名称必须在表格中是唯一的,所有标识都是用它们完成的,而不是用id来完成的。
您还必须替换form
,type
和formset的容器。 (在上面的例子中choices
)
答案 14 :(得分:0)
对于那些正在寻找资源以便更好地理解上述解决方案的编码员:
阅读完上述链接后,Django文档和以前的解决方案应该更有意义。
作为我对此感到困惑的快速摘要:管理表格包含对其中表格的概述。您必须保持该信息的准确性,以便Django了解您添加的表单。 (社区,如果我的一些措辞不在这里,请给我建议。我是Django的新手。)