This commit is contained in:
Ilya Kantor 2017-03-05 01:06:09 +03:00
parent cf9969f7ab
commit 497c9fd2d3
14 changed files with 268 additions and 596 deletions

View file

@ -1,16 +1,16 @@
Дело в том, что обработчик из атрибута `onclick` делается браузером как функция с заданным телом.
When the browser reads the `on*` attribute like `onclick`, it creates the handler from its content.
То есть, в данном случае он будет таким:
For `onclick="handler()"` the function will be:
```js
function(event) {
handler() // тело взято из атрибута onclick
handler() // the content of onclick
}
```
При этом возвращаемое `handler` значение никак не используется и не влияет на результат.
Now we can see that the value returned by `handler()` is not used and does not affect the result.
Рабочий вариант:
The fix is simple:
```html run
<script>
@ -23,7 +23,7 @@ function(event) {
<a href="http://w3.org" onclick="*!*return handler()*/!*">w3.org</a>
```
Также можно использовать объект события для вызова `event.preventDefault()`, например:
Also we can use `event.preventDefault()`, like this:
```html run
<script>
@ -37,4 +37,3 @@ function(event) {
<a href="http://w3.org" onclick="*!*handler(event)*/!*">w3.org</a>
```

View file

@ -2,9 +2,9 @@ importance: 3
---
# Почему не работает return false?
# Why "return false" doesn't work?
Почему в этом документе `return false` не работает?
Why in the code below `return false` doesn't work at all?
```html autorun run
<script>
@ -14,9 +14,9 @@ importance: 3
}
</script>
<a href="http://w3.org" onclick="handler()">w3.org</a>
<a href="http://w3.org" onclick="handler()">the browser will go to w3.org</a>
```
По замыслу, переход на `w3.org` при клике должен отменяться. Однако, на самом деле он происходит.
The browser follows the URL on click, but we don't want it.
В чём дело и как поправить?
How to fix?

View file

@ -1,28 +1,5 @@
Это -- классическая задача на тему делегирования.
That's a great use of the event delegation pattern.
В реальной жизни, мы можем перехватить событие и создать AJAX-запрос к серверу, который сохранит информацию о том, по какой ссылке ушел посетитель.
Мы перехватываем событие на `contents` и поднимаемся до `parentNode` пока не получим `A` или не упремся в контейнер.
```js
contents.onclick = function(evt) {
var target = evt.target;
function handleLink(href) {
var isLeaving = confirm('Уйти на ' + href + '?');
if (!isLeaving) return false;
}
while (target != this) {
if (target.nodeName == 'A') {
*!*
return handleLink(target.getAttribute('href')); // (*)
*/!*
}
target = target.parentNode;
}
};
```
В строке `(*)` используется атрибут, а не свойство `href`, чтобы показать в `confirm` именно то, что написано в HTML-атрибуте, так как свойство может отличаться, оно обязано содержать полный валидный адрес.
In real life instead of asking we can send a "logging" request to the server that saves the information about where the visitor left. Or we can load the content and show it right in the page (if allowable).
All we need is to catch the `contents.onclick` and use `confirm` to ask the user. A good idea would be to use `link.getAttribute('href')` instead of `link.href` for the URL. See the solution for details.

View file

@ -16,28 +16,25 @@
<fieldset id="contents">
<legend>#contents</legend>
<p>
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards?
</p>
</fieldset>
<script>
document.getElementById('contents').onclick = function(event) {
contents.onclick = function(event) {
function handleLink(href) {
var isLeaving = confirm('Уйти на ' + href + '?');
let isLeaving = confirm(`Leave for ${href}?`);
if (!isLeaving) return false;
}
var target = event.target;
let target = event.target.closest('a');
while (target != this) {
if (target.nodeName == 'A') {
return handleLink(target.getAttribute('href'));
}
target = target.parentNode;
if (target && contents.contains(target)) {
return handleLink(target.getAttribute('href'));
}
}
};
</script>
</body>
</html>
</html>

View file

@ -16,10 +16,9 @@
<fieldset id="contents">
<legend>#contents</legend>
<p>
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards?
</p>
</fieldset>
</body>
</html>
</html>

View file

@ -2,16 +2,15 @@ importance: 5
---
# Поймайте переход по ссылке
# Catch links in the element
Сделайте так, чтобы при клике на ссылки внутри элемента `#contents` пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу и если он не хочет, то прерывать переход по ссылке.
Make all links inside the element with `id="contents"` ask the user if he really wants to leave. And if he doesn't then don't follow.
Так это должно работать:
Like this:
[iframe height=100 border=1 src="solution"]
Детали:
- Содержимое `#contents` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование.
- Содержимое может содержать вложенные теги, *в том числе внутри ссылок*, например, `<a href=".."><i>...</i></a>`.
Details:
- HTML inside the element may be loaded or regenerated dynamically at any time, so we can't find all links and put handlers on them. Use the event delegation.
- The content may have nested tags. Inside links too, like `<a href=".."><i>...</i></a>`.

View file

@ -1,54 +1 @@
Решение состоит в том, чтобы добавить обработчик на контейнер `#thumbs` и отслеживать клики на ссылках.
Когда происходит событие, обработчик должен изменять `src` `#largeImg` на `href` ссылки и заменять `alt` на ее `title`.
Код решения:
```js
var largeImg = document.getElementById('largeImg');
document.getElementById('thumbs').onclick = function(e) {
var target = e.target;
while (target != this) {
if (target.nodeName == 'A') {
showThumbnail(target.href, target.title);
return false;
}
target = target.parentNode;
}
}
function showThumbnail(href, title) {
largeImg.src = href;
largeImg.alt = title;
}
```
**Предзагрузка картинок**
Для того, чтобы картинка загрузилась, достаточно создать новый элемент `IMG` и указать ему `src`, вот так:
```js
var imgs = thumbs.getElementsByTagName('img');
for (var i = 0; i < imgs.length; i++) {
var url = imgs[i].parentNode.href;
*!*
var img = document.createElement('img');
img.src = url;
*/!*
}
```
Как только элемент создан и ему назначен `src`, браузер сам начинает скачивать файл картинки.
При правильных настройках сервера как-то использовать этот элемент не обязательно -- картинка уже закеширована.
**Семантичная верстка**
Для списка картинок используется `DIV`. С точки зрения семантики более верный вариант -- список `UL/LI`.
The solution is to assign the handler to the container and track clicks. If a click is on the `<a>` link, then change `src` of `#largeImg` to the `href` of the thumbnail.

View file

@ -2,69 +2,48 @@
<html>
<head>
<title>Галерея</title>
<title>Gallery</title>
<link rel="stylesheet" href="gallery.css">
<meta charset="utf-8">
</head>
<body>
<p><img id="largeImg" src="https://js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<p><img id="largeImg" src="https://en.js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<ul id="thumbs">
<!-- При наведении на изображение показывается встроенная подсказка браузера (title) -->
<!-- the browser shows a small built-in tooltip on hover with the text from "title" attribute -->
<li>
<a href="https://js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://js.cx/gallery/img2-thumb.jpg"></a>
<a href="https://en.js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://en.js.cx/gallery/img2-thumb.jpg"></a>
</li>
<li>
<a href="https://js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://js.cx/gallery/img3-thumb.jpg"></a>
<a href="https://en.js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://en.js.cx/gallery/img3-thumb.jpg"></a>
</li>
<li>
<a href="https://js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://js.cx/gallery/img4-thumb.jpg"></a>
<a href="https://en.js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://en.js.cx/gallery/img4-thumb.jpg"></a>
</li>
<li>
<a href="https://js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://js.cx/gallery/img5-thumb.jpg"></a>
<a href="https://en.js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://en.js.cx/gallery/img5-thumb.jpg"></a>
</li>
<li>
<a href="https://js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://js.cx/gallery/img6-thumb.jpg"></a>
<a href="https://en.js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://en.js.cx/gallery/img6-thumb.jpg"></a>
</li>
</ul>
<script>
var largeImg = document.getElementById('largeImg');
var thumbs = document.getElementById('thumbs');
thumbs.onclick = function(e) {
var target = e.target;
while (target != this) {
if (target.nodeName == 'A') {
showThumbnail(target.href, target.title);
return false;
}
target = target.parentNode;
}
thumbs.onclick = function(event) {
let thumbnail = event.target.closest('a');
if (!thumbnail) return;
showThumbnail(target.href, target.title);
event.preventDefault();
}
function showThumbnail(href, title) {
largeImg.src = href;
largeImg.alt = title;
}
/* предзагрузка */
var imgs = thumbs.getElementsByTagName('img');
for (var i = 0; i < imgs.length; i++) {
var url = imgs[i].parentNode.href;
var img = document.createElement('img');
img.src = url;
}
</script>
</body>
</html>
</html>

View file

@ -2,24 +2,33 @@
<html>
<head>
<title>Галерея</title>
<title>Gallery</title>
<link rel="stylesheet" href="gallery.css">
<meta charset="utf-8">
</head>
<body>
<p><img id="largeImg" src="https://js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<p><img id="largeImg" src="https://en.js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<div id="thumbs">
<!-- При наведении на изображение показывается встроенная подсказка браузера (title) -->
<a href="https://js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://js.cx/gallery/img2-thumb.jpg"></a>
<a href="https://js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://js.cx/gallery/img3-thumb.jpg"></a>
<a href="https://js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://js.cx/gallery/img4-thumb.jpg"></a>
<a href="https://js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://js.cx/gallery/img5-thumb.jpg"></a>
<a href="https://js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://js.cx/gallery/img6-thumb.jpg"></a>
</div>
<ul id="thumbs">
<!-- the browser shows a small built-in tooltip on hover with the text from "title" attribute -->
<li>
<a href="https://en.js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://en.js.cx/gallery/img2-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://en.js.cx/gallery/img3-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://en.js.cx/gallery/img4-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://en.js.cx/gallery/img5-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://en.js.cx/gallery/img6-thumb.jpg"></a>
</li>
</ul>
</body>
</html>
</html>

View file

@ -2,19 +2,12 @@ importance: 5
---
# Галерея изображений
# Image gallery
Создайте галерею изображений, в которой основное изображение изменяется при клике на уменьшенный вариант.
Create an image gallery where the main image changes by the click on a thumbnail.
Результат должен выглядеть так:
Like this:
[iframe src="solution" height=600]
Для обработки событий используйте делегирование, т.е. не более одного обработчика.
P.S. Обратите внимание -- клик может быть как на маленьком изображении `IMG`, так и на `A` вне него. При этом `event.target` будет, соответственно, либо `IMG`, либо `A`.
Дополнительно:
- Если получится -- сделайте предзагрузку больших изображений, чтобы при клике они появлялись сразу.
- Всё ли в порядке с семантической вёрсткой в HTML исходного документа? Если нет -- поправьте, чтобы было, как нужно.
P.S. Use event delegation.

View file

@ -1,145 +1,116 @@
# Действия браузера по умолчанию
# Browser actions
Многие события автоматически влекут за собой действие браузера.
Many events automatically lead to browser actions.
Например:
For instance:
- Клик по ссылке инициирует переход на новый URL.
- Нажатие на кнопку "отправить" в форме -- отсылку ее на сервер.
- Двойной клик на тексте -- инициирует его выделение.
- A click on a link -- initiates going to its URL.
- A click on submit button inside a form -- initiates its submission to the server.
- Pressing a mouse button over a text and moving it -- selects the text.
Если мы обрабатываем событие в JavaScript, то зачастую такое действие браузера нам не нужно. К счастью, его можно отменить.
If we handle an event in JavaScript, often we don't want browser actions. Fortunately, it can be prevented.
[cut]
## Отмена действия браузера
## Preventing browser actions
Есть два способа отменить действие браузера:
There are two ways to tell the browser we don't want it to act:
- **Основной способ -- это воспользоваться объектом события. Для отмены действия браузера существует стандартный метод `event.preventDefault()`.**
- Если же обработчик назначен через `onсобытие` (не через `addEventListener`), то можно просто вернуть `false` из обработчика.
- The main way is to use the `event` object. There's a method `event.preventDefault()`.
- If the handler is assigned using `on<event>` (not by `addEventListener`), then we can just return `false` from it.
В следующем примере при клике по ссылке переход не произойдет:
In the example below there a click to links don't lead to URL change:
```html autorun height=60 no-beautify
<a href="/" onclick="return false">Нажми здесь</a>
или
<a href="/" onclick="event.preventDefault()">здесь</a>
<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>
```
```warn header="Возвращать `true` не нужно"
Обычно значение, которое возвращает обработчик события, игнорируется.
```warn header="Not necessary to return `true`"
The value returned by an event handler is usually ignored.
Единственное исключение -- это `return false` из обработчика, назначенного через `onсобытие`.
The only exception -- is `return false` from a handler assigned using `on<event>`.
Иногда в коде начинающих разработчиков можно увидеть `return` других значений. Но они не нужны и никак не обрабатываются.
In all other cases, the return is not needed and it's not processed anyhow.
```
### Пример: меню
### Example: the menu
Рассмотрим задачу, когда нужно создать меню для сайта, например такое:
Consider a site menu, like this:
```html
<ul id="menu" class="menu">
<li><a href="/php">PHP</a></li>
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/flash">Flash</a></li>
<li><a href="/css">CSS</a></li>
</ul>
```
Данный пример при помощи CSS может выводиться так:
Here's how it looks with some CSS:
[iframe height=70 src="menu" link edit]
HTML-разметка сделана так, что все элементы меню являются не кнопками, а ссылками, то есть тегами `<a>`.
Menu items are links `<a>`, not buttons. There are several benefits, for instance:
Это потому, что некоторые посетители очень любят сочетание "правый клик - открыть в новом окне". Да, мы можем использовать и `<button>` и `<span>`, но если правый клик не работает -- это их огорчает. Кроме того, если на сайт зайдёт поисковик, то по ссылке из `<a href="...">` он перейдёт, а выполнить сложный JavaScript и получить результат -- вряд ли захочет.
- Many people like to use "right click" -- "open in a new window". If we use `<button>` or `<span>`, that doesn't work.
- Search engines follow `<a href="...">` links while indexing.
Поэтому в разметке мы используем именно `<a>`, но обычно клик будет обрабатываться полностью в JavaScript, а стандартное действие браузера (переход по ссылке) -- отменяться.
So we use `<a>` in the markup. But normally we intend to handle clicks in JavaScript. So we should prevent the default browser action.
Например, вот так:
Like here:
```js
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
var href = event.target.getAttribute('href');
alert( href ); // может быть подгрузка с сервера, генерация интерфейса и т.п.
let href = event.target.getAttribute('href');
alert( href ); // ...can be loading from the server, UI generation etc
*!*
return false; // отменить переход по url
return false; // prevent browser action (don't go to the URL)
*/!*
};
```
В конце `return false`, иначе браузер перейдёт по адресу из `href`.
If we omit `return false`, then after our code executes the browser will do its "default action" -- following to the URL in `href`.
Так как мы применили делегирование, то меню может увеличиваться, можно добавить вложенные списки `ul/li`, стилизовать их при помощи CSS -- обработчик не потребует изменений.
By the way, using event delegation here makes our menu flexible. We can add nested lists and style them using CSS to "slide down".
## Другие действия браузера
## Other browser actions
Действий браузера по умолчанию достаточно много.
There are many default browser actions.
Вот некоторые примеры событий, которые вызывают действие браузера:
Here are more events that cause browser actions:
- `mousedown` -- нажатие кнопкой мыши в то время как курсор находится на тексте начинает его выделение.
- `click` на `<input type="checkbox">` -- ставит или убирает галочку.
- `submit` -- при нажатии на `<input type="submit">` в форме данные отправляются на сервер.
- `wheel` -- движение колёсика мыши инициирует прокрутку.
- `keydown` -- при нажатии клавиши в поле ввода появляется символ.
- `contextmenu` -- при правом клике показывается контекстное меню браузера.
- `mousedown` -- starts the selection (move the mouse to select).
- `click` on `<input type="checkbox">` -- checks/unchecks the `input`.
- `submit` -- clicking an `<input type="submit">` or hitting `key:Enter` inside a form field causes this event to happen, and the browser submits the form after it.
- `wheel` -- rolling a mouse wheel event has scrolling as the default action.
- `keydown` -- pressing a key may lead to adding a character into a field, or other actions.
- `contextmenu` -- the event happens on a right-click, the action is to show the browser context menu.
- ...
Все эти действия можно отменить, если мы хотим обработать событие исключительно при помощи JavaScript.
All the default actions can be prevented if we want to handle the event exclusively by JavaScript.
````warn header="События могут быть связаны между собой"
Некоторые события естественным образом вытекают друг из друга.
````warn header="Events may be related"
Certain events flow one into another.
Например, нажатие мышкой `mousedown` на поле ввода `<input>` приводит к фокусировке внутрь него. Если отменить действие `mousedown`, то и фокуса не будет.
For instance, `mousedown` on an `<input>` field leads to focusing in it, and the `focus` event. If we prevent the `mousedown` event, there's no focus.
Попробуйте нажать мышкой на первый `<input>` -- произойдёт событие `onfocus`. Это обычная ситуация.
Try to click on the first `<input>` below -- the `focus` event happens. That's normal.
Но если нажать на второй, то фокусировки не произойдёт.
But if you click the second one, there's no focus.
```html run autorun
<input value="Фокус работает" onfocus="this.value=''">
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Кликни меня">
<input value="Focus works" onfocus="this.value=''">
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Click me">
```
Это потому, что отменено стандартное действие при `onmousedown`.
...С другой стороны, во второй `<input>` можно перейти с первого нажатием клавиши `key:Tab`, и тогда фокусировка сработает. То есть, дело здесь именно в `onmousedown="return false"`.
That's because the browser action is canceled on `mousedown`. The focusing is still possible if we use another way to enter the input. For instance, the `key:Tab` key to switch from the 1st input into the 2nd.
````
## Особенности IE8-
В IE8- для отмены действия по умолчанию нужно назначить свойство `event.returnValue = false`.
Кроссбраузерный код для отмены действия по умолчанию:
```js
element.onclick = function(event) {
event = event || window.event;
if (event.preventDefault) { // если метод существует
event.preventDefault(); // то вызвать его
} else { // иначе вариант IE8-:
event.returnValue = false;
}
}
```
Можно записать в одну строку:
```js no-beautify
...
event.preventDefault ? event.preventDefault() : (event.returnValue=false);
...
```
## Итого
- Браузер имеет встроенные действия при ряде событий -- переход по ссылке, отправка формы и т.п. Как правило, их можно отменить.
- Есть два способа отменить действие по умолчанию: первый -- использовать `event.preventDefault()` (IE8-: `event.returnValue=false`), второй -- `return false` из обработчика. Второй способ работает только если обработчик назначен через `onсобытие`.
## Summary
- Browser has default actions for many events -- following a link, submitting a form etc.
- To prevent a default action -- use either `event.preventDefault()` or `return false`. The second method works only for handlers assigned with `on<event>`.

View file

@ -9,14 +9,13 @@
<body>
<ul id="menu" class="menu">
<li><a href="/php">PHP</a></li>
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/flash">Flash</a></li>
<li><a href="/css">CSS</a></li>
</ul>
<script src="menu.js"></script>
</body>
</html>
</html>

View file

@ -1,8 +1,8 @@
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
var href = event.target.getAttribute('href');
let href = event.target.getAttribute('href');
alert(href);
return false; // prevent url change
};
};

View file

@ -1,49 +1,168 @@
# Генерация событий на элементах
# Custom events
Можно не только назначать обработчики на события, но и генерировать их самому.
We can not only assign handlers, but also generate events form JavaScript.
Мы будем использовать это позже для реализации компонентной архитектуры, при которой элемент, представляющий собой, к примеру, меню, генерирует события, к этому меню относящиеся -- `select` (выбран пункт меню) или `open` (меню раскрыто), и другие.
Custom events can be used to create "graphical components". For instance, a root element of the menu may trigger events telling what happens with the menu: `open` (menu open), `select` (an item is selected) and so on.
Кроме того, события можно генерировать для целей автоматического тестирования.
Also we can generate built-in events like `click`, `mousedown` etc, that may be good for testing.
[cut]
## Конструктор Event
## Event constructor
Вначале рассмотрим современный способ генерации событий, по стандарту [DOM 4](http://www.w3.org/TR/dom/#introduction-to-dom-events). Он поддерживается всеми браузерами, кроме IE11-. А далее рассмотрим устаревшие варианты, поддерживаемые IE.
Events form a hierarchy, just like DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class.
Объект события в нём создаётся при помощи встроенного конструктора [Event](http://www.w3.org/TR/dom/#event).
Синтаксис:
We can create `Event` objects like this:
```js
var event = new Event(тип события[, флаги]);
let event = new Event(event type[, flags]);
```
Где:
Arguments:
- *Тип события* -- может быть как своим, так и встроенным, к примеру `"click"`.
- *Флаги* -- объект вида `{ bubbles: true/false, cancelable: true/false }`, где свойство `bubbles` указывает, всплывает ли событие, а `cancelable` -- можно ли отменить действие по умолчанию.
- *event type* -- may be any string, like `"click"` or our own like `"hey-ho!"`.
- *flags* -- the object with two optional properties:
- `bubbles: true/false` -- if `true`, then the event bubbles.
- `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
Флаги по умолчанию: `{bubbles: false, cancelable: false}`.
By default both flags are false: `{bubbles: false, cancelable: false}`.
## Метод dispatchEvent
## The method dispatchEvent
Затем, чтобы инициировать событие, запускается `elem.dispatchEvent(event)`.
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
При этом событие срабатывает наравне с браузерными, то есть обычные браузерные обработчики на него отреагируют. Если при создании указан флаг `bubbles`, то оно будет всплывать.
Then handlers react on it as if it were a regular built-in event. If the event was created with the `bubbles` flag, then it bubbles.
При просмотре примера ниже кнопка обработчик `onclick` на кнопке сработает сам по себе, событие генерируется скриптом:
In the example below the `click` event is initiated in JavaScript. The handler works same way as if the button was clicked:
```html run no-beautify
<button id="elem" onclick="alert('Клик');">Автоклик</button>
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
var event = new Event("click");
let event = new Event("click");
elem.dispatchEvent(event);
</script>
```
```smart header="event.isTrusted"
There is a way to tell a "real" user event from a script-generated one.
The property `event.isTrusted` is `true` for events that come from real user actions and `false` for script-generated events.
```
## Bubbling example
We can create a bubbling event with the name `"hello"` and catch it on `document`.
All we need is to set `bubbles` to `true`:
```html run no-beautify
<h1 id="elem">Hello from the script!</h1>
<script>
// catch on document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// ...dispatch on elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
</script>
```
Notes:
1. We must use `addEventListener` for our custom events, because `on<event>` only exists for built-in events.
2. Must set `bubbles`, otherwise the event won't bubble up.
The bubbling mechanics is the same for built-in (`click`) and custom (`hello`) events. There are also capturing and bubbling stages.
## MouseEvent, KeyboardEvent and others
Here's a short list of classes for UI Events from the [UI Event specification](https://www.w3.org/TR/uievents):
- `UIEvent`
- `FocusEvent`
- `MouseEvent`
- `WheelEvent`
- `KeyboardEvent`
- ...
We should use them instead of `new Event` if we want to create such events. For instance, `new MouseEvent("click")`.
The right constructor allows to specify standard properties for that type of event.
Like `clientX/clientY` for a mouse event:
```js run
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
*!*
alert(event.clientX); // 100
*/!*
```
Please note: the generic `Event` constructor does not allow that.
Let's try:
```js run
let event = new Event("click", {
bubbles: true, // only bubbles and cancelable
cancelable: true, // work in the Event constructor
clientX: 100,
clientY: 100
});
*!*
alert(event.clientX); // undefined, the unknown property is ignored!
*/!*
```
Technically, we can work around that by assigning directly `event.clientX=100` after creation. So that's a matter of convenience and following the rules. Browser-generated events always have the right type.
The full list of properties for different UI events is in the specification, for instance [MouseEvent](https://www.w3.org/TR/uievents/#mouseevent).
## Custom events
For our own, custom events like `"hello"` we should use `new CustomEvent`. Technically [CustomEvent](https://dom.spec.whatwg.org/#customevent) is the same as `Event`, with one exception.
In the second argument (object) we can add an additional property `detail` for any custom information that we want to pass with the event.
For instance:
```html run
<h1 id="elem">Hello for John!</h1>
<script>
elem.addEventListener("hello", function(event) {
alert(*!*event.detail.name*/!*);
});
let event = new CustomEvent("hello", {
*!*
detail: { name: "John" }
*/!*
});
elem.dispatchEvent(event);
</script>
```
The `detail` can be anything. Once again, technically we can assign any properties into a regular `Event` object after its creation. But `CustomEvent` clearly states that the event is not built-in, but our own. And reserves the special `detail` field, so that we can write to it safely, without conflicts with standard event properties.
## Отмена действия по умолчанию
На сгенерированном событии, как и на встроенном браузерном, обработчик может вызвать метод `event.preventDefault()`. Тогда `dispatchEvent` возвратит `false`.
@ -92,324 +211,9 @@ var event = new Event(тип события[, флаги]);
</script>
```
```smart header="Как отличить реальное нажатие от скриптового?"
В целях безопасности иногда хорошо бы знать -- инициировано ли действие посетителем или это кликнул скрипт.
Единственный способ, которым код может отличить реальное нажатие от программного, является проверка свойства `event.isTrusted`.
Оно на момент написания статьи поддерживается IE и Firefox и равно `true`, если посетитель кликнул сам, и всегда `false` -- если событие инициировал скрипт.
```
## Другие свойства событий
При создании события браузер автоматически ставит следующие свойства:
- `isTrusted: false` -- означает, что событие сгенерировано скриптом, это свойство изменить невозможно.
- `target: null` -- это свойство ставится автоматически позже при `dispatchEvent`.
- `type: тип события` -- первый аргумент `new Event`.
- `bubbles`, `cancelable` -- по второму аргументу `new Event`.
Другие свойства события, если они нужны, например координаты для события мыши -- можно присвоить в объект события позже, например:
```js no-beautify
var event = new Event("click", {bubbles: true, cancelable: false});
event.clientX = 100;
event.clientY = 100;
```
## Пример со всплытием
Сгенерируем совершенно новое событие `"hello"` и поймаем его на `document`.
Всё, что для этого нужно -- это флаг `bubbles`:
```html run no-beautify
<h1 id="elem">Привет от скрипта!</h1>
<script>
document.addEventListener("hello", function(event) { // (1)
alert("Привет");
event.preventDefault(); // (2)
}, false);
var event = new Event("hello", {bubbles: true, cancelable: true}); // (3)
if (elem.dispatchEvent(event) === false) {
alert('Событие было отменено preventDefault');
}
</script>
```
Обратите внимание:
1. Обработчик события `hello` стоит на `document`. Мы его поймаем на всплытии.
2. Вызов `event.preventDefault()` приведёт к тому, что `dispatchEvent` вернёт `false`.
3. Чтобы событие всплывало и его можно было отменить, указан второй аргумент `new Event`.
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, их можно сгенерировать и запустить совершенно одинаково.
## Конструкторы MouseEvent, KeyboardEvent и другие
Для некоторых конкретных типов событий есть свои, специфические, конструкторы.
Вот список конструкторов для различных событий интерфейса которые можно найти в спецификации [UI Event](http://www.w3.org/TR/uievents/):
- `UIEvent`
- `FocusEvent`
- `MouseEvent`
- `WheelEvent`
- `KeyboardEvent`
- `CompositionEvent`
Вместо `new Event("click")` можно вызвать `new MouseEvent("click")`.
**Специфический конструктор позволяет указать стандартные свойства для данного типа события.**
Например, `clientX/clientY` для события мыши:
```js run
var e = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
*!*
alert( e.clientX ); // 100
*/!*
```
Это нельзя было бы сделать с обычным конструктором `Event`:
```js run
var e = new Event("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
*!*
alert( e.clientX ); // undefined, свойство не присвоено!
*/!*
```
Обычный конструктор `Event` не знает про "мышиные" свойства, поэтому их игнорирует.
Впрочем, использование конкретного конструктора не является обязательным, можно обойтись `Event`, а свойства записать в объект отдельно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
Полный список свойств по типам событий вы найдёте в спецификации, например для `MouseEvent`: [MouseEvent Constructor](http://www.w3.org/TR/uievents/#constructor-mouseevent).
## Свои события
Для генерации своих, нестандартных, событий, хоть и можно использовать конструктор `Event`, но существует и специфический конструктор [CustomEvent](http://www.w3.org/TR/dom/#customevent).
Технически, он абсолютно идентичен `Event`, кроме небольшой детали: у второго аргумента-объекта есть дополнительное свойство `detail`, в котором можно указывать информацию для передачи в событие.
Например:
```html run
<h1 id="elem">Привет для Васи!</h1>
<script>
elem.addEventListener("hello", function(event) {
alert( *!*event.detail.name*/!* );
}, false);
var event = new CustomEvent("hello", {
*!*
detail: { name: "Вася" }
*/!*
});
elem.dispatchEvent(event);
</script>
```
Надо сказать, что никто не мешает и в обычное `Event` записать любые свойства. Но `CustomEvent` более явно говорит, что событие не встроенное, а своё, и выделяет отдельно "информационное" поле `detail`, в которое можно записать что угодно без конфликта со стандартными свойствами объекта.
## Старое API для IE9+
Способ генерации событий, описанный выше, не поддерживается в IE11-, там нужен другой, более старый способ, описанный в стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events).
В нём была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
Она поддерживается как современными браузерами, так и IE9+. Там используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
Можно использовать этот немного устаревший способ, если нужно поддерживать IE9+. Далее мы на его основе создадим полифилл.
Объект события создаётся вызовом `document.createEvent`:
```js
var event = document.createEvent(eventInterface);
```
Аргументы:
- `eventInterface` -- это тип события, например `MouseEvent`, `FocusEvent`, `KeyboardEvent`. В [секции 5 DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#events-module) есть подробный список, какое событие к какому интерфейсу относится.
На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.
Далее событие нужно инициализировать:
```js
event.initEvent(type, boolean bubbles, boolean cancelable);
```
Аргументы:
- `type` -- тип события, например `"click"`.
- `bubbles` -- всплывает ли событие.
- `cancelable` -- можно ли отменить событие.
Эти два кода аналогичны:
```js
// современный стандарт
var event = new Event("click", {
bubbles: true,
cancelable: true
});
// старый стандарт
var event = document.createEvent("Event");
event.initEvent("click", true, true);
```
Единственная разница -- старый стандарт поддерживается IE9+.
Этот пример с событием `hello` будет работать во всех браузерах, кроме IE8-:
```html run
<h1 id="elem">Привет от скрипта!</h1>
<script>
document.addEventListener("hello", function(event) {
alert( "Привет" );
event.preventDefault();
}, false);
*!*
var event = document.createEvent("Event");
event.initEvent("hello", true, true);
*/!*
if (elem.dispatchEvent(event) === false) {
alert( 'Событие было отменено preventDefault' );
}
</script>
```
````smart header="`initMouseEvent`, `initKeyboardEvent` и другие..."
У конкретных типов событий, например `MouseEvent`, `KeyboardEvent`, есть методы, которые позволяют указать стандартные свойства.
Они называются по аналогии: `initMouseEvent`, `initKeyboardEvent`.
Их можно использовать вместо базового `initEvent`, если хочется, чтобы свойства событий соответствовали встроенным браузерным.
Выглядят они немного страшновато, например (взято из [спецификации](http://www.w3.org/TR/DOM-Level-3-Events/#idl-interface-MouseEvent-initializers)):
```js
void initMouseEvent(
DOMString typeArg, // тип
boolean bubblesArg, // всплывает?
boolean cancelableArg, // можно отменить?
AbstractView ? viewArg, // объект window, null означает текущее окно
long detailArg, // свойство detail и другие...
long screenXArg,
long screenYArg,
long clientXArg,
long clientYArg,
boolean ctrlKeyArg,
boolean altKeyArg,
boolean shiftKeyArg,
boolean metaKeyArg,
unsigned short buttonArg,
EventTarget ? relatedTargetArg);
};
```
Для инициализации мышиного события нужно обязательно указать *все* аргументы, например:
```html run
<button id="elem">Автоклик</button>
<script>
elem.onclick = function(e) {
alert( 'Клик на координатах ' + e.clientX + ':' + e.clientY );
};
var event = document.createEvent("MouseEvent");
*!*
event.initMouseEvent("click", true, true, null, 0, 0, 0, 100, 100, true, true, true, null, 1, null);
*/!*
elem.dispatchEvent(event);
</script>
```
Браузер, по стандарту, может сгенерировать отсутствующие свойства самостоятельно, например `pageX`, но это нужно проверять в конкретных случаях, иногда это не работает или работает некорректно, так что лучше указать все.
````
## Полифилл 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()`.
Пример с ними для IE8:
```html run
<button id="elem">Автоклик</button>
<script>
document.body.onclick = function() {
alert( "Клик, event.type=" + event.type );
return false;
};
*!*
var event = document.createEventObject();
if (!elem.fireEvent("onclick", event)) {
alert( 'Событие было отменено' );
}
*/!*
</script>
```
**При помощи `fireEvent` можно сгенерировать только встроенные события.**
Если указать `"hello"` вместо `"onclick"` в примере выше -- будет ошибка.
Параметры `bubbles` и `cancelable` настраивать нельзя, браузер использует стандартные для данного типа событий.
Существуют полифиллы для генерации произвольных событий и для IE8-, но они, по сути, полностью подменяют встроенную систему обработки событий браузером. И кода это требует тоже достаточно много.
Альтернатива -- фреймворк, например jQuery, который также реализует свою мощную систему работы с событиями, доступную через методы jQuery.
## Итого
@ -426,4 +230,3 @@ try {
- Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.
- Либо для автоматического тестирования, чтобы скриптом "нажать на кнопку" и посмотреть, произошло ли нужное действие.
- Либо при создании своих "элементов интерфейса". Например, никто не мешает при помощи JavaScript создать из `<div class="calendar">` красивый календарь и генерировать на нём событие `change` при выборе даты. Эту тему мы разовьём позже.