FieldList dynamisch füllen

Django, Flask, Bottle, WSGI, CGI…
Antworten
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Hallo

Ich möchte eine wtforms FieldList mit nur einer Reihe im Template und dann dynamisch weitere gleiche Reihen hinzufügen (über jQuery). Wie mache ich das? Ich schaffe nur, via min_entries 7 Reihen anzuzeigen.

Im form:

Code: Alles auswählen

openinghours = FieldList(FormField(OpeningHour), min_entries=7, max_entries=15)
Im template:

Code: Alles auswählen

  	{% for entry in form.openinghours %}
	       	<div class="row">
				<div class="input-field col s3 m2">
					{{ render_inputfield(entry.day)}}
			    </div>
			    <div class="input-field col s3 m2 l1">
					{{ render_inputfield(entry.opening, class_="time") }}
			    </div>
				<div class="input-field col s3 m2 l1">
					{{ render_inputfield(entry.closing, class_="time") }}
			    </div>
			   	<div class="input-field col s4 m3 l2">
					{{ render_inputfield(entry.closed) }}
			    </div>
	        </div>
  	{% endfor %}
  			<div class="row">
  				<div class="input-field col s3 m2">
  					<a class="btn-floating waves-effect waves-light" id="add_button"><i class="material-icons">add</i></a>
  				</div>
  			</div>
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@meego: wo hast Du Deinen JavaScript-Code der mit dem add-Button verbunden ist?
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Hallo Sirius. Hier:

Code: Alles auswählen

<script>
var $html_string = $('.row').text("bla bla bla");

$("add_button").on('click', function() {
	$('#openinghours').('.row').append($html_string);
});
</script>
Obwohl das noch ziemlich provisorisch ist und wohl auch nicht funktioniert (jQuery ist Neuland für mich).
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@meego: das macht noch nicht viel Sinn. Wenn Du die Grundlagen von JavaScript drauf hast, solltest Du mal einen Blick in die jQuery-Dokumentation werfen.
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Was mache ich falsch?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@meego: $html_string ist kein html_string sondern eine jQuery-Elementesammlung, die Elemente enthält, die Du sicher nicht meinst. add_button ist eine ID und kein Tag. Ein String als Attribut für zu einem SyntaxError. Variablen die mit einem $ anfangen sind in JavaScript eher speziell. das hier ist kein PHP.
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Ja, stimmt. Aber eine solche soll es auch sein. Ich glaube, jQuery Variablen werden aus Konventionsgründen mit einem $ begonnen, um sie von normalen JavaScript Variablen zu unterscheiden. Also müsste ich einfach das .html attribut verwenden?
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Ich glaube, jQuery Variablen werden aus Konventionsgründen mit einem $ begonnen, um sie von normalen JavaScript Variablen zu unterscheiden
Nein, das ist komplett falsch. jQuery ist auch nur JavaScript - sowas wie "jQuery" Variablen gibt's nicht. Also doch lieber nochmal in Ruhe die Doku lesen ;-) Bzw. bei Wikipedia (https://de.wikipedia.org/wiki/JQuery) ist es auch kurz erklärt.

Gruß, noisefloor
BlackJack

So komplett falsch ist das nicht, ich habe das schon öfter gesehen das Namen für jQuery-Datentypen ein $ vorangestellt wird, weil man nicht selten gleiche oder sehr ähnliche Namen für die ”GUI”-Elemente und für die Daten hat. Beispielsweise ein `row`-Array und ein `$row` für das jQuery-Objekt das die Zeile in der HTML-Tabelle repräsentiert in das die Daten aus `row` als <td>s eingefügt werden sollen.
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Eben Konvention: Link

Interessieren täte mich aber eher, ob ich den For Loop für die FieldList brauche, wenn ich nur das erste Element anzeigen will.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok, komplett falsch ist falsch ;-) Hatte das eben ein bisschen anders verstanden...

@meego: kannst du mal bitte alle relevanten Wtforms Klassen für das Beispiel posten - ich habe gerade noch ein Problem nachzuvollziehen, was die eigentlich vor hast. Nur ohne die `OpeningHour` Klasse ist das schwierig...

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich werkel gerade an einer privaten Rezepteverwaltung und - auch motiviert durch den Thread hier - bin ich das Thema "dynamische Formularerweiterung" auch mal angegangen.

Folgender Prototyp funktioniert:

Server:

Code: Alles auswählen

from bottle import route, run, template, debug, static_file, request
from my_forms2 import InputForm2

@route('/test')
def index():
    form = InputForm2()
    return template('dyn_form_in_wt2.html', form=form)

@route('/test', method='POST')
def post_data():
    form_data =  request.forms
    form = InputForm2(form_data)
    for k, v in form_data.items():
        print(k, v)
    if not form.validate():
            print(form.errors)
    else:
        print('Form is valid')
    return 'Done'

@route('/static/<filename>')
def server_static(filename):
    return static_file(filename, root='/home/noisefloor/code/bottle/dyn_form/static')

debug=True
run(host='localhost', port=8080, reloader=True)
Formular:

Code: Alles auswählen

from wtforms.form import Form
from wtforms.fields import StringField, SelectField, DecimalField, FormField, FieldList
from wtforms.validators import NumberRange, InputRequired

UNITS = [('kg', 'kg'),
         ('g', 'g'),
         ('TL', 'TL'),
         ('EL', 'EL'),
         ('St', 'St')] 

class InputForm(Form):
    description = StringField('description',
                              [InputRequired()])                                  
    unit = SelectField('unit', choices=UNITS)
    quantity = DecimalField('quantity',
                            [InputRequired(), NumberRange(min=0)],
                            places=3)
                            
class InputForm2(Form):
    ingredients = FieldList(FormField(InputForm), min_entries=1)
Template:

Code: Alles auswählen

<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Forms</title>
    <script src="static/jquery-2.2.0.min.js"></script>
</head>
<body>
<form action="/test" method="POST">
    <button class="add_field_button">Mehr Felder...</button>
    <div class="input_fields_wrap">
    <!-- here goes the form fields -->
    </div>
    <input type="submit">
</form>
<script>
$(document).ready(function() {
    var max_fields      = 10; //maximum input boxes allowed
    var wrapper         = $(".input_fields_wrap"); //Fields wrapper
    var add_button      = $(".add_field_button"); //Add button ID
    var html_string = '{{ !form.ingredients }}<a href="#" class="remove_field">Remove</a>';
    var x = -1; //initlal text box count
    $(wrapper).append(make_string());
    $(add_button).click(function(e){ //on add input button click
        e.preventDefault();
        if(x < max_fields){ //max input box allowed
            x++; //text box increment
            $(wrapper).append(make_string());
        }
    });
   
    $(wrapper).on("click",".remove_field", function(e){ //user click on remove text
        e.preventDefault(); $(this).parent('div').remove(); x--;
    });
    function make_string() {
        var my_string = html_string;
        var output = ''
        output = my_string.replace(0, x+1, "g");
        return output;
    };
});
</script>
</body>
</html>
Das JS ist ein angepasstes Beispiel aus dem Web - das ist nicht alles auf meinem Mist gewachsen.

Vielleicht nützt es ja was für das ähnliche, hier beschriebene Problem :-)

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Poste ich doch gerne für unseren Hobbykoch:

Code: Alles auswählen

class OpeningHour(Form):
	day = SelectField(_('Wochentag'), [DataRequired()], 
		choices=[(0, _('Montag')), (1, _('Dienstag')), (2, _('Mittwoch')), (3, _('Donnerstag')), (4, _('Freitag')), (5, _('Samstag')), (6, _('Sonntag'))], coerce=int)
	opening = StringField(_('von'))
	closing = StringField(_('bis'))
	closed = BooleanField(_('Geschlossen'))


class EnterBusiness(Form):
	name = StringField(_('Business Name'), [DataRequired()])
	street = StringField(_('Strasse'), [DataRequired()])
	zipcode = StringField(_('Postleitzahl'), [DataRequired()])
	city = StringField(_('Stadt'), [DataRequired()])
	country = QuerySelectFieldWithChooseOption(_('Land'), query_factory=lambda: Country.query, get_label=lambda x: x.name)
	description = TextAreaField(_('Beschreibung'))
	phone = StringField(_('Telefon'), [DataRequired()])
	website = StringField(_('Website'), [DataRequired()])

	openinghours = FieldList(FormField(OpeningHour), min_entries=1, max_entries=21)
Neben den üblichen Daten soll man also dynamisch noch Öffnungszeiten zuschalten und eingeben können.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die Dynmaik kannst du ja von oben übernehmen, das Prinzip ändert sich ja nicht.

Ich würde das mit den Öffnungszeiten aber anders machen, nämlich je ein Select-Feld von 0-23 für die Stunden und eins für die Minuten von 0-59. 1) eliminierst du so "Freestyle"-Eingaben, die beim String-Feld möglich sind, 2) brauchst du den Eingabe-String nicht zerlegen, um daraus Zahlen für die DB zu machen.
Und ob man ein explizites "geschlossen" braucht, ist Ansichtssache. In dem Fall würde ich es eher implizit machen -> kein Zeiten für einen Tag in der DB -> geschlossen.

Gruß, noisefloor
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Hi

Das ist ganz schön viel Javascript, ich glaube mit jQuery müsste es einfacher gehen.

Ich verwende einen jQueryTimepicker, kann man zwar überschreiben, aber in der Regel wird das keiner machen (vielleicht kann man das Überschreiben sogar irgendwie verhindern). Keine Ahnung, wie man das dann in ein db.Column(db.Time) kriegt. Aber jedes Problem zu seiner Zeit. :mrgreen:
meego
User
Beiträge: 380
Registriert: Montag 4. März 2013, 14:36

Bei deinem Javascript meldet sich bei mir übrigens der Debugger:
jinja2.exceptions.TemplateSyntaxError: unexpected char '!' at 7296
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Das ist ganz schön viel Javascript, ich glaube mit jQuery müsste es einfacher gehen.
Die Hauptarbeit macht doch jQuery. Lediglich das notwendige durchnummerieren der Namen der Formfelder ist plattes JS ohne Query.
jinja2.exceptions.TemplateSyntaxError: unexpected char '!' at 7296
Dann hast du mit Sicherheit den Übertrag von Bottle nach Flask falsch gemacht. in Bottle steht das `!` dafür, dass der nachfolgende String im Template _nicht_ escaped wird.

Was anders: du hast eine überschaubare endliche Zahl (nämlich 15) an Einträgen, von denen die 7 direkt angezeigt haben willst.
Warum generierst du nicht direkt die komplette Form, blendest die Elemente 8-15 aus und bei Bedarf nacheinander dynamisch ein (z.B. per jQuery). hat den Vorteil, dass die dich nicht um die korrekte manuelle Durchnummerierung des `name`Attributes des Form kümmern musst.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Nachtrag bzw. Update: Das 1. Version des JavaScript funktioniert zwar grundsätzlich, hat aber diverse Stolperfallen wie nicht-fortlaufendes Hochzählen der Felder, wenn man ein Feld löscht oder das man auch alle Felder löschen kann.

Diese Version ist besser - weil sie die o.g. Macken nicht mehr hat:

Code: Alles auswählen

<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Forms</title>
    <script src="static/jquery-2.2.0.min.js"></script>
</head>
<body>
<form action="/ingredients" method="POST">
    <div class="input_fields_wrap">
    <!-- here goes the form fields -->
    </div>
    <button class="add_field_button">Add field</button>
    <button class="remove_field_button">Remove field</button>
    <input type="submit">
</form>
<script>
$(document).ready(function() {
    var max_fields = 20; //maximum input boxes allowed
    var wrapper = $(".input_fields_wrap"); //Fields wrapper
    var addButton = $(".add_field_button"); //Add button ID
    var removeButton = $(".remove_field_button"); //Add button ID
    var htmlString = '<div id="input_fields_0">{{ !form.ingredients }}</div>';
    var field_counter = 0;
    $(wrapper).append(makeString()); //add the first form
    $(addButton).click(function(e){ //on add input button click
        e.preventDefault();
        if(field_counter < max_fields){ //max input box allowed
            field_counter++;
            $(wrapper).append(makeString());}
        else { window.alert('max number of ingredients reached!')}
    });
    $(removeButton).click(function(e){ //on remove input button click
        e.preventDefault();
        if(field_counter > 0){ //make sure at least one field is there
            $('#input_fields_'+field_counter).remove();
            field_counter--;}
        else { window.alert('Cannot delete, one input has to remain.')}
    });
    function makeString() {
        var myString = htmlString;
        return myString.replace(/0/g, field_counter);
    };
});
</script>
</body>
</html>
Gruß, noisefloor
Antworten