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

Теперь самое время рассмотреть один ключевой вопрос Web-программирования: события, их возникновение и обработка.

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

Рассматривая примеры Web-сценариев, мы исходили из предположения, что они выполняются при загрузке Web-страницы. Как мы уже знаем из главы 14, Web-сценарий исполняется в том месте HTML-кода Web-страницы, в котором присутствует создающий его тег <SCRIPT>. При этом неважно, является Web-сценарий внутренним (помещенном прямо в HTML-код) или внешним (хранящимся в отдельном файле Web-сценария).

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

Событием в терминологии Web-программирования называется некое условие, которое возникает в Web-обозревателе в ответ на действия посетителя или в процессе работы самого Web-обозревателя. Так, щелчок левой кнопкой мыши на элементе Web-страницы приводит к возникновению события "щелчок левой кнопкой мыши", а перемещение курсора мыши над элементом Web-страницы — "перемещение курсора мыши". Нажатие клавиши на клавиатуре приводит к возникновению события "нажатие клавиши", а ошибка в загрузке изображения — "ошибка загрузки".

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

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

Что может делать обработчик события? Да что угодно! При наведении курсора мыши он может привязывать к элементу Web-страницы другой стилевой класс, меняя его представление. (Именно такой обработчик события мы создали в главе 14.) При щелчке левой кнопкой мыши на элементе Web-страницы — разворачивать или сворачивать блочный контейнер, открывая или скрывая его содержимое. А при изменении размеров окна Web-обозревателя — менять размеры блочных контейнеров, чтобы полностью занять ими клиентскую область окна.

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

- Обработчик события оформляется в виде функции, которая принимает два па-раметра. Подробнее об этом мы поговорим потом.

- Обработчик события привязывается к конкретному элементу Web-страницы, в котором возникают события, требующие обработки. Так, если нужно обработать событие "щелчок левой кнопкой мыши" в каком-либо абзаце, обработчик привязывается к данному абзацу.

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

- Обработчик события выполняется только при возникновении заданного события в элементе Web-страницы, к которому он привязан. Во время загрузки Web-страницы он не выполняется.

- Мы можем привязать один и тот же обработчик сразу к нескольким элементам Web-страницы и нескольким событиям. Так, один и тот же обработчик может обрабатывать событие "щелчок левой кнопкой мыши" в абзаце и в гиперссылке. Кстати, так часто и делают.


События объекта Element

Самые полезные для нас на данный момент события, поддерживаемые объектом Element библиотеки Ext Core, представлены в табл. 15.1. Их довольно много, и некоторые из них поддерживаются только определенными элементами Web-страницы.

События объекта Element

 

Привязка и удаление обработчиков событий

Метод on объекта Element выполняет привязку указанной функции к указанному событию данного элемента Web-страницы в качестве обработчика:

<экземпляр объекта Element>.on(<событие>, <функция-обработчик>)

Первым параметром методу передается строка с названием события, к которому выполняется привязка обработчика. Названия событий приведены в первом столбце табл. 15.1.

Второй параметр — функция, которая станет обработчиком события. Эта функция должна принимать следующие параметры:

- первый — экземпляр объекта EventObject, представляющий сведения о событии и позволяющий им управлять (мы рассмотрим этот объект потом);

- второй — экземпляр объекта HTMLElement, представляющий элемент Web-страницы, в котором изначально возникло данное событие.

Кроме того, в функцию-обработчик неявно передается еще один параметр — экземпляр объекта HTMLElement, представляющий элемент Web-страницы, в котором в данный момент обрабатывается данное событие, — тот самый элемент, к которому привязан этот обработчик. Событие могло возникнуть в нем изначально, а могло всплыть из дочернего элемента; подробнее об этом будет рассказано в следующем разделе. Данный параметр доступен в теле функции-обработчика через переменную this.

Пример:

Ext.get("navbar").on("mouseover",
function(e, t) {
Ext.get(this).addClass("hovered");
}
);

Здесь мы привязываем к списку navbar обработчик события mouseover. Первый параметр метода on определяет название события, которое мы хотим обрабатывать. Второй параметр этого метода содержит объявление функции-обработчика.

В теле функции-обработчика мы обращаемся к переменной this, чтобы получить экземпляр объекта HTMLElement, представляющий элемент Web-страницы, чье событие мы обрабатываем. Чтобы получить из него соответствующий экземпляр объекта Element, мы используем метод get. После чего привязываем к полученному экземпляру объекта Element стилевой класс hovered вызовом метода addClass.

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

Ext.get("navbar").on("mouseover",
function() {
Ext.get(this).addClass("hovered");
}
);

Мы можем оформить обработчик события в виде функции, имеющей имя, а потом указать это имя в качестве второго параметра метода on:

function navbarMouseOver() {
Ext.get(this).addClass("hovered");
}
Ext.get("navbar").on("mouseover", navbarMouseOver);

Это полезно, если мы хотим привязать один обработчик сразу к нескольким событиям одного или нескольких элементов Web-страницы.

Метод removeAllListeners объекта Element удаляет все привязанные к данному элементу Web-страницы обработчики событий. Он не принимает параметров.

Пример:

Ext.get("navbar").removeAllListeners();


Всплытие и действие по умолчанию

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

Давайте рассмотрим пункты списков, формирующих полосу навигации, и "внешнего", и вложенного. Мы привязали к ним обработчики событий mouseOver и mouseOut — это выполняет второй Web-сценарий, написанный нами в главе 14. Его фрагмент приведен в листинге 15.5.

Обработчики событий

Откроем Web-страницу index.htm в Web-обозревателе и наведем курсор мыши на пункт "HTML" "внешнего" списка. Рамка вокруг него тотчас станет более темной. Уберем курсор — и рамка примет прежний цвет. Обработчики событий mouseOver и mouseOut работают.

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

Но при этом изменит свой цвет и рамка вокруг пункта "HTML" "внешнего" списка! Выходит, данные события возникают не только в пункте "AUDIO" вложенного списка, но и в пункте "HTML" "внешнего" списка, в который он вложен. Почему?

Вот тут мы столкнулись со всплытием событий. Событие, возникшее в каком-либо элементе Web-страницы, после этого возникает в его родителе, далее в родителе родителя и т. д., пока оно не достигнет секции тела Web-страницы (тега <BODY>). Можно сказать, что событие "всплывает" из элемента в элемент "вверх" по уровню их вложенности друг в друга.

Давайте рассмотрим всплытие событий на примере нашей полосы навигации. Мы наводим курсор мыши на пункт "AUDIO" вложенного списка. В нем (в теге <LI>) возникает событие mouseOver. Поскольку к данному пункту списка привязан обработчик этого события, Web-обозреватель его выполняет. Далее событие "всплывает" в сам вложенный список (тег <UL>). Никаких обработчиков событий мы к нему не привязали, поэтому событие сразу же следует в пункт "HTML" "внешнего" списка, в котором находится вложенный список. К нему, опять же, привязан обработчик события mouseOver, который выполняется Web-обозревателем. Далее событие "всплывает" во "внешний" список, контейнер cnavbar и секцию тела Web-страницы, в которой и прекращает свое существование.

Теперь посмотрим на пункт "AUDIO" вложенного списка. В него вложен тег <CODE>, в который, в свою очередь, вложена гиперссылка (тег <A>), а в нее — уже текст "AUDIO". Когда мы наводим курсор мыши на этот текст, событие mouseOver возникает-то в теге <A>! Потом оно "всплывает" в тег <CODE>, а уже после этого — в тег <LI>, формирующий пункт списка "AUDIO", где и обрабатывается.

А сейчас представим, что событие mouseOver не всплывает. Тогда, чтобы обработать его в пункте вложенного списка, нам пришлось бы привязывать разные обработчики к тегам <LI>, <CODE> и <A>. Представляете, сколько потребуется кода! Который в этом случае будет еще и заметно сложнее...

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

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

- Первый явный параметр — экземпляр объекта EventObject, хранящий сведения о событии. Разговор о нем пока отложим.

- Второй явный параметр — экземпляр объекта HTMLElement, представляющий элемент Web-страницы, в котором изначально возникло это событие. Отметим — возникло изначально, т. е. событие гарантированно не всплыло в него из какого-либо дочернего элемента.

- Неявный параметр, доступный в теле функции-обработчика через переменную this, — экземпляр объекта HTMLElement, представляющий элемент Web-страницы, в котором это событие обрабатывается, т. е. тот элемент, к которому привязан обработчик. Причем событие могло как возникнуть в этом элементе изначально, так и всплыть из дочернего элемента.

Если событие всплыло в данный элемент Web-страницы из дочернего элемента, то оба эти параметра указывают на разные элементы Web-страницы. Помните об этом!

Ради эксперимента давайте изменим код второго Web-сценария, написанного в главе 14, так, как показано в листинге 15.6.

Событие mouseover

Здесь мы в теле функций-обработчиков получаем не элемент, в котором обрабатывается событие (переменная this), а элемент, в котором это событие изначально возникло (второй параметр этих функций t). Что получится в результате?

Наведем курсор мыши на текст пункта "AUDIO" списка. В теге <A> возникнет событие mouseover, которое потом всплывет в тег <CODE> и далее — в тег <LI>. В теге <LI> оно и будет обработано. Только стилевой класс hovered в результате будет привязан не к тегу <LI>, а к тегу <A>! А поскольку для этого тега больше никаких параметров рамки не задано (стилевой класс hovered задает только цвет рамки — см. главу 14), рамка вокруг него не появится. Совсем другой результат!

В табл. 15.1, перечисляющей события объекта Element, прямо указано, какие события всплывают, а какие — нет. Да-да, не все события относятся к всплывающим... Но такие "невсплывающие" события обычно очень специфичны и требуют обработки, что называется, на месте.

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

Некоторые события, возникшие в определенных элементах Web-страницы, Web-обозреватель обрабатывает сам, реализуя так называемое действие по умолчанию. Так, действие по умолчанию для события click в гиперссылке — переход на целевую Web-страницу, а для события focus — получение гиперссылкой фокуса ввода.

Обработчик, привязанный к событию, для которого Web-обозреватель выполняет действие по умолчанию, выполняется перед тем, как действие по умолчанию будет выполнено. Это предоставляет возможность отмены действия по умолчанию (не для всех событий) — вызовом особого метода все того же объекта EventObject. Мы рассмотрим данный метод очень скоро.


Получение сведений о событии. Объект EventObject

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

Метод getCharCode возвращает код алфавитно-цифрового символа, введенного с клавиатуры, в кодировке Unicode в виде числа. Он не принимает параметров.

Коды алфавитно-цифровых символов можно узнать с помощью утилиты Таблица символов, поставляемой в составе Windows.

Метод getKey возвращает код нажатой на клавиатуре клавиши в кодировке Unicode в виде числа. Он не принимает параметров.

Коды клавиш клавиатуры можно найти на Web-странице http://msdn.microsoft.com/ en-us/library/ms927178.aspx.

Методы getPageX и getPageY возвращают, соответственно, горизонтальную и вертикальную координаты курсора мыши относительно Web-страницы в виде чисел в пикселах. Они не принимают параметров.

Метод preventDefault отменяет действия по умолчанию для события. Он не принимает параметров и не возвращает значения.

Метод stopPropagation отменяет дальнейшее всплытие события. Он не принимает параметров и не возвращает значения.

Метод stopEvent отменяет действия по умолчанию для события и отменяет его дальнейшее всплытие. Фактически он объединяет действие методов preventDefault и stopPropagation. Этот метод также не принимает параметров и не возвращает значения.

Мы можем исправить код нашего второго Web-сценария, написанного в главе 14, как показано в листинге 15.7, и посмотреть, что получится.

Объект EventObject

 

Объект CompositeElementLite

Вернемся в начало этой главы и вспомним, как мы получали доступ к нужному нам элементу Web-страницы.

Мы можем получить доступ к одному элементу Web-страницы:

var elCMain = Ext.get("cmain");

Или сразу к нескольким:

var clContainers = Ext.select("DIV");

Мы помним, что метод select объекта Ext возвращает экземпляр объекта CompositeElementLite — коллекцию экземпляров объекта Element, представляющих все подходящие под указанный селектор CSS элементы Web-страницы. Настала пора рассмотреть объект CompositeElementLite подробнее.

Прежде всего, объект CompositeElementLite поддерживает все методы объекта Element, предназначенные для управления привязкой стилевых классов, атрибутами тега и стиля, привязкой обработчиков событий и т. п. Так что мы можем привязывать стилевые классы и обработчики событий сразу к нескольким элементам Web-страницы. (Собственно, мы это уже делали.)

Метод getCount возвращает число элементов данной коллекции. Он не принимает параметров:

var i = clContainers.getCount();

В переменной i окажется число элементов в полученной ранее коллекции clContainers — 5.

Метод item возвращает элемент данной коллекции с указанным индексом в виде экземпляра объекта Element:

<экземпляр объекта CompositeElementLite>.item(<индекс>)

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

Пример:

var elDiv = clContainers.item(i — 1);

В переменной elDiv окажется последний элемент коллекции clContainers. Поскольку элементы коллекции, как и элементы обычного массива, нумеруются, начиная с нуля, мы передали методу item значение, на единицу меньшее, чем число элементов в коллекции.

А в следующем примере мы последовательно извлекаем все элементы коллекции clContainers и выполняем над ними какие-то действия:

for(var k = 0; k < i; k++) {
var elDiv = clContainers.item(k);
// Что-то делаем
}

Метод indexOf возвращает индекс указанного элемента в данной коллекции в виде числа:

<экземпляр объекта CompositeElementLite>.indexOf(<элемент>)

Единственным параметром этому методу передается искомый элемент. Им может быть строка с именем элемента, экземпляр объекта Element или HTMLElement.

Если переданный элемент в коллекции отсутствует, метод indexOf возвращает –1.

Пример:

var ind = clContainers.indexOf("cnavbar");

В переменной ind окажется индекс контейнера cnavbar в коллекции clContainers — 1.

Метод each вызывает для каждого элемента данной коллекции указанную функцию:

<экземпляр объекта CompositeElementLite>.each(<функция>)

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

- элемент коллекции в виде экземпляра объекта Element;

- сама эта коллекция в виде экземпляра объекта CompositeElementLite;

- индекс элемента коллекции в виде числа.

Кроме того, элемент коллекции доступен в теле этой функции через переменную this.

В примере из листинга 15.8 мы привязываем к каждому контейнеру, входящему в коллекцию clContainers, стилевой класс hovered.

Стилевой класс hovered

Другой вариант того же Web-сценария иллюстрирует листинг 15.9.

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

Еще проще написать так:

clContainers.addClass("hovered");

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