Ajout dynamique d'une forme à un formset Django avec Ajax
je veux ajouter automatiquement de nouveaux formulaires à un formset Django en utilisant Ajax, de sorte que lorsque l'utilisateur clique sur un bouton" Ajouter " il exécute JavaScript qui ajoute un nouveau formulaire (qui fait partie du formset) à la page.
15 réponses
C'est comme ça que je le fais, en utilisant jQuery :
mon modèle:
<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>
dans un fichier 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);
}
Ce qu'il fait:
cloneMore
accepte selector
comme premier argument, et le type
de formset comme deuxième. Ce que le selector
devrait faire, c'est lui passer ce qu'il devrait dupliquer. Dans ce cas, je passe div.table:last
pour que jQuery cherche la dernière table avec une classe de table
. La partie :last
est importante parce que le selector
est également utilisé pour déterminer ce que la nouvelle forme sera insérée après. Il est plus que probable que vous le vouliez à la fin du reste des formulaires. L'argument type
permet de mettre à jour le champ management_form
, notamment TOTAL_FORMS
, ainsi que les champs de formulaires actuels. Si vous avez un formset rempli de, disons, Client
modèles, les champs de gestion auront ID de id_clients-TOTAL_FORMS
et id_clients-INITIAL_FORMS
, tandis que les champs de formulaire seront dans un format de id_clients-N-fieldname
avec N
étant le numéro de formulaire, en commençant par 0
. Ainsi avec l'argument type
la fonction cloneMore
regarde combien de formes il y a actuellement, et passe en revue chaque entrée et étiquette à l'intérieur de la nouvelle forme remplaçant tous les noms de champ/ids de quelque chose comme id_clients-(N)-name
à id_clients-(N+1)-name
et ainsi de suite. Une fois terminé, il met à jour le champ TOTAL_FORMS
pour tenir compte de la nouvelle forme et l'ajoute à la fin de la série.
cette fonction est particulièrement utile pour moi parce que la façon dont il est configuré, il me permet de l'utiliser tout au long de l'application quand je veux fournir plus de formulaires dans un formset, et ne me fait pas besoin d'avoir un formulaire caché" modèle " pour dupliquer aussi longtemps que je lui passe le nom de formset et le format dans lequel les formulaires sont disposés. Espérons que cela aide.
Version Simplifiée de la réponse de Paolo en utilisant empty_form
comme modèle.
<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>
j'ai posté un extrait d'une application sur laquelle j'ai travaillé il y a quelques temps. Similaire à Paolo, mais permet également de supprimer des formulaires.
la suggestion de Paolo fonctionne à merveille avec une mise en garde - les boutons de retour/avant du navigateur.
les éléments dynamiques créés avec le script de Paolo ne seront pas rendus si l'utilisateur retourne au formset en utilisant le bouton back/forward. Une question qui peut être une rupture pour certains.
exemple:
1) l'utilisateur ajoute deux nouveaux formulaires au jeu de formulaires en utilisant le bouton" Ajouter-plus
2) l'utilisateur forme et soumet le jeu de formulaires
3) l'utilisateur clique sur le bouton de retour du navigateur
4) Formset est maintenant réduit à la forme originale, tous les formulaires dynamiquement ajoutés ne sont pas là
ce n'est pas un défaut du tout avec le script de Paolo, mais un fait de la vie avec la manipulation de dom et la cache du navigateur.
je suppose que l'on pourrait stocker les valeurs de la forme dans la session et avoir un peu de magie ajax lorsque le formset charge pour créer les éléments à nouveau et recharger les valeurs de la session; mais en fonction de la façon dont anal vous voulez être sur le même utilisateur et les instances multiples du formulaire, cela peut devenir très compliqué.
Quelqu'un a-t-il une bonne suggestion à faire?
Merci!
découvrez les solutions suivantes aux formes dynamiques de django:
http://code.google.com/p/django-dynamic-formset /
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
ils utilisent tous les deux jQuery et sont spécifiques à django. Le premier semble un peu plus poli et offre un téléchargement qui vient w/démos qui sont excellents.
simuler et imiter:
- créer un jeu de formulaires correspondant à la situation avant en cliquant sur le bouton" Ajouter".
- charger la page, voir la source et prendre note de tous les champs
<input>
. - modifier le jeu de formulaires pour correspondre à la situation après en cliquant sur le bouton "Ajouter" (changer le nombre de champs supplémentaires).
- chargez le page, voir la source et noter comment les champs
<input>
ont changé. - crée du JavaScript qui modifie le DOM d'une manière appropriée pour le déplacer de l'état avant à l'état après .
- joindre ce JavaScript au bouton" Ajouter".
bien que je sache que les forms utilisent des champs spéciaux cachés <input>
et savent approximativement ce que le script doit faire, je ne me souviens pas des détails sur le dessus de ma tête. Ce que j'ai décrit ci-dessus est ce que je ferais dans votre situation.
il y a un plugin jquery pour ce , Je l'ai utilisé avec inline_form set dans Django 1.3, et il fonctionne parfaitement, y compris la pré-population, le côté client Forme ajoutant, supprimer, et plusieurs inline_formsets.
une option serait de créer un formset avec toutes les formes possibles, mais dans un premier temps, définissez les formes non requises à hidden - ie, display: none;
. Quand il est nécessaire d'afficher un formulaire, définissez son affichage css à block
ou ce qui est approprié.
sans plus de détails sur ce que fait votre" Ajax", il est difficile de donner une réponse plus détaillée.
une autre version de cloneMore, qui permet une désinfection sélective des champs. Utilisez-le lorsque vous avez besoin d'empêcher plusieurs champs d'être effacés.
$('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);
}
il y a un petit problème avec la fonction cloneMore. Comme il nettoie également la valeur des champs cachés générés automatiquement par django, il provoque django à se plaindre si vous essayez de sauvegarder un formset avec plus d'un formulaire vide.
voici un fix:
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);
}
je pense que c'est une bien meilleure solution.
comment feriez-vous un formset dynamique à Django?
"151900920 Fait les choses clone n'est pas:- Ajouter un formulaire lorsque aucune forme initiale existe
- Poignées de javascript dans le formulaire de mieux, par exemple django-ckeditor
- Conserver les données initiales
@Paolo Bergantino
pour cloner tous les maîtres-chiens attachés juste modifier la ligne
var newElement = $(selector).clone();
pour
var newElement = $(selector).clone(true);
pour prévenir ce problème.
Yea je recommande aussi de les rendre simplement dans le html si vous avez un nombre limité d'entrées. (Si vous ne le faites pas, vous devrez utiliser une autre méthode).
vous pouvez les cacher comme ceci:
{% for form in spokenLanguageFormset %}
<fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">
alors le js est vraiment simple:
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();
};
};
}
parce que toutes les réponses ci-dessus utilisent jQuery et rendent certaines choses un peu complexes, j'ai écrit le script suivant:
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
}
};
vous devez d'abord définir auto_id à false et donc désactiver la duplication d'id et de nom. Comme les noms d'entrée doivent être uniques dans leur forme, toute identification se fait avec eux et non avec des id.
Vous devez également remplacer le form
, type
et le contenant du formset. (Dans l'exemple ci-dessus choices
)
pour les codeurs là-bas qui sont des ressources de chasse pour comprendre les solutions ci-dessus un peu mieux:
après avoir lu le lien ci-dessus, la documentation de Django et les solutions précédentes devraient avoir beaucoup plus de sens.
comme un résumé rapide de ce que je devenais confus par: le Le formulaire de gestion donne un aperçu des formulaires utilisés. Vous devez garder ces informations exactes pour que Django soit au courant des formulaires que vous ajoutez. (Communauté, merci de me donner des idées si certains de mes libellé est ici. Im nouveau à Django.)