framework - jquery ajax




Dynamisches Hinzufügen eines Formulars zu einem Django-Formset mit Ajax (10)

@ Paolo Bergantino

Um alle angehängten Handler zu klonen, ändern Sie einfach die Zeile

var newElement = $(selector).clone();

zum

var newElement = $(selector).clone(true);

um dieses Problem zu vermeiden .

Ich möchte automatisch neue Formulare zu einem Django-Formset mit Ajax hinzufügen, so dass, wenn der Benutzer auf einen "Hinzufügen" -Button klickt, JavaScript ausgeführt wird, das der Seite ein neues Formular (das Teil des Formsets ist) hinzufügt.


Da alle obigen Antworten jQuery verwenden und einige Dinge ein wenig komplex machen, habe ich folgendes Skript geschrieben:

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
    }
};

Zuerst sollten Sie auto_id auf false setzen und so die Duplizierung von ID und Name deaktivieren. Da die Eingabenamen in dieser Form eindeutig sein müssen, erfolgt die Identifikation mit ihnen und nicht mit den IDs. Sie müssen auch das form , den type und den Container des Formsets ersetzen. (Im Beispiel oben choices )


Eine weitere cloneMore-Version, die eine selektive Bereinigung von Feldern ermöglicht. Verwenden Sie es, wenn Sie verhindern möchten, dass mehrere Felder gelöscht werden.

$('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);
}

Es gibt ein jquery plugin dafür , ich habe es mit inline_form benutzt, das in Django 1.3 gesetzt wurde, und es funktioniert tadellos, einschließlich prepopulation, clientseitiges Formular Hinzufügen, Entfernen und mehrere inline_formsets.


Für die Programmierer, die Ressourcen jagen, um die oben genannten Lösungen ein wenig besser zu verstehen:

Dynamische Formsets von Django

Nach dem Lesen des obigen Links sollten die Django-Dokumentation und frühere Lösungen viel sinnvoller sein.

Django Formset-Dokumentation

Als kurze Zusammenfassung dessen, worüber ich verwirrt wurde: Das Management-Formular enthält eine Übersicht über die Formulare innerhalb. Sie müssen diese Informationen korrekt aufbewahren, damit Django die von Ihnen hinzugefügten Formulare kennt. (Community, bitte geben Sie mir Vorschläge, wenn einige meiner Formulierungen hier fehlen. Ich bin neu in Django.)


Ich denke, das ist eine viel bessere Lösung.

Wie würdest du ein dynamisches Formset in Django erstellen?

Klingt Sachen klonen nicht:

  • Fügen Sie Formular hinzu, wenn keine ersten Formulare vorhanden sind
  • Griffe Javascript in der Form besser, zum Beispiel django-ckeditor
  • Behalten Sie die Anfangsdaten bei

Ja, ich würde auch empfehlen, sie nur im HTML-Code auszugeben, wenn Sie eine endliche Anzahl von Einträgen haben. (Wenn Sie dies nicht tun, müssen Sie eine andere Methode verwenden).

Du kannst sie so verstecken:

{% for form in spokenLanguageFormset %}
    <fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">

Dann ist die js wirklich einfach:

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();
        };
    };
}

Paolos Vorschlag funktioniert wunderbar mit einem Vorbehalt - den Vor- / Zurück-Tasten des Browsers.

Die dynamischen Elemente, die mit Paolos Skript erstellt wurden, werden nicht gerendert, wenn der Benutzer mit der Zurück- / Vorwärts-Taste zum Formularsatz zurückkehrt. Ein Problem, das für einige ein Deal Breaker sein kann.

Beispiel:

1) Der Benutzer fügt dem Formularsatz zwei neue Formulare hinzu, indem er die Schaltfläche "add-more" verwendet

2) Benutzer füllt die Formulare aus und sendet das Formularset

3) Der Benutzer klickt auf den Zurück-Button im Browser

4) Formset ist jetzt auf die ursprüngliche Form reduziert, alle dynamisch hinzugefügten Formulare sind nicht da

Dies ist kein Mangel mit Paolos Skript überhaupt; aber eine Tatsache des Lebens mit Dom Manipulation und Browser-Cache.

Ich nehme an, man könnte die Werte des Formulars in der Sitzung speichern und etwas Ajax-Magie haben, wenn das Formularset geladen wird, um die Elemente erneut zu erstellen und die Werte aus der Sitzung neu zu laden; Je nachdem, wie anal Sie über den gleichen Benutzer und mehrere Instanzen des Formulars sein möchten, kann dies jedoch sehr kompliziert werden.

Hat jemand einen guten Vorschlag dafür?

Vielen Dank!


Simulieren und imitieren:

  • Erstellen Sie ein Formularset, das der Situation entspricht, bevor Sie auf die Schaltfläche "Hinzufügen" klicken.
  • Laden Sie die Seite, sehen Sie sich die Quelle an und notieren Sie alle <input> -Felder.
  • Passen Sie das Formularset an die Situation an, nachdem Sie auf die Schaltfläche "Hinzufügen" geklickt haben (ändern Sie die Anzahl der zusätzlichen Felder).
  • Laden Sie die Seite, sehen Sie sich die Quelle an und notieren Sie sich, wie sich die Felder <input> geändert haben.
  • Erstellen Sie JavaScript, das das DOM in geeigneter Weise ändert, um es vom Vorher- Zustand in den Nachher- Zustand zu verschieben.
  • Hängen Sie das JavaScript an die Schaltfläche "Hinzufügen" an.

Obwohl ich weiß, dass Formsets spezielle verborgene <input> -Felder verwenden und ungefähr wissen, was das Skript tun muss, kann ich mich nicht an die Details erinnern, die sich in meinem Kopf befinden. Was ich oben beschrieben habe, ist, was ich in Ihrer Situation tun würde.


So mache ich es mit jQuery :

Meine Vorlage:

<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>

In einer Javascript-Datei:

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);
}

Was es macht:

cloneMore akzeptiert selector als erstes Argument und den type des Formsets als zweites Argument. Was der selector tun sollte, ist, ihm zu übergeben, was er duplizieren soll. In diesem Fall übergebe ich div.table:last so dass jQuery nach der letzten Tabelle mit einer Klasse von table sucht. Der :last Teil ist wichtig, da der selector auch verwendet wird, um zu bestimmen, wohin das neue Formular eingefügt wird. Mehr als wahrscheinlich würden Sie es am Ende des Rests der Formulare wollen. Mit dem Argument type können wir das Feld management_form , insbesondere TOTAL_FORMS , sowie die tatsächlichen Formularfelder aktualisieren. Wenn Sie ein Formularset mit beispielsweise Client Modellen haben, haben die Verwaltungsfelder die IDs id_clients-TOTAL_FORMS und id_clients-INITIAL_FORMS , während die Formularfelder das Format id_clients-N-fieldname wobei N die Formularnummer ist. beginnend mit 0 . Mit dem Argument type cloneMore die cloneMore Funktion, wie viele Formulare derzeit vorhanden sind und geht durch jede Eingabe und Bezeichnung innerhalb des neuen Formulars, wobei alle Feldnamen / IDs von etwas wie id_clients-(N)-name bis id_clients-(N+1)-name und so weiter. Nachdem es beendet ist, aktualisiert es das TOTAL_FORMS Feld, um das neue Formular TOTAL_FORMS , und fügt es an dem Ende des Satzes hinzu.

Diese Funktion ist besonders hilfreich für mich, da die Art der Einrichtung es mir ermöglicht, sie in der gesamten App zu verwenden, wenn ich mehr Formulare in einem Formularsatz bereitstellen möchte, und ich brauche nicht ein verborgenes "Vorlagen" -Formular für die Duplizierung Solange ich den Formularsatznamen und das Format, in dem die Formulare angeordnet sind, übergeben. Ich hoffe es hilft.





django