ฉันต้องการเพิ่มแบบฟอร์มใหม่โดยอัตโนมัติไปยังชุดรูปแบบ Django โดยใช้ Ajax ดังนั้นเมื่อผู้ใช้คลิกที่ปุ่ม "เพิ่ม" มันจะเรียกใช้ JavaScript ที่เพิ่มรูปแบบใหม่ (ซึ่งเป็นส่วนหนึ่งของชุดรูปแบบ) ไปยังหน้า
ฉันต้องการเพิ่มแบบฟอร์มใหม่โดยอัตโนมัติไปยังชุดรูปแบบ Django โดยใช้ Ajax ดังนั้นเมื่อผู้ใช้คลิกที่ปุ่ม "เพิ่ม" มันจะเรียกใช้ JavaScript ที่เพิ่มรูปแบบใหม่ (ซึ่งเป็นส่วนหนึ่งของชุดรูปแบบ) ไปยังหน้า
คำตอบ:
นี่คือวิธีที่ฉันทำโดยใช้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
ของฟอร์มเป็นอาร์กิวเมนต์ที่ 2 สิ่งที่selector
ควรทำคือผ่านสิ่งที่ควรทำซ้ำ ในกรณีนี้ผมผ่านมันdiv.table:last
เพื่อให้ jQuery table
มองหาโต๊ะที่ผ่านมากับการเรียนของ :last
ส่วนหนึ่งของมันเป็นสิ่งสำคัญเพราะselector
จะใช้ในการตรวจสอบสิ่งที่รูปแบบใหม่จะถูกแทรกหลัง มากกว่าที่คุณต้องการในตอนท้ายของแบบฟอร์มที่เหลือ type
อาร์กิวเมนต์เป็นเพื่อให้เราสามารถปรับปรุงmanagement_form
ข้อมูลสะดุดตาTOTAL_FORMS
เช่นเดียวกับเขตข้อมูลรูปแบบที่เกิดขึ้นจริง หากคุณมีชุดรูปแบบที่เต็มไปด้วยพูดClient
แบบจำลองเขตข้อมูลการจัดการจะมีรหัสid_clients-TOTAL_FORMS
และid_clients-INITIAL_FORMS
ในขณะที่เขตข้อมูลแบบฟอร์มจะอยู่ในรูปแบบของid_clients-N-fieldname
ด้วยN
0
เป็นจำนวนรูปแบบที่เริ่มต้นด้วย ดังนั้นที่มีtype
การโต้แย้งcloneMore
ฟังก์ชั่นดูที่วิธีการหลายรูปแบบมีอยู่ในปัจจุบันนี้และผ่านไปทุก input และฉลากภายในรูปแบบใหม่เปลี่ยนทุกเขตข้อมูลชื่อ / รหัสจากสิ่งที่ชอบid_clients-(N)-name
ไปid_clients-(N+1)-name
และอื่น ๆ หลังจากเสร็จสิ้นแล้วมันจะอัปเดตTOTAL_FORMS
ฟิลด์เพื่อสะท้อนถึงฟอร์มใหม่และเพิ่มลงในส่วนท้ายของชุด
ฟังก์ชั่นนี้มีประโยชน์สำหรับฉันเป็นพิเศษเพราะวิธีการตั้งค่าทำให้ฉันสามารถใช้งานได้ตลอดทั้งแอปเมื่อฉันต้องการให้ฟอร์มเพิ่มเติมในชุดรูปแบบและไม่ทำให้ฉันต้องมีแบบฟอร์ม "เทมเพลต" ที่ซ่อนอยู่เพื่อทำซ้ำ ตราบใดที่ฉันผ่านมันชื่อชุดรูปแบบและรูปแบบที่วางแบบฟอร์ม หวังว่ามันจะช่วย
prefix
สมาชิกของ Formset Object ค่านี้ควรเป็นค่าเดียวกับtype
อาร์กิวเมนต์ของcloneMore
ฟังก์ชัน
คำตอบของเปาโลฉบับย่อโดยใช้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)
i รับรูปแบบเดียวเท่านั้นในวิธีสะอาด คุณช่วยอธิบายวิธีจัดการเรื่องนี้ในมุมมองได้ไหม?
empty_form
) ซึ่งฉันขอบคุณ
ฉันโพสต์ตัวอย่างจากแอปที่ฉันทำงานไปสักพัก คล้ายกับของเปาโล แต่ยังอนุญาตให้คุณลบแบบฟอร์ม
ข้อเสนอแนะของเปาโลทำงานได้อย่างสวยงามด้วยหนึ่งคำเตือน - ปุ่มย้อนกลับ / ไปข้างหน้าของเบราว์เซอร์
องค์ประกอบแบบไดนามิกที่สร้างด้วยสคริปต์ของ Paolo จะไม่ถูกแสดงผลหากผู้ใช้กลับสู่ formset โดยใช้ปุ่มย้อนกลับ / ไปข้างหน้า ปัญหาที่อาจเป็นตัวทำลายข้อตกลงสำหรับบางคน
ตัวอย่าง:
1) ผู้ใช้เพิ่มสองรูปแบบใหม่ให้กับ formset โดยใช้ปุ่ม "เพิ่มมากขึ้น"
2) ผู้ใช้เติมแบบฟอร์มและส่งแบบฟอร์ม
3) ผู้ใช้คลิกปุ่มย้อนกลับในเบราว์เซอร์
4) ตอนนี้ชุดรูปแบบจะลดลงเป็นรูปแบบเดิมทุกรูปแบบที่เพิ่มแบบไดนามิกไม่ได้มี
นี่ไม่ใช่ข้อบกพร่องของสคริปต์ของเปาโลเลย แต่ความจริงของชีวิตด้วยการจัดการ Dom และแคชของเบราว์เซอร์
ฉันคิดว่าหนึ่งสามารถเก็บค่าของแบบฟอร์มในเซสชั่นและมีอาแจ็กซ์มายากลบางอย่างเมื่อ formset โหลดเพื่อสร้างองค์ประกอบอีกครั้งและโหลดค่าจากเซสชั่น; แต่ขึ้นอยู่กับว่าคุณต้องการมีส่วนร่วมกับผู้ใช้เดียวกันและหลายอินสแตนซ์ของแบบฟอร์มนี้อาจซับซ้อนมาก
ใครมีข้อเสนอแนะที่ดีสำหรับการจัดการกับเรื่องนี้?
ขอบคุณ!
ลองดูวิธีแก้ปัญหาต่อไปนี้กับรูปแบบ 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 สำหรับสิ่งนี้ฉันใช้กับ inline_form set ใน Django 1.3 และทำงานได้อย่างสมบูรณ์แบบรวมถึงการเพิ่มการเติมการเพิ่มแบบฟอร์มฝั่งไคลเอ็นต์การลบและหลาย inline_formsets
ทางเลือกหนึ่งที่จะสร้าง formset ที่มีรูปแบบที่เป็นไปได้ทุกครั้ง แต่แรกตั้งในรูปแบบที่ไม่จำเป็นที่จะซ่อนอยู่ - 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 ได้ตระหนักถึงแบบฟอร์มที่คุณเพิ่ม (ชุมชนโปรดให้คำแนะนำกับฉันหากข้อความบางส่วนของฉันไม่ได้อยู่ที่นี่ฉันใหม่กับ Django)
@Paolo Bergantino
เพื่อโคลนตัวจัดการทั้งหมดที่แนบมาเพียงแค่ปรับเปลี่ยนสาย
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เป็นเท็จและปิดการใช้งานการทำซ้ำของ id และชื่อ เนื่องจากชื่ออินพุตจะต้องไม่ซ้ำกันในแบบฟอร์มการระบุทั้งหมดจึงถูกทำขึ้นด้วยชื่อเหล่านั้นและไม่ใช่รหัส นอกจากนี้คุณยังต้องเปลี่ยนform
, type
และภาชนะของ formset ที่ (ในตัวอย่างด้านบนchoices
)