JavaScript: полезные события

Владимир | | JavaScript, Web разработка.

input event

Недавно мне нужно было написать обычный счетчик символов в текстовой области (textarea). Задача тривиальная, но меня заинтересовал один нюанс.

Вопрос в том, какое событие обрабатывать?

Пользователь может вводить текст как угодно, с помощью клавиатуры, вставлять из буфера обмена.
Если ввод происходит с помощью клавиатуры или из буфера комбинацией клавиш Ctrl+V, то можно обрабатывать событие onKeyup. Но если текст вставлен с помощью контекстного меню, то onKeyup не работает (действительно, ведь клавиатуру мы не трогаем). Точно также не имеет смысла обрабатывать события мыши. Ведь клик выполняется по контекстному меню, а обработчик мы назначаем для textarea.

Событие onChange тоже не подходит, т.к. оно появляется после того как textarea теряет фокус.

В результате получается, что отследить вставку данных через контекстное меню с помощью этих событий не получится.

Но я решил поискать. И прежде всего, отправился поэкспериментировать с формой twitter’а. Первый же эксперимент дал положительный результат. Вставка через контекстное меню изменила значение счетчика.

Сначала я подумал, что они решают задачу с помощью таймера и проверок длины поля через заданный интервал времени. Но потом я заметил, что эта функция работает только в FireFox и не работает в IE.

А чем отличается Firefox от IE? Конечно, поддержкой новых стандартов!

После этого я быстро нашел нужное событие. Оно называется input и определено в спецификации DOM Level 3 Load And Save.
Это событие возникает сразу после ввода любых символов в поле не зависимо от способа ввода.

Чтобы не оставаться голословным я привожу страницу, с которой экспериментировал.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <title>textArea</title>

    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <meta http-equiv="Content-Style-Type" content="text/css" />

</head>

<body>

<div><textarea id="messageText" cols="45" rows="15"></textarea></div> <div>Количество символов: <span id="symbolsCounter"></span></div> <script type="text/javascript" src="jquery-1.3.1.min.js"></script> <script type="text/javascript"> $(function() { var ta = $("#messageText"); var counter = $("#symbolsCounter"); counter.html(ta.val().length); if ($.browser.mozilla && $.browser.version.substr(0,3)=="1.9") { ta.bind("input", function() { updateCounter(); }); } else { ta.bind("keyup", function() { updateCounter(); }); } function updateCounter() { counter.html(ta.val().length); var split = ta.val().split("\n"); if (split.length > 15) { ta.attr("rows", split.length); } } }); </script> </body> </html>

На странице размещены текстовая область (строка 16) и блок для вывода количества символов (строка 18).

Наибольший интерес представляет JS код. Посмотрим, как он работает.

Прежде всего, проверяем какой браузер используется. Если это FireFox последних версий, назначаем для текстовой области обработчик события input (строки 26-29), если нет – будем обрабатывать keyup (строки 31-34).

При возникновении этих событий вызывается функция updateCounter, в которой мы определяем и показываем количество символов в текстовой области (строка 38).

Остальной код в функции updateCounter испольлзуется для изменения высоты текстовой области, если введенный текст превышает её размер. Алгоритм тут простой.

Разбиваем текст с помощью функции split на лексемы (строка 39), а в качестве разделителя используем символ конца строки. В результате получаем массив с количеством элементов равным количеству строк. Если это количество превысит дефолтное значение (15) – изменяем размер текстовой области (строки 40-42).

Как видите, в новых стандартах есть интересные и, самое главное, полезные нововведения.

Жаль, что разработчики браузеров от них сильно отстают.

Интересно почитать

Реклама в блогах от студии SMOpro —
наиболее эффективный способ продвижения вашего ресурса.

  • а почему не повесить updateCounter() на setTimeout?
    конечно будет бегать в холостую зато у осла ничего не пропустит

    • Наверное вы имели ввиду на setInterval()?

      Насчет того что onChange действует только при блюре не знал, юзал только на селектах потому-то.

    • Страница может висеть во вкладках целый день и не правильно, что на ней будет постоянно выполняться скрипт (хоть и маленький).

      • Serator

        Набрел на заметку в поисках того, когда события input появится в IE. Но к комментарию хотелось бы заметить, что для ie можно таймер (setTimeout на себя зацикленный) вешать по событию focus и сносить по событию blur.

        • Правильнее вопрос будет звучать так: «Когда пользователи перейдут на ту версию IE, которая будет поддерживать input?» 🙂

        • Serator

          (на комментарий чуть ниже)
          Искал информацию про ie9 и input, но нашел. Есть информация?

        • Точно не знаю. Думаю, до релиза ещё многое успеют поменять (или доделать).

  • а почему не повесить updateCounter() на setTimeout?
    конечно будет бегать в холостую зато у осла ничего не пропустит

    • Наверное вы имели ввиду на setInterval()?

      Насчет того что onChange действует только при блюре не знал, юзал только на селектах потому-то.

    • Страница может висеть во вкладках целый день и не правильно, что на ней будет постоянно выполняться скрипт (хоть и маленький).

      • Serator

        Набрел на заметку в поисках того, когда события input появится в IE. Но к комментарию хотелось бы заметить, что для ie можно таймер (setTimeout на себя зацикленный) вешать по событию focus и сносить по событию blur.

        • Правильнее вопрос будет звучать так: «Когда пользователи перейдут на ту версию IE, которая будет поддерживать input?» 🙂

        • Serator

          (на комментарий чуть ниже)
          Искал информацию про ie9 и input, но нашел. Есть информация?

        • Точно не знаю. Думаю, до релиза ещё многое успеют поменять (или доделать).

  • Понравилось как на ютубе сделали:

    oninput="updateCharCount()"
    onpaste="updateCharCount()"
    onkeyup="updateCharCount()"

  • Понравилось как на ютубе сделали:

    oninput="updateCharCount()"
    onpaste="updateCharCount()"
    onkeyup="updateCharCount()"

  • при чем здесь красиво, главное работает все

  • при чем здесь красиво, главное работает все

  • >Понравилось как на ютубе сделали
    ну и правильно сделали, испоьзовать новые стандарты пока не представляеться возможным…

  • >Понравилось как на ютубе сделали
    ну и правильно сделали, испоьзовать новые стандарты пока не представляеться возможным…

  • ka3yc

    почему-то у меня событие paste срабатывает перед вставкой (т.е. изменения текста еще не было). поэтому, видимо, буду делать так:

    elem.bind('keyup', updateCharCount);

    elem.bind('input', updateCharCount);

    elem.bind('paste', function() { setTimeout(updateCharCount, 100); });

  • Если я не ошибаюсь, довольно распространенное решение (я имею ввиду использование таймера).

  • Капец какой-то, все равно сделать универсальный вариант не прокатывает, я уже всё добавил, что только в голову пришло, а все равно нет отслеживания «на лету»… Видимо, нужно так или иначе на счетчик вешать по времени…

    Не знаю, правильно это или дико, но в поля написал действия такие: oninput, onpaste, onKeyUp, onKeyDown и onChange, но если кликнуть на подсказку браузера (под полем появляется, когда выполняется действие onfocus), то проверки нет ни в одном браузере. Нет никаких идей как это обойти? или «плюнуть» на это? — в любом случае, когда фокус поля теряется, проверка есть, но хотелось бы именно «на лету».

    • А как вы вводите символы если убрали фокус из поля?

      В любом случае, подсчет символов, функция вспомогательно.
      По-моему, лучше всего не заниматься 100% поддержкой всех браузеров (если, конечно, этого не требует заказчик), а сделать еще одну проверку перед отправкой данных. Тогда даже если пользователь каким-то образом вставит текст и счетчик не сработает, то при попытке этот текст отправить он увидит правильные показания счетчика.

      И, естественно, такую же проверку нужно сделать на сервере.

      • Уточню как происходит: кликаем в пустое поле, браузер из кеша предлагает на выбор символы, введенные ранее (для мыла — почту, для пароля — набор символов пароля). Если выбрать подсказку браузера, то форма не проверяется «на лету», а только в случае выбранного действия, которые я перечислил выше, например, ончейндж — смена фокуса или ввод клавиши. Кроссбраузерность не преследую, на сервере дополнительно обрабатываю. Интересует пока только клиентская сторона. Но судя по всему нельзя это никак обойти, уже все перепробовал.

        • Я все-таки до конца идею не понял.
          Пользователь выбирает подсказку, например, email. Фокус ввода из этого
          поля уйдет. Но в поле окажется выбранный email, зачем его проверят?
          Если же пользователь захочет изменить email, то он должен будет
          переместить фокус ввода в него, и проверки снова заработают.

        • По началу так и делал — форма реагировала на фокусы и т.д., сейчас все сделал «на лету», т.е. все проверяется при любом «движении» и сразу же правильно подставляется… сейчас автоподстановку телефона еще добавил — поле автоматом меняется на нужное, если ошибка или очищается, если исправить нельзя — гораздо удобней, чем смена фокуса и т.д. — лишние парки ввода, а так «горячая» замена всего. Если нужно, могу последней версией поделиться (бесплатно, конечно), но над ней еще поработать нужно чуток…

          П.С. Иногда в подсказках левые варианты бывают — это же просто кеш браузера… Я один раз ошибся почтой, теперь в Мозиле этот косяк, поэтому поле проверяю досканально.

          Спасибо за Ваши примеры, Ваши топики мне частенько помогают, как не гуглю, а все дороги у вас сходятся 🙂

        • Спасибо 🙂
          Правда у меня все дороги ведут за пределы рунета 🙂

          Идею я понял, правда сам бы остановился на варианте со сменой фокуса. В 90% случаев это проблему решает. К тому же, проверку можно повторить перед отправкой запроса, т.е. пользователь свои ошибки все-равно увидит.

          Но советовать не рискну, думаю, Вы свои задачи знаете лучше.

  • Neznayka

    для этого случая в последних IE и Fox работает onPaste а в Opere oninput. Маленькая странность в том что onpaste срабатывает до вставки. Решается так: obj.onpaste = function(){setTimeout(myHandler, 0)}

    • Это не странность. onPaste должно возникать при вставке, а не после изменения содержимого поля.

      Пока идеальным решением остается событие input, т.к. оно разработано именно для отслеживания содержимого поля.

      • Neznayka

        Насчет onPaste согласен, так даже удобней. А на счет input недопонял… он же не работает в IE и FireFox, или я ошибаюсь?

        • Я имел ввиду «идеальным» с точки зрения разработки, а не поддержки браузерами.