renovations

This commit is contained in:
Ilya Kantor 2015-02-08 10:03:24 +03:00
parent e1d099ae97
commit 53d9080aad
50 changed files with 653 additions and 471 deletions

View file

@ -1,8 +1,10 @@
# Генерация событий на элементах
Можно не только слушать браузерные события, но и генерировать их самому.
Можно не только назначать обработчики на события, но и генерировать их самому.
Это нужно довольно редко, преимущественно -- для целей автоматического тестирования.
Мы будем использовать это позже для реализации компонентной архитектуры, при которой элемент, представляющий собой, к примеру, меню, генерирует события, к этому меню относящиеся -- `select` (выбран пункт меню) или `open` (меню раскрыто), и другие.
Кроме того, события можно генерировать для целей автоматического тестирования.
[cut]
@ -10,7 +12,7 @@
Вначале рассмотрим современный способ генерации событий, по стандарту [DOM 4](http://www.w3.org/TR/dom/#introduction-to-dom-events). Он поддерживается всеми браузерами, кроме IE11-. А далее рассмотрим устаревшие варианты, поддерживаемые IE.
**Объект события создаётся при помощи конструктора [Event](http://www.w3.org/TR/dom/#event).**
Объект события в нём создаётся при помощи встроенного конструктора [Event](http://www.w3.org/TR/dom/#event).
Синтаксис:
@ -23,16 +25,16 @@ var event = new Event(тип события[, флаги]);
<li>*Тип события* -- может быть как своим, так и встроенным, к примеру `"click"`.</li>
<li>*Флаги* -- объект вида `{ bubbles: true/false, cancelable: true/false }`, где свойство `bubbles` указывает, всплывает ли событие, а `cancelable` -- можно ли отменить действие по умолчанию.
Не обязателен, по умолчанию `{bubbles: false, cancelable: false}`.</li>
Флаги по умолчанию: `{bubbles: false, cancelable: false}`.</li>
</ul>
### Метод dispatchEvent
## Метод dispatchEvent
Затем, чтобы инициировать событие, запускается `elem.dispatchEvent(event)`.
Событие обрабатывается "как обычно", передаётся обработчикам, всплывает... Этот метод возвращает `false`, если событие было отменено при помощи `preventDefault()`. Конечно, отмена возможна лишь если событие изначально было создано с флагом `cancelable`.
При этом событие срабатывает наравне с браузерными, то есть обычные браузерные обработчики на него отреагируют. Если при создании указан флаг `bubbles`, то оно будет всплывать.
При просмотре примера ниже кнопка будет нажата скриптом:
При просмотре примера ниже кнопка обработчик `onclick` на кнопке сработает сам по себе, событие генерируется скриптом:
```html
<!--+ run -->
@ -44,6 +46,50 @@ var event = new Event(тип события[, флаги]);
</script>
```
## Отмена действия по умолчанию
На сгенерированном событии, как и на встроенном браузерном, обработчик может вызвать метод `event.preventDefault()`. Тогда `dispatchEvent` возвратит `false`.
Остановимся здесь подробнее. Обычно такой вызов предотвращает действие браузера. В случае, если событие придумано нами -- никакого действия браузера, конечно, нет, но код, который генерирует событие, может быть заинтересован узнать, что его "отменили" и не продолжать свои действия.
Иначе говоря, `event.preventDefault()` является возможностью для обработчика сообщить в сгенерировавший событие код, что некие действия продолжать не надо.
В примере ниже функция `hide()` генерирует событие `hide` на элементе `#rabbit`, уведомляя всех интересующихся, что кролик собирается спрятаться.
И, если никакой обработчик не отменит действие по умолчанию, то кролик действительно исчезнет:
```html
<!--+ run -->
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<script>
// прячемся через 2 секунды
setTimeout(hide, 2000);
function hide() {
var event = new Event("hide", {cancelable: true});
if (!rabbit.dispatchEvent(event)) {
alert('действие отменено');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Вызвать preventDefault?")) {
event.preventDefault();
}
});
</script>
```
[smart header="Как отличить реальное нажатие от скриптового?"]
В целях безопасности иногда хорошо бы знать -- инициировано ли действие посетителем или это кликнул скрипт.
@ -52,7 +98,9 @@ var event = new Event(тип события[, флаги]);
Оно на момент написания статьи поддерживается IE и Firefox и равно `true`, если посетитель кликнул сам, и всегда `false` -- если событие инициировал скрипт.
[/smart]
Браузер автоматически ставит следующие свойства объекта `event`:
## Другие свойства событий
При создании события браузер автоматически ставит следующие свойства:
<ul>
<li>`isTrusted: false` -- означает, что событие сгенерировано скриптом, это свойство изменить невозможно.</li>
@ -64,16 +112,16 @@ var event = new Event(тип события[, флаги]);
Другие свойства события, если они нужны, например координаты для события мыши -- можно присвоить в объект события позже, например:
```js
var event = new Event("click");
var event = new Event("click", {bubbles: true, cancelable: false});
event.clientX = 100;
event.clientY = 100;
```
### Пример с hello
## Пример со всплытием
Можно генерировать события с любыми названиями.
Сгенерируем совершенно новое событие `"hello"` и поймаем его на `document`.
Для примера сгенерируем совершенно новое событие `"hello"`:
Всё, что для этого нужно -- это флаг `bubbles`:
```html
<!--+ run -->
@ -99,11 +147,11 @@ event.clientY = 100;
<li>Чтобы событие всплывало и его можно было отменить, указан второй аргумент `new Event`.</li>
</ol>
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, они создаются и работают совершенно одинаково.
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, их можно сгенерировать и запустить совершенно одинаково.
## Конструкторы MouseEvent, KeyboardEvent и другие
Для конкретных типов событий есть свои конструкторы.
Для некоторых конкретных типов событий есть свои, специфические, конструкторы.
Вот список конструкторов для различных событий интерфейса которые можно найти в спецификации [UI Event](http://www.w3.org/TR/uievents/):
<ul>
@ -117,9 +165,9 @@ event.clientY = 100;
Вместо `new Event("click")` можно вызвать `new MouseEvent("click")`.
**Конкретный конструктор позволяет указать стандартные свойства для данного типа события.**
**Специфический конструктор позволяет указать стандартные свойства для данного типа события.**
Например:
Например, `clientX/clientY` для события мыши:
```js
//+ run
@ -135,7 +183,7 @@ alert(e.clientX); // 100
*/!*
```
Сравните это с обычным `Event`:
Это нельзя было бы сделать с обычным конструктором `Event`:
```js
//+ run
@ -147,21 +195,19 @@ var e = new Event("click", {
});
*!*
alert(e.clientX); // undefined
alert(e.clientX); // undefined, свойство не присвоено!
*/!*
```
...То есть, "мышиные" свойства можно сразу же в конструкторе указать только если это `MouseEvent`, а `Event` их игнорирует.
Обычный конструктор `Event` не знает про "мышиные" свойства, поэтому их игнорирует.
**Использование конкретного конструктора не является обязательным, можно обойтись `Event`.**
Свойства можно присвоить и явно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
Впрочем, использование конкретного конструктора не является обязательным, можно обойтись `Event`, а свойства записать в объект отдельно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
Полный список свойств по типам событий вы найдёте в спецификации, например для `MouseEvent`: [MouseEvent Constructor](http://www.w3.org/TR/uievents/#constructor-mouseevent).
## Свои события
Для генерации встроенных событий существуют описанные выше конструкторы, а для генерации своих, нестандартных, событий существует конструктор [CustomEvent](http://www.w3.org/TR/dom/#customevent).
Для генерации своих, нестандартных, событий, хоть и можно использовать конструктор `Event`, но существует и специфический конструктор [CustomEvent](http://www.w3.org/TR/dom/#customevent).
Технически, он абсолютно идентичен `Event`, кроме небольшой детали: у второго аргумента-объекта есть дополнительное свойство `detail`, в котором можно указывать информацию для передачи в событие.
@ -190,9 +236,13 @@ alert(e.clientX); // undefined
## Старое API для IE9+
В предыдущем стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events) была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
Способ генерации событий, описанный выше, не поддерживается в IE11-, там нужен другой, более старый способ, описанный в стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events).
Она поддерживается как современными браузерами, так и IE9+. Для генерации событий используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
В нём была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
Она поддерживается как современными браузерами, так и IE9+. Там используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
Можно использовать этот немного устаревший способ, если нужно поддерживать IE9+. Далее мы на его основе создадим полифилл.
Объект события создаётся вызовом `document.createEvent`:
@ -206,7 +256,7 @@ var event = document.createEvent(eventInterface);
</li>
</ul>
**На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.**
На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.
Далее событие нужно инициализовать:
@ -306,9 +356,37 @@ void initMouseEvent (
</script>
```
Браузер, по стандарту, может сгенерировать отсутствующие свойства самостоятельно, например `pageX`, но это нужно проверять в конкретных случаях, обычно это не работает или работает некорректно.
Браузер, по стандарту, может сгенерировать отсутствующие свойства самостоятельно, например `pageX`, но это нужно проверять в конкретных случаях, иногда это не работает или работает некорректно, так что лучше указать все.
[/smart]
## Полифилл CustomEvent
Для поддержки `CustomEvent` в IE9+ можно сделать небольшой полифилл:
```js
try {
new CustomEvent("IE has CustomEvent, but doesn't support constructor");
} catch (e) {
window.CustomEvent = function(event, params) {
var evt;
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
evt = document.createEvent("CustomEvent");
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
CustomEvent.prototype = Object.create(window.Event.prototype);
}
```
Здесь мы сначала проверяем -- в IE9-11 есть `CustomEvent`, но его нельзя создать через `new`, будет ошибка. В этом случае заменяем браузерную реализацию на свою, совместимую.
## Антистандарт: IE8-
В совсем старом IE были "свои" методы `document.createEventObject()` и `elem.fireEvent()`.
@ -340,39 +418,20 @@ void initMouseEvent (
Параметры `bubbles` и `cancelable` настраивать нельзя, браузер использует стандартные для данного типа событий.
## Кросс-браузерный пример
Существуют полифиллы для генерации произвольных событий и для IE8-, но они, по сути, полностью подменяют встроенную систему обработки событий браузером. И кода это требует тоже достаточно много.
Для поддержки IE9+ достаточно использовать методы `document.createEvent` и `event.initEvent`, как показано выше, и всё будет хорошо.
Альтернатива -- фреймворк, например jQuery, который также реализует свою мощную систему работы с событиями, доступную через методы jQuery.
Если же нужен IE8, то подойдёт такой код:
```js
function trigger(elem, type){
if (document.createEvent) {
var event = document.createEvent('Event') :
event.initEvent(type);
return elem.dispatchEvent(event);
}
var event = document.createEventObject();
return elem.fireEvent("on"+type, event);
}
// использование:
trigger(elem, "click");
```
Конечно, надо иметь в виду, что в IE8 события можно использовать только встроенные, а `bubbles` и `cancelable` поставить нельзя.
## Итого
<ul>
<li>Все браузеры, кроме IE, позволяют генерировать любые события, следуя стандарту DOM4.</li>
<li>IE9+ тоже справляется, если использовать вызовы более старого стандарта, и имеет в итоге тот же функционал.</li>
<li>Все браузеры, кроме IE9-11, позволяют генерировать любые события, следуя стандарту DOM4.</li>
<li>В IE9+ поддерживается более старый стандарт, можно легко сделать полифилл, например для `CustomEvent` он рассмотрен в этой главе.</li>
<li>IE8- может генерировать только встроенные события.</li>
</ul>
**Несмотря на техническую возможность генерировать браузерные события -- пользоваться ей стоит с большой осторожностью.**
Несмотря на техническую возможность генерировать встроенные браузерные события типа `click` или `keydown` -- пользоваться ей стоит с большой осторожностью.
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.