angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

A textarea mit einem Zeichenzähler widget für Flask, WTForms und Bootstrap

Das Hinzufügen eines WTForms textarea widget sieht einfach aus, aber die Unterschiede zwischen Linux und Windows verursachen unerwartete Probleme.

15 Februar 2020
post main image
https://unsplash.com/@creativegangsters

Ich hatte gehofft, Ihnen heute sagen zu können, dass Sie jetzt die Blog-Posts dieser Website kommentieren können. Das hätte bedeutet, dass ich die erste Implementierung des Kommentarsystems abgeschlossen hätte. Leider bin ich über einige Probleme gestolpert, ja natürlich, ich bin Programmierer, und eines davon betraf den TextAreaField.

Ich wollte nur eine einfache erweiterte Version des WTFormulars TextAreaField, fügen Sie einfach ein Zeichenzählerfeld unter dem textarea hinzu, und das war's. Ich dachte, das würde ein paar Stunden dauern, aber ich habe mich total geirrt, aber ich habe das irgendwie gelöst und dachte, ich könnte Ihnen das mitteilen. Lassen Sie mich Ihre Gedanken wissen ... hmmm ... wenn die Kommentare online sind ... :-)

Ich verwende Bootstrap und jQuery. Für jQuery wurde die textarea mit den restlichen Zeichen mehrfach dokumentiert. Viele Lösungen schlagen vor, dem Beispiel von Twitter zu folgen. Das bedeutet, dass Sie den vollständigen Text auch dann anzeigen, wenn die Anzahl der Zeichen die zulässige Anzahl übersteigt. Wenn die Anzahl der Zeichen die zulässige Anzahl überschreitet, zeigen wir die Anzahl der Zeichen in der Farbe Rot an. Kein Problem, ich werde es umsetzen.

Angabe der maximalen Anzahl von Zeichen an einer Stelle

Ich möchte, dass der widget universell ist, also keine fest programmierten Werte enthält. jQuery wird verwendet, um die tatsächliche Anzahl von Zeichen zu zählen, aber woher weiß jQuery , was die maximal zulässige Anzahl ist? Wir können Konstanten definieren und diese überall verwenden, aber es ist flexibler, zusätzliche HTML5-Datenattribute für die textarea zu implementieren:

  • data-char-count-min
  • Daten-Chart-Zahl-Max

Wir fügen ein zusätzliches Element hinzu, das die verbleibende Anzahl von Zeichen unterhalb des textarea-Codes anzeigt. Die Datenattribute können durch den jQuery -Code referenziert und zur Berechnung der Anzahl der verbleibenden Zeichen verwendet werden.

Umsetzung

Auch hier sehen wir uns zunächst den Code WTForms für den TextAreaField an. Siehe auch einen früheren Beitrag. Ich habe diesen Code kopiert und in diesen modifiziert:

class TextAreaWithCounterWidget(object):
    """
    Renders a multi-line text area.

    `rows` and `cols` ought to be passed as keyword args when rendering.
    """
    def __init__(self):
        pass

    def __call__(self, field, **kwargs):
        fname = 'TextAreaWithCounterWidget - __call__'

         kwargs.setdefault('id', field.id)
        if 'required' not in  kwargs  and 'required' in getattr(field, 'flags', []):
             kwargs['required'] = True

        return  HTMLString('<textarea  %s>%s</textarea>' % (
            html_params(name=field.name, **kwargs),
            escape(text_type(field._value()), quote=False)
        )  +  '<span class="" id="'  +  field.id  +  '-char-count-num'  +  '"></span>' )


class TextAreaWithCounterField(StringField):
    """
    This field represents an  HTML  ``<textarea>`` and can be used to take
    multi-line input.
    """
     widget  = TextAreaWithCounterWidget()

Dann benutze ich sie wie folgt:

def strip_whitespace(s):
    if isinstance(s, str):
        s = s.strip()
    return s

class ContentItemCommentForm(FlaskForm):

    message = TextAreaWithCounterField(_l('Your message'), 
        render_kw={'data-char-count-min': 0, 'data-char-count-max': 1000, 'rows': 4},
        validators=[ InputRequired(), Length(min=6, max=1000) ],
        filters=[ strip_whitespace ] )

    submit = SubmitField(_l('Add'))

Manchmal sind die Dinge einfacher als erwartet. Wir können unsere neuen Parameter einfach mit render_kw in das Formular einfügen. Dann werden sie in der widget als Attribute an die textarea übergeben. Wenn wir wollen, können wir auf diese Parameter auch in der widget mit kwargs zugreifen. Wir können sie als:

    data_char_count_max =  kwargs['data-char-count-max']

Wir können sie auch aus kwargs herausnehmen, also lesen und entfernen. Dann erscheinen sie nicht als Attribute im textarea:

    char_count_max =  kwargs.pop('char_count_max',  None)

Aber es gibt keinen Grund, dies hier zu verwenden. Innerhalb des widget können wir auch auf die Werte des Längen-Validators zugreifen, aber auch hier ist dies nicht notwendig.

Ich habe auch die Anzahl der Zeilen in render_kw angegeben. Diese wird an das Feld textarea übergeben. Das generierte HTML wird mit dem zusätzlichen Element angehängt, das die verbleibende Anzahl von Zeichen anzeigt. Die ID dieses Elements wird aus der ID textarea konstruiert:

    field.id  +  '-char-count-num'

Der Filter strip_whitespace wird aufgerufen, um den vorderen und hinteren Weißraum zu beschneiden.

Der Code jQuery ist nicht so schwierig. Ich verwende Bootstrap, die Klassen ändern die Farbe und setzen die Füllung:

function update_character_count(textarea_id){

	var char_count_num_id =  textarea_id  +  '-char-count-num';

	if( ($("#"  +   textarea_id).length == 0) || ($("#"  +  char_count_num_id).length == 0) ){
		// must exist
		return;
	}

	var char_count_min = parseInt( $('#'  +   textarea_id).data('char-count-min'), 10 );
	var char_count_max = parseInt( $('#'  +   textarea_id).data('char-count-max'), 10 );

	var remaining = char_count_max - $('#'  +   textarea_id).val().length;
	$('#'  +  char_count_num_id).html( ''  +  remaining );

	if(remaining >= 0){
		$('#'  +  char_count_num_id).removeClass('text-danger');
		$('#'  +  char_count_num_id).addClass('pl-2 text-secondary');
	}else{
		$('#'  +  char_count_num_id).removeClass('text-secondary');
		$('#'  +  char_count_num_id).addClass('pl-2 text-danger');
	}
}

und:

$(document).ready(function(){
	...
	// comment form: character count
	update_character_count('comment_form-message');
	$('#comment_form-message').on('change input paste', function(event){
		update_character_count('comment_form-message');
	});
}

Prüfung und Nichtübereinstimmung bei der Zeichenanzahl

Jetzt können wir mit dem Testen des neuen TextAreaWithCounterField beginnen. Alles sah gut aus, bis ich anfing, neue Linien zu betreten. Die Länge wurde von jQuery korrekt angegeben, was bedeutet, dass sie als ein einziges Zeichen gezählt wurden. Aber der WTForms -Validator sagte, dass die maximale Länge überschritten wurde. Noch einmal zur Fehlerbehebung: In der widget habe ich die von WTForms empfangenen Zeichen gedruckt:

    message = field.data
    if message is  None:
         current_app.logger.debug(fname  +  ': message is  None')
    else:
         current_app.logger.debug(fname  +  ': message = {}, len(message) = {}, list(message) = {}'.format(message, len(message), list(message)))

Dies brachte mir folgendes Ergebnis:

    len(message) = 4, list(message) = ['a', '\r', '\n', 'b']

jQuery zählt \r\n als ein einzelnes Zeichen, aber WTForms zählt es als zwei Zeichen. Wenn wir uns in den Code WTForms , validators.py, einarbeiten, sehen wir, dass er die Len-Funktion Python zur Bestimmung der Länge verwendet:

    l = field.data and len(field.data) or 0

Verständlich, aber in diesem Fall ist es falsch! Was ist zu tun? Ich wollte die Standardvalidierungsfunktion WTForms nicht außer Kraft setzen. Glücklicherweise haben wir das Filterfeld, das vor der (!) Validierung aufgerufen wird. Ich habe bereits strip_whitespace verwendet und einen neuen Filter hinzugefügt:

def compress_newline(s):
    if isinstance(s, str):
        s = s.replace("\r\n", "\n")
        s = re.sub(r'\n+', '\n', s)
    return s

Dieser Filter ersetzt \r\n durch einen einzelnen \n. Außerdem ersetzt es mehrere \n -Zeichen durch ein einziges \n. Letzteres ist nur ein sehr primitiver Schutz gegen unvermeidlich falsche (und verrückte) Unterwerfungen. Die Filterlinie wird dann:

    filters=[ strip_whitespace, compress_newline ] )

Jetzt funktionierte es wie beabsichtigt.

Zusammenfassung

Die Verwendung von render_kw ist eine einfache Möglichkeit, Parameter an den widget zu übergeben. Ich habe im Internet nach dem Problem \r\n gesucht, konnte aber keine Hinweise finden. Bitte sagen Sie mir nicht, dass ich der Einzige bin. Das Problem wird durch eine Diskrepanz zwischen den Systemen Windows und Linux verursacht. Außerdem benötigt die Kodierung von Javascript/jQuery Zeit, wenn Sie dies nicht ständig tun.

Links / Impressum

How to specify rows and columns of a <textarea > tag using wtforms
https://stackoverflow.com/questions/4930747/how-to-specify-rows-and-columns-of-a-textarea-tag-using-wtforms

New line in text area
https://stackoverflow.com/questions/8627902/new-line-in-text-area

Using data attributes
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes

WTForms - Fields
https://wtforms.readthedocs.io/en/stable/fields.html

WTForms - Widgets
https://wtforms.readthedocs.io/en/stable/widgets.html

Mehr erfahren

Flask WTForms

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare (1)

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.

avatar

Thanks for sharing! I wrote long comment but token has expired since I was reading the other tabs and I lost my comment :/. In short - great article and real demo, which I can see here, while typing this one again.