17. Обработка событий

blankКлиентские программы на языке JavaScript основаны на модели программирования, когда выполнение программы управляется событиями (представленной в разделе 13.3.2). При таком стиле программирования веб-браузер генерирует событие, когда с документом или некоторым его элементом что-то происходит. Например, веб-браузер генерирует событие, когда завершает загрузку документа, когда пользователь наводит указатель мыши на гиперссылку или нажимает клавишу на клавиатуре. Если JavaScript-приложение интересует определенный тип события для определенного элемента документа, оно может зарегистрировать одну или более функций, которая будет вызываться при возникновении этого события. Имейте в виду, что это не является уникальной особенностью веб-программирования: все приложения с графическим интерфейсом пользователя действуют именно таким образом – они постоянно ожидают, пока что-то произойдет (т. е. ждут появления событий), и откликаются на происходящее.

Клиентские программы на языке JavaScript основаны на модели программирования, когда выполнение программы управляется событиями (представленной в разделе 13.3.2). При таком стиле программирования веб-браузер генерирует событие, когда с документом или некоторым его элементом что-то происходит. Например, веб-браузер генерирует событие, когда завершает загрузку документа, когда пользователь наводит указатель мыши на гиперссылку или нажимает клавишу на клавиатуре. Если JavaScript-приложение интересует определенный тип события для определенного элемента документа, оно может зарегистрировать одну или более функций, которая будет вызываться при возникновении этого события. Имейте в виду, что это не является уникальной особенностью веб-программирования: все приложения с графическим интерфейсом пользователя действуют именно таким образом – они постоянно ожидают, пока что-то произойдет (т. е. ждут появления событий), и откликаются на происходящее.

Обратите внимание, что слово событие не является техническим термином, требующим строгого определения. События - это просто некоторые происшествия, о которых браузер извещает программу. События не являются объектами языка JavaScript и никак не описываются в программном коде программы. Однако существует множество объектов, имеющих отношение к событиям, использующихся в программном коде, и эти объекты требуют дополнительного технического описания. Поэтому мы начнем эту главу с некоторых важных определений.

Тип события – это строка, определяющая тип происшествия. Тип «mousemove», например, означает, что пользователь переместил указатель мыши. Тип «keydown» означает, что была нажата клавиша на клавиатуре. А тип «load» означает, что завершилась загрузка документа (или какого-то другого ресурса) из сети. Поскольку тип события – это просто строка, его иногда называют именем события. И действительно, мы будем использовать эти имена для идентификации типов событий, о которых будет идти речь. Современные веб-браузеры поддерживают множество типов событий, краткий обзор которых приводится в разделе 17.1.

Цель события – это объект, в котором возникло событие или с которым это событие связано. Когда говорят о событии, обычно упоминают тип и цель события. Например: событие «load» объекта Window или событие «click» элемента <button>. Самыми типичными целями событий в клиентских приложениях на языке JavaScript являются объекты Window, Document и Element, но некоторые типы событий могут происходить и в других типах объектов. Например, в главе 18 мы познакомимся с событием «readystatechange», которое возбуждается объектом XMLHttpRequest.

Обработчик события, или приемник события, – это функция, которая обрабатывает, или откликается на событие (Некоторые источники, включая спецификацию HTML5, различают обработчики событий и приемники событий, в соответствии со способом, которым они были зарегистрированы. В этой книге мы будем использовать эти два термина как синонимы ). Приложения должны зарегистрировать свои функции обработчиков событий в веб-браузере, указав тип события и цель. Когда в указанном целевом объекте возникнет событие указанного типа, браузер вызовет обработчик. Когда обработчики событий вызываются для какого-то объекта, мы иногда говорим, что браузер «возбудил», «сгенерировал» или «доставил» событие. Существует несколько способов регистрации обработчиков событий, описание которых приводится в разделах 17.2 и 17.3.

Объект события – это объект, связанный с определенным событием и содержащий информацию об этом событии. Объекты событий передаются функции обработчика события в виде аргумента (кроме IE8 и более ранних версий, где объект события доступен только в виде глобальной переменной event). Все объекты событий имеют свойство type, определяющее тип события, и свойство target, определяющее цель события. (В IE8 в более ранних версиях вместо свойства target следует использовать свойство srcElement.) Для каждого типа события в связанном объекте события определяется набор свойств. Например, объект, связанный с событиями от мыши, включает координаты указателя мыши, а объект, связанный с событиями от клавиатуры, содержит информацию о нажатой клавише и о нажатых клавишах-модификаторах. Для многих типов событий определяются только стандартные свойства, такие как type и target, и не передается никакой дополнительной полезной информации. Для таких типов событий важно само наличие происшествия события, и никакая другая информация не имеет значения. В этой главе нет отдельного раздела, посвященного объекту Event. Вместо этого здесь описываются свойства объектов событий для событий определенных типов. Дополнительные сведения об объектах событий вы найдете в справочной статье Event в четвертой части книги (Стандарты определяют целую иерархию объектов событий для событий различных типов. Интерфейс Event описывает «простые» события, которые не сопровождаются дополнительной информацией. Например, подкласс MouseEvent описывает дополнительные поля, доступные в объектах событий, передаваемых при возбуждении события от мыши, а подкласс KeyEvent описывает поля, которые можно использовать при обработке событий от клавиатуры. Описания всех этих классов в данной книге помещены в общую справочную статью Event ).

Распространение события – это процесс, в ходе которого браузер решает, в каких объектах следует вызвать обработчики событий. В случае событий, предназначенных для единственного объекта (таких как событие «load» объекта Window), надобность в их распространении отсутствует. Однако, когда некоторое событие возникает в элементе документа, оно распространяется, или «всплывает», вверх по дереву документа. Бели пользователь щелкнет мышью на гиперссылке, событие «mousemove» сначала будет возбуждено в элементе <a>, определяющем эту ссылку. Затем оно будет доставлено вмещающим элементам: возможно, элементу <p>, элементу <div> и самому объекту Document. Иногда удобнее бывает зарегистрировать единственный обработчик события в объекте Document или в другом контейнерном элементе, чем выполнять регистрацию во всех интересующих нас элементах. Обработчик события может прервать дальнейшее распространение события, чтобы оно прекратило всплытие и не привело к вызову обработчиков вмещающих элементов. Делается это вызовом метода или установкой свойства объекта события. Детально распространение событий рассматривается в разделе 17.3.6.

Бще одна форма распространения событий, которая называется перехватом события, позволяет специально зарегистрированным обработчикам или контейнерным элементам «перехватывать» события до того, как они достигнут фактической цели. Перехват событий не поддерживается в IE8 и в более ранних версиях и поэтому редко используется на практике. Однако возможность перехватывать события от мыши совершенно необходима при обработке событий буксировки объектов мышью; как это делается, будет показано в примере 17.2.

Для некоторых событий предусматриваются связанные с ними действия по умолчанию. Например, для события «click», возникающего в гиперссылке, по умолчанию предусматривается операция перехода по ссылке и загрузки новой страницы. Обработчики могут предотвратить выполнение действий по умолчанию, вернув соответствующее значение, вызвав метод или установив свойство объекта события. Это иногда называют «отменой» события; подробнее эта тема рассматривается в разделе 17.3.7.

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

После представления типов событий в следующих двух разделах описывается, как регистрировать обработчики событий и как браузер вызывает эти обработчики событий. Из-за особенностей развития модели событий в JavaScript и из-за отсутствия поддержки стандартной модели в IE8 и в более ранних версиях обе эти темы являются более сложными, чем можно было бы ожидать. Завершится эта глава серией примеров, демонстрирующих, как правильно обрабатывать некоторые типы событий. В этих разделах рассматриваются:

содержание 17.1. Типы событий содержание

На заре развития Всемирной паутины веб-разработчикам приходилось иметь дело лишь с небольшим количеством событий: «load», «click», «mouseover» и другими. Эти довольно старые типы событий хорошо поддерживаются всеми браузерами и рассматриваются в разделе 17.1.1. По мере развития веб-платформы в нее были включены более мощные прикладные интерфейсы, а количество событий существенно увеличилось. Не существует стандарта, который определял бы полный набор событий, и к моменту написания этих строк количество поддерживаемых событий продолжает быстро увеличиваться. Эти новые события определяются в следующих трех источниках:

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

содержание 17.1.1. Старые типы событий

События, которые вам чаще всего придется использовать в своих веб-приложениях, обычно будут относиться к категории давно существующих и поддерживаемых всеми браузерами: это события для работы с мышью, с клавиатурой, с HTML-формами и с объектом Window. В следующих разделах описывается множество важных особенностей событий этих типов.

содержание 17.1.1.1. События форм

Формы и гиперссылки стали первыми элементами веб-страниц, возможность управления которыми была реализована в начале развития Всемирной паутины и JavaScript. Это означает, что события форм являются наиболее устойчивыми и хорошо поддерживаемыми из всех типов событий. Элементы <form> возбуждают события «submit» при отправке формы и «reset» перед сбросом формы в исходное состояние. Элементы форм, внешним видом напоминающие кнопки (включая радиокнопки и флажки), возбуждают события «click», когда пользователь, взаимодействуют с ними. Элементы форм, позволяющие вводить некоторую информацию, обычно возбуждают события «change», когда пользователь изменяет их состояние, вводя текст, выбирая элемент списка или отмечая флажок. Для текстовых элементов ввода событие «change» не возбуждается, пока пользователь не завершит взаимодействие с ними и не передаст фокус ввода другому элементу. Элементы форм откликаются на изменение фокуса ввода, возбуждая события «focus» и «blur» при получении и утере фокуса ввода.

Все эти события, связанные с формами, подробно рассматривались в разделе 15.9.3. Тем не менее, здесь следует сделать несколько важных дополнений.

Категории событий

События могут быть сгруппированы в некоторые обобщенные категории, знание которых поможет вам понять и использовать события из длинного перечня, приведенного ниже:

Аппаратно-зависимые события ввода

События из этой категории непосредственно связаны с конкретными устройствами ввода, такими как мышь или клавиатура. В эту категорию входят такие старые события, как «mousedown», «mousemove», «mouseup», «keydown», «keypress» и «keyup», а также новые события, имеющие отношение к сенсорным устройствам, такие как «touchmove» и «gesturechange».

Аппаратно-независимые события ввода

Эти события ввода не связаны непосредственно с каким-либо определенным устройством. Например, событие «click» указывает на то, что была активирована ссылка или кнопка (или другой элемент документа). Это событие часто порождается щелчком мыши, но его причиной также может быть нажатие клавиши на клавиатуре или (для сенсорных устройств) некоторое перемещение по экрану. Событие «textinput» (которое пока реализовано не во всех браузерах) является аппаратно-независимой альтернативой событию «keypress» и поддерживает не только ввод с клавиатуры, но и такие альтернативы, как вставка из буфера обмена или ввод рукописного текста.

События пользовательского интерфейса

События ПИ (пользовательского интерфейса) – это высокоуровневые события, которые часто возникают в элементах HTML-форм, составляющих пользовательский интерфейс веб-приложения. В эту категорию входит событие «focus» (возникающее, когда текстовое поле получает фокус ввода), событие «change» (возникающее, когда пользователь изменяет значение, отображаемое элементом формы) и событие «submit» (возникающее, когда пользователь щелкает на кнопке отправки формы).

События изменения состояния

Некоторые события не связаны непосредственно с деятельностью пользователя, но имеют отношение к выполнению сетевых операций браузером и указывают на переход к другому этапу операции или на изменение состояния. Событие «load», которое возбуждается в объекте Window по окончании загрузки документа, является, пожалуй, наиболее часто используемым типом событий из этой категории. Еще одним представителем из этой категории является событие «DOMContentLoaded» (обсуждалось в разделе 13.3.4). Механизм управления историей посещений, определяемый стандартом HTML5 (раздел 22.2), возбуждает событие «popstate» в ответ на нажатие клавиши Back (Назад) браузера. Прикладной интерфейс автономных веб-приложений, описываемый стандартом HTML5 (раздел 20.4), включает события «online» и «offline». В главе 18 демонстрируется, как пользоваться событием «readystatechange», сообщающем о получении данных с сервера. Аналогично новый API чтения локальных файлов, выбранных пользователем (раздел 22.6.5), использует события, такие как «loadstart», «progress» и «loadend», для отправки асинхронных извещений о ходе выполнения операций ввода-вывода.

Прикладные события

Некоторые прикладные интерфейсы, определяемые стандартом HTML5 и связанными с ним спецификациями, включают собственные типы событий. Интерфейс drag-and-drop (раздел 17.7) определет такие события, как «dragstart», «dragenter», «dragover» и «drop». Приложения, обеспечивающие поддержку буксировки элементов мышью, должны реализовывать обработку некоторых из этих событий. Элементы <video> и <audio> (раздел 21.2), определяемые стандартом HTML5, добавляют длинный список связанных с ними типов событий, таких как «waiting», «playing», «seeking», «volumechange» и т.д. Эти события обычно представляют интерес только для веб-приложений, определяющих собственные элементы управления проигрыванием аудио- и видеороликов.

Обработчики ошибок и событий от таймеров

Обработчики ошибок и событий от таймеров (были описаны в главе 14) являются частью асинхронной модели программирования в клиентском JavaScript и похожи на обработчики обычных событий. Несмотря на то, что обработчики ошибок и событий от таймеров не рассматриваются в этой главе, их очень удобно воспринимать как обработчики обычных событий, и, возможно, вам будет интересно прочитать разделы 14.1 и 14.6 еще раз с позиций, предлагаемых этой главой.

Для событий «submit» и «reset» предусматриваются действия по умолчанию, выполнение которых можно отменить в обработчиках событий, как и в случае некоторых событий «click». Все события форм всплывают, кроме событий «focus» и «blur». IE определяет события «focusin» и «focusout», которые являются всплывающими альтернативами событий «focus» и «blur». Библиотека jQuery (глава 19) имитирует события «focusin» и «focusout» в браузерах, не поддерживающих их. Кроме того, эти события были стандартизованы спецификацией «DOM Level 3 Events».

Наконец, имейте в виду, что все браузеры, кроме IE, возбуждают событие «input» в элементах <textarea> и в других текстовых элементах ввода, когда пользователь вводит текст (посредством клавиатуры или вставкой из буфера обмена) в элемент. В отличие от события «change», данное событие «input» возбуждается при каждой вставке. К сожалению, объект события, соответствующий событию «input», не позволяет узнать, какой текст был введен (более полезной альтернативой этому событию является новое событие «textinput», описываемое ниже).

содержание17.1.1.2. События объекта Window

События объекта Window представляют происшествия, имеющие отношение к самому окну браузера, а не к определенному содержимому документа, отображаемому в окне (однако некоторые из этих событий имеют имена, совпадающие с именами событий, возбуждаемых для элементов документа).

Самым важным из этих событий является событие «load»: оно возбуждается сразу после того, как будут загружены и отображены документ и все внешние ресурсы (такие, как изображения). Событие «load» обсуждалось на протяжении главы 13. Альтернативами событию «load» являются события «DOMContentLoaded» и «readystatechange»: они возбуждаются сразу же, как только документ и его элементы будут готовы к выполнению операций, но до того, как полностью будут загружены внешние ресурсы. Примеры использования этих событий, имеющих отношение к документу, приводятся в разделе 17.4 .

Событие «unload» является противоположностью событию «load»: оно возбуждается, когда пользователь покидает документ. Обработчик события «unload» можно использовать, чтобы сохранить информацию о состоянии, но в нем нельзя отменить переход на другую страницу. Событие «beforeunload» похоже на событие «unload», но оно дает возможность узнать у пользователя, действительно ли он желает покинуть вашу веб-страницу. Если обработчик события «beforeunload» вернет строку, эта строка будет выведена в диалоге подтверждения перед тем, как будет загружена новая страница; этот диалог даст пользователю возможность отменить переход и остаться на текущей странице.

Свойство oneerror объекта Window является своего рода обработчиком событий, который вызывается в случае появления ошибок в программном коде на языке JavaScript. Однако это не настоящий обработчик событий, потому что он вызывается совсем с другим набором аргументов. Подробности смотрите в разделе 14.6.

Имеется также возможность регистрировать обработчики событий «load» и «error» для отдельных элементов документа, таких как <img>. Эти события возбуждаются, когда внешний ресурс (например, изображение) будет полностью загружен или когда возникнет ошибка, препятствующая его загрузке. Некоторые браузеры поддерживают также событие «abort» (стандартизованное спецификацией HTML5), которое возбуждается, когда загрузка изображения (или другой ресурс, загружаемый из сети) прерывается из-за того, что пользователь остановил процесс загрузки.

События «focus» и «blur», описанные выше вместе с другими событиями элементов форм, также поддерживаются объектом Window: они возбуждаются, когда текущее окно браузера получает или теряет фокус ввода.

Наконец, события «resize» и «scroll» возбуждаются в объекте Window, когда выполняется изменение размеров или прокрутка окна браузера. События «scroll» могут также возбуждать все прокручиваемые элементы документа, например те, что имеют CSS-свойство overflow (раздел 16.2.6). Объект события, передаваемый обработчикам событий «resize» и «scroll», - это самый обычный объект Event, не имеющий свойств, которые позволяли бы узнать новый размер окна или величину прокрутки. Чтобы узнать новый размер окна или позиции полос прокрутки, следует использовать приемы, продемонстрированные в разделе 15.8.

содержание17.1.1.3. События мыши

События от мыши возбуждаются, когда пользователь перемещает указатель мыши или выполняет щелчок. Эти события генерируются в наиболее глубоко вложенных элементах, над которыми находится указатель мыши, но они всплывают вверх по дереву документа. Объект события, передаваемый обработчикам событий от мыши, имеет свойства, позволяющие узнать координаты указателя, состояние кнопок мыши, а также состояние клавиш-модификаторов на момент возникновения события. Свойства clientX и clientY определяют положение указателя мыши в системе координат окна. Свойства button и which позволяют узнать, какая кнопка была нажата. (Обязательно ознакомьтесь со справочной статьей Event в четвертой части книги, чтобы знать, как организовать работу с этими свойствами переносимым способом.) Свойства altKey, ctrlKey, metaKey и shiftKey получают значение true, если в момент возникновения события удерживалась нажатой соответствующая клавиша-модификатор. А свойство detail для события «click» указывает, был ли выполнен одинарный, двойной или тройной щелчок.

Событие «mousemove» генерируется всякий раз, когда пользователь перемещает указатель мыши. Это событие возбуждается очень часто, поэтому его обработчики не должны выполнять тяжелые вычисления. События «mousedown» и «mouseup» генерируются, когда пользователь нажимает и отпускает кнопку мыши. Зарегистрировав обработчик события «mousedown», который регистрирует обработчик события «mousemove», можно организовать определение и обработку ситуаций буксировки элементов мышью. При этом необходимо обеспечить возможность перехватывать события от мыши, чтобы продолжать получать события «mousemove», даже когда указатель мыши выходит за пределы элемента, где была начата буксировка. В разделе 17.5 приводится пример реализации буксировки элементов мышью.

После последовательности событий «mousedown» и «mouseup» браузер генерирует событие «click». Событие «click» было описано выше, как аппаратно-независимое событие форм, но на самом деле оно может генерироваться для любого элемента документа, а не только для элементов форм, и вместе с ним обработчику передается объект события со всеми дополнительными полями, описанными выше. Если вы дважды щелкнете мышью на строке (в течение достаточно короткого промежутка времени), второй щелчок сгенерирует событий «dblclick». Когда щелчок выполняется правой кнопкой мыши, браузеры часто выводят контекстное меню. Вообще, прежде чем вывести контекстное меню, они возбуждают событие «contextmenu», и, если отменить это событие в обработчике, можно предотвратить появление меню. Кроме того, это наиболее простой способ организовать обработку щелчка правой кнопкой мыши.

Когда пользователь перемещает указатель мыши так, что он оказывается над другим элементом, браузер возбуждает событие «mouseover» для этого элемента. Когда указатель мыши покидает границы элемента, браузер генерирует для него событие «mouseout». Объект события для этих событий будет иметь свойство relatedTarget, определяющее другой элемент, вовлеченный в переход. (Эквивалент свойства relatedTarget в IE вы найдете в справочной статье Event.) События «mouseover» и «mouseout» всплывают, как и все остальные описанные здесь события от мыши. Иногда это оказывается неудобно, потому что в обработчике события «mouseout» приходится проверять, действительно ли указатель мыши покинул данный элемент или он просто переместился из одного дочернего элемента в другой. По этой причине в IE поддерживается невсплывающие версии этих событий, известные как «mouseenter» и «mouseleave». Библиотека jQuery эмулирует поддержку этих событий в браузерах, отличных от IE (глава 19), а кроме того, эти события стандартизованы в спецификации «DOM Level 3 Events».

Когда пользователь вращает колесико мыши, браузеры генерируют событие «mou- sewheel» (или в Firefox событие «DOMMouseScroll»). Объект события, передаваемый вместе с этими событиями, включает свойства, позволяющие узнать, на какое расстояние и в каком направлении было повернуто колесико. Спецификация «DOM Level 3 Events» стандартизует более универсальное многомерное событие от колесика, которое, если будет реализовано, заменит оба события, «mousewheel» и «DOMMouseScroll». Пример обработки события «mousewheel» приводится в разделе 17.6.

содержание17.1.1.4. События клавиатуры

Когда веб-браузер получает фокус ввода, он начинает генерировать события всякий раз, когда пользователь нажимает и отпускает клавиши на клавиатуре. Нажатия горячих комбинаций, имеющих значение для операционной системы или самого браузера, часто «съедаются» операционной системой или браузером и не передаются обработчикам событий на JavaScript. События от клавиатуры генерируются в любом элементе документа, обладающем фокусом ввода, и всплывают вверх до объектов документа и окна. Если ни один элемент не обладает фокусом ввода, события возбуждаются непосредственно в объекте документа. Обработчикам событий от клавиатуры передается объект события, имеющий свойство keyCode, позволяющее узнать, какая клавиша была нажата или отпущена. В дополнение к свойству keyCode объект события от клавиатуры также имеет свойства altKey, ctrlKey, metaKey и shiftKey, описывающие состояние клавиш-модификаторов.

События «keydown» и «keyup» являются низкоуровневыми событиями от клавиатуры: они генерируются, когда производится нажатие или отпускание клавиши (даже если это клавиша-модификатор). Когда событие «keydown» генерируется нажатием клавиши, соответствующей печатаемому символу, после события «key- down», но перед событием «keyup» дополнительно генерируется событие «keypress». (В случае если клавиша удерживается в нажатом состоянии настолько долго, что начинается автоповтор символа, перед событием «keyup» будет сгенерировано множество событий «keypress».) Событие «keypress» является высокоуровневым событием ввода текста и соответствующий ему объект события содержит информацию о введенном символе, а не о нажатой клавише.

События «keydown», «keyup» и «keypress» поддерживаются всеми браузерами, однако существуют некоторые проблемы совместимости из-за того, что не были стандартизованы значения свойства keyCode объекта события. Спецификация «DOM Level 3 Events», описываемая ниже, пытается решить эти проблемы совместимости, но эти решения пока не реализованы. Пример обработки событий «keydown» приводится в разделе 17.9, а в разделе 17.8 приводится пример обработки событий «keypress».

содержание 17.1.2. События модели DOM

Спецификация «DOM Level 3 Events» разрабатывалась консорциумом W3C около десяти лет. К моменту написания этих строк она была подвергнута существенному пересмотру с целью привести в соответствие с текущими реалиями и наконец достигла стадии стандартизации «последней версии рабочего проекта». Она стандартизует многие из старых событий, описанных выше, и добавляет несколько новых событий, описываемых здесь. Эти новые типы событий пока не получили широкой поддержки, но производители браузеров предполагают реализовать их к моменту окончательного утверждения стандарта.

Как отмечалось выше, спецификация «DOM Level 3 Events» стандартизует события «focusin» и «focusout» как всплывающие альтернативы событий «focus» и «blur», а также события «mouseenter» и «mouseleave» - как невсплывающие альтернативы событий «mouseover» и «mouseout». Кроме того, эта версия стандарта не рекомендует использовать некоторые типы событий, которые были определены спецификацией «DOM Level 2 Events», но никогда не были реализованы. браузеры по-прежнему обеспечивают поддержку событий, таких как «DOMActivate», «DOMFocusIn» и «DOMNodelnserted», но теперь это необязательно, и потому данные события не описываются в этой книге. (Единственным часто используемым событием, в имени которого присутствует приставка «DOM», является событие «DOMContentLoaded». Это событие было введено компанией Mozilla и никогда не являлось частью стандарта «DOM Events». )

Из новшеств, появившихся в спецификации «DOM Level 3 Events», можно назвать стандартизацию поддержки двунаправленных колесиков мыши через событие «wheel» и улучшенную поддержку событий ввода текста через событие «text- input» и новый объект Keyboard Event, который передается обработчикам событий «keydown», «keyup» и «keypress».

Согласно этой спецификации обработчику события «wheel» должен передаваться объект события, содержащий все свойства, обычные для объектов событий от мыши, а также свойства deltaX, deltaY и deltaZ, позволяющие узнать величину прокрутки вокруг трех разных осей колесика мыши. (В большинстве мышей колесико вращается в одном или двух измерениях, и поэтому свойство deltaZ пока остается неиспользуемым.) Подробнее о событиях «mousewheel» рассказывается в разделе 17.6.

Стандарт «DOM Level 3 Events» определяет событие «keypress», описанное выше, но не рекомендует использовать его и отдает предпочтение новому событию с именем «textinput». Вместо сложного в использовании числового значения в свойстве keyCode, объект события, передаваемый обработчикам события «textinput», имеет свойство data, содержащее введенную строку текста. Событие «textinput» не является в полной мере событием от клавиатуры: оно возбуждается при выполнении любой операции ввода текста, которая может быть выполнена с помощью клавиатуры, копированием из буфера обмена, операцией буксировки (drag-and-drop) и т.д. Спецификация определяет свойство inputMethod объекта события и множество констант, представляющих различные способы ввода текста (с клавиатуры, копированием из буфера обмена или буксировкой мышью, путем распознавания рукописного текста или голоса и т. д.). К моменту написания этих строк браузеры Safari и Chrome поддерживали версию этого события с именем «textlnput»! Соответствующий ему объект события включает свойство data, но в нем отсутствует свойство inputMethod. Пример использования события «textlnput» приводится в разделе 17.8.

Новый стандарт DOM также упрощает события «keydown», «keyup» и «keypress», добавляя новые свойства key и char в объект события. Оба эти свойства содержат строковые значения. Для клавиш, генерирующих печатаемые символы, свойства key и char будут хранить один и тот же сгенерированный текст. Для управляющих клавиш свойство key будет хранить строку вида «Enter», «Delete» или «Left», идентифицирующую клавишу. А свойство char будет хранить либо значение null, либо для таких управляющих клавиш, как Tab – имеющих соответствующий управляющий символ – строку, сгенерированную клавишей. На момент написания этих строк ни один браузер не поддерживал эти свойства key и char, но пример 17.8 будет использовать свойство key, когда оно будет реализовано.

содержание 17.1.3. События HTML5

Стандарт HTML5 и связанные с ним стандарты определяют основу новых API для веб-приложений (глава 22). Многие из этих API определяют события. В этом разделе перечисляются и коротко описываются эти события HTML5 и веб-приложений. Некоторые из этих событий уже готовы к использованию и более подробно описываются в разных главах книги. Другие пока реализованы не во всех браузерах и не описываются подробно.

Одной из широко рекламируемых особенностей HTML является возможность включения элементов <audio> и <video> для проигрывания аудио- и видеороликов. Эти элементы имеют длинный перечень генерируемых ими событий, позволяющих отправлять извещения о сетевых событиях, о состоянии механизма буферизации данных и механизма воспроизведения:

Эти события, имеющие отношение к медиапроигрывателям, передаются в виде простого объекта события, не имеющего специальных свойств. Однако свойство target идентифицирует элемент <audio> или <video>, и этот элемент имеет множество специфических свойств и методов. Более подробно об этих элементах, их свойствах и событиях рассказывается в разделе 21.2.

Интерфейс механизма буксировки (drag-and-drop), определяемый стандартом HTML5, позволяет приложениям на языке JavaScript участвовать в операциях буксировки объектов мышью, опираясь на механизмы, реализованные в операционной системе, и обмениваться данными с обычными приложениями. Этот прикладной интерфейс определяет следующие семь типов событий:

Эти события буксировки сопровождаются объектами событий, подобными тем, что передаются вместе с событиями от мыши. Отличаются они единственным дополнительным свойством dataTransfer, хранящим объект DataTransfer с информацией о передаваемых данных и о форматах, в которых эти данные доступны. Прикладной интерфейс механизма drag-and-drop, определяемый стандартом HTML5, описывается и демонстрируется в разделе 17.7.

Спецификация HTML5 определяет также механизм управления историей посещений раздел 22.2), что позволяет веб-приложениям взаимодействовать с кнопками браузера Back (Назад) и Forward (Вперед). Этот механизм вводит события с именами «hashchange» и «popstate», которые возникают тогда же, когда и события «load» и «unload», и возбуждаются в объекте Window, а не в документе.

В HTML5 определяется множество новых особенностей HTML-форм. В дополнение к событиям ввода, описанным выше, спецификация HTML5 также определяет механизм проверки форм, который привносит событие «invalid», возбуждаемое в элементе формы, не прошедшем проверку. Однако производители браузеров, кроме Opera, не торопятся воплощать новые особенности форм и новые события, поэтому они не рассматриваются в этой книге.

Спецификация HTML5 включает поддержку веб-приложений, способных выполняться без подключения к сети (раздел 20.4), которые могут быть установлены локально в кэше приложений, чтобы их можно было запускать, даже когда браузер работает в автономном режиме (например, когда мобильное устройство находится вне сети). С этой поддержкой связаны два наиболее важных события, «offline» и «online»: они генерируются в объекте Window, когда браузер теряет или обретает соединение с сетью. Кроме того, определено несколько дополнительных событий, посредством которых приложение извещается о ходе выполнения загрузки и обновления кэша приложений:

Событие «message» используется множеством новых API веб-приложений для организации асинхронных взаимодействий. Прикладной интерфейс взаимодействий между документами (раздел 22.3) позволяет сценариям в документе с одного сервера обмениваться сообщениями со сценариями в документе с другого сервера. Это дает возможность безопасно обойти ограничения политики общего происхождения (раздел 13.6.2). При передаче каждого сообщения в объекте Window документа, принимающего сообщение, генерируется событие «message». Объект события, передаваемый обработчику, включает свойство data, хранящее содержимое сообщения, а также свойства source и origin, идентифицирующие отправителя сообщения. Событие «message» используется также для взаимодействия с фоновыми потоками Web Workers (раздел 22.4) и для сетевых взаимодействий посредством прикладных интерфейсов, определяемых спецификациями «Server-Sent Events» (раздел ) и «WebSockets» (раздел 22.9).

Стандарт HTML5 и связанные с ним спецификации определяют некоторые события, генерируемые в объектах, не являющихся окнами, документами и элементами документов. Версия 2 спецификации «XMLHttpRequest», а также спецификация «File API» определяют множество событий, помогающих следить за ходом выполнения асинхронных операций ввода/вывода. Эти события генерируются в объектах XMLHttpRequest или FileReader. Каждая операция чтения начинается с события «loadstart», за которым следует последовательность событий «progress» и событие «loadend». Кроме того, каждая операция завершается событием «load», «error» или «abort», генерируемым непосредственно перед заключительным событием^ «loadend». Подробнее об этих событиях рассказывается в разделах 18.1.4 и 22.6.5.

Наконец, стандарт HTML5 и связанные с ним спецификации определяют еще несколько различных типов событий. Спецификация «Web Storage API» (раздел 20.1) определяет событие «storage» (генерируемое в объекте Window), извещающее об изменении хранимых данных. В спецификации HTML5 также определены события «beforeprint» и «afterprint», впервые введенные компанией Microsoft в IE. Как следует из их имен, эти события генерируются в окне Window непосредственно до и после того, как документ будет напечатан, и предоставляют возможность добавить или удалить содержимое, такое как дата и время печати документа. (Эти события не должны использоваться для изменения представления документа для печати, потому что для этой цели в CSS уже имеются директивы определения типа носителя.)

содержание 17.1.4. События, генерируемые сенсорными экранами и мобильными устройствами

Широкое распространение мощных мобильных устройств, особенно устройств с сенсорными экранами, потребовало создания новых категорий событий. Во многих случаях события от сенсорных экранов отображаются на традиционные типы событий, такие как «click» и «scroll». Но не все виды взаимодействий с пользовательским интерфейсом через сенсорный экран можно имитировать с помощью мыши, и не все прикосновения к такому экрану можно интерпретировать, как события от мыши. В этом разделе кратко описываются жесты и события прикосновений, генерируемые браузером Safari, когда он выполняется на устройствах iPhone и iPad компании Apple, а также рассматривается событие «orientation- change», генерируемое, когда пользователь поворачивает устройство. На момент написания этих строк данные события не были стандартизованы, но консорциум W3C уже приступил к работе над спецификацией «Touch Events Specification», в которой за основу приняты события прикосновения, внедренные компанией Apple. Эти события не описываются в справочном разделе данной книги, но дополнительную информацию вы сможете найти на сайте Apple Developer Center (http://developer.apple.com/).

браузер Safari генерирует события для жестов масштабирования и вращения из двух пальцев. Событие «gesturestart» возбуждается, когда начинается выполнение жеста, а событие «gestureend» по его окончании. Между этими двумя событиями генерируется последовательность событий «gesturechange», позволяющих отслеживать выполнение жеста. Объект события, передаваемый вместе с этими событиями, имеет числовые свойства scale и rotation. Свойство scale определяет отношение текущего и начального расстояний между двумя пальцами. Для жеста, когда пальцы сводятся, свойство scale получает значение меньше 1.0, а для жеста, когда пальцы разводятся, свойство scale получает значение больше 1.0. Свойство rotation определяет угол поворота пальцев с момента события «gesturestart». Значение угла всегда является положительным и измеряется в градусах по часовой стрелке.

События жестов являются высокоуровневыми событиями, извещающими о жесте, уже прошедшем интерпретацию. Реализовать поддержку собственных жестов можно с помощью обработчиков низкоуровневых событий прикосновений. Когда палец касается экрана, генерируется событие «touchstart». Когда палец перемещается, генерируется событие «touchmove». А когда палец отнимается от экрана, генерируется событие «touchend». В отличие от событий мыши, события прикосновений не несут непосредственной информации о координатах прикосновения. Вместо этого в объекте события, который поставляется вместе с событием прикосновения, имеется свойство changedTouches. Это свойство хранит объект, подобный массиву, каждый элемент которого описывает позицию прикосновения.

Событие «orientationchanged» генерируется в объекте Window устройствами, позволяющими пользователям поворачивать экран для перехода из книжной ориентации в альбомную. Объект, передаваемый вместе с событием «orientationchanged», не очень полезен сам по себе. Однако в мобильной версии браузера Safari объект Window имеет свойство orientation, определяющее текущую ориентацию в виде числовых значений 0, 90, 180 или –90.

содержание 17.2. Регистрация обработчиков событий содержание

Существует два основных способа регистрации обработчиков событий. Первый, появившийся на раннем этапе развития Всемирной паутины, заключается в установке свойства объекта или элемента документа, являющегося целью события. Второй способ, более новый и более универсальный, заключается в передаче обработчика методу объекта или элемента. Дело осложняется тем, что каждый прием имеет две версии. Свойство обработчика события можно установить в программном коде на языке JavaScript или в элементе документа, определив соответствующий атрибут непосредственно в разметке HTML. Регистрация обработчиков вызовом метода может быть выполнена стандартным методом с именем addEventListener(), который поддерживается всеми браузерами, кроме IE версии 8 и ниже, и другим методом с именем attachEvent(), поддерживаемым всеми версиями IE до IE9.

содержание17.2.1. Установка свойств обработчиков событий

Самый простой способ зарегистрировать обработчик события заключается в том, чтобы присвоить свойству целевого объекта события желаемую функцию обработчика. Свойства обработчиков событий по соглашению имеют имена, состоящие из слова «on», за которым следует имя события: onclick, onchange, onload, onmouseover и т.д. Обратите внимание, что эти имена свойств чувствительны к регистру и в них используются только строчные символы, даже когда имя типа события состоит из нескольких слов (например «readystatechange»). Ниже приводятся два примера регистрации обработчиков событий:

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

Недостаток использования свойств обработчиков событий состоит в том, что они проектировались в предположении, что цели событий будут иметь не более одного обработчика для каждого типа событий. При создании библиотеки для использования в произвольных документах для регистрации обработчиков лучше использовать прием (такой как вызов метода addEventListener()), не изменяющий и не затирающий ранее зарегистрированные обработчики.

содержание17.2.2. Установка атрибутов обработчиков событий

Свойства обработчиков событий в элементах документа можно также устанавливать, определяя значения атрибутов в соответствующих HTML-тегах. В этом случае значение атрибута должно быть строкой программного кода на языке JavaScript. Этот программный код должен быть не полным объявлением функции обработчика события, а только ее телом. То есть реализация обработчика события в разметке HTML не должна заключаться в фигурные скобки и предваряться ключевым словом function. Например:

<button onclick="alert('Спасибо');">Щелкните здесь</button>

Если значение HTML-атрибута обработчика события состоит из нескольких JavaScript-инструкций, они должны отделяться точками с запятой либо значение атрибута должно располагаться в нескольких строках.

Некоторые типы событий предназначены для браузера в целом, а не для какого- то конкретного элемента документа. Обработчики таких событий в языке JavaScript регистрируются в объекте Window. В разметке HTML они должны помещаться в тег <body>, но браузер зарегистрирует их в объекте Window. Ниже приводится полный список таких обработчиков событий, определяемых проектом спецификации HTML5:

Когда в качестве значения атрибута обработчика события в разметке HTML указывается строка с программным кодом на языке JavaScript, браузер преобразует эту строку в функцию, которая будет выглядеть примерно так:

Если браузер поддерживает стандарт ES5, функция определяется в нестрогом режиме (раздел 5.7.3). Мы еще встретимся с аргументом event и инструкциями with, когда будем рассматривать вызов обработчиков событий в разделе 17.3.

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

содержание17.2.3. addEventListener()

В стандартной модели событий, поддерживаемой всеми браузерами, кроме IE версии 8 и ниже, целью события может быть любой объект – включая объекты Window и Document и все объекты Elements элементов документа - определяющий метод с именем addEventListener(), с помощью которого можно регистрировать обработчики событий для этой цели. Метод addEventListener() принимает три аргумента. Первый- тип события, для которого регистрируется обработчик. Тип (или имя) события должен быть строкой и не должен включать префикс «on», используемый при установке свойств обработчиков событий. Вторым аргументом методу addEventListener() передается функция, которая должна вызываться при возникновении события указанного типа. В последнем аргументе методу add Event- List ener() передается логическое значение. Обычно в этом аргументе передается значение false. Если передать в нем значение true, функция будет зарегистрирована как перехватывающий обработчик и будет вызываться в другой фазе распространения события. Более подробно фаза перехвата событий будет рассматриваться в разделе 17.3.6. Спецификация со временем может измениться так, что будет допустимо опускать третий аргумент вместо того, чтобы явно передавать в нем значение false, но на момент написания этих строк отсутствие третьего аргумента в некоторых текущих браузерах приводила к ошибке.

Следующий фрагмент регистрирует два обработчика события «click» в элементе <button>. Обратите внимание на различия двух используемых приемов:

Вызов метода addEventListener() со строкой «click» в первом аргументе никак не влияет на значение свойства onclick. Во фрагменте, приведенном выше, щелчок на кнопке приведет к выводу двух диалогов alert(). Но важнее то, что метод addEventListener() можно вызвать несколько раз и зарегистрировать с его помощью несколько функций-обработчиков для одного и того же типа события в том же самом объекте. При появлении события в объекте будут вызваны все обработчики, зарегистрированные для этого типа события, в порядке их регистрации. Многократный вызов метода addEventListener() для одного и того же объекта с теми же самы ми аргументами не дает никакого эффекта – функция-обработчик регистрируется только один раз, и повторные вызовы не влияют на порядок вызова обработчиков.

Парным к методу addEventListener() является метод removeEventListener(), который принимает те же три аргумента, но не добавляет, а удаляет функцию-обработчик из объекта. Это часто бывает удобно, когда необходимо зарегистрировать временный обработчик события, а затем удалить его в какой-то момент. Например, при получении события «mousedown» может потребоваться зарегистрировать временный перехватывающий обработчик событий «mousemove» и «mouseup», чтобы можно было наблюдать за тем, как пользователь выполняет буксировку объектов мышью, а по событию «mouseup» эти обработчики могут удаляться. В такой ситуации реализация удаления обработчиков событий может иметь вид, как показано ниже:

document.removeEventListener("mousemove", handleMouseMove, true);
document.removeEventListener("mouseup", handleMouseUp, true);

содержание17.2.4. attachEvent()

Internet Explorer версии ниже IЕ9 не поддерживает методы addEventListener() и removeEventListener(). В версии IE5 и выше определены похожие методы, attachEvent() и detachEvent().

По своему действию методы attachEvent() и detachEvent() похожи на методы addEventListener() и removeEventListener()

Ниже показано, как обычно выполняется регистрация обработчика с помощью метода addEventListener() в браузерах, поддерживающих его, и с помощью метода attachEvent() в других браузерах:

var b = document.getElementById("mybutton");
var handler = function() { alert("Thanks!");
if (b.addEventListener)
    b.addEventListener("click", handler, false);
else if (b.attachEvent)
    b.attachEvent("onclick", handler);

содержание 17.3. Вызов обработчиков событийсодержание

После регистрации обработчика событий веб-браузер будет вызывать его автоматически, когда в указанном объекте будет возникать событие указанного типа. В этом разделе подробно описывается порядок вызова обработчиков событий, аргументы обработчиков, контекст вызова (значение this), область видимости и назначение возвращаемого значения обработчика. К сожалению, некоторые из этих подробностей отличаются между IE версии 8 и ниже и другими браузерами.

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

содержание17.3.1. Аргумент обработчика событий

При вызове обработчика событий ему обычно (за одним исключением, о котором рассказывается ниже) передается объект события в виде единственного аргумента. Свойства объекта события содержат дополнительную информацию о событии. Свойство type, например, определяет тип возникшего события. В разделе 17.1 упоминалось множество других свойств объекта события для различных типов событий.

В IE версии 8 и ниже обработчикам событий, зарегистрированным установкой свойства, объект события при вызове не передается. Вместо этого объект события сохраняется в глобальной переменной window.event. Для переносимости обработчики событий можно оформлять, как показано ниже, чтобы они использовали переменную window.event при вызове без аргумента:

function handler(event)
  event = event || window.event;
  // Handler code goes here
}

Объект события передается обработчикам событий, зарегистрированным с помощью метода attachEvent(), но они также могут использовать переменную window.event.

В разделе 17.2.2 говорилось, что при регистрации обработчика события посредством HTML-атрибута браузер преобразует строку с программным кодом на языке JavaScript в функцию. браузеры, отличные от IE, создают функцию с единственным аргументом event. В IE создается функция, не принимающая аргументов. Если в таких функциях использовать идентификатор event, он будет ссылаться на window.event. В любом случае обработчики событий, определяемые в разметке HTML, могут ссылаться на объект события, используя идентификатор event.

содержание17.3.2. Контекст обработчиков событий

Когда обработчик событий регистрируется установкой свойства, это выглядит как определение нового метода элемента документа:

e.onclick = function() { /* handler code */ };
Поэтому нет ничего удивительного, что обработчики событий вызываются (с одним исключением, касающимся IE, которое описывается ниже) как методы объектов, в которых они определены. То есть в теле обработчика событий ключевое слово this ссылается на цель события.

В обработчиках ключевое слово this ссылается на целевой объект, даже когда они были зарегистрированы с помощью метода addEventListener(). Однако, к сожалению, это не относится к методу attachEvent(): обработчики, зарегистрированные с помощью метода attachEvent(), вызываются как функции, и в них ключевое слово this ссылается на глобальный (Window) объект. Эту проблему можно решить следующим способом:

Обратите внимание, что обработчики событий, зарегистрированные таким способом, нельзя удалить, потому что ссылка на функцию-обертку, передаваемую методу attachEvent(), нигде не сохраняется, чтобы ее можно было передать методу detachEvent().

содержание17.3.3. Область видимости обработчика событий

Подобно всем функциям в языке JavaScript, обработчики событий имеют лексическую область видимости. Они выполняются в той области видимости, в какой были определены, а не в той, где они были вызваны, и имеют доступ ко всем локальным переменным в этой области видимости. (Это, например, демонстрируется в функции addEvent(), представленной выше.)

Особый случай представляют обработчики событий, которые регистрируются посредством HTML-атрибутов. Они преобразуются в функции верхнего уровня, которые не имеют доступа ни к каким локальным переменным - только к глобальным. Но по историческим причинам они выполняются в модифицированной цепочке областей видимости. Обработчики событий, определяемые посредством HTML-атрибутов, могут использовать свойства целевого объекта, объемлющего элемента <form> (если таковой имеется) и объекта Document, как если бы они были локальными переменными. В разделе 17.2.2 было показано, как из HTML-атрибута создается функция обработчика события, программный код в которой использует цепочку областей видимости, модифицированную с помощью инструкций with.

HTML-атрибуты плохо подходят для включения длинных строк программного кода, и такая модифицированная цепочка областей видимости помогает сократить его. Она позволяет использовать tagName вместо this.tagName, getElementById вместо document.getElementById, а в обработчиках, привязанных к элементам документа внутри элемента <form>, можно ссылаться на другие элементы формы по значению атрибута id, используя, например, имя zipcode вместо this.form.zipcode.

С другой стороны, модифицированная цепочка областей видимости обработчика событий, определяемого с помощью HTML-атрибута, может стать источником ошибок, потому что свойства всех объектов в цепочке видимости скрывают одноименные свойства глобального объекта. Например, объект Document определяет (редко используемый) метод open(), поэтому если обработчику событий, созданному с помощью HTML-атрибута, потребуется вызвать метод open() объекта Window, он вынужден будет явно вызывать его как window.open(), вместо open(). Аналогичная (но более пагубная) проблема наблюдается при работе с формами, потому что имена и значения атрибутов id элементов формы определяют свойства во вмещающем элементе формы (раздел 15.9.1). То есть если, к примеру, форма содержит элемент со значением «location» атрибута id, все обработчики событий, созданные внутри этой формы с помощью HTML-атрибутов, должны будут использовать window.location вместо location, если им потребуется сослаться на объект location окна.

содержание17.3.4. Возвращаемые значения обработчиков

Значение, возвращаемое обработчиком события, зарегистрированным установкой свойства объекта или с помощью HTML-атрибута, следует учитывать. Обычно возвращаемое значение false сообщает браузеру, что он не должен выполнять действия, предусмотренные для этого события по умолчанию. Например, обработчик onclick кнопки отправки формы может вернуть false, чтобы предотвратить отправку формы браузером. (Это может пригодиться, если ввод пользователя не прошел проверку на стороне клиента.) Аналогично обработчик события onkeypress поля ввода может фильтровать ввод с клавиатуры, возвращая false при вводе недопустимых символов. (Пример 17.6фильтрует ввод с клавиатуры именно таким способом.)

Также важно значение, возвращаемое обработчиком onbeforeunload объекта Window. Это событие генерируется, когда браузер выполняет переход на другую страницу. Если этот обработчик вернет строку, она будет выведена в модальном диалоге, предлагающем пользователю подтвердить свое желание покинуть страницу.

Важно понимать, что учитываются значения, возвращаемые обработчиками событий, только если обработчики зарегистрированы посредством установки свойств. Далее мы увидим, что обработчики, зарегистрированные с помощью addEventListener() или attachEvent(), вместо этого должны вызывать метод preventDefault() или устанавливать свойство returnValue объекта события.

содержание17.3.5. Порядок вызова

Для элемента документа или другого объекта можно зарегистрировать более одного обработчика одного и того же типа события. При возникновении этого события браузер вызовет все обработчики в порядке, определяемом следующими правилами:

содержание17.3.6. Распространение событий

Когда целью события является объект Window или какой-то другой самостоятельный объект (такой как XMLHttpRequest), браузер откликается на событие простым вызовом соответствующего обработчика в этом объекте. Однако когда целью события является объект Document или элемент Element документа, ситуация несколько осложняется.

После вызова обработчиков событий, зарегистрированных в целевом элементе, большинство событий «всплывают» вверх по дереву DOM. В результате вызываются обработчики в родителе целевого элемента. Затем вызываются обработчики, зарегистрированные в родителе родителя целевого элемента. Так продолжается, пока не будет достигнут объект Document и затем объект Window. Способность событий всплывать обеспечивает возможность реализации альтернативы множеству обработчиков, зарегистрированных в отдельных элементах документа: можно зарегистрировать единственный обработчик в общем элементе-предке и обрабатывать события в нем. Например, вместо того чтобы регистрировать обработчик события «change» в каждом элементе формы, его можно зарегистрировать в единственном элементе <form>>.

Способностью всплывать обладает большинство событий, возникающих в элементах документа. Заметным исключением являются события «focus», «blur» и «scroll». Событие «load», возникающее в элементах, также всплывает, но оно прекращает всплывать в объекте Document и не достигает объекта Window. Событие «load» в объекте Window возбуждается, только когда будет загружен весь документ.

Всплытие - это третья «фаза» распространения события. Вызов обработчика события в целевом объекте - это вторая фаза. Первая фаза протекает еще до вызова обработчиков целевого объекта и называется фазой «перехвата». Напомню, что метод addEventListener() имеет третий аргумент, в котором принимает логическое значение. Если передать в этом аргументе значение true, обработчик события будет зарегистрирован как перехватывающий обработчик для вызова в первой фазе распространения события. Фаза всплытия событий реализована во всех браузерах, включая IE, и в ней участвуют все обработчики, независимо от того, как они были зарегистрированы (если только они не были зарегистрированы как перехватывающие обработчики). В фазе перехвата, напротив, участвуют только обработчики, зарегистрированные с помощью метода addEventListener(), когда в третьем аргументе ему было передано значение true. Это означает, что фаза перехвата событий недоступна в IE версии 8 и ниже, и на момент написания этих строк имела ограничения в использовании.

Фаза перехвата напоминает фазу всплытия, только событие распространяется в обратном направлении. В первую очередь вызываются перехватывающие обработчики объекта Window, затем вызываются перехватывающие обработчики объекта Document, затем обработчики объекта body и так далее, вниз по дереву DOM, пока не будут вызваны перехватывающие обработчики родителя целевого объекта. Перехватывающие обработчики, зарегистрированные в самом целевом объекте, не вызываются.

Наличие фазы перехвата позволяет обнаруживать события еще до того, как они достигнут своей цели. Перехватывающий обработчик может использоваться для отладки или для фильтрации событий, чтобы в комплексе с приемом отмены события, описываемом ниже, предотвратить вызов обработчиков в целевом объекте. Одной из типичных областей применения фазы перехвата является реализация буксировки элементов мышью, когда событие перемещения указателя мыши должен обрабатывать буксируемый объект, а не документ, в пределах которого осуществляется буксировка. Пример реализации приводится в примере 17.2.

содержание17.3.7. Отмена событий

В разделе 17.3.4 говорилось, что значение, возвращаемое обработчиком события, зарегистрированным как свойство, можно использовать для отмены действий, выполняемых браузером по умолчанию в случае этого события. В браузерах, поддерживающих метод addEventListener(), отменить выполнение действий по умолчанию можно также вызовом метода preventDefault() объекта события. Однако в IE, версии 8 и ниже, тот же эффект достигается установкой свойства returnValue объекта события в значение false. В следующем фрагменте демонстрируется обработчик вымышленного события, который использует все три способа отмены события:

function cancelHandler(event) {{
  var event = event || window.event; // For IE
  /* Здесь выполняется обработка события */
  // Теперь отменить действие по умолчанию, связанное с событием
  if (event.preventDefault) event.preventDefault(); // Стандартный прием
  if (event.returnValue) event.returnValue = false; // IE
  return false; // Для обработчиков, зарегистрированных как свойства
}

Текущий проект модуля «DOM Events» определяет в объекте Event свойство с именем defaultPrevented. Оно пока поддерживается не всеми браузерами, но суть его в том, что при обычных условиях оно имеет значение false и принимает значение true только в случае вызова метода preventDefault( ). (Объект события, определяемый в библиотеке jQuery (глава 19), вместо свойства имеет метод preventDefault(). )

Отмена действий, по умолчанию связанных с событием, - это лишь одна из разновидностей отмены события. Имеется также возможность остановить распространение события. В браузерах, поддерживающих метод addEventListener(),, объект события имеет метод stopPropagation(), вызов которого прерывает дальнейшее распространение события. Если в том же целевом объекте будут зарегистрированы другие обработчики этого события, то остальные обработчики все равно будут вызваны, но никакие другие обработчики событий в других объекта не будут вызваны после вызова метода stopPropagation(). Метод stopPropagation() можно вызвать в любой момент в ходе распространения события. Он действует и в фазе перехвата, и в вызове обработчика целевого объекта, и в фазе всплытия.

В IE версии 8 и ниже метод stopPropagation() не поддерживается. Вместо этого объект события в IE имеет свойство cancelBubble. Установка этого свойства в значение true предотвращает распространение события. (В IE версии 8 и ниже фаза перехвата не поддерживается, поэтому отменить событие можно только в фазе всплытия.)

Текущий проект спецификации «DOM Events» определяет в объекте Event еще один метод - метод с именем stopImmediatePropagation(). Подобно методу stopPropagation(), он предотвращает распространение события по любым другим объектам. Но кроме того, он также предотвращает вызов любых других обработчиков событий, зарегистрированных в том же объекте. На момент написания этих строк метод stopImmediatePropagation() поддерживался не во всех браузерах. Некоторые библиотеки, такие как jQuery и YUI, определяют свою, переносимую, версию метода stopImmediatePropagation().

содержание 17.4. События механизма буксировки (drag-and-drop)содержание

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

Большинству веб-приложений совершенно необходимо, чтобы веб-браузер извещал их о моменте, когда закончится загрузка документа и он будет готов для выполнения операций над ним. Этой цели служит событие «load» в объекте Window, и оно уже подробно обсуждалось в главе 13, где в примере 13.5 была представлена реализация вспомогательной функции onLoad(). Событие «load» возбуждается только после того, как документ и все его изображения будут полностью загружены. Однако обычно сценарии можно запускать сразу после синтаксического анализа документа, до того как будут загружены изображения. Можно существенно сократить время запуска веб-приложения, если начинать выполнение сценариев по событиям, отличным от «load».

Событие «DOMContentLoaded» возбуждается, как только документ будет загружен, разобран синтаксическим анализатором, и будут выполнены все отложенные сценарии. К этому моменту изображения и сценарии с атрибутом async могут продолжать загружаться, но сам документ уже будет готов к выполнению операций. (Отложенные и асинхронные сценарии обсуждались в разделе 13.3.1.) Это событие впервые было введено в Firefox и впоследствии заимствовано всеми другими производителями браузеров, включая корпорацию Microsoft, которая добавила поддержку этого события в IE9. Несмотря на приставку «DOM» в имени, это событие не является частью стандарта модели событий «DOM Level 3 Events», но оно стандартизовано спецификацией HTML5.

Как описывалось в разделе 13.3.4, в ходе загрузки документа изменяется значение свойства document.readyState. Каждое изменение значения этого свойства в IE сопровождается событием «readystatechange» в объекте Document, благодаря чему в IE это событие можно использовать для определения момента появления состояния «complete». Спецификация HTML5 стандартизует событие «readystatechange», но предписывает возбуждать его непосредственно перед событием «load», поэтому не совсем понятно, в чем заключается преимущество события «readystatechange» перед «load».

В примере 17.1 определяется функция whenReady(), близко напоминающая функцию onLoad() из примера 13.5. Функция, передаваемая функции whenReady(), вызывается (как метод объекта Document) сразу, как только документ будет готов к выполнению операций. В отличие от ранее представленной функции onLoad() whenReady() ожидает появления событий «DOMContentLoaded» и «readystatechange» и использует событие «load» только как запасной вариант, на случай если она будет задействована в старых браузерах, не поддерживающих первые два события. Функция whenReady() будет использоваться в некоторых сценариях, следующих далее (в этой и в других главах).

Пример 17.1. Вызов функций, когда документ будет готов к выполнению операций

Example 17-1. Invoking functions when the document is ready
/* * Передайте функции whenReady() свою функцию, и она вызовет ее (как метод * объекта документ), как только завершится синтаксический анализ документа * и он будет готов к выполнению операций. Зарегистрированные функции * вызываются при первом же событии DOMContentLoaded, readystatechange * или load. Как только документ станет готов и будут вызваны все функции, * whenReady() немедленно вызовет все функции, которые были ей переданы. */ var whenReady = (function() { // Эта функция возвращается функцией whenReady() var funcs = []; // Функции, которые должны вызываться по событию var ready = false; // Получит значение true при вызове функции handler // Обработчик событий, который вызывается, как только документ // будет готов к выполнению операций function handler(e) { // Если обработчик уже вызывался, просто вернуть управление if (ready) return; // Если это событие readystatechange и состояние получило значение, // отличное от "complete", значит, документ пока не готов if (e.type === "readystatechange" && document.readyState !== "complete") return; // Вызвать все зарегистрированные функции. // Обратите внимание, что здесь каждый раз проверяется значение // свойства funcs.length, на случай если одна из вызванных функций // зарегистрирует дополнительные функции. for(var i = 0; i < funcs.length; i++) funcs[i].call(document); // Теперь можно установить флаг ready в значение true и забыть // о зарегистрированных функциях ready = true; funcs = null; } // Зарегистрировать обработчик handler для всех ожидаемых событий if (document.addEventListener) { document.addEventListener("DOMContentLoaded", handler, false); document.addEventListener("readystatechange", handler, false); window.addEventListener("load", handler, false); } else if (document.attachEvent) { document.attachEvent("onreadystatechange", handler); window.attachEvent("onload", handler); } // Вернуть функцию whenReady return function whenReady(f) { if (ready) f.call(document); // Вызвать функцию, если документ готов else funcs.push(f); // Иначе добавить ее в очередь, . } // чтобы вызвать позже. }());

содержание 17.5. События мышисодержание

С мышью связано довольно много событий. Все они перечислены в табл. 17.1. Все события мыши, кроме «mouseenter» и «mouseleave», всплывают. Для событий «click», возникающих в ссылках и кнопках отправки форм, предусматриваются действия по умолчанию, которые можно отменить. Теоретически имеется возможность отменить событие «contextmenu» и предотвратить появление контекстного меню, но некоторые браузеры имеют параметры настройки, которые делают это событие неотменяемым.

Таблица 17. События мыши

Тип;
Описание
clickВысокоуровневое событие, возбуждаемое, когда пользователь нажимает и отпускает кнопку мыши или иным образом «активирует» элемент.
contextmenuОтменяемое событие, возбуждаемое перед выводом контекстного меню. Текущие браузеры выводят контекстное меню по щелчку правой кнопки мыши, поэтому данное событие можно также использовать как событие «click».
dblclickВозбуждается, когда пользователь выполняет двойной щелчок.
mousedownВозбуждается, когда пользователь нажимает кнопку мыши.
mouseupВозбуждается, когда пользователь отпускает кнопку мыши.
mousemoveВозбуждается, когда пользователь перемещает указатель мыши.
mouseoverВозбуждается, когда указатель мыши помещается над элементом. Свойство relatedTarget (или fromElement в IE) определяет элемент, с которого был перемещен указатель мыши.
mouseoutВозбуждается, когда указатель мыши покидает элемент. Свойство relatedTarget (или toElement в IE) определяет элемент, на который был перемещен указатель мыши.
mouseenterПодобно «mouseover», но не всплывает. Впервые появилось в IE и было стандартизовано в HTML5, но пока поддерживается не всеми браузерами.
mouseleaveПодобно «mouseout», но не всплывает. Впервые появилось в IE и было стандартизовано в HTML5, но пока поддерживается не всеми браузерами.

Объект, передаваемый обработчикам событий от мыши, имеет свойства clientX и clientY определяющие координаты указателя относительно окна. Чтобы преобразовать их в координаты документа, к ним необходимо добавить позиции полос прокрутки окна (как показано в примере 15.8).

Свойства altKey, ctrlKey, metaKey и shiftKey определяют состояния различных клавиш-модификаторов, которые могли удерживаться в нажатом состоянии в момент события: с их помощью можно отличать простой щелчок от щелчка с нажатой клавишей Shift, например.

Свойство button определяет, какая кнопка мыши удерживалась в нажатом состоянии в момент события. Однако разные браузеры записывают в это свойство разные значения, поэтому его сложно использовать переносимым способом. Подробности смотрите в справочной статье Event в четвертой части книги. Некоторые браузеры возбуждают событие «click» только в случае щелчка левой кнопкой. Поэтому, если потребуется обрабатывать щелчки другими кнопками, следует использовать события «mousedown» и «mouseup». Событие «contextmenu» обычно сигнализирует о том, что был выполнен щелчок правой кнопкой, но, как отмечалось выше, в обработчиках этого события не всегда бывает возможным предотвратить появление контекстного меню.

Объект события, передаваемый вместе с событием мыши, имеет еще пять характерных свойств, но они используются значительно реже, чем вышеперечисленные. Полный их перечень вы найдете в справочной статье Event в четвертой части книги.

В примере 17.2 демонстрируется функция drag(), которая при вызове из обработчика события «mousedown» позволяет пользователю буксировать мышью абсолютно позиционированные элементы документа. Функция drag() работает с обеими моделями событий, DOM и IE.

Функция drag() принимает два аргумента. Первый – буксируемый элемент. Это может быть элемент, в котором возникло событие «mousedown», и содержащий его элемент (например, можно дать пользователю возможность ухватить мышью элемент, который выглядит как заголовок окна, и буксировать содержащий его элемент, который выглядит как окно). Однако в любом случае это должен быть элемент документа, абсолютно позиционированный с помощью CSS-атрибута position. Второй аргумент - объект события, полученный с событием «mousedown». Ниже приводится простой пример использования функции drag(). В нем определяется элемент , который пользователь может двигать мышью при нажатой клавише Shift:

<img src="draggable.gif"
style="position:absolute; left:100px; top:100px;"
onmousedown="if (event.shiftKey) drag(this, event);">>

Функция drag() преобразует координаты события «mousedown» в координаты документа, чтобы определить расстояние от указателя мыши до верхнего левого угла буксируемого элемента. При этом она использует вспомогательную функцию getScrollOffsets() из примера 15.8. Затем функция drag() регистрирует обработчики событий «mousemove» и «mouseup», которые последуют за событием «mousedown». Обработчик события «mousemove» отвечает за перемещение элемента документа, а обработчик события «mouseup» – за удаление себя и обработчика события «mousemove».

Важно отметить, что обработчики событий «mousemove» и «mouseup» регистрируются как перехватывающие обработчики. Это обусловлено тем, что пользователь может перемещать мышь быстрее, чем сможет перемещаться элемент документа, в результате чего некоторые события «mousemove» могут возникать за пределами буксируемого элемента. В фазе всплытия эти события просто не будут передаваться нужным обработчикам. В отличие от стандартной модели модель событий, реализованная в IE, не поддерживает фазу перехвата, но она поддерживает специализированный метод setCapture(), позволяющий перехватывать события мыши в подобных случаях. Пример наглядно демонстрирует, как действует этот метод.

Пример 17.2. Буксировка элементов документа

/**  
 * Drag.js: буксировка абсолютно позиционированных HTML-элементов.  
 *  
 * Этот модуль определяет единственную функцию drag(), которая должна вызываться  
 * из обработчика события onmousedown. Последующие события mousemove будут вызывать  
 * перемещение указанного элемента. Событие mouseup будет завершать буксировку.  
 * Эта реализация действует в обеих моделях событий, стандартной и IE.  
 * Использует функцию getScrollOffsetsO, представленную выше в книге.  
 *  
 * Аргументы:  
 *  
 * elementToDrag: элемент, принявший событие mousedown или содержащий его элемент.  
 * Этот элемент должен иметь абсолютное позиционирование. Значения его свойств style.left  
 * и style.top будут изменяться при перемещении указателя мыши пользователем.  
 *  
 * event: объект Event, полученный обработчиком события mousedown.  
**/  
function drag(elementToDrag, event)   
  // Преобразовать начальные координаты указателя мыши в координаты документа  
  var scroll = getScrollOffsets(); // Вспомогательная функция, объявленная  
  // где-то в другом месте  
  var startX = event.clientX + scroll.x;
  var startY = event.clientY + scroll.y;
 
  // Первоначальные координаты (относительно начала документа) элемента, который  
  // будет перемещаться. Так как elementToDrag имеет абсолютное позиционирование.  
  // предполагается, что его свойство offsetParent ссылается на тело документа,  
  var origX = elementToDrag.offsetLeft; 
  var origY = elementToDrag.offsetTop; 
 
  // Найти расстояние между точкой события mousedown и верхним левым углом элемента.  
  // Это расстояние будет учитываться при перемещении указателя мыши.   
  var deltaX = startX - origX; 
  var deltaY = startY - origY; 
 
  // Зарегистрировать обработчики событий mousemove и mouseup,  
  // которые последуют за событием mousedown.  
  if (document.addEventListener) { // Стандартная модель событий 
    // Зарегистрировать перехватывающие обработчики  
    document.addEventListener("mousemove", moveHandler, true); 
    document.addEventListener("mouseup", upHandler, true); 
  } 
  else if (document.attachEvent) { //Модель событий IE для IЕ5-8  
    // В модели событий IE перехват событий осуществляется вызовом  
    // метода setCapture() элемента.  
    elementToDrag.setCapture(); 
    elementToDrag.attachEvent("onmousemove", moveHandler); 
    elementToDrag.attachEvent("onmouseup", upHandler); 
    // Интерпретировать потерю перехвата событий мыши как событие mouseup. 
    elementToDrag.attachEvent("onlosecapture", upHandler); 
  } 
 
  // Это событие обработано и не должно передаваться другим обработчикам  
  if (event.stopPropagation) event.stopPropagation(); // Стандартная модель  
  else event.cancelBubble = true; // IE  
 
  // Предотвратить выполнение действий, предусмотренных по умолчанию.  
  if (event.preventDefault) event.preventDefault(); // Standard model 
  else event.returnValue = false; // IE 
   
 
  /**  
   * Этот обработчик перехватывает события mousemove, возникающие  
   * в процессе буксировки элемента. Он отвечает за перемещение элемента.  
  **/ 
 
function moveHandler(e) { 
  if (!e) e = window.event; // Модель событий IE  
    // Переместить элемент в позицию указателя мыши с учетом позиций  
    // полос прокрутки и смещений относительно начального щелчка.  
    var scroll = getScrollOffsets(); 
    elementToDrag.style.left = (e.clientX + scroll.x - deltaX) + "px"; 
    elementToDrag.style.top = (e.clientY + scroll.y - deltaY) + "px"; 
  
    // И прервать дальнейшее распространение события.  
    if (e.stopPropagation) e.stopPropagation(); // Стандартная модель  
    else e.cancelBubble = true; // IE 
  } 
 
  /**  
   * Этот обработчик перехватывает заключительное событие mouseup,  
   * которое завершает операцию буксировки.  
  **/  
function upHandler(e) { 
  if (!e) e = window.event; // Модель событий  IE   
    // Удалить перехватывающие обработчики событий.  
    if (document.removeEventListener) { // Модель событий DOM   
    document.removeEventListener("mouseup", upHandler, true); 
    document.removeEventListener("mousemove", moveHandler, true); 
  } 
  else if (document.detachEvent) { // Модель событий IE 5+   
    elementToDrag.detachEvent("onlosecapture", upHandler); 
    elementToDrag.detachEvent("onmouseup", upHandler); 
    elementToDrag.detachEvent("onmousemove", moveHandler); 
    elementToDrag.releaseCapture(); 
  } 
 
  // И прервать дальнейшее распространение события. 
  if (e.stopPropagation) e.stopPropagation(); // Стандартная модель  
  else e.cancelBubble = true; // IE 
  } 
}

Следующий фрагмент демонстрирует порядок использования функции drag() в HTML-файле (это упрощенная версия примера 16.2 с добавленной поддержкой буксировки):

Drag Me

This is a test. Testing, testing, testing.

Test

Test

  <script src="getScrollOffsets.js"></script> <!-- требуется функция drag() --> 
  <script src="Drag.js"></script> <!-- определение drag() --> 
  <!-- The element to be dragged --> 
  <div style="position:absolute; left:100px; top:100px; width:250px; 
              
background-color: white; border: solid black;"> 
  <!-- "Заголовок" окна. Обратите внимание на атрибут  onmousedown. --> 
  <div style="background-color: gray; border-bottom: dotted black; 
              
padding: 3px; font-family: sans-serif; font-weight: bold;" 
       onmousedown="drag(this.parentNode, event);"> 
    Перетащи меня  <!-- Содержимое заголовка  --> 
  </div> 
<!-- Содержимое буксируемого элемента  --> 
  <p> Это тест. Проверка, проверка, проверка. .</p><p>Test</p><p>Test</p> 
</div> 

Ключевым здесь является атрибут onmousedown во вложенном элементе <div>. Обратите внимание, что в нем используется свойство this.parentNode. Оно говорит о том, что перемещаться будет весь контейнерный элемент.

содержание 17.6. События колесика мыши содержание

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

Существует множество проблем совместимости, связанных с событиями «mousewheel», тем не менее вполне возможно написать программный код, действующий на всех платформах. На момент написания этих строк все браузеры, кроме Firefox, поддерживали событие с именем «mousewheel». В Firefox это событие называется «DOMMouseScroll». А в проекте спецификации «DOM Level 3 Events» предложено имя «wheel» вместо «mousewheel». Вдобавок к разным именам события различаются и имена свойств объектов, передаваемых с этими различными событиями, которые определяют величину поворота колесика. Наконец, следует отметить, что существуют также важные отличия в аппаратной реализации колесиков в разных мышах. Некоторые колесики могут вращаться только в одной плоскости, назад и вперед, а некоторые (особенно в мышах компании Apple) могут вращаться также влево и вправо (в действительности такие «колесики» являются трекболами (trackball)). Стандарт «DOM Level 3» даже включает поддержку «колесиков», которые могут вращаться в трех плоскостях – по или против часовой стрелки вдобавок к вращению вперед/назад и влево/вправо.

Объект события, передаваемый обработчику события «mousewheel», имеет свойство wheelDelta, определяющее величину прокрутки колесика. Один «щелчок» колесика в направлении от пользователя обычно соответствует значению 120, а один щелчок в направлении к пользователю – значению –120. В Safari и Chrome для поддержки мышей компании Apple, имеющих трекбол вместо колесика, вращающегося в одной плоскости, вдобавок к свойству wheelDelta объект события имеет свойства wheelDelta и wheelDeltaY, при этом значения свойств wheelDelta и wheelDeltaY всегда совпадают.

В Firefox вместо события «mousewheel» можно обрабатывать нестандартное событие «DOMMouseScroll» и использовать свойство detail объекта события вместо wheelDelta. Однако масштаб и знак изменения свойства detail отличается от wheelDelta: чтобы получить значение, эквивалентное значению свойства wheelDelta, свойство detail нужно умножить на –40.

На момент написания этих строк проект стандарта «DOM Level 3 Events» определял стандартное событие с именем «wheel» как стандартизованную версию событий «mousewheel» и «DOMMouseScroll». Согласно спецификации, объект, передаваемый обработчику события «wheel», должен иметь свойства deltaX, deltaY и deltaZ, определяющие величину прокрутки колесика в трех плоскостях. Значения этих свойств следует умножать на –120, чтобы получить значение и знак события «mousewheel».

Для всех этих типов событий объект события напоминает объекты событий мыши: он включает координаты указателя мыши и состояние клавиш-модификаторов на клавиатуре.

Пример 17.3 демонстрирует, как обрабатывать события колесика мыши и как обходить проблемы совместимости. В нем определяется функция с именем enclose(), которая обертывает в «кадр» или «видимую область» заданного размера более крупный элемент (такой как изображение) и определяет обработчик события колесика мыши, который позволяет пользователю прокручивать элемент в пределах видимой области и изменять размеры этой области. Эту функцию enclose() можно использовать, как показано ниже:

<script src="whenReady.js"></script>
<script src="Enclose.js"></script>
<script>
whenReady(function() {
    enclose(document.getElementById("content"),400,200,-200,-300);
});
</script>
<style>div.enclosure { border: solid black 10px; margin: 10px; }</style>
<img id="content" src="testimage.jpg"/>

Для обеспечения корректной работы во всех основных браузерах в примере 17.3 выполняется проверка типа браузера (раздел 13.4.5). Пример опирается на положения спецификации «DOM Level 3 Events» и включает программный код, который будет использовать событие «wheel», когда его поддержка будет реализована в браузерах. (Довольно рискованное решение: если будущие версии стандарта не будут соответствовать проекту спецификации, имевшейся на момент написания этих строк, это может привести к нежелательным последствиям и сделать пример неработоспособным. ) Он также включает дополнительную проверку на будущее, чтобы не использовать событие «DOMMouseScroll», если Firefox начнет использовать событие «wheel» или «mousewheel». Обратите внимание, что пример 17.3 также является практическим примером управления геометрией элементов и использования приемов позиционирования средствами CSS, о которых рассказывалось в разделах 15.8 и 16.2.1.

Пример 17.3. Обработка событий «mousewheel»

Example 17-3. Handling mousewheel events  
// Enclose the content element in a frame or viewport of the specified width  
// and height (minimum 50x50). The optional contentX and contentY arguments  
// specify the initial offset of the content relative to the frame. (If  
// specified, they must be <= 0.) The frame has mousewheel event handlers that  
// allow the user to pan the element, and to shrink or enlarge the frame.   
// Заключает элемент содержимого в фрейм, или видимую область заданной ширины  
// и высоты (минимальные размеры 50x50). Необязательные аргументы contentX  
// и contentY определяют начальное смещение содержимого относительно кадра.  
// (Их значения должны быть <= 0.) Фрейму придается обработчик события mousewheel,  
// который позволяет пользователю прокручивать элемент и изменять размеры фрейма,  

function enclose(content, framewidth, frameheight, contentX, contentY) {  
		// These arguments aren't just the initial values: they maintain the  
		// current state and are used and modified by the mousewheel handler.  
		// Эти аргументы являются не только начальными значениями: они хранят информацию  
		// о текущем состоянии, изменяются и используются обработчиком события mousewheel. 

		framewidth = Math.max(framewidth, 50);  
		frameheight = Math.max(frameheight, 50);  
		contentX = Math.min(contentX, 0) || 0;  
		contentY = Math.min(contentY, 0) || 0;  
	 
		// Create the frame element and set a CSS classname and styles  
		var frame = document.createElement("div");  
		frame.className = "enclosure";              // So we can define styles in a stylesheet   
		frame.style.width = framewidth + "px";      // Set the frame size.   
		frame.style.height = frameheight + "px";  
		frame.style.overflow = "hidden";            // No scrollbars, no overflow   
		frame.style.boxSizing = border-box;         // Border-box simplifies the   
		frame.style.webkitBoxSizing = "border-box"; // calculations for resizing   
		frame.style.MozBoxSizing = "border-box";    // the frame.  
	 
		// Put the frame in the document and move the content elt into the frame.  
		content.parentNode.insertBefore(frame, content);  
		frame.appendChild(content);  
		 
		// Position the element relative to the frame  
		content.style.position = "relative";   
		content.style.left = contentX + "px";  
		content.style.top = contentY + "px";  
		 
		// We'll need to work around some browser-specific quirks below  
		var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&  
				navigator.userAgent.indexOf("WebKit") !== -1);  
		var isFirefox = (navigator.userAgent.indexOf("Gecko") !== -1);  
		 
		// Register mousewheel event handlers.  
		frame.onwheel = wheelHandler;      // Future browsers  
		frame.onmousewheel = wheelHandler; // Most current browsers  
		if (isFirefox)            // Firefox only  
		frame.addEventListener("DOMMouseScroll", wheelHandler, false);  
		 
		function wheelHandler(event) {  
			var e = event || window.event;   // Standard or IE event object  
			 
			// Extract the amount of rotation from the event object, looking  
			// for properties of a wheel event object, a mousewheel event object  
			// (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.  
			// Scale the deltas so that one "click" toward the screen is 30 pixels.  
			// If future browsers fire both "wheel" and "mousewheel" for the same  
			// event, we'll end up double-counting it here. Hopefully, however,  
			// cancelling the wheel event will prevent generation of mousewheel.  
		 
			// Получить величину прокрутки из объекта события, проверив свойства объекта  
			// события wheel, mousewheel (в обоих, 2-мерном и 1-мерном вариантах)  
			// и DOMMouseScroll в Firefox. Масштабировать значение так, чтобы один «щелчок»  
			// колесика соответствовал 30 пикселам. Если какой-либо из браузеров в будущем  
			// будет возбуждать оба типа событий, "wheel" и "mousewheel", одно и то же  
			// событие будет обрабатываться дважды. Но будем надеяться, что отмена  
			// события wheel будет предотвращать возбуждение события mousewheel. 

			var deltaX = e.deltaX*-30 ||  // wheel event  
				 e.wheelDeltaX/4 ||  // mousewheel  
					  0;   // property not defined  
			var deltaY = e.deltaY*-30 ||  // wheel event  
				e.wheelDeltaY/4 ||   // mousewheel event in Webkit  
			(e.wheelDeltaY===undefined && // if there is no 2D property then  
				e.wheelDelta/4) ||  // use the 1D wheel property  
				  e.detail*-10 || // Firefox DOMMouseScroll event  
					  0;   // property not defined  
			 
			// Most browsers generate one event with delta 120 per mousewheel click.  
			// On Macs, however, the mousewheels seem to be velocity-sensitive and  
			// the delta values are often larger multiples of 120, at  
			// least with the Apple Mouse. Use browser-testing to defeat this.  
		 
			// Большинство браузеров генерируют одно событие со значением 120  
			// на один щелчок колесика. Однако похоже, что мыши компании Apple  
			// более чувствительные, и значения свойств delta часто оказываются  
			// в несколько раз больше 120, по крайней мере, для Apple Mouse.  
			// Использовать прием проверки типа браузера, чтобы решить эту проблему. 


			if (isMacWebkit) {  
				deltaX /= 30;  
				deltaY /= 30;  
			}  
			 
			// If we ever get a mousewheel or wheel event in (a future version of)  
			// Firefox, then we don't need DOMMouseScroll anymore.  
			if (isFirefox && e.type !== "DOMMouseScroll")  
				frame.removeEventListener("DOMMouseScroll", wheelHandler, false);  
			 
			// Get the current dimensions of the content element var contentbox =   
			content.getBoundingClientRect();  

			var contentwidth = contentbox.right - contentbox.left;  
			var contentheight = contentbox.bottom - contentbox.top;  
			 
			if (e.altKey) {  // If Alt key is held down, resize the frame  
				if (deltaX) {  
					framewidth -= deltaX; // New width, but not bigger than the  
					framewidth = Math.min(framwidth, contentwidth);  // content  
					framewidth = Math.max(framewidth,50);           // and no less than 50.   
					frame.style.width = framewidth + "px";     // Set it on frame  
				} 

				if (deltaY) {  
					frameheight -= deltaY;  // Do the same for the frame height  
					frameheight = Math.min(frameheight, contentheight);  
					frameheight = Math.max(frameheight-deltaY, 50);  
					frame.style.height = frameheight + "px";  
				} 
			}  
			else { // Without the Alt modifier, pan the content within the frame  
					// Клавиша Alt не нажата, прокрутить содержимое в фрейме  
				if (deltaX) {  
					// Don't scroll more than this  
					var minoffset = Math.min(framewidth-contentwidth, 0);  
					// Add deltaX to contentX, but don't go lower than minoffset  
					contentX = Math.max(contentX + deltaX, minoffset);  
					contentX = Math.min(contentX, 0);    // or higher than 0  
					content.style.left = contentX + "px"; // Set new offset  
				}  
				if (deltaY) {  
					var minoffset = Math.min(frameheight - contentheight, 0);  
					// Add deltaY to contentY, but don't go lower than minoffset  
					// Приплюсовываем deltaY к contentY, но не меньше, чем minoffset  
					contentY = Math.max(contentY + deltaY, minoffset);  
					contentY = Math.min(contentY, 0);   // Or higher than 0  
					content.style.top = contentY + "px";  // Set the new offset.  
				}  
			}  
		 
			// Don't let this event bubble. Prevent any default action.  
			// This stops the browser from using the mousewheel event to scroll  
			// the document. Hopefully calling preventDefault() on a wheel event  
			// will also prevent the generation of a mousewheel event for the  
			// same rotation.
			
			// He позволять всплывать этому событию. Предотвратить выполнение действий  
			// по умолчанию. Это позволит предотвратить прокрутку содержимого документа  
			// в окне браузера. Будем надеяться, что вызов preventDefault() для события wheel  
			// также предотвратит возбуждение дублирующего события mousewheel. 

			if (e.preventDefault) e.preventDefault();  
			if (e.stopPropagation) e.stopPropagation();  
			e.cancelBubble = true;  // IE events  
			e.returnValue = false;  // IE events  
			return false;  
	  }  
} 

содержание 17.7. События механизма буксировки (drag-and-drop)содержание

В примере 17.2 было показано, как реализовать операцию буксировки элементов мышью. Она позволяет перетаскивать и оставлять элементы в пределах веб-страницы, но истинная буксировка - это нечто иное. Буксировка (drag-and-drop, или DnD) – это интерфейс взаимодействия с пользователем, позволяющий перемещать данные между «источником» и «приемником», которые могут находиться в одном или в разных приложениях. Буксировка (DnD) – это сложный механизм организации взаимодействий между человеком и машиной, и прикладные интерфейсы, реализующие поддержку буксировки, всегда отличались высокой сложностью:

Корпорация Microsoft реализовала прикладной интерфейс механизма буксировки в ранних версиях IE. Он был не очень хорошо продуман и плохо документирован, тем не менее, другие производители браузеров попытались скопировать его, а спецификация HTML5 стандартизовала некоторый API, напоминающий прикладной интерфейс в IE, и добавила новые особенности, делающие этот API более простым в использовании. На момент написания этих строк данный новый, более простой в использовании API буксировки еще не был реализован, поэтому в этом разделе будет рассматриваться прикладной интерфейс в IE как взятый за основу стандартом HTML5.

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

Механизм буксировки всегда опирался на события, поэтому в JavaScript API реализовано два множества событий: события из первого множества возбуждаются в источнике данных, а из второго – в приемнике. Все обработчики событий буксировки получают объект события, подобный объекту события мыши, с дополнительным свойством dataTransfer. Это свойство ссылается на объект DataTransfer, определяющий методы и свойства прикладного интерфейса механизма буксировки.

События, возбуждаемые в источнике, относительно просты, поэтому начнем с них. Источником для механизма буксировки является любой элемент документа, имеющий HTML-атрибут draggable. Когда пользователь начинает перемещать указатель мыши с нажатой кнопкой над элементом-источником, браузер не выделяет содержимое элемента, а возбуждает в нем событие «dragstart». Обработчик этого события должен вызвать метод dataTransfer.setData(), чтобы определить данные (и тип этих данных), доступные в источнике (когда будет реализован новый HTML5 API, вместо этого метода нужно будет вызывать метод dataTransfer.items.add()). Обработчику также может потребоваться установить свойство dataTransfer.effectAllowed, чтобы определить тип операции – «перемещение», «копирование» или «создание ссылки» – поддерживаемой источником и, возможно, необходимо будет вызвать метод dataTransfer.setDragImage() или dataTransfer.addElement() (в браузерах, поддерживающих эти методы), чтобы определить изображение или элемент документа, который будет использоваться для визуального представления перемещаемых данных.

В процессе буксировки браузер возбуждает в источнике данных события «drag». Обработчик этого события можно использовать для изменения перемещаемого ярлыка или перемещаемых данных, но в общем случае нет никакой необходимости регистрировать обработчики событий «drag».

Когда выполняется сброс, возбуждается событие «dragend». Если источник поддерживает операцию «перемещения», он должен проверить свойство dataTransfer.dropEffect, чтобы убедиться, действительно ли была выполнена операция перемещения. Если это так, данные, перемещенные в другое место, следует удалить из источника.

Событие «dragstart» является единственным, обработку которого необходимо реализовать в простейшем источнике данных. Реализация такого источника представлена в примере 17.4. Он отображает текущее время в формате «hh:mm» в элементе <span> и обновляет время раз в минуту. Если бы это было все, что реализует пример, пользователь мог бы просто выделить отображаемый текст и отбуксировать его. Но этот пример превращает часы в источник данных для механизма буксировки, устанавливая свойство draggable элемента часов в значение true и определяя функцию-обработчик ondragstart. Обработчик вызывает метод dataTransfer.setData(), чтобы определить перемещаемые данные – строку с полной информацией о текущем времени (включая дату, секунды и информацию о часовом поясе). Он также вызывает dataTransfer.setDragIcon(), чтобы определить изображение (ярлык с изображением часов), которое будет перемещаться в процессе буксировки.

Пример 17.4. Источник данных для механизма буксировки

<script src="whenReady.js"></script>
<script>
whenReady(function() {
  var clock = document.getElementById("clock"); // Элемент часов
  var icon = new Image();// Буксируемое изображение
  icon.src = "clock-icon.png";// URL- адрес изображения

  // Отображает время раз в минуту
  
  function displayTime() {  
    var now = new Date();// Получить текущее время
    var hrs = now.getHours(), mins = now.getMinutes();
    if (mins < 10) mins = "0" + mins;  
    clock.innerHTML = hrs + ":" + mins;// Отобразить текущее время
    setTimeout(displayTime, 60000);// Запустить через 1 минуту
  } 
  displayTime();  

  // Сделать часы доступными для буксировки
 
  // То же самое можно сделать с помощью HTML-атрибута: 
  //<span draggable="true">... 
  clock.draggable = true; 
  
// Обработчики событий (Set up drag event handlers)
  clock.ondragstart = function(event) { 
    var event = event || window.event; // Для совместимости с IE

    // Свойство dataTransfer является ключом к drag-and-drop API
    var dt = event.dataTransfer;

    // Сообщить браузеру, какие данные будут буксироваться.
    // Если конструктор Date() вызывается как функция, он возвращает
    // строку с полной информацией о текущем времени
    dt.setData("Text", Date() + "\n");

    // Определить ярлык, который будет служить визуальным представлением перемещаемой
    // строки, в браузер ах, поддерживающих эту возможность. Без этого для визуального
    // представления браузер мог бы использовать изображение текста в часах.
    if (dt.setDragImage) dt.setDragImage(icon, 0, 0);
  }; 
});  
</script>
<style>

#clock { /* Make the clock look nice */
  font: bold 24pt sans; background: #ddf; padding: 10px;
  border: solid black 2px; border-radius: 10px;
}
</style>
<h1>Drag timestamps from the clock</h1>
<span id="clock"></span> <!-- The time is displayed here -->
<textarea cols=60 rows=20></textarea> <!-- You can drop timestamps here -->
/* */

Drag timestamps from the clock

Приемники буксируемых данных сложнее в реализации, чем источники. Приемником может быть любой элемент документа: чтобы создать приемник, не требуется устанавливать HTML-атрибуты, как при создании источников, &ndash достаточно просто определить соответствующие обработчики событий. (Однако с реализацией нового прикладного интерфейса буксировки, определяемого стандартом HTML5, вместо некоторых обработчиков событий, описываемых ниже, в элементе-приемнике необходимо будет установить атрибут dropzone.) В приемнике возбуждается четыре события. Когда буксируемый объект оказывается над элементом документа, браузер возбуждает в этом элементе событие «dragenter». Определить, содержит ли буксируемый объект данные в формате, понятном приемнику, можно с помощью свойства dataTransfer.types. (При этом может также потребоваться проверить свойство dataTransfer.effectAllowed, чтобы убедиться, что источник и приемник поддерживают выполняемую операцию: перемещение, копирование или создание ссылки.) В случае успешного прохождения этих проверок элемент-приемник должен дать знать пользователю и браузеру, что он может принять буксируемый объект. Обратную связь с пользователем можно реализовать в виде изменения цвета рамки или фона. Самое интересное: приемник сообщает браузеру, что он готов принять буксируемый объект, отменяя это событие.

Если элемент не отменит событие «dragenter», переданное ему браузером, браузер не будет считать этот элемент приемником для данной операции буксировки и не будет передавать ему дополнительные события. Но если приемник отменит событие «dragenter», браузер будет посылать ему события «dragover», пока пользователь будет буксировать объект над приемником. Интересно (снова) отметить, что приемник должен обрабатывать и отменять все эти события, чтобы сообщить, что он по-прежнему готов принять буксируемый объект. Если приемнику потребуется сообщить, что он поддерживает только операцию перемещение, копирования или создания ссылки, он должен устанавливать свойство dataTransfer.dropEffect в обработчике события «dragover».

Если пользователь переместит буксируемый объект за границы приемника, который сообщал о своей готовности принять объект отменой событий, в приемнике будет возбуждено событие «dragleave». Обработчик этого события должен восстановить прежний цвет рамки или фона элемента или отменить любые другие визуальные изменения, выполненные в ответ на событие «dragenter». К сожалению, оба события, «dragenter» и «dragleave», всплывают; и если приемник имеет вложенные элементы, будет сложно отличить, говорит ли событие «dragleave» о том, что указатель мыши с буксируемым объектом вышел за границы элемента-приемника или что он вышел за границы вложенного элемента внутри приемника.

Наконец, если пользователь сбросит буксируемый объект над приемником, в приемнике будет возбуждено событие «drop». Обработчик этого события должен получить перемещаемые данные с помощью dataTransfer.getData() и выполнить над ними соответствующие операции. Если пользователь сбросит над приемником один или более файлов, свойство dataTransfer.files будет содержать объект, подобный массиву, с объектами File. (Работа с этим свойством демонстрируется в примере 18.11.) После реализации нового HTML5 API обработчики события «drop» должны будут выполнить обход элементов массива dataTransfer.items[], чтобы обработать данные, которые могут быть или не быть файлами.

Пример 17.5 демонстрирует, как превратить в приемники элементы <ul> и как превратить вложенные в них элементы <li> в источники. Этот пример является демонстрацией концепции ненавязчивого JavaScript. Он отыскивает элементы списка <ul>, атрибут class которых включает класс «dnd», и регистрирует в них обработчики событий буксировки. Обработчики событий превращают список в приемник: любой текст, сброшенный над таким списком, будет преобразован в новый элемент списка и добавлен в конец. Обработчики событий также обслуживают ситуации перемещения элементов внутри списка и делают текст каждого элемента списка доступным для буксировки. Обработчики событий источников поддерживают операции копирования и перемещения и удаляют элементы списка, которые были сброшены в ходе выполнения операции перемещения. (Обратите внимание, что не все браузеры обеспечивают переносимую поддержку операции перемещения.)

Пример 17.5. Список как приемник и источник


/* Прикладной программный интерфейс механизма буксировки весьма сложен, его реализации
 * в разных браузерах не являются полностью совместимыми. В своей основе этот пример 
 * реализован правильно, но все браузеры немного отличаются друг от друга, 
 * и каждый из них имеет свои уникальные особенности. В данном примере не делается 
 * попыток реализовать обходные решения, характерные для отдельных браузеров. 
 */ 
 whenReady(function() { // Вызовет эту функцию, когда документ будет загружен 
 
  // Отыскать все элементы <ul и вызвать функцию dnd() для них 
  var lists = document.getElementsByTagName("ul"); 
  var regexp = /bdndb/; 
  for(var і = 0; і < lists.length; i++) 
    if (regexp.test(lists[i].className)) dnd(lists[i]); 
		
  // Добавляет обработчики событий буксировки в элемент списка  
  function dnd(list) { 
    var original_class = list.className; // Сохранить начальный CSS-class 
    var entered =0; // Вход и выход за границы 
		
    // Этот обработчик вызывается, когда буксируемый объект оказывается над списком. 
    // Он проверяет, содержит ли буксируемый объект данные в поддерживаемом формате. 
    // и, если это так, отменяет событие, чтобы сообщить, что список готов 
    // принять объект. В этом случае он также подсвечивает элемент-приемник, 
    // чтобы показать пользователю, что готов к приему данных, 
    list.ondragenter = function(e) { 
      е = е И window.event; // Объект события, стандартный или IE 
      var from = е.relatedTarget; 
			
      // События dragenter и dragleave всплывают, из-за чего сложнее определить, 
      // когда следует подсвечивать элемент, а когда снимать подсветку в случаях, 
      // подобных этому, где элемент <ui> содержит дочерние элементы <li>. 
      // В браузерах, поддерживающих свойство relatedTarget, эту проблему можно решить. 
      // В других браузерах приходится считать пары событий входа/выхода. 
			
      // Если указатель мыши оказался над списком, переместившись из-за его пределов, 
      // или он оказался над списком впервые, необходимо выполнить некоторые операции  
      entered++; 
      if ((from && !ischild(from, list)) || entered == 1) { 
        // Вся информация о буксируемом объекте находится в объекте dataTransfer 
        var dt = е.dataTransfer;
				
        // Объект dt.types содержит список типов, или форматов, в которых доступны 
        // буксируемые данные. Спецификация HTML5 требует, чтобы свойство types имело 
        // метод contains(). В некоторых браузерах это свойство является массивом 
        // с методом indexOf. В IE версии 8 и ниже оно просто отсутствует, 
        var types = dt.types; // В каких форматах доступны данные 
        // Если информация о типах отсутствует или данные доступны в простом 
        // текстовом формате, подсветить список, чтобы показать пользователю, что он 
        // готов принять данные, и вернуть false, чтобы известить о том же и браузер, 
        if (!types ||                                            //IE 
             (types.contains && types.contains("text/plain")) || //HTML5 
             (types.indexOf && types.indexOf("text/plain")!=-1)) //Webkit 
        { 
          list.className = original_class + " droppable"; 
          return false; 
        } 
        // Если тип данных не поддерживается, мы не сможем принять их return; 
        // без отмены 
      } 
      return false; // Если это не первое вхождение, мы по-прежнему готовы 
    }; 
		
    // Этот обработчик вызывается в ходе буксировки объекта над списком. 
    // Этот обработчик должен быть определен, и он должен возвращать false, 
    // иначе сброс объектов будет невозможен,  
    list.ondragover = function(e) { return false; };
		
    // Этот обработчик вызывается, когда буксируемый объект выходит за границы списка 
    // или за границы одного из его дочерних элементов. Если объект действительно 
    // покидает границы списка (а не границы одного из его элементов), 
    // то нужно снять подсветку списка, 
    list.ondragleave = function(e) { 
      е = е || window.event; 
      var to = e.relatedTarget; 
			
      // Если буксируемый объект покидает границы списка или если количество выходов 
      // за границы совпадает с количеством входов, следует снять подсветку списка entered--; 
      if ((to && !ischild(to,list)) || entered <= 0) { 
        list.className = original_class; 
        entered = 0; 
      } 
      return false; 
    } 
		
    // Этот обработчик вызывается, когда происходит сброс объекта. 
    // Он извлекает сброшенный текст и превращает его в новый элемент <li> 
    list.ondrop = function(e) { 
      е = е И window.event;                              // Получить объект события 
			
      // Получить сброшенные данные в текстовом формате. 
      // "Text" - это псевдоним для "text/plain". 
      // IE не поддерживает "text/plain”, поэтому здесь используется "Text". 
      var dt = e.dataTransfer;                           // объект dataTransfer 
      var text = dt.getData("Text");                     // Получить данные в текстовом формате. 
			
      // Если был получен некоторый текст, превратить его в новый элемент 
      // списка и добавить в конец, 
      if (text) { 
        var item = document.createElement("li");         // Создать новый <li> 
        item.draggable = true;                           // Сделать буксируемым 
        item.appendChild(document.createTextNode(text)); // Добавить текст 
        list.appendChild(item);                          // Добавить в список 
				
        // Восстановить первоначальный стиль списка и сбросить счетчик entered 
        list.className = original_class; 
        entered = 0; 
				
        return false; 
      } 
    }; 
    // Сделать все элементы списка буксируемыми 
    var items = list.getElementsByTagName("li"); 
    for(var і = 0; і < items.length; i++) items[i].draggable = true;
		
    // И зарегистрировать обработчики для поддержки буксировки элементов списка. 
    // Обратите внимание, что мы поместили эти обработчики в список и ожидаем, 
    // что события будут всплывать вверх от элементов списка. 
		
    // Этот обработчик вызывается, когда буксировка начинается внутри списка, 
    list.ondragstart = function(e) { 
      var е = е || window.event; 
      var target = e.target || e.srcElement; 
      // Если всплыло событие от элемента, отличного от <li>, игнорировать его 
      if (target.tagName !== "li") return false; 
      // Получить важный объект dataTransfer 
      var dt = e.dataTransfer; 
      // Сохранить данные и указать информацию об их формате 
      dt.setData("Text", target.innerText || target.textContent); 
      // Сообщить, что поддерживаются операции копирования и перемещения 
      dt.effectAllowed = "copyMove"; 
    };
		
    // Этот обработчик вызывается после успешного сброса 
    list.ondragend = function(e) { 
      е = е || window.event; 
      var target = e.target || e.srcElement; 
			
      // Если выполнялась операция перемещения, удалить элемент списка. 
      // В IE8 это свойство будет иметь значение "none", если явно 
      // не установить его в значение "move" в обработчике ondrop выше. 
      // Но принудительная установка в значение "move" для IE будет 
      // препятствовать другим браузерам дать пользователю возможность 
      // выбирать между операцияим перемещения и копирования, 
      if (e.dataTransfer.dropEffect === "move") target.parentNode.removeChild(target); 
    } 
		
    // Вспомогательная функция, используемая в обработчиках ondragenter и ondragleave. 
    // Возвращает true, если элемент а является дочерним по отношению к элементу b. 
    function ischild(a,b) { 
      for(; а; а = a.parentNode) 
        if (а === b) return true; 
      return false; 
    } 
  } 
});

содержание 17.8. События ввода текста содержание

17.8. События ввода текста

браузеры поддерживают три старых события ввода с клавиатуры. События «keydown» и «keyup» являются низкоуровневыми событиями и рассматриваются в следующем разделе. Однако событие «keypress» является высокоуровневым, сообщающим, что был введен печатаемый символ. Проект спецификации «DOM Level 3 Events» определяет более обобщенное событие «textinput», генерируемое в ответ на ввод текста, независимо от того, каким способом он был введен (например, с клавиатуры, копированием данных из буфера обмена или с помощью операции буксировки, методом ввода текста на азиатских языках или с помощью системы распознавания речи или распознавания рукописного текста). На момент написания этих строк событие «textinput» еще не поддерживалось, но браузеры на основе ядра Webkit поддерживали очень похожее событие «textInput» (с заглавной буквой «I»).

С предлагаемым событием «textinput» и реализованным в настоящее время событием «textInput» передается простой объект события, имеющий свойство data, хранящее введенный текст. (Другое предлагаемое спецификацией свойство, inputMethod, должно определять источник ввода, но оно пока не реализовано.) В случае ввода с клавиатуры свойство data обычно будет содержать единственный символ, но при вводе из других источников в нем может содержаться множество символов.

Объект события, передаваемый с событием «keypress», имеет более сложную организацию. Событие «keypress» представляет ввод единственного символа. Этот символ содержится в объекте события в виде числового значения кодового пункта Юникода и, чтобы преобразовать его в строку, необходимо использовать метод String.fromCharCode(). В большинстве браузеров кодовый пункт введенного символа сохраняется в свойстве keyCode объекта события. Однако по историческим причинам в Firefox вместо него используется свойство charCode. В большинстве браузеров событие «keypress» возбуждается только при вводе печатаемого символа. Однако в Firefox событие «keypress» возбуждается также для непечатаемых символов. Чтобы отличить эти два случая (и проигнорировать непечатаемые символы), можно проверить, существует ли свойство charCode объекта события и содержит ли оно значение 0.

События «textinput», «textinput» и «keypress» можно отменить, чтобы предотвратить ввод символа. То есть эти события можно использовать для фильтрации ввода. Например, можно предотвратить ввод алфавитных символов в поле, предназначенное для ввода числовых данных. В примере 17.6 демонстрируется модуль на языке JavaScript, реализующий такого рода фильтрацию. Он отыскивает элементы <input type=text> с дополнительным (нестандартным) атрибутом data-allowed-chars. Регистрирует обработчики событий «textinput», «textInput» и «keypress» для всех найденных элементов и ограничивает возможность ввода символами, перечисленными в атрибуте data-allowed-chars. Первый комментарий в начале примера 17.6 включает образец разметки HTML, использующей этот модуль.

Пример 17.6. Фильтрация ввода пользователя

/**
* InputFilter.js: фильтрация ввода для элементов <input>
*
* Этот модуль отыскивает все элементы <input type="text"> в документе, имеющие
* атрибут "data-allowed-chars". Регистрирует обработчики событий keypress, textinput
* и textinput для этих элементов, чтобы ограничить набор допустимых для ввода символов,
* чтобы разрешить вводить только символы, указанные в атрибуте. Если элемент <input>
* также имеет атрибут "data-messageid", значение этого атрибута интерпретируется как id
* другого элемента документа. Если пользователь вводит недопустимый символ, элемент
* с указанным id делается видимым. Если пользователь вводит допустимый символ, элемент
* с сообщением скрывается. Данный элемент с сообщением предназначается для вывода
* пояснений, почему ввод пользователя был отвергнут. Его оформление необходимо
* реализовать с помощью CSS так, чтобы изначально он был невидим.
*
* Ниже приводится образец разметки HTML, использующей этот модуль.
* Zipcode: <inputtext"
* data-allowed-chars="0123456789" data-messageid="zipwarn">
* <span>TonbKo цифры</зрап>
*
* Этот модуль полностью реализован в ненавязчивом стиле: он не определяет
* никаких переменных в глобальном пространстве имен.
*/
whenReady(function () { // Вызовет эту функцию, когда документ будет загружен
  // Отыскать все элементы <input>
  var inputelts = document.getElementsByTagName("input");
  // Обойти их в цикле
  for(var і = 0 ; і < inputelts.length; i++) {
    var elt = inputelts[i];
    // Пропустить элементы, не являющиеся текстовыми полями ввода
    // и не имеющие атрибута data-allowed-chars.
    if (elt.type != "text" || !elt.getAttribute(”data-allowed-chars")) continue;

    // Зарегистрировать наш обработчик события в этом элементе input
    // keypress - старое событие и реализовано во всех браузерах.
    // textInput (смешанный регистр символов) поддерживается в Safari
    // и Chrome с 2010 года.
    // textinput (все символы строчные) - версия проекта
    // стандарта "DOM Level 3 Events",
    if (elt.addEventListener) {
      elt.addEventListener("keypress", filter, false);
      elt.addEventListener("textlnput", filter, false);
      elt.addEventListened"textinput", filter, false);
    }
    // textinput не поддерживается версиями IE, в которых не реализован
    // метод addEventListener()
    else {
      elt.attachEvent("onkeypress", filter);
    }
  }

  // Обработчик событий keypress и textlnput, фильтрующий ввод пользователя
  function filter(event) {
    // Получить объект события и целевой элемент target
     var е = event || window.event; // Модель стандартная или IE
    var target = e.target || e.srcElement; // Модель стандартная или IE
    var text = null; // Введенный текст

    // Получить введенный символ или текст
    if (e.type === "textinput" || e.type === "textlnput")
      text = e.data;
    else { // Это было событие keypress
      // Введенный печатаемый символ в Firefox сохраняется в свойстве charCode
      var code = е.charCode || e.keyCode;

      // Если была нажата какая-либо функциональная клавиша, не фильтровать ее
      if (code < 32 || // Управляющий символ ASCII
               е.charCode == 0 || // Функциональная клавиша (в Firefox)
              e.ctrlKey || e.altKey) // Удерживаемая клавиша-модификатор
        return; // Не фильтровать это событие

      // Преобразовать код символа в строку
      var text = String.fromCharCode(code);
    }

    // Отыскать необходимую нам информацию в этом элементе input
    var allowed = target.getAttribute("data-allowed-chars");// Допустимые символы
    var messageid = target.getAttribute("data-messageid"); // Сообщение id
    if (messageid) // Если указано значение id, получить элемент
      var messageElement = document.getElementByld(messageid);

    // Обойти в цикле символы во введенном тексте
    for(var і = 0; і < text.length; i++) {
       var c = text.charAt(i);
      if (allowed.indexOf(c) == -1) { // Недопустимый символ?
        // Отобразить элемент с сообщением, если указан
        if (messageElement) messageElement.style.visibility="visible";
        // Отменить действия по умолчанию, чтобы предотвратить вставку текста
        if (е.preventDefault) е.preventDefault();
        if (е.returnValue) е.returnValue = false;
        return false;
      }
    }

    // Если все символы оказались допустимыми, скрыть элемент
    // с сообщением, если он был указан.
    if (messageElement) messageElement.style.visibility = "hidden";
  }
}):

События «keypress» и «textinput» генерируются непосредственно перед фактической вставкой нового текста в элемент документа, обладающий фокусом ввода, благодаря чему обработчики этих событий могут предотвратить вставку текста, отменив событие. браузеры также реализуют событие «input», которое возбуждается после вставки текста в элемент. Это событие нельзя отменить и соответствующий ему объект события не содержит информации о вставленном тексте - оно просто извещает о том, что текстовое содержимое элемента изменилось. Если, к примеру, потребуется обеспечить ввод только символов в верхнем регистре, можно определить обработчик события «input», как показано ниже:

SURNAME: <input type="text">

Событие «input» стандартизовано в спецификации HTML5 и поддерживается всеми современными браузерами, кроме IE. Похожего эффекта в IE можно добиться, обнаруживая изменение значения свойства value текстового элемента ввода с помощью нестандартного события «propertychange*. В примере 17.7 демонстрируется, как можно реализовать преобразование всех вводимых символов в верхний регистр переносимым образом.

Пример 17.7. Использование события «propertychange» для определения факта ввода текста

function forceToUpperCase(element) {
  if (typeof element === "string") element=document.getElementByld(element);
  element.oninput = upcase;
  element.onpropertychange = upcaseOnPropertyChange;

  // Простой случай: обработчик события input
  function upcase(event) { this.value = this.value.toUpperCase(): }
  // Сложный случай: обработчик события propertychange
  function upcaseOnPropertyChange(event) {
    var e = event || window.event;
    // Если значение свойства value изменилось
    if (e.propertyName === "value") {
      // Удалить обработчик onpropertychange, чтобы избежать рекурсии
      this.onpropertychange = null;
      // Преобразовать все символы в верхний регистр
      this.value = this.value.toUpperCase();
      // И восстановить обработчик события propertychange
      this.onpropertychange = upcaseOnPropertyChange;
    }
  }
}

содержание 17.9. События клавиатуры содержание

События «keydown» и «keyup» возбуждаются, когда пользователь нажимает или отпускает клавишу на клавиатуре. Они генерируются для клавиш-модификаторов, функциональных клавиш и алфавитно-цифровых клавиш. Если пользователь удерживает клавишу нажатой настолько долго, что включается режим автоповтора, будет сгенерировано множество событий «keydown», прежде чем появится событие «keyup».

Объект события, соответствующий этим событиям, имеет свойство keyCode с числовым значением, которое определяет нажатую клавишу. Для клавиш, генерирующих печатаемые символы, в общем случае свойство keyCode содержит кодовый пункт Юникода, соответствующий основному символу, изображенному на клавише. Клавиши с буквами всегда генерируют значения keyCode, соответствующие символам в верхнем регистре, независимо от состояния клавиши Shift, поскольку именно такие символы изображены на клавишах. Аналогично цифровые клавиши всегда генерируют значения keyCode, соответствующие цифровым символам, изображенным на клавишах, даже если при этом вы удерживали нажатой клавишу Shift, чтобы ввести знак препинания. Для клавиш, не соответствующих печатаемым символам, свойство keyCode будет иметь некоторое другое значение. Эти значения свойства keyCode никогда не были стандартизованы. Однако в разных браузерах они отличаются не настолько сильно, чтобы нельзя было обеспечить переносимость. Это демонстрирует пример 17.8, включающий реализацию отображения значений keyCode в имена функциональных клавиш.

Подобно объектам событий мыши, объекты событий клавиатуры имеют свойства altKey, ctrlKey, metaKey и shiftKey, которые получают значение true, если в момент возникновения события удерживалась нажатой соответствующая клавиша-модификатор.

События «keydown* и «кеуuр», а также свойство keyCode используются уже более десяти лет, но они так и не были стандартизованы. Проект стандарта «DOM Level 3 Events» стандартизует типы «keydown» и «кеуuр» событий, но не стандартизует свойство keyCode. Вместо этого он определяет новое свойство key, которое должно содержать название клавиши в виде строки. Если клавиша соответствует печатаемому символу, свойство key должно содержать этот печатаемый символ. Для функциональных клавиш свойство key должно содержать такие значения, как «F2», «Home» или «Left».

На момент написания этих строк свойство key, определяемое стандартом «DOM Level 3 Events», еще не было реализовано ни в одном из браузеров. Однако браузеры на базе механизма Webkit, Safari и Chrome определяют в объектах этих событий свойство keyIdentifier. Для функциональных клавиш, подобно свойству key, свойство keyldentifier содержит не число, а строку с именем клавиши, таким как «Shift» или «Enter». Для клавиш, соответствующих печатаемым символам, это свойство содержит менее удобное в использовании строковое представление кодового пункта Юникода символа. Например, клавише «А» соответствует значение «U+0041».

В примере 17.8 определяется класс Keymap, который отображает идентификаторы комбинаций клавиш, такие как «PageUp», «Alt_Z» и «ctrl+alt+shift+F5» в функции на языке JavaScript, вызываемые в ответ на нажатия этих комбинаций. Определения привязок клавиш передаются конструктору Кеуmap() в форме объекта JavaScript, имена свойств которого соответствуют идентификаторам комбинаций клавиш, а значения этих свойств содержат ссылки на функции-обработчики. Добавление и удаление привязок осуществляется с помощью методов bind() и unbind(). Устанавливается объект Keymap в HTML-элемент (обычно в объект Document) с помощью метода install(). При установке объекта Keymap в этом элементе регистрируется обработчик события «keydown». Каждый раз, когда нажимается клавиша, обработчик проверяет наличие функции, соответствующей этой комбинации. Если функция существует, она вызывается. Обработчик события «keydown» использует свойство key, определяемое стандартом «DOM Level 3 Events», если оно существует. В противном случае он пытается использовать Webkit-свойство keyIdentifіег. И как запасной вариант, обработчик использует нестандартное свойство keyCode. Пример 17.8 начинается с длинного комментария, подробно описывающего работу модуля.

Пример 17.8. Класс Keymap для обработки нажатий комбинаций клавиш

/*
* Keymap.js: связывает события клавиатуры с функциями-обработчиками.
*
* Этот модуль определяет класс Keymap. Экземпляр этого класса представляет
* собой отображение идентификаторов комбинаций клавиш (определяемых ниже)
* в функции-обработчики. Объект Keymap можно установить в HTML-элемент
* для обработки событий keydown. Когда возникает это событие, объект Keymap
* использует свою карту привязок для вызова соответствующего обработчика.
*
* При создании объекта Keymap конструктору можно передать JavaScript-объект,
* представляющий начальную карту привязок. Имена свойств этого объекта должны
* соответствовать идентификаторам клавиш, а значениями должны быть функции-обработчики.
* После создания объекта Keymap в него можно добавлять новые привязки, передавая
* идентификатор клавиши и функцию-обработчик методу bind(). Имеется также возможность
* удалить привязку, передав идентификатор клавиши методу unbind().
*
* Чтобы задействовать объект Keymap, следует вызвать его метод install(), передав ему
* HTML-элемент, такой как объект document. Метод install() добавит в указанный объект
* обработчик события onkeydown. Когда этот обработчик будет вызван, он определит
* идентификатор нажатой клавиши и вызовет функцию-обработчик (если таковая имеется),
* привязанную к этому идентификатору клавиши. Один и тот же объект Keymap
* можно установить сразу в несколько HTML-элементов.
*
* Идентификаторы клавиш
*
* Идентификатор клавиши - это нечувствительная к регистру символов строка,
* представляющая клавишу, плюс любое количество удерживаемых нажатыми
* клавиш-модификаторов. Именем клавиши является основной текст, изображаемый
* на клавише. Допустимыми именами клавиш являются: "А", "7", "F2", "PageUp",
* "Left", "Backspace" и "Esc".
*
* Список имен находится в объекте Keymap.keyCodeToKeyName, внутри этого модуля.
* Они являются подмножеством имен, определяемых стандартом "DOM Level 3".
* Кроме того, этот класс будет использовать свойство key, когда оно будет реализовано.
*
* Идентификатор клавиши может также включать имена клавиш-модификаторов.
* Это имена Alt, Ctrl, Meta и Shift. Они нечувствительны к регистру символов и должны
* отделяться от имени клавиши и друг от друга пробелами или подчеркиваниями, дефисами
* или знаками +. Например: "SHIFT+A”, "Alt_F2", "meta-v" и "Ctrl alt left".
* В компьютерах Mac клавише Meta соответствует клавиша Command, а клавише Alt -
* клавиша Option. Некоторые браузеры отображают клавишу Windows в клавишу Meta.
*
* Функции-обработчики
*
* Обработчики вызываются как методы объекта document или элемента документа,
* в зависимости от того, куда был установлен объект Keymap, и им передаются
* два аргумента:
* 1) объект события keydown
* 2) идентификатор нажатой клавиши
* Значение, возвращаемое функцией, становится возвращаемым значением
* обработчика события keydown. Если функция-обработчик вернет false,
* объект Keymap прервет всплытие события и предотвратит выполнение любых
* действий по умолчанию, связанных с событием keydown.
*
* Ограничения
*
* Функцию-обработчик можно привязать не ко всем клавишам. Некоторые комбинации
* используются самой операционной системой (например, Alt-F4). А некоторые комбинации
* могут перехватываться браузером (например, Ctrl-S). Эта реализация зависит
* от особенностей браузера, ОС и региональных настроек. Вы с успехом можете
* использовать функциональные клавиши и функциональные клавиши с модификаторами,
* а также алфавитно-цифровые клавиши без модификаторов. Комбинации алфавитно-цифровых
* клавиш с модификаторами Ctrl и Alt менее надежны.
*
* Поддерживается большинство знаков препинания, кроме дефиса, для ввода которых
* не требуется удерживать клавишу Shift ('=[];',./) на клавиатурах
* со стандартной раскладкой US. Но они плохо совместимы с другими
* раскладками клавиатур, и их желательно не использовать.
*/
// Функция-конструктор
function Keymap(bindings) {
  this.map = {}; // Определить отображение идентификатор->обработчик
  if (bindings) { // Скопировать в него начальную карту привязок
    for(name in bindings) this.bind(name, bindings[name]);
  }
}

// Связывает указанный идентификатор клавиши с указанной функцией-обработчиком
Keymap.prototype.bind = function(key, func) {
  this.map[Keymap.normalize(key)] = func;
};

// Удаляет привязку для указанного идентификатора клавиши
Keymap.prototype.unbind = function(key) {
  delete this.map[Keymap.normalize(key)];
};

// Устанавливает этот объект Keymap в указанный HTML-элемент
Keymap.prototype.install = function(element) {
  var keymap = this;
  // Определить функции-обработчика события
  function handler(event) { return keymap.dispatch(event, element); }

  // Установить ее
  if (element.addEventListener)
    element.addEventListener("keydown", handler, false);
  else
    if (element.attachEvent)
      element.attachEvent("onkeydown", handler);
};

// Этот метод делегирует обработку события клавиатуры, опираясь на привязки.
Keymap.prototype.dispatch = function(event, element) {
  // Изначально нет ни имен клавиш-модификаторов, ни имени клавиши
  var modifiers = ""
  var keyname = null;

  // Сконструировать строки модификаторов в каноническом виде из символов
  // в нижнем регистре, расположив их в алфавитном порядке.
  if (event.altKey) modifiers += "alt_”;
  if (event.ctrlKey) modifiers += "Ctrl,";
  if (event.metaKey) modifiers += "meta_";
  if (event.shiftKey) modifiers += "shift.”;

  // Имя клавиши легко получить, если реализовано свойство key,
  // определяемое стандартом DOM Level 3:
  if (event.key) keyname = event.key;
  // Для получения имен функциональных клавиш в Safari и Chrome можно
  // использовать свойство keyldentifier
  else
    if (event. keyldentifier&&event. keyldentifier. substrings, 2) !== "U+")
      keyname = event.keyldentifier;
    // В противном случае можно использовать свойство keyCode и отображение код->имя ниже
    else
      keyname = Keymap.keyCodeToKeyName[event.keyCode];
  // Если имя клавиши не удалось определить, просто проигнорировать событие
  // и вернуть управление,
  if (!keyname) return;

  // Канонический идентификатор клавиши состоит из имен модификаторов
  // и имени клавиши в нижнем регистре
  var keyid = modifiers + keyname.toLowerCase();

  // Проверить, имеется ли привязка для данного идентификатора клавиши
  var handler = this.map[keyid];

  if (handler) { // Если обработчик для данной клавиши, вызвать его
                 // Вызвать функцию-обработчик
    var retval = handler.call(element, event, keyid);

    // Если обработчик вернул false, отменить действия по умолчанию
    // и прервать всплытие события
    if (retval === false) {
      if (event.stopPropagation)
        event.stopPropagation();    // модель DOM
      else
         event.cancelBubble = true; // модель IE
      if (event.preventDefault)
        event.preventDefault();     // DOM
      else
        event.returnValue = false;  // IE
    }

    // Вернуть значение, полученное от обработчика
     return retval;
  }
};

// Вспомогательная функция преобразования идентификатора клавиши в каноническую форму.
// Нам необходимо преобразовать идентификатор "meta” в "Ctrl", чтобы превратить
// идентификатор Meta-C в "Command-C" на компьютерах Маc и в "Ctrl-C" на всех остальных.
Keymap.normalize = function(keyid) {
  keyid = keyid.toLowerCaseO;                   // В нижний регистр
  var words = keyid.split(/s+|[-+_]/);          // Вычленить модификаторы
  var keyname = words.pop();                    // keyname - последнее слово
  keyname = Keymap.aliases[keyname] || keyname; // Это псевдоним?
  words.sort();                                 // Сортировать модификаторы
  words.push(keyname);                          // Поместить обратно
                                                // нормализованное имя
 return words.join                              // Объединить все вместе
};

Keymap.aliases = {  // Отображение привычных псевдонимов клавиш в их
  "escape":"esc",   // "официальные" имена, используемые в DOM Level 3,
  "delete":"del",   // и отображение кодов клавиш в имена ниже.
  "return”:"enter", // Имя и значение должны состоять только из символов
  "Ctrl":"control", // нижнего регистра.
  "space":"spacebar",
  "ins":"insert"
};

// Старое свойство keyCode объекта события keydown не стандартизовано
// Но следующие значения с успехом могут использоваться в большинстве браузеров и ОС.
Keymap.keyCodeToKeyName = {
  // Клавиши со словами или стрелками на них
  8:"Backspace", 9:"Tab", 13:"Enter", 16:"Shift", 17:"Control". 18:"Alt",
  19:"Pause”, 20:"CapsLock", 27:"Esc", 32:"Spacebar", 33:"PageUp",
  34:"PageDown", 35:"End", 36:"Home", 37:"Left", 38:"Up", 39:"Right",
  40:"Down", 45:"Insert", 46:"Del",
  // Цифровые клавиши на основной клавиатуре (не на дополнительной)
  48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9".

  // Буквенные клавиши. Обратите внимание, что здесь не различаются
  // символы верхнего и нижнего регистров
  65:"А", 66:"В", 67:"С". 68:"D". 69:"Е". 70:"F", 71:"G", 72:"Н". 73:"I",
  74:"J", 75:"К", 76:"L”, 77:"М", 78:"N". 79:"О". 80:"Р", 81:"Q". 82:"R",
  83:”S", 84:"Т", 85:"U". 86:"V", 87:"W", 88:"X". 89:"Y", 90:"Z",

  // Цифровые клавиши на дополнительной клавиатуре и клавиши со знаками препинания.
  // (Не поддерживаются в Opera.)
  96:"О",97:"1",98:"2",99:"3",100:"4",101:"5",102:”6",103:"7",104:"8",
  105:"9",106:"Multiply", 107:"Add", 109:"Subtract", 110:"Decimal",
  111:"Divide",

  // Функциональные клавиши
  112:"F1", 113:"F2", 114:"F3", 115:"F4". 116:"F5", 117:"F6",
  118:”F7", 119:"F8", 120:"F9", 121:"F10", 122:"F11", 123:"F12",
  124:"F13", 125:"F14", 126:"F15", 127:”F16", 128:"F17", 129:"F18",
  130:"F19", 131:"F20", 132:"F21”, 133:"F22”, 134:"F23", 135:"F24".

  // Клавиши со знаками препинания, для ввода которых не требуется
  // удерживать нажатой клавишу Shift.
  // Дефис не может использоваться переносимым способом: FF возвращает
  // тот же код, что и для клавиши Subtract
  59:";”, 61:"=", 186:";", 187:"=", // Firefox и Opera возвращают 59,61
  188:",", 190:".", 191:"/", 192: , 219:"[", 220:"". 221:"]", 222:"'"
};