Ajaxを使用してDjangoフォームセットに新しいフォームを自動的に追加し、ユーザーが「追加」ボタンをクリックすると、ページに新しいフォーム(フォームセットの一部)を追加する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
を受け入れ、2番目の引数としてformsetのtype
を受け入れます。 selector
がすべきことは、複製すべきものを渡すことです。この場合、jQueryがtable
のクラスを持つ最後のテーブルを探すように、div.table:last
を渡します。 selector
は、新しいフォームが挿入される内容を決定するためにも使用されるため、その:last
部分は重要です。おそらく、残りのフォームの最後にそれが必要になるでしょう。 type
引数は、management_form
フィールド、特にTOTAL_FORMS
フィールド、および実際のフォームフィールドを更新できるようにするためのものです。たとえばClient
モデルで満たされたフォームセットがある場合、管理フィールドのIDはid_clients-TOTAL_FORMS
およびid_clients-INITIAL_FORMS
になりますが、フォームフィールドはid_clients-N-fieldname
の形式になり、N
は0
で始まります。したがって、type
引数を使用すると、cloneMore
関数は現在のフォームの数を調べ、新しいフォーム内のすべての入力とラベルを調べて、すべてのフィールド名/ IDをid_clients-(N)-name
からid_clients-(N+1)-name
などに置き換えます。終了後、TOTAL_FORMS
フィールドを更新して新しいフォームを反映し、セットの最後に追加します。
この機能は、フォームセットでより多くのフォームを提供したいときにアプリ全体で使用できるようにセットアップされ、複製するための非表示の「テンプレート」フォームを必要としないため、特に役立ちます。フォームセット名とフォームがレイアウトされる形式を渡す限り。それが役に立てば幸い。
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>
スニペットを投稿 しばらく前に作業したアプリから。 Paoloに似ていますが、フォームを削除することもできます。
Paoloの提案は、ブラウザの「戻る/進む」ボタンという1つの注意事項でうまく機能します。
ユーザーが戻る/進むボタンを使用してフォームセットに戻った場合、Paoloのスクリプトで作成された動的要素はレンダリングされません。一部の人にとっては取引を妨げる可能性のある問題。
例:
1)ユーザーは「追加」ボタンを使用して、2つの新しいフォームをフォームセットに追加します
2)ユーザーがフォームに入力し、フォームセットを送信します
3)ユーザーがブラウザの戻るボタンをクリックする
4)Formsetは元のフォームに縮小され、動的に追加されたすべてのフォームはそこにありません
これは、Paoloのスクリプトの欠陥ではありません。しかし、dom操作とブラウザのキャッシュのある現実。
フォームの値をセッションに保存し、formsetがロードされて要素を再度作成し、セッションから値を再ロードするときに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を含めて完全に機能します。
フィールドの選択的なサニタイズを可能にする別の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);
}
1つのオプションは、可能なすべてのフォームでフォームセットを作成することですが、最初は不要なフォームを非表示に設定します(つまり、display: none;
)。フォームを表示する必要がある場合、CSSディスプレイをblock
または適切なものに設定します。
あなたの「Ajax」が何をしているかの詳細を知らなければ、より詳細な応答をすることは困難です。
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が初めてです。)
また、エントリの数が限られている場合は、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();
};
};
}
@パオロ・ベルガンティーノ
接続されているすべてのハンドラーを複製するには、行を変更するだけです
var newElement = $(selector).clone();
for
var newElement = $(selector).clone(true);
防止するために この問題
上記の答えはすべて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
、およびフォームセットのコンテナを置き換える必要があります。 (上記の例ではchoices
)