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

textarea со счетчиком символов widget для Flask, WTForms и Bootstrap

Добавление WTForms textarea widget выглядит просто, но различия между Linux и Windows вызывают неожиданные проблемы.

15 февраля 2020
post main image
https://unsplash.com/@creativegangsters

Я надеялся сказать вам сегодня, что вы можете прокомментировать записи в блоге этого сайта уже сейчас. Это означало бы, что я завершил первое внедрение системы комментариев. К сожалению, я наткнулся на некоторые проблемы, да, конечно, я программист, и одна из них связана с TextAreaField.

Мне просто нужна была простая расширенная версия WTForm TextAreaField, просто добавьте поле счетчика символов под textarea и все. Я думал, что это займет несколько часов, но был совершенно неправ, но я как-то решил эту проблему и подумал поделиться этим с тобой. Сообщите мне о своих мыслях... хм... когда комментарии появятся в сети... :-)

Я использую Bootstrap и jQuery. Для jQuery textarea с оставшимися символами был документирован много раз. Многие решения предлагают следовать примеру Twitter. Это означает, что вы продолжаете показывать полный текст, даже если количество символов превышает допустимое. Если количество символов превышает допустимое, то количество символов показывается красным цветом. Нет проблем, я выполню.

Указание максимального количества символов в одном месте

Я хочу, чтобы widget был универсальным, поэтому никаких жестко закодированных значений в нем нет. jQuery используется для подсчета фактического количества символов, но как jQuery узнает, что такое максимально допустимое количество? Мы можем определить константы и использовать их везде, но более гибко реализовывать дополнительные атрибуты данных HTML5 для textarea:

  • дата-чар-мин
  • data-char-count-max

Мы добавляем дополнительный элемент, показывающий оставшееся количество символов под кодом textarea. На data-attrbutes можно ссылаться кодом jQuery и использовать его для вычисления количества оставшихся символов.

Реализация

Снова мы смотрим на код WTForms для TextAreaField. См. также предыдущий пост. Я скопировал этот код и модифицировал его в этот:

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

Тогда я использую это следующим образом:

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

Иногда все намного проще, чем ожидалось. Мы можем просто добавить наши новые параметры в форму, используя render_kw. Затем в widget они передаются как атрибуты textarea. Если мы хотим, мы также можем получить доступ к этим параметрам в widget , используя kwargs. Мы можем назвать их:

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

Мы также можем вывести их из kwargs, имея в виду чтение и удаление. Тогда они не будут отображаться как атрибуты в textarea:

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

Но здесь нет необходимости использовать это. Внутри widget мы также можем получить доступ к значениям валидатора Length, но опять же, здесь в этом нет необходимости.

Я также указал количество строк в render_kw. Это передается в поле textarea . Сгенерированный HTML добавляется с дополнительным элементом, показывающим оставшееся количество символов. Ид этого элемента строится из идентификатора textarea :

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

Фильтрующая полоса_whitespace вызывается для обрезки ведущего и заднего белого пространства.

Код jQuery не так уж и сложен. Я использую Bootstrap, классы меняют цвет и устанавливают подкладку:

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

и..:

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

Тестирование и несовпадение по количеству символов

Теперь мы можем приступить к тестированию нового TextAreaWithCounterField. Все выглядело прекрасно, пока я не начал выходить на новые линии. Длина была сообщена правильно jQuery , что означает, что они считались как один символ. Но валидатор WTForms сказал, что максимальная длина превышена. Снова время отладки, в widget я распечатал символы, полученные 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)))

Это дало мне следующий результат:

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

jQuery считает \r\n как один символ, но WTForms считает как два символа. Войдя в код WTForms , validators.py, мы видим, что он использует функцию Python len для определения длины:

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

Понятно, но в данном случае это неправильно! Что делать? Я не хотел переопределять функцию проверки WTForms по умолчанию. К счастью, у нас есть поле фильтров, которое вызывается перед (!) проверкой. Я уже использовал strip_whitespace и добавил новый фильтр:

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

Данный фильтр заменяет \r\n на один \n. Кроме того, он заменяет несколько символов \n на один \n. Последнее является просто очень примитивной защитой от неизбежных неправильных (и сумасшедших) подает. Тогда линия фильтров становится:

    filters=[ strip_whitespace, compress_newline ] )

Теперь все сработало, как и планировалось.

Резюме

Использование render_kw является простым способом передачи параметров widget. Я искал в интернете проблему \r\n с WTForms TextAreaField , но не смог найти никаких ссылок. Пожалуйста, не говорите мне, что я единственный. Проблема вызвана несовпадением систем Windows и Linux . Кроме того, кодирование Javascript/jQuery требует времени, если вы не делаете этого постоянно.

Ссылки / кредиты

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

Подробнее

Flask WTForms

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии (1)

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.

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.