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

Een textarea met een tekenteller widget voor Flask, WTForms en Bootstrap

Het toevoegen van een WTForms textarea widget ziet er gemakkelijk uit, maar verschillen tussen Linux en Windows veroorzaken onverwachte problemen.

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

Ik hoopte u vandaag te kunnen vertellen dat u nu commentaar kunt geven op de blogberichten van deze website. Dat zou hebben betekend dat ik de eerste implementatie van het opmerkingensysteem heb afgerond. Helaas stuitte ik op een aantal problemen, ja natuurlijk, ik ben een programmeur, en een daarvan betrof de TextAreaField.

Ik wilde gewoon een eenvoudige uitgebreide versie van de WTForm TextAreaField, voeg gewoon een karaktertelerveld toe onder de textarea en dat is het. Ik dacht dat dit een paar uur zou duren, maar ik had het helemaal mis, maar ik heb dit op de een of andere manier opgelost en dacht om dit met u te delen. Laat me weten wat je denkt ... hmmm ... wanneer de opmerkingen online komen ... :-)

Ik gebruik Bootstrap en jQuery. Voor jQuery is de textarea met resterende karakters vele malen gedocumenteerd. Veel oplossingen stellen voor om het voorbeeld van Twitter te volgen. Dit betekent dat u de volledige tekst blijft tonen, zelfs als het aantal karakters het toegestane aantal overschrijdt. Als het aantal karakters groter is dan het toegestane aantal tonen we het aantal karakters in de kleur rood. Geen probleem, ik zal het uitvoeren.

Het opgeven van het maximum aantal tekens op één plaats

Ik wil dat de widget universeel is, dus geen hardgecodeerde waarden erin. jQuery wordt gebruikt om het werkelijke aantal karakters te tellen, maar hoe weet jQuery wat het maximaal toegestane aantal karakters is? We kunnen constanten definiëren en deze overal gebruiken, maar het is flexibeler om extra HTML5 data-attributen te implementeren voor de textarea:

  • gegevens-telling-min
  • data-chartcount-max

We voegen een extra element toe, dat het resterende aantal karakters onder de textarea laat zien. De data-attrbuten kunnen worden gerefereerd aan jQuery code en worden gebruikt om het aantal resterende karakters te berekenen.

Uitvoering

Ook hier kijken we eerst naar de WTForms code voor de TextAreaField. Zie ook een vorig bericht. Ik heb deze code gekopieerd en in deze gewijzigd:

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

Dan gebruik ik het als volgt:

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

Soms zijn de dingen gemakkelijker dan verwacht. We kunnen eenvoudigweg onze nieuwe parameters aan het formulier toevoegen met behulp van render_kw. Vervolgens worden ze in de widget als attributen doorgegeven aan de textarea. Als we willen kunnen we deze parameters ook benaderen in de widget met behulp van kwargs. We kunnen ze noemen:

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

We kunnen ze ook uit kwargs laten knallen, dat wil zeggen lezen en verwijderen. Dan verschijnen ze niet als attributen in de textarea:

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

Maar het is niet nodig om dit hier te gebruiken. Binnen de widget hebben we ook toegang tot de Lengte validator waarden, maar ook hier is dit niet nodig.

Ik heb ook het aantal rijen in render_kw opgegeven. Dit wordt doorgegeven aan het veld textarea . De gegenereerde HTML is toegevoegd met het extra element dat het resterende aantal karakters aangeeft. Het id van dit element is opgebouwd uit het id van textarea :

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

De filterstrip_witte ruimte wordt genoemd om de voorloop- en achterloopwitte ruimte te trimmen.

De code jQuery is niet zo moeilijk. Ik gebruik Bootstrap, de klassen veranderen de kleur en stellen de vulling in:

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

en:

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

Testen en mismatch in karaktertelling

Nu kunnen we beginnen met het testen van de nieuwe TextAreaWithCounterField. Alles zag er goed uit tot ik in nieuwe lijnen begon. De lengte werd correct gerapporteerd door jQuery wat betekent dat ze als één enkel karakter werden geteld. Maar de WTForms validator zei dat de maximale lengte werd overschreden. Debugging tijd weer, in de widget I afgedrukt de tekens ontvangen door WTForms:

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

Dit gaf me het volgende resultaat:

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

jQuery telt \r\n als één teken, maar WTForms telt het als twee tekens. Graven in WTForms code, validators.py, zien we dat het gebruik van de Python len functie om de lengte te bepalen:

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

Begrijpelijk, maar in dit geval is het verkeerd! Wat moet ik doen? Ik wilde de standaard WTForms validatiefunctie niet opheffen. Gelukkig hebben we het filterveld dat voor (!) validatie wordt aangeroepen. Ik gebruikte al strip_witte ruimte en voegde een nieuw filter toe:

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

Dit filter vervangt \r\n door een enkele \n. Bovendien vervangt het meerdere \n -tekens door een enkele \n. Dit laatste is slechts een zeer primitieve bescherming tegen onvermijdelijk verkeerd (en gek) indienen. De filterlijn wordt dan:

    filters=[ strip_whitespace, compress_newline ] )

Nu werkte het zoals bedoeld.

Samenvatting

Het gebruik van render_kw is een eenvoudige manier om parameters door te geven aan de widget. Ik heb op het internet gezocht naar het \r\n probleem met de WTForms TextAreaField maar kon geen referenties vinden. Zeg me alsjeblieft niet dat ik de enige ben. Het probleem wordt veroorzaakt door een mismatch tussen Windows en Linux systemen. Ook kost het coderen van Javascript/jQuery tijd als u dit niet altijd doet.

Links / credits

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

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

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.