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

Un textarea con un contador de caracteres widget para Flask, WTForms y Bootstrap

Añadir un WTForms textarea widget parece fácil pero las diferencias entre Linux y Windows causan problemas inesperados.

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

Esperaba decirles hoy que ahora pueden comentar las entradas del blog de este sitio web. Eso habría significado que yo completara la primera aplicación del sistema de comentarios. Desafortunadamente me tropecé con algunos problemas, sí por supuesto, soy un programador, y uno de ellos involucraba el TextAreaField.

Sólo quería una versión extendida simple del WTForm TextAreaField, sólo añadir un campo de contador de caracteres debajo del textarea y eso es todo. Pensé que esto tomaría unas pocas horas pero estaba totalmente equivocado, pero de alguna manera lo resolví y pensé en compartirlo contigo. Hazme saber lo que piensas... hmmm... cuando los comentarios estén en línea... :-)

Estoy usando Bootstrap y jQuery. Para jQuery el textarea con los caracteres restantes ha sido documentado muchas veces. Muchas soluciones sugieren seguir el ejemplo de Twitter. Esto significa que sigues mostrando el texto completo aunque el número de caracteres exceda el número permitido. Si el número de caracteres excede el número permitido, mostramos el número de caracteres en el color rojo. No hay problema, lo implementaré.

Especificando el número máximo de caracteres en un lugar

Quiero que el widget sea universal, por lo que no hay valores codificados en él. jQuery se utiliza para contar el número real de caracteres pero, ¿cómo sabe jQuery cuál es el máximo permitido? Podemos definir constantes y usarlas en todas partes, pero es más flexible implementar atributos de datos extra HTML5 para el textarea:

  • data-char-count-min
  • data-char-count-max

Añadimos un elemento extra, mostrando el número de caracteres restantes debajo del textarea. Los datos de los atributos pueden ser referenciados por el código jQuery y usados para calcular el número de caracteres restantes.

Implementación

De nuevo, primero miramos el código WTForms para el TextAreaField. Ver también un post anterior. Copié este código y lo modifiqué en esto:

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

Entonces lo uso de la siguiente manera:

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

A veces las cosas son más fáciles de lo esperado. Podemos simplemente añadir nuestros nuevos parámetros al formulario usando render_kw. Luego en el widget se pasan como atributos al textarea. Si queremos, también podemos acceder a estos parámetros en el widget usando kwargs. Podemos referirnos a ellos como:

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

También podemos hacerlos estallar desde kwargs, que significa leer y eliminar. Entonces no aparecerán como atributos en el textarea:

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

Pero no hay necesidad de usar esto aquí. Dentro del widget también podemos acceder a los valores del validador de longitud, pero de nuevo, no es necesario hacerlo aquí.

También especifiqué el número de filas en render_kw. Esto se pasa al campo textarea . El HTML generado se añade con el elemento extra que muestra el número de caracteres restantes. El id de este elemento se construye a partir del id de textarea :

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

La tira de filtro del espacio blanco es llamada para recortar el espacio blanco que va adelante y atrás.

El código jQuery no es tan difícil. Estoy usando Bootstrap, las clases cambian el color y establecen el relleno:

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

y:

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

Las pruebas y la falta de coincidencia en el recuento de caracteres

Ahora podemos empezar a probar el nuevo TextAreaWithCounterField. Todo parecía estar bien hasta que empecé a entrar en las nuevas líneas. La longitud fue reportada como correcta por jQuery , lo que significa que fueron contados como un solo carácter. Pero el validador WTForms dijo que la longitud máxima fue excedida. Una vez más, en el widget imprimí los caracteres recibidos por 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)))

Esto me dio el siguiente resultado:

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

jQuery cuenta \r\n como un solo carácter, pero WTForms lo cuenta como dos caracteres. Excavando en el código WTForms , validadores.py, vemos que utiliza la función len Python para determinar la longitud:

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

Es comprensible, pero en este caso está mal. ¿Qué hacer? No quería anular la función de validación WTForms por defecto. Afortunadamente tenemos el campo de filtros que se llaman antes (!) de la validación. Ya estaba usando el espacio en blanco y añadí un nuevo filtro:

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

Este filtro sustituye a \r\n por un solo \n. Además, reemplaza varios caracteres \n por un solo \n. Esta última es sólo una protección muy primitiva contra los inevitables envíos erróneos (y locos). La línea de filtros se convierte entonces:

    filters=[ strip_whitespace, compress_newline ] )

Ahora funcionó como estaba previsto.

Resumen

Usar render_kw es una forma fácil de pasar parámetros al widget. Busqué en Internet el problema de \r\n con el WTForms TextAreaField pero no pude encontrar ninguna referencia. Por favor, no me digas que soy el único. El problema es causado por un desajuste entre los sistemas Windows y Linux . Además, la codificación de Javascript/jQuery toma tiempo si no se hace todo el tiempo.

Enlaces / créditos

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

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios (1)

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.

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.