我想使用Ajax自动将新表单添加到Django表单集中,以便当用户单击“添加”按钮时,它运行JavaScript,该JavaScript将新表单(属于表单集的一部分)添加到页面中。
我想使用Ajax自动将新表单添加到Django表单集中,以便当用户单击“添加”按钮时,它运行JavaScript,该JavaScript将新表单(属于表单集的一部分)添加到页面中。
Answers:
这就是我使用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
作为第一个参数,type
formset的形式作为第二个参数。什么是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
withN
是表格编号,以开头0
。因此,使用该type
参数,该cloneMore
函数将查看当前有多少个表单,并遍历新表单中的每个输入和标签,从而替换诸如id_clients-(N)-name
to id_clients-(N+1)-name
等的所有字段名称/ id 。完成后,它将更新TOTAL_FORMS
字段以反映新表单并将其添加到集合的末尾。
此功能对我特别有用,因为它的设置方式使我可以在整个应用程序中使用它,以便在表单集中提供更多表单,而不必让我需要隐藏的“模板”表单来进行复制只要我通过它,表单集名称和表单的布局格式。希望能帮助到你。
prefix
Formset Object 的成员分配一个值。该值应与type
该cloneMore
函数的参数相同。
Paolo答案的简化版本,empty_form
用作模板。
<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>
CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets)
ctx['competitor_form_set'] = CompetitorFormSet(request.POST)
我只能以一种干净的方法获得一种形式。您能以意见解释如何处理吗?
empty_form
),对此我表示赞赏。
Paolo的建议可以很好地解决一个警告-浏览器的后退/前进按钮。
如果用户使用后退/前进按钮返回到表单集,则不会渲染用Paolo脚本创建的动态元素。对于某些人来说,这可能是一个破坏交易的问题。
例:
1)用户使用“添加更多”按钮将两个新表单添加到表单集中
2)用户填充表单并提交表单集
3)用户在浏览器中单击后退按钮
4)现在将Formset缩减为原始表单,不存在所有动态添加的表单
这根本不是Paolo脚本的缺陷。但事实证明,使用dom操纵和浏览器的缓存是可行的。
我想可以在表单中存储表单的值,并且在加载表单集时再次使用ajax魔术来再次创建元素并从会话中重新加载值。但是根据您想要成为同一个用户的方式以及该表单的多个实例的情况,这可能会变得非常复杂。
有人对此有很好的建议吗?
谢谢!
查看以下针对动态Django表单的解决方案:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
它们都使用jQuery,并且是特定于Django的。第一个看起来更加精致,并提供带有演示的出色下载。
模拟和模仿:
<input>
字段。<input>
字段的变化。虽然我知道表单集使用特殊的隐藏<input>
字段并且大致了解脚本必须执行的操作,但我不记得这些细节。我上面所描述的就是在您遇到的情况下我将要做的事情。
有一个jquery插件,我在Django 1.3中将它与inline_form一起使用,它可以完美地工作,包括预填充,客户端表单添加,删除以及多个inline_formsets。
一种选择是使用所有可能的表单创建一个表单集,但最初将不需要的表单设置为hidden,即display: none;
。当需要显示表单时,将其设置为css display block
或其他合适的方法。
在不了解“ Ajax”正在做什么的更多细节的情况下,很难给出更详细的响应。
另一个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);
}
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);
}
我认为这是一个更好的解决方案。
克隆的东西不会:
对于那些正在寻找资源以更好地理解上述解决方案的编码人员:
阅读完以上链接后,Django文档和以前的解决方案应该更有意义。
作为让我感到困惑的快速摘要:管理表单包含其中的表单的概述。您必须保持准确的信息,以便Django知道添加的表单。(社区,如果我的某些措辞在这里不对,请给我建议。我是Django的新手。)
@保罗·贝尔甘蒂诺
克隆所有附加的处理程序,只需修改该行
var newElement = $(selector).clone();
对于
var newElement = $(selector).clone(true);
以防止出现此问题。
是的,如果条目数量有限,我还建议您仅将它们呈现在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来完成。您还可以更换form
,type
和表单集的容器。(在上面的示例中choices
)