update
This commit is contained in:
parent
962caebbb7
commit
87bf53d076
1825 changed files with 94929 additions and 0 deletions
|
@ -0,0 +1 @@
|
|||
[edit src="solution"]Решение задачи[/edit]
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<input type="button" id="hider" value="Нажмите, чтобы спрятать текст"/>
|
||||
|
||||
<div id="hide">Текст</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('hider').onclick = function() {
|
||||
document.getElementById('hide').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<input type="button" id="hider" value="Нажмите, чтобы спрятать текст"/>
|
||||
|
||||
<div id="hide">Текст</div>
|
||||
|
||||
<script>
|
||||
/* ваш код */
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
# Спрятать при клике
|
||||
|
||||
[importance 5]
|
||||
|
||||
Используя JavaScript, сделайте так, чтобы при клике на кнопку исчезал элемент с `id="hide"`.
|
||||
|
||||
Демо:
|
||||
[iframe border=1 src="solution"]
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1,7 @@
|
|||
Решение задачи заключается в использовании `this` в обработчике.
|
||||
|
||||
```html
|
||||
<!--+ run height=50 -->
|
||||
<input type="button" onclick="this.style.display='none'" value="Нажми, чтобы меня спрятать"/>
|
||||
```
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Спрятаться
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте кнопку, при клике на которую, она будет скрывать сама себя.
|
||||
|
||||
Как эта:
|
||||
<input type="button" onclick="this.style.display='none'" value="Нажми, чтобы меня спрятать"/>
|
17
2-ui/2-events-and-interfaces/1-introduction-browser-events/2.html
Executable file
17
2-ui/2-events-and-interfaces/1-introduction-browser-events/2.html
Executable file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script>
|
||||
function countRabbits() {
|
||||
for(var i=1; i<=3; i++) {
|
||||
alert("Кролик номер " + i);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="button" onclick="countRabbits()" value="Считать кроликов!"/>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
Ответ: будет выведено `1` и `2`.
|
||||
|
||||
Первый обработчик сработает, так как он не убран вызовом `removeEventListener`. Для удаления обработчика нужно передать в точности ту же функцию (ссылку на нее), что была назначена, а в коде передается такая же с виду функция, но, тем не менее, это другой объект.
|
||||
|
||||
Для того, чтобы удалить функцию-обработчик, нужно где-то сохранить ссылку на неё, например так:
|
||||
|
||||
```js
|
||||
function handler() {
|
||||
alert("1");
|
||||
}
|
||||
|
||||
button.addEventListener("click", handler, false);
|
||||
button.removeEventListener("click", handler, false);
|
||||
```
|
||||
|
||||
Обработчик `button.onclick` сработает независимо и в дополнение к назначенному в `addEventListener`.
|
|
@ -0,0 +1,18 @@
|
|||
# Какие обработчики сработают?
|
||||
|
||||
[importance 5]
|
||||
|
||||
В переменной `button` находится кнопка.
|
||||
|
||||
Изначально обработчиков на ней нет.
|
||||
|
||||
Что будет выведено при клике после выполнения кода?
|
||||
|
||||
```js
|
||||
button.addEventListener("click", function() { alert("1"); }, false);
|
||||
|
||||
button.removeEventListener("click", function() { alert("1"); }, false);
|
||||
|
||||
button.onclick = function() { alert(2); };
|
||||
```
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# Структура HTML/CSS
|
||||
|
||||
Для начала, зададим структуру HTML/CSS.
|
||||
|
||||
Меню является отдельным графическим компонентом, его лучше поместить в единый DOM-элемент.
|
||||
|
||||
Элементы меню с точки зрения семантики являются списком `UL/LI`. Заголовок должен быть отдельным кликабельным элементом.
|
||||
|
||||
Получаем структуру:
|
||||
|
||||
```html
|
||||
<div class="menu">
|
||||
<span class="title">Сладости (нажми меня)!</span>
|
||||
<ul>
|
||||
<li>Пирог</li>
|
||||
<li>Пончик</li>
|
||||
<li>Мед</li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
Для заголовка лучше использовать именно `SPAN`, а не `DIV`, так как `DIV` постарается занять 100% ширины, и мы не сможем ловить `click` только на тексте:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=50 -->
|
||||
<div style="border: solid red 1px">[Сладости (нажми меня)!]</div>
|
||||
```
|
||||
|
||||
...А `SPAN` -- это элемент с `display: inline`, поэтому он занимает ровно столько места, сколько занимает текст внутри него:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=50 -->
|
||||
<span style="border: solid red 1px">[Сладости (нажми меня)!]</span>
|
||||
```
|
||||
|
||||
Раскрытие/закрытие делайте путём добавления/удаления класса `.menu-open` к меню, которые отвечает за стрелочку и отображение `UL`.
|
||||
|
||||
# CSS
|
||||
|
||||
CSS для меню:
|
||||
|
||||
```css
|
||||
.menu ul {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu .title {
|
||||
padding-left: 16px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
|
||||
background: url(...arrow-right.png) left center no-repeat;
|
||||
}
|
||||
```
|
||||
|
||||
Если же меню раскрыто, то есть имеет класс `.menu-open`, то стрелочка слева заголовка меняется и список детей показывается:
|
||||
|
||||
```css
|
||||
.menu-open .title {
|
||||
background: url(...arrow-down.png) left center no-repeat;
|
||||
}
|
||||
|
||||
.menu-open ul {
|
||||
display: block;
|
||||
}
|
||||
```
|
||||
|
||||
Теперь сделайте JavaScript.
|
||||
|
||||
[edit src="solution"]Полное решение в песочнице[/edit]
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
|
||||
.menu ul {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu .title {
|
||||
padding-left: 16px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
|
||||
background: url(http://js.cx/clipart/arrow-right.png) left center no-repeat;
|
||||
}
|
||||
|
||||
.menu-open .title {
|
||||
background: url(http://js.cx/clipart/arrow-down.png) left center no-repeat;
|
||||
}
|
||||
|
||||
.menu-open ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="sweeties" class="menu">
|
||||
<span id="sweeties-title" class="title">Сладости (нажми меня)!</span>
|
||||
<ul>
|
||||
<li>Торт</li>
|
||||
<li>Пончик</li>
|
||||
<li>Пирожное</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var titleElem = document.getElementById('sweeties-title');
|
||||
|
||||
titleElem.onclick = function() {
|
||||
var menu = this.parentNode;
|
||||
menu.classList.toggle('menu-open');
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<img src="http://js.cx/clipart/arrow-down.png">
|
||||
<img src="http://js.cx/clipart/arrow-right.png">
|
||||
|
||||
Сладости (нажми меня)!
|
||||
<ul>
|
||||
<li>Торт</li>
|
||||
<li>Пончик</li>
|
||||
<li>Пирожное</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
# Раскрывающееся меню
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте меню, которое раскрывается/сворачивается при клике:
|
||||
|
||||
[iframe border=1 height=100 src="solution"]
|
||||
|
||||
HTML/CSS исходного документа, возможно, понадобится изменить.
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1,26 @@
|
|||
# Алгоритм решения
|
||||
|
||||
<ol>
|
||||
<li>Разработать структуру HTML/CSS. Позиционировать кнопку внутри сообщения.</li>
|
||||
<li>Найти все кнопки</li>
|
||||
<li>Присвоить им обработчики</li>
|
||||
<li>Обработчик будет ловить событие на кнопке и удалять соответствующий элемент.</li>
|
||||
<ol>
|
||||
|
||||
# Вёрстка
|
||||
|
||||
Исправьте HTML/CSS, чтобы кнопка была в нужном месте сообщения. Кнопку лучше сделать как `div`, а картинка --- будет его `background`. Это более правильно, чем `img`, т.к. в данном случае картинка является *оформлением кнопки*, а оформление должно быть в CSS.
|
||||
|
||||
Расположить кнопку справа можно при помощи `position: relative` для `pane`, а для кнопки `position: absolute + right/top`. Так как `position: absolute` вынимает элемент из потока, то кнопка может перекрыть текст заголовка. Чтобы этого не произошло, можно добавить `padding-right` к заголовку.
|
||||
|
||||
Потенциальным преимуществом способа с `position` по сравнению с `float` в данном случае является возможность поместить элемент кнопки в HTML *после текста*, а не до него.
|
||||
|
||||
# Обработчики
|
||||
|
||||
Для того, чтобы получить кнопку из контейнера, можно найти все `IMG` в нём и выбрать из них кнопку по `className`. На каждую кнопку можно повесить обработчик.
|
||||
|
||||
# Решение
|
||||
|
||||
[edit src="solution"]Решение в песочнице[/edit]
|
||||
|
||||
Для поиска элементов `span` с нужным классом в нём используется `getElementsByTagName` с фильтрацией. К сожалению, это единственный способ, доступный в IE 6,7. Если же эти браузеры вам не нужны, то гораздо лучше -- искать элементы при помощи `querySelector` или `getElementsByClassName`.
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.pane {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
background: url(http://js.cx/clipart/delete.gif) no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="messages-container">
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var spans = document.getElementById('messages-container').getElementsByTagName('span');
|
||||
|
||||
for(var i=0; i<spans.length; i++) {
|
||||
var span = spans[i];
|
||||
if (span.className != 'remove-button') continue;
|
||||
|
||||
span.onclick = function() {
|
||||
var el = this.parentNode;
|
||||
el.parentNode.removeChild(el);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
body {
|
||||
margin: 10px auto;
|
||||
width: 470px;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding-bottom: .3em;
|
||||
padding-right: 20px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 .5em;
|
||||
}
|
||||
.pane {
|
||||
background: #edf5e1;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
background: url(delete.gif) no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Картинка для кнопки удаления: <img src="http://js.cx/clipart/delete.gif">
|
||||
|
||||
<div>
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
body {
|
||||
margin: 10px auto;
|
||||
width: 470px;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding-bottom: .3em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 .5em;
|
||||
}
|
||||
.pane {
|
||||
background: #edf5e1;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# Спрятать сообщение
|
||||
|
||||
[importance 5]
|
||||
|
||||
Есть список сообщений. Добавьте каждому сообщению по кнопке для его скрытия.
|
||||
|
||||
Результат:
|
||||
[iframe src="solution"]
|
||||
|
||||
Как лучше отобразить кнопку справа-сверху: через `position:absolute` или `float`?
|
||||
|
||||
[edit src="source" task/]
|
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 8 KiB |
|
@ -0,0 +1,28 @@
|
|||
# HTML/CSS
|
||||
Лента изображений должна быть оформлена как список, согласно принципам семантической вёрстки.
|
||||
|
||||
Нужно стилизовать его так, чтобы он был длинной лентой, из которой внешний `DIV` вырезает нужную часть для просмотра:
|
||||
|
||||
<img src="carousel1.png">
|
||||
|
||||
Чтобы список был длинный и элементы не переходили вниз, ему ставится `width: 9999px`, а элементам, соответственно, `float:left`.
|
||||
|
||||
[warn header="Не используйте display:inline"]
|
||||
Элементы с `display:inline` имеют дополнительные отступы для возможных "хвостов букв".
|
||||
|
||||
В частности, для `img` нужно поставить в стилях явно `display:block`, чтобы пространства под ними не оставалось.
|
||||
[/warn]
|
||||
|
||||
При прокрутке UL сдвигается назначением `margin-left`:
|
||||
|
||||
<img src="carousel2.png">
|
||||
|
||||
У внешнего `DIV` фиксированная ширина, поэтому "лишние" изображения обрезаются.
|
||||
|
||||
Снаружи окошка находятся стрелки и внешний контейнер.
|
||||
|
||||
Реализуйте эту структуру, и к ней прикручивайте обработчики, которые меняют `ul.style.marginLeft`.
|
||||
|
||||
# Полное решение
|
||||
|
||||
[edit src="solution"]Открыть в песочнице[/edit]
|
|
@ -0,0 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="carousel">
|
||||
<a href="#" class="arrow left-arrow" id="prev">‹ </a>
|
||||
<div class="gallery">
|
||||
<ul id="images">
|
||||
<li><img src="http://js.cx/carousel/1.png"></li>
|
||||
<li><img src="http://js.cx/carousel/2.png"></li>
|
||||
<li><img src="http://js.cx/carousel/3.png"></li>
|
||||
<li><img src="http://js.cx/carousel/4.png"></li>
|
||||
<li><img src="http://js.cx/carousel/5.png"></li>
|
||||
<li><img src="http://js.cx/carousel/6.png"></li>
|
||||
<li><img src="http://js.cx/carousel/7.png"></li>
|
||||
<li><img src="http://js.cx/carousel/8.png"></li>
|
||||
<li><img src="http://js.cx/carousel/9.png"></li>
|
||||
<li><img src="http://js.cx/carousel/10.png"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="#" class="arrow right-arrow" id="next">› </a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* этот код помечает картинки, для удобства разработки */
|
||||
var lis = document.getElementsByTagName('li');
|
||||
for(var i=0; i<lis.length; i++) {
|
||||
lis[i].style.position='relative';
|
||||
var span = document.createElement('span');
|
||||
// обычно лучше использовать CSS-классы,
|
||||
// но этот код - для удобства разработки, так что не будем трогать стили
|
||||
span.style.cssText='position:absolute;left:0;top:0';
|
||||
span.innerHTML = i+1;
|
||||
lis[i].appendChild(span);
|
||||
}
|
||||
|
||||
/* конфигурация */
|
||||
var width = 130; // ширина изображения
|
||||
var count = 3; // количество изображений
|
||||
|
||||
var ul = document.getElementById('images');
|
||||
var imgs = ul.getElementsByTagName('li');
|
||||
|
||||
var position = 0; // текущий сдвиг влево
|
||||
|
||||
document.getElementById('prev').onclick = function() {
|
||||
if (position >= 0) return false; // уже до упора
|
||||
|
||||
// последнее передвижение влево может быть не на 3, а на 2 или 1 элемент
|
||||
position = Math.min(position + width*count, 0)
|
||||
ul.style.marginLeft = position + 'px';
|
||||
return false;
|
||||
}
|
||||
|
||||
document.getElementById('next').onclick = function() {
|
||||
if (position <= -width*(imgs.length-count)) return false; // уже до упора
|
||||
|
||||
// последнее передвижение вправо может быть не на 3, а на 2 или 1 элемент
|
||||
position = Math.max(position-width*count, -width*(imgs.length-count));
|
||||
ul.style.marginLeft = position + 'px';
|
||||
return false;
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,52 @@
|
|||
body {
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
.carousel {
|
||||
position: relative;
|
||||
width: 398px;
|
||||
padding: 10px 40px;
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 15px;
|
||||
background: #eee;
|
||||
}
|
||||
.carousel img {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
display: block; /* если не поставить block, в ряде браузеров будет inline -> лишнее пространтсво вокруг элементов */
|
||||
}
|
||||
.carousel .arrow {
|
||||
position: absolute;
|
||||
top: 57px;
|
||||
padding: 3px 8px 8px 9px;
|
||||
background: #ddd;
|
||||
border-radius: 15px;
|
||||
font-size: 24px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.carousel .arrow:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
.carousel .left-arrow {
|
||||
left: 7px;
|
||||
}
|
||||
.carousel .right-arrow {
|
||||
right: 7px;
|
||||
}
|
||||
.gallery {
|
||||
width: 390px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gallery ul {
|
||||
height: 130px;
|
||||
width: 9999px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.gallery ul li {
|
||||
float: left;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- ваша верстка виджета, ваши стили -->
|
||||
<ul id="images">
|
||||
<li><img src="http://js.cx/carousel/1.png"></li>
|
||||
<li><img src="http://js.cx/carousel/2.png"></li>
|
||||
<li><img src="http://js.cx/carousel/3.png"></li>
|
||||
<li><img src="http://js.cx/carousel/4.png"></li>
|
||||
<li><img src="http://js.cx/carousel/5.png"></li>
|
||||
<li><img src="http://js.cx/carousel/6.png"></li>
|
||||
<li><img src="http://js.cx/carousel/7.png"></li>
|
||||
<li><img src="http://js.cx/carousel/8.png"></li>
|
||||
<li><img src="http://js.cx/carousel/9.png"></li>
|
||||
<li><img src="http://js.cx/carousel/10.png"></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<script>
|
||||
/* этот код помечает картинки цифрами, для удобства разработки
|
||||
его можно убрать, если не нужен */
|
||||
var lis = document.getElementsByTagName('li');
|
||||
for(var i=0; i<lis.length; i++) {
|
||||
lis[i].style.position='relative';
|
||||
var span = document.createElement('span');
|
||||
span.style.cssText='position:absolute;left:0;top:0';
|
||||
span.innerHTML = i+1;
|
||||
lis[i].appendChild(span);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
// ваш код..
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
# Карусель
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите "Карусель" -- ленту изображений, которую можно листать влево-вправо нажатием на стрелочки.
|
||||
|
||||
[iframe height=200 src="solution"]
|
||||
|
||||
В дальнейшем к ней можно легко добавить анимацию, динамическую подгрузку и другие возможности.
|
||||
|
||||
В этой задаче разработка HTML/CSS-структуры составляет 90% решения.
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1,526 @@
|
|||
# Введение в браузерные события
|
||||
|
||||
Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют *события*.
|
||||
|
||||
*Событие* - это сигнал от браузера о том, что что-то произошло.
|
||||
[cut]
|
||||
Существует много видов событий.
|
||||
|
||||
Посмотрим список самых часто используемых, пока просто для ознакомления:
|
||||
|
||||
<dl>
|
||||
<dt>События мыши</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`click` -- происходит, когда кликнули на элемент левой кнопкой мыши</li>
|
||||
<li>`contextmenu` -- происходит, когда кликнули на элемент правой кнопкой мыши</li>
|
||||
<li>`mouseover` -- возникает, когда на элемент наводится мышь</li>
|
||||
<li>`mousedown` и `mouseup` -- когда кнопку мыши нажали или отжали</li>
|
||||
<li>`mousemove` -- при движении мыши</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>События на элементах управления</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`submit` -- посетитель отправил форму `<form>`</li>
|
||||
<li>`focus` -- посетитель фокусируется на элементе, например нажимает на `<input>`</li>
|
||||
</ul>
|
||||
<dt>Клавиатурные события</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`keydown` -- когда посетитель нажимает клавишу</li>
|
||||
<li>`keyup` -- когда посетитель отпускает клавишу</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>События документа</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`DOMContentLoaded` -- когда HTML загружен и обработан, DOM документа полностью построен и доступен.</li>
|
||||
</ul></dd>
|
||||
<dt>События CSS</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`transitionend` -- когда CSS-анимация завершена.</li>
|
||||
</ul></dd>
|
||||
</dl>
|
||||
|
||||
Также есть и много других событий.
|
||||
|
||||
## Назначение обработчиков событий
|
||||
|
||||
Событию можно назначить обработчик, то есть функцию, которая сработает, как только событие произошло.
|
||||
|
||||
Именно благодаря событиям JavaScript-код может реагировать на действия посетителя.
|
||||
|
||||
Есть несколько способов назначить событию обработчик. Сейчас мы их рассмотрим, начиная от самого простого.
|
||||
|
||||
### Использование атрибута HTML
|
||||
|
||||
Обработчик может быть назначен прямо в разметке, в атрибуте, который называется `on<событие>`.
|
||||
|
||||
Например, чтобы прикрепить `click`-событие к `input` кнопке, можно присвоить обработчик `onclick`, вот так:
|
||||
|
||||
```html
|
||||
<input value="Нажми меня" *!*onclick="alert('Клик!')"*/!* type="button">
|
||||
```
|
||||
|
||||
При клике мышкой на кнопке выполнится код, указанный в атрибуте `onclick`.
|
||||
|
||||
В действии:
|
||||
|
||||
<input value="Нажми меня" onclick="alert('Клик!');" type="button">
|
||||
|
||||
Обратите внимание, для строки *внутри* `alert('Клик!')` используются *одиночные кавычки*, так как сам атрибут находится в двойных.
|
||||
|
||||
Частая ошибка новичков в том, что они забывают, что код находится внутри атрибута. Запись вида `onclick="alert("Клик!")"` не будет работать. Если вам действительно нужно использовать именно двойные кавычки, то это можно сделать, заменив их на `"`: <code>onclick="alert(&quot;Клик!&quot;)"</code>.
|
||||
|
||||
Однако, обычно этого не требуется, так как в разметке пишутся только очень простые обработчики. Если нужно сделать что-то сложное, то имеет смысл описать это в функции, и в обработчике вызвать уже её.
|
||||
|
||||
Следующий пример по клику запускает функцию `countRabbits()`.
|
||||
|
||||
```html
|
||||
<!--+ src="2.html" run height=80 -->
|
||||
|
||||
```
|
||||
|
||||
Как мы помним, атрибут HTML-тега не чувствителен к регистру, поэтому `ONCLICK` будет работать так же, как `onClick` или `onclick`... Но, как правило, атрибуты пишут в нижнем регистре: `onclick`.
|
||||
|
||||
### Использование свойства DOM-объекта
|
||||
|
||||
Можно назначать обработчик, используя свойство DOM-элемента `on<событие>`.
|
||||
|
||||
Пример установки обработчика `click`:
|
||||
|
||||
```html
|
||||
<input id="elem" type="button" value="Нажми меня"/>
|
||||
<script>
|
||||
*!*
|
||||
elem.onclick = function() {
|
||||
alert('Спасибо');
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
В действии:
|
||||
<input id="elem" type="button" value="Нажми меня"/>
|
||||
<script>
|
||||
elem.onclick = function() {
|
||||
alert('Спасибо');
|
||||
};
|
||||
</script>
|
||||
|
||||
Если обработчик задан через атрибут, то браузер читает HTML-разметку, создаёт новую функцию из содержимого атрибута и записывает в свойство `onclick`.
|
||||
|
||||
**Обработчик хранится именно в свойстве, а атрибут -- лишь один из способов его инициализации.**
|
||||
|
||||
Эти два примера кода работают одинаково:
|
||||
|
||||
<ol>
|
||||
<li>Только HTML:
|
||||
|
||||
```html
|
||||
<!--+ run height=50 -->
|
||||
<input type="button" *!*onclick="alert('Клик!')"*/!* value="Кнопка"/>
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>HTML + JS:
|
||||
|
||||
```html
|
||||
<!--+ run height=50 -->
|
||||
<input type="button" id="button" value="Кнопка"/>
|
||||
<script>
|
||||
*!*
|
||||
button.onclick = function() {
|
||||
alert('Клик!');
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
**Так как свойство, в итоге, одно, то назначить более одного обработчика так нельзя.**
|
||||
|
||||
В примере ниже назначение через JavaScript перезапишет обработчик из атрибута:
|
||||
|
||||
```html
|
||||
<!--+ run height=50 autorun -->
|
||||
<input type="button" id="elem" onclick="alert('До')" value="Нажми меня"/>
|
||||
<script>
|
||||
*!*
|
||||
elem.onclick = function() { // перезапишет существующий обработчик
|
||||
alert('После');
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
Кстати, обработчиком можно назначить и уже существующую функцию:
|
||||
|
||||
```js
|
||||
function sayThanks() {
|
||||
alert('Спасибо!');
|
||||
}
|
||||
|
||||
elem.onclick = sayThanks;
|
||||
```
|
||||
|
||||
Если обработчик надоел -- его всегда можно убрать назначением `elem.onclick = null`.
|
||||
|
||||
### Доступ к элементу через this
|
||||
|
||||
Внутри обработчика события `this` ссылается на текущий элемент, то есть на тот, на котором он сработал.
|
||||
|
||||
Это можно использовать, чтобы получить свойства или изменить элемент.
|
||||
|
||||
В коде ниже `button` выводит свое содержимое, используя `this.innerHTML`:
|
||||
|
||||
```html
|
||||
<button onclick="alert(this.innerHTML)">Нажми меня</button>
|
||||
```
|
||||
|
||||
В действии:
|
||||
<button onclick="alert(this.innerHTML)">Нажми меня</button>
|
||||
|
||||
|
||||
### Частые ошибки
|
||||
|
||||
Если вы только начинаете работать с событиями -- обратите внимание на следующие особенности.
|
||||
|
||||
<dl>
|
||||
<dt>Функция должна быть присвоена как `sayThanks`, а не `sayThanks()`.</dt>
|
||||
<dd>
|
||||
|
||||
```js
|
||||
button.onclick = sayThanks;
|
||||
```
|
||||
|
||||
Если добавить скобки, то `sayThanks()` -- будет уже *результат* выполнения функции (а так как в ней нет `return`, то в `onclick` попадёт `undefined`). Нам же нужна именно функция.
|
||||
|
||||
...А вот в разметке как раз скобки нужны:
|
||||
|
||||
```html
|
||||
<input type="button" id="button" onclick="sayThanks()"/>
|
||||
```
|
||||
|
||||
Это различие просто объяснить. При создании обработчика браузером по разметке, он автоматически создает функцию из его содержимого. Поэтому последний пример -- фактически то же самое, что:
|
||||
|
||||
```js
|
||||
button.onclick = function() {
|
||||
*!*
|
||||
sayThanks(); // содержимое атрибута
|
||||
*/!*
|
||||
};
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Используйте именно функции, а не строки.</dt>
|
||||
<dd>
|
||||
Назначение обработчика строкой `elem.onclick = 'alert(1)'` будет работать, но не рекомендуется, могут быть проблемы при сжатии JavaScript.
|
||||
|
||||
Передавать код в виде строки по меньшей мере странно в языке, который поддерживает Function Expressions, оно здесь доступно только по соображениям совместимости с древними временами.
|
||||
</dd>
|
||||
<dt>Не используйте `setAttribute`.</dt>
|
||||
<dd>
|
||||
Такой вызов работать не будет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// при нажатии на body будут ошибки
|
||||
// потому что при назначении в атрибут функция будет преобразована в строку
|
||||
document.body.setAttribute('onclick', function() { alert(1) });
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Регистр свойства имеет значение.</dt>
|
||||
<dd>Свойство называется `onclick`, а не `ONCLICK`.</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
## Специальные методы
|
||||
|
||||
Фундаментальный недостаток описанных выше способов назначения обработчика -- невозможность повесить *несколько* обработчиков на одно событие.
|
||||
|
||||
Например, одна часть кода хочет при клике на кнопку делать ее подсвеченной, а другая -- выдавать сообщение. Нужно в разных местах два обработчика повесить.
|
||||
|
||||
При этом новый обработчик будет затирать предыдущий. Например, следующий код на самом деле назначает один обработчик -- последний:
|
||||
|
||||
```js
|
||||
input.onclick = function() { alert(1); }
|
||||
// ...
|
||||
input.onclick = function() { alert(2); } // заменит предыдущий обработчик
|
||||
```
|
||||
|
||||
Разработчики стандартов достаточно давно это поняли и предложили альтернативный способ назначения обработчиков при помощи специальных методов, который свободен от указанного недостатка.
|
||||
|
||||
### addEventListener и removeEventListener
|
||||
|
||||
Методы `addEventListener` и `removeEventListener` являются современным способом назначить или удалить обработчик, и при этом позволяют использовать сколько угодно любых обработчиков.
|
||||
|
||||
Назначение обработчика осуществляется вызовом `addEventListener` с тремя аргументами:
|
||||
|
||||
```js
|
||||
element.addEventListener( event, handler, phase );
|
||||
```
|
||||
|
||||
<dl>
|
||||
<dt>`event`</dt>
|
||||
<dd>Имя события, например `click`</dd>
|
||||
<dt>`handler`</dt>
|
||||
<dd>Ссылка на функцию, которую надо поставить обработчиком.</dd>
|
||||
<dt>`phase`</dt>
|
||||
<dd>Фаза, на которой обработчик должен сработать. Этот аргумент мы рассмотрим далее в учебнике. Пока что будем использовать значение `phase = false`, которое нужно в 99% случаев.</dd>
|
||||
</dl>
|
||||
|
||||
Удаление обработчика осуществляется вызовом `removeEventListener`:
|
||||
|
||||
```js
|
||||
element.removeEventListener( event, handler, phase );
|
||||
```
|
||||
|
||||
[warn header="Удаление требует ту же функцию"]
|
||||
Для удаления нужно передать именно ту функцию-обработчик которая была назначена.
|
||||
|
||||
Вот так `removeEventListener` не сработает:
|
||||
|
||||
```js
|
||||
input.addEventListener( "click" , function() {alert('Спасибо!')}, false);
|
||||
// ....
|
||||
input.removeEventListener( "click", function() {alert('Спасибо!')}, false);
|
||||
```
|
||||
|
||||
Это не одна и та же функция, а две независимо созданные (с одинаковым кодом, но это не важно).
|
||||
|
||||
Вот так правильно:
|
||||
|
||||
```js
|
||||
function handler() {
|
||||
alert('Спасибо!');
|
||||
}
|
||||
|
||||
input.addEventListener( "click" , handler, false);
|
||||
// ....
|
||||
input.removeEventListener( "click", handler, false);
|
||||
```
|
||||
|
||||
[/warn]
|
||||
|
||||
**Использование `addEventListener` позволяет добавлять несколько обработчиков на одно событие одного элемента:**
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<input id="elem" type="button" value="Нажми меня"/>
|
||||
|
||||
<script>
|
||||
function handler1() {
|
||||
alert('Спасибо!');
|
||||
};
|
||||
|
||||
function handler2() {
|
||||
alert('Спасибо ещё раз!');
|
||||
}
|
||||
|
||||
*!*
|
||||
elem.onclick = function() { alert("Привет"); };
|
||||
elem.addEventListener("click", handler1, false); // Спасибо!
|
||||
elem.addEventListener("click", handler2, false); // Спасибо ещё раз!
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
Как видно из примера выше, можно одновременно назначать обработчики и через `onсвойство` (только один) и через `addEventListener`. Однако, во избежание путаницы обычно рекомендуется выбрать один способ.
|
||||
|
||||
[warn header="`addEventListener` работает всегда, а `onсвойство` -- нет"]
|
||||
У специальных методов есть ещё одно перимущество перед "старой школой".
|
||||
|
||||
Есть некоторые события, которые нельзя назначить через `onсвойство`, но можно через `addEventListener`.
|
||||
|
||||
Например, таково событие `transitionend`, то есть окончание CSS-анимации. В большинстве браузеров оно требует назначения через `addEventListener`.
|
||||
|
||||
При нажатии на кнопку в примере ниже сработает второй обработчик, но не первый.
|
||||
|
||||
```html
|
||||
<!--+ autorun run -->
|
||||
<style>
|
||||
button {
|
||||
transition: width 3s;
|
||||
width: 100px;
|
||||
}
|
||||
.wide {
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button id="elem" onclick="this.classList.toggle('wide');">
|
||||
Нажми меня
|
||||
</button>
|
||||
|
||||
<script>
|
||||
elem.ontransitionend = function() {
|
||||
alert("ontransitionend"); // не сработает
|
||||
};
|
||||
|
||||
*!*
|
||||
elem.addEventListener("transitionend", function() {
|
||||
alert("addEventListener"); // сработает по окончании анимации
|
||||
}, false);
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
[/warn]
|
||||
|
||||
|
||||
## Отличия IE8-
|
||||
|
||||
При работе с событиями в IE8- есть много отличий. Как правило, они формальны -- некое свойство или метод называются по-другому. Начиная с версии 9, также работают и стандартные свойства и методы, которые предпочтительны.
|
||||
|
||||
**В IE8- вместо `addEventListener/removeEventListener` используются свои методы.**
|
||||
|
||||
Назначение обработчика осуществляется вызовом `attachEvent`:
|
||||
|
||||
```js
|
||||
element.attachEvent( "on"+event, handler);
|
||||
```
|
||||
|
||||
Удаление обработчика -- вызовом `detachEvent`:
|
||||
|
||||
```js
|
||||
element.detachEvent( "on"+event, handler);
|
||||
```
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
function handler() {
|
||||
alert('Спасибо!');
|
||||
}
|
||||
button.attachEvent( "onclick" , handler) // Назначение обработчика
|
||||
// ....
|
||||
button.detachEvent( "onclick", handler) // Удаление обработчика
|
||||
```
|
||||
|
||||
Как видите, почти то же самое, только событие должно включать префикс `on` и нет третьего аргумента, который нам пока не нужен.
|
||||
|
||||
|
||||
[warn header="У обработчиков, назначенных с `attachEvent`, нет `this`"]
|
||||
Обработчики, назначенные с `attachEvent` не получают `this`!
|
||||
|
||||
Это важная особенность и подводный камень старых IE.
|
||||
[/warn]
|
||||
|
||||
### Кроссбраузерный способ назначения обработчиков
|
||||
|
||||
Можно объединить способы для IE<9 и современных браузеров, создав свои методы `addEvent(elem, type, handler)` и `removeEvent(elem, type, handler)`:
|
||||
|
||||
```js
|
||||
var addEvent, removeEvent;
|
||||
|
||||
if (document.addEventListener) { // проверка существования метода
|
||||
addEvent = function(elem, type, handler) {
|
||||
elem.addEventListener(type, handler, false);
|
||||
};
|
||||
removeEvent = function(elem, type, handler) {
|
||||
elem.removeEventListener(type, handler, false);
|
||||
};
|
||||
} else {
|
||||
addEvent = function(elem, type, handler) {
|
||||
elem.attachEvent("on" + type, handler);
|
||||
};
|
||||
removeEvent = function(elem, type, handler) {
|
||||
elem.detachEvent("on" + type, handler);
|
||||
};
|
||||
}
|
||||
|
||||
...
|
||||
// использование:
|
||||
addEvent(elem, "click", function() { alert("Привет"); });
|
||||
```
|
||||
|
||||
Это хорошо работает в большинстве случаев, но у обработчика не будет `this` в IE, потому что `attachEvent` не поддерживает `this`.
|
||||
|
||||
Кроме того, в IE7- есть проблемы с утечками памяти... Но если вам не нужно `this`, и вы не боитесь утечек (как вариант -- не поддерживаете IE7-), то это решение может подойти.
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Есть три способа назначения обработчиков событий:
|
||||
|
||||
<ol>
|
||||
<li>Атрибут HTML: `onclick="..."`.</li>
|
||||
<li>Свойство: <code>elem.onclick = function</code>.</li>
|
||||
<li>Специальные методы:
|
||||
<ul>
|
||||
<li>Для IE8-: `elem.attachEvent( on+событие, handler )` (удаление через `detachEvent`).</li>
|
||||
<li>Для остальных: `elem.addEventListener( событие, handler, false )` (удаление через `removeEventListener`).</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Сравнение `addEventListener` и `onclick`:
|
||||
[compare]
|
||||
+Некоторые события можно назначить только через `addEventListener`.
|
||||
+Метод `addEventListener` позволяет назначить много обработчиков на одно событие.
|
||||
-Обработчик, назначенный через `onclick`, проще удалить или заменить.
|
||||
-Метод `onclick` кросс-браузерный.
|
||||
[/compare]
|
||||
|
||||
**Этим введением мы только начинаем работу с событиями, но вы уже можете решать разнообразные задачи с их использованием.**
|
||||
|
||||
|
||||
[head]
|
||||
<style type="text/css">
|
||||
.d0 { text-align:center;margin:auto; }
|
||||
.d1 p { margin: 0 }
|
||||
.d1 {
|
||||
margin:2em;
|
||||
background-color:green;
|
||||
width:13em;
|
||||
height:13em;
|
||||
text-align:center;
|
||||
}
|
||||
.d1 .number {
|
||||
line-height: 2em;
|
||||
}
|
||||
.d2 {
|
||||
text-align:center;
|
||||
margin:auto;
|
||||
background-color:blue;
|
||||
width:9em;
|
||||
height:9em;
|
||||
}
|
||||
.d1 .d2 ,number {
|
||||
line-height: 2em;
|
||||
}
|
||||
.d3 {
|
||||
text-align:center;
|
||||
margin:auto;
|
||||
background-color:red;
|
||||
width:5em;
|
||||
height:5em;
|
||||
}
|
||||
.d1 .d2 .d3 .number {
|
||||
line-height: 5em;
|
||||
}
|
||||
.d1 .d2 .d2a {
|
||||
color:white;
|
||||
line-height: 2em;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function highlightMe(elem) {
|
||||
elem.style.backgroundColor='yellow'
|
||||
alert(elem.className)
|
||||
elem.style.backgroundColor = ''
|
||||
}
|
||||
|
||||
function highlightMe2(e) {
|
||||
highlightMe(e.currentTarget);
|
||||
}
|
||||
</script>
|
||||
[/head]
|
|
@ -0,0 +1,175 @@
|
|||
# Порядок обработки событий
|
||||
|
||||
События могут возникать не только по очереди, но и пачкой, по многу сразу. Возможно и такое, что во время обработки одного события возникают другие.
|
||||
|
||||
Здесь и далее, очень важно понимать, как браузер обычно работает с событиями и важные исключения из этого правила. Это мы и разберём в этой главе.
|
||||
|
||||
[cut]
|
||||
## Главный поток
|
||||
|
||||
В каждом окне выполняется только один *главный* поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM.
|
||||
|
||||
Он выполняет команды последовательно и блокируется при выводе модальных окон, таких как `alert`.
|
||||
|
||||
|
||||
[smart header="Дополнительные потоки тоже есть"]
|
||||
Есть и другие, служебные потоки, например, для сетевых коммуникаций.
|
||||
|
||||
Поэтому скачивание файлов может продолжаться пока главный поток ждёт реакции на `alert`. Но управлять служебными потоками мы не можем.
|
||||
[/smart]
|
||||
|
||||
[smart header="Web Workers"]
|
||||
Существует спецификация <a href="http://www.w3.org/TR/workers/">Web Workers</a>, которая позволяет запускать дополнительные JavaScript-процессы(workers).
|
||||
|
||||
Они могут обмениваться сообщениями с главным процессом, но их переменные полностью независимы.
|
||||
|
||||
В частности, дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно.
|
||||
[/smart]
|
||||
|
||||
## Очередь событий
|
||||
|
||||
Произошло одновременно несколько событий или во время работы одного случилось другое -- как главному потоку обработать это?
|
||||
|
||||
Если главный поток прямо сейчас занят, то он не может срочно выйти из середины одной функции и прыгнуть в другую. А потом третью. Отладка при этом могла бы превратиться в кошмар, потому что пришлось бы разбираться с совместным состоянием нескольких функций сразу.
|
||||
|
||||
Поэтому используется альтернативный подход.
|
||||
|
||||
**Когда происходит событие, оно попадает в очередь.**
|
||||
|
||||
Внутри браузера существует главный внутренний цикл, который проверяет очередь и обрабатывает события, запускает соответствующие обработчики и т.п.
|
||||
|
||||
**Иногда события добавляются в очередь сразу пачкой.**
|
||||
|
||||
Например, при клике на элементе генерируется несколько событий:
|
||||
<ol>
|
||||
<li>Сначала `mousedown` -- нажата кнопка мыши.</li>
|
||||
<li>Затем `mouseup` -- кнопка мыши отпущена.</li>
|
||||
<li>Так как это было над одним элементом, то дополнительно генерируется `click`</li>
|
||||
</ol>
|
||||
|
||||
|
||||
В действии:
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<textarea rows="6" cols="40" id="area">Кликни меня
|
||||
</textarea>
|
||||
|
||||
<script>
|
||||
area.onmousedown = function(e) { this.value += "mousedown\n"; this.scrollTop = 1e9; };
|
||||
area.onmouseup = function(e) { this.value += "mouseup\n"; this.scrollTop = 1e9; };
|
||||
area.onclick = function(e) { this.value += "click\n"; this.scrollTop = 1e9; };
|
||||
</script>
|
||||
```
|
||||
|
||||
Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер сначала обработает первое, а потом -- второе.
|
||||
|
||||
**При этом каждое событие из очереди обрабатывается полностью отдельно от других.**
|
||||
|
||||
## Вложенные (синхронные) события
|
||||
|
||||
В тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас.
|
||||
|
||||
Рассмотрим в качестве примера событие `onfocus`.
|
||||
|
||||
### Пример: событие onfocus
|
||||
|
||||
Когда посетитель фокусируется на элементе, возникает событие `onfocus`. Обычно оно происходит, когда посетитель кликает на поле ввода, например:
|
||||
|
||||
```html
|
||||
<!--+ run autorun -->
|
||||
<p>При фокусе на поле оно изменит значение.</p>
|
||||
<input type="text" onfocus="this.value = 'Фокус!'" value="Кликни меня">
|
||||
```
|
||||
|
||||
Но ту же фокусировку можно вызвать и явно, вызовом метода `elem.focus()`:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<input type="text" id="elem" onfocus="this.value = 'Фокус!'">
|
||||
|
||||
<script>
|
||||
*!*
|
||||
// сфокусируется на input и вызовет обработчик onfocus
|
||||
elem.focus();
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже. При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`.
|
||||
|
||||
**Событие `onfocus`, инициированное вызовом `text.focus()`, будет обработано синхронно, прямо сейчас, до завершения `onclick`.**
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<input type="button" id="button" value="Нажми меня">
|
||||
<input type="text" id="text" size="60">
|
||||
|
||||
<script>
|
||||
|
||||
button.onclick = function() {
|
||||
text.value += ' ->в onclick ';
|
||||
|
||||
text.focus(); // вызов инициирует событие onfocus
|
||||
|
||||
text.value += ' из onclick-> ';
|
||||
};
|
||||
|
||||
text.onfocus = function() {
|
||||
text.value += ' !focus! ';
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`.
|
||||
|
||||
**Так ведут себя все браузеры, кроме IE.**
|
||||
|
||||
В нём событие `onfocus` -- всегда асинхронное, так что будет сначала полностью обработан клик, а потом -- фокус. В остальных -- фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу.
|
||||
|
||||
|
||||
## Делаем события асинхронными через setTimeout(...,0)
|
||||
|
||||
А что, если мы хотим, чтобы *сначала* закончилась обработка `onclick`, а потом уже произошла обработка `onfocus` и связанные с ней действия?
|
||||
|
||||
Можно добиться и этого.
|
||||
|
||||
Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика.
|
||||
|
||||
Если это неудобно, можно запланировать `text.focus()` чуть позже через `setTimeout(..., 0)`, вот так
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<input type="button" id="button" value="Нажми меня">
|
||||
<input type="text" id="text" size="60">
|
||||
|
||||
<script>
|
||||
|
||||
button.onclick = function() {
|
||||
text.value += ' ->в onclick ';
|
||||
|
||||
*!*
|
||||
setTimeout(function() {
|
||||
text.focus(); // сработает после onclick
|
||||
}, 0);
|
||||
*/!*
|
||||
|
||||
text.value += ' из onclick-> ';
|
||||
};
|
||||
|
||||
text.onfocus = function() {
|
||||
text.value += ' !focus! ';
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Такой вызов обеспечит фокусировку через минимальный "тик" таймера, по стандарту равный 4мс. Обычно такая задержка не играет роли, а необходимую асинхронность мы получили.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>JavaScript выполняется в едином потоке. Современные браузеры позволяют порождать подпроцессы <a href="http://www.w3.org/TR/workers/">Web Workers</a>, они выполняются параллельно и могут отправлять/принимать сообщения, но не имеют доступа к DOM.</li>
|
||||
<li>Обычно события становятся в очередь и обрабатываются в порядке поступления, асинхронно, независимо друг от друга.</li>
|
||||
<li>Синхронными являются вложенные события, инициированные из кода.</li>
|
||||
<li>Чтобы сделать событие гарантированно асинхронным, используется вызов через `setTimeout(func, 0)`.</li>
|
||||
</ul>
|
|
@ -0,0 +1,35 @@
|
|||
# Мяч под курсор мыши
|
||||
|
||||
Основная сложность первого этапа -- сдвинуть мяч под курсор, т.к. координаты клика `e.clientX/Y` -- относительно окна, а мяч позиционирован абсолютно внутри поля, его координаты `left/top` нужно ставить относительно левого-верхнего внутреннего (внутри рамки!) угла поля.
|
||||
|
||||
Чтобы правильно вычислить координаты мяча, нужно получить координаты угла поля и вычесть их из `clientX/Y`:
|
||||
|
||||
```js
|
||||
var field = document.getElementById('field');
|
||||
var ball = document.getElementById('ball');
|
||||
|
||||
field.onclick = function(e) {
|
||||
|
||||
*!*
|
||||
var fieldCoords = field.getBoundingClientRect();
|
||||
var fieldInnerCoords = {
|
||||
top: fieldCoords.top + field.clientTop,
|
||||
left: fieldCoords.left + field.clientLeft
|
||||
};
|
||||
|
||||
ball.style.left = e.clientX - fieldInnerCoords.left + 'px';
|
||||
ball.style.top = e.clientY - fieldInnerCoords.top + 'px';
|
||||
*/!*
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
Далее мяч нужно сдвинуть на половину его ширины и высоты `ball.clientWidth/clientHeight`, чтобы он оказался центром под курсором.
|
||||
|
||||
Здесь есть важный "подводный камень" -- размеры мяча в исходном документе не прописаны. Там просто стоит `<img>`. Но на момент выполнения JavaScript картинка, возможно, ещё не загрузилась, так что высота и ширина мяча будут неизвестны (а они необходимы для центрирования).
|
||||
|
||||
Нужно добавить `width/height` в тег `<img>` или задать размеры в CSS, тогда на момент выполнения JavaScript будет знать их и передвинет мяч правильно.
|
||||
|
||||
Код, который полностью центрирует мяч, вы найдете в полном решении:
|
||||
|
||||
[iframe border="1" src="solution" height="260" link edit]
|
|
@ -0,0 +1,93 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#field {
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
border: 10px groove black;
|
||||
background-color: #00FF00;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#ball {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
-webkit-transition: all 1s;
|
||||
-moz-transition: all 1s;
|
||||
-o-transition: all 1s;
|
||||
-ms-transition: all 1s;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body style="height:2000px">
|
||||
|
||||
Кликните на любое место поля, чтобы мяч перелетел туда.<br>
|
||||
|
||||
|
||||
<div id="field">
|
||||
<img src="http://js.cx/clipart/ball.gif" id="ball">
|
||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
var field = document.getElementById('field');
|
||||
var ball = document.getElementById('ball');
|
||||
|
||||
|
||||
field.onclick = function(event) {
|
||||
|
||||
// координаты поля относительно окна
|
||||
var fieldCoords = this.getBoundingClientRect();
|
||||
|
||||
// координаты левого-верхнего внутреннего угла поля
|
||||
var fieldInnerCoords = {
|
||||
top: fieldCoords.top + field.clientTop,
|
||||
left: fieldCoords.left + field.clientLeft
|
||||
};
|
||||
|
||||
// разместить по клику,
|
||||
// но сдвинув относительно поля (т.к. position:relative)
|
||||
// и сдвинув на половину ширины/высоты
|
||||
// (!) используются координаты относительно окна clientX/Y, как и в fieldCoords
|
||||
var ballCoords = {
|
||||
top: event.clientY - fieldInnerCoords.top - ball.clientHeight / 2,
|
||||
left: event.clientX - fieldInnerCoords.left - ball.clientWidth / 2
|
||||
};
|
||||
|
||||
// вылезает за верхнюю границу - разместить по ней
|
||||
if(ballCoords.top < 0) ballCoords.top = 0;
|
||||
|
||||
// вылезает за левую границу - разместить по ней
|
||||
if(ballCoords.left < 0) ballCoords.left = 0;
|
||||
|
||||
|
||||
// вылезает за правую границу - разместить по ней
|
||||
if(ballCoords.left + ball.clientWidth > field.clientWidth) {
|
||||
ballCoords.left = field.clientWidth - ball.clientWidth;
|
||||
}
|
||||
|
||||
// вылезает за нижнюю границу - разместить по ней
|
||||
if(ballCoords.top + ball.clientHeight > field.clientHeight) {
|
||||
ballCoords.top = field.clientHeight - ball.clientHeight;
|
||||
}
|
||||
|
||||
ball.style.left = ballCoords.left + 'px';
|
||||
ball.style.top = ballCoords.top + 'px';
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#field {
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
border: 10px groove black;
|
||||
background-color: #00FF00;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#ball {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="height:2000px">
|
||||
|
||||
Кликните на любое место поля, чтобы мяч перелетел туда.<br>
|
||||
Мяч никогда не вылетит за границы поля.
|
||||
|
||||
|
||||
<div id="field">
|
||||
<img src="http://js.cx/clipart/ball.gif" id="ball">
|
||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
# Передвигать мяч по полю
|
||||
|
||||
[importance 5]
|
||||
|
||||
Сделайте так, что при клике по полю мяч перемещался на место клика.
|
||||
|
||||
[iframe src="solution" height="260" link]
|
||||
|
||||
Требования:
|
||||
<ul>
|
||||
<li>Мяч после перелёта должен становиться центром ровно под курсор мыши, если это возможно без вылета за край поля.</li>
|
||||
<li>CSS-анимация не обязательна, но желательна.</li>
|
||||
<li>Мяч должен останавливаться у границ поля, ни в коем случае не вылетать за них.</li>
|
||||
<li>При прокрутке страницы ничего не должно ломаться.</li>
|
||||
</ul>
|
||||
|
||||
Замечания:
|
||||
<ul>
|
||||
<li>Код не должен зависеть от конкретных размеров мяча и поля.</li>
|
||||
<li>Текущий HTML/CSS нельзя менять, можно лишь "украшать" (анимация).</li>
|
||||
<li>Вам пригодятся свойства `event.clientX/event.clientY`</li>
|
||||
</ul>
|
||||
|
||||
[edit src="source" task/]
|
||||
|
||||
P.S. Центрировать мяч можно и при помощи CSS, но JavaScript в перспективе позволит делать это гибче и определять позицию более динамически.
|
109
2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md
Normal file
109
2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
# Объект события
|
||||
|
||||
Чтобы хорошо обработать событие, недостаточно знать о том, что это -- "клик" или "нажатие клавиши". Могут понадобиться детали: координаты курсора, введённый символ и другие, в зависимости от события.
|
||||
|
||||
**Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик.**
|
||||
[cut]
|
||||
|
||||
## Получение объекта события
|
||||
|
||||
Пример ниже демонстрирует использования объекта события:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<input type="button" value="Нажми меня" id="elem">
|
||||
|
||||
<script>
|
||||
elem.onclick = function(*!*event*/!*) {
|
||||
alert(event.type + " на " + event.currentTarget);
|
||||
alert(event.clientX + ":" + event.clientY);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Свойства объекта `event`:
|
||||
<dl>
|
||||
<dt>`event.type`</dt>
|
||||
<dd>Тип события, в данном случае `click`</dd>
|
||||
<dt>`event.currentTarget`</dt>
|
||||
<dd>Элемент, на котором сработал обработчик -- то же, что и `this`, но бывают ситуации, когда обработчик является методом объекта и его `this` при помощи `bind` привязан к объекту, тогда `event.currentTarget` полезен.</dd>
|
||||
<dt>`event.clientX / event.clientY`</dt>
|
||||
<dd>Координаты курсора в момент клика (относительно окна)</dd>
|
||||
</dl>
|
||||
|
||||
Есть также и ряд других свойств, в зависимости от событий, которые мы разберём в дальнейших главах, когда будем подробно знакомиться с событиями мыши, клавиатуры и так далее.
|
||||
|
||||
[smart header="Объект события доступен и в HTML"]
|
||||
При назначении обработчика в HTML, тоже можно использовать переменную `event`, это будет работать кросс-браузерно:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<input type="button" onclick="*!*alert(event.type)*/!*" value="Тип события">
|
||||
```
|
||||
|
||||
Это возможно потому, что когда браузер из атрибута создаёт функцию-обработчик, то она выглядит так: `function(event) { alert(event.type) }`. То есть, её первый аргумент называется `"event"`.
|
||||
[/smart]
|
||||
|
||||
## Особенности IE8-
|
||||
|
||||
IE8- вместо передачи параметра обработчику создаёт глобальный объект `window.event`. Обработчик может обратиться к нему.
|
||||
|
||||
Работает это так:
|
||||
|
||||
```js
|
||||
elem.onclick = function() {
|
||||
// window.event - объект события
|
||||
alert( window.event.clientX );
|
||||
};
|
||||
```
|
||||
|
||||
### Кроссбраузерное решение
|
||||
|
||||
Универсальное решение для получения объекта события:
|
||||
|
||||
```js
|
||||
element.onclick = function(event) {
|
||||
event = event || window.event; // (*)
|
||||
|
||||
// Теперь event - объект события во всех браузерах.
|
||||
};
|
||||
```
|
||||
|
||||
Строка `(*)`, в случае, если функция не получила `event` (IE8-), использует `window.event`.-событие `event`.
|
||||
|
||||
Можно написать и иначе, если мы сами не используем переменную `event` в замыкании:
|
||||
|
||||
```js
|
||||
element.onclick = function(e) {
|
||||
e = e || event;
|
||||
|
||||
// Теперь e - объект события во всех браузерах.
|
||||
};
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Объект события содержит ценную информацию о деталях события.</li>
|
||||
<li>Он передается первым аргументом `event` в обработчик для всех браузеров, кроме IE8-, в которых используется глобальная переменная `window.event`.</li>
|
||||
</ul>
|
||||
|
||||
Кросс-браузерно для JavaScript-обработчика получаем объект события так:
|
||||
|
||||
```js
|
||||
element.onclick = function(event) {
|
||||
event = event || window.event;
|
||||
|
||||
// Теперь event - объект события во всех браузерах.
|
||||
};
|
||||
```
|
||||
|
||||
Еще вариант:
|
||||
|
||||
```js
|
||||
element.onclick = function(e) {
|
||||
e = e || event; // если нет другой внешней переменной event
|
||||
...
|
||||
};
|
||||
```
|
||||
|
255
2-ui/2-events-and-interfaces/4-event-bubbling/article.md
Normal file
255
2-ui/2-events-and-interfaces/4-event-bubbling/article.md
Normal file
|
@ -0,0 +1,255 @@
|
|||
# Всплытие и перехват
|
||||
|
||||
Давайте сразу начнём с примера.
|
||||
|
||||
Этот обработчик для `<div>` сработает, если вы кликните по вложенному тегу `<em>` или `<code>`:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<div onclick="alert('Обработчик для Div сработал!')">
|
||||
<em>Кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em>
|
||||
</div>
|
||||
```
|
||||
|
||||
Вам не кажется это странным? Почему же сработал обработчик на `<div>`, если клик произошёл на `<em>`?
|
||||
|
||||
## Всплытие
|
||||
|
||||
Основной принцип всплытия:
|
||||
|
||||
**При наступлении события обработчики сначала срабатывают на самом вложенном элементе, затем на его родителе, затем выше и так далее, вверх по цепочке вложенности.**
|
||||
|
||||
Например, есть 3 вложенных элемента `FORM > DIV > P`, с обработчиком на каждом:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<form onclick="alert('form')">FORM
|
||||
<div onclick="alert('div')">DIV
|
||||
<p onclick="alert('p')">P</p>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
Всплытие гарантирует, что клик по внутреннему `P` вызовет обработчик `onclick` (если есть) сначала на самом `P`, затем на элементе `DIV` далее на элементе `FORM`, и так далее вверх по цепочке родителей до самого `document`.
|
||||
|
||||
<img src="event-order-bubbling.png" alt="Порядок всплытия событий">
|
||||
|
||||
Этот процесс называется *всплытием*, потому что события "всплывают" от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырек воздуха в воде.
|
||||
|
||||
[warn header="Всплывают *почти* все события."]
|
||||
Ключевое слово в этой фразе -- "почти".
|
||||
|
||||
Например, событие `focus` не всплывает. В дальнейших главах мы будем детально знакомиться с различными событиями и увидим ещё примеры.
|
||||
[/warn]
|
||||
|
||||
## Целевой элемент event.target
|
||||
|
||||
На каком бы элементе мы ни поймали событие, всегда можно узнать, где конкретно оно произошло.
|
||||
|
||||
**Самый глубокий элемент, который вызывает событие, называется *"целевым"* или *"исходным"* элементом и доступен как `event.target`.**
|
||||
|
||||
Отличия от `this` (=`event.currentTarget`):
|
||||
<ul>
|
||||
<li>`event.target` -- это **исходный элемент**, на котором произошло событие, в процессе всплытия он неизменен.</li>
|
||||
<li>`this` -- это **текущий элемент**, до которого дошло всплытие, на нём сейчас выполняется обработчик.</li>
|
||||
</ul>
|
||||
|
||||
Например, если стоит только один обработчик `form.onclick`, то он "поймает" все клики внутри него. Где бы ни был клик внутри -- он всплывёт до элемента `<form>`, на котором сработает обработчик.
|
||||
|
||||
<ul>
|
||||
<li>`event.target` будет содержать элемент, на котором произошёл клик.</li>
|
||||
<li>`this` (`=event.currentTarget`) всегда будет сама форма, так как обработчик сработал на ней.</li>
|
||||
</ul>
|
||||
|
||||
[example height=220 src="bubble-target"]
|
||||
|
||||
## Прекращение всплытия
|
||||
|
||||
Всплытие идет прямо наверх. Обычно событие будет всплывать наверх и наверх, до элемента `<html>`, а затем до `document` и даже до `window`, вызывая все обработчики на своем пути.
|
||||
|
||||
**Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.**
|
||||
|
||||
Для остановки всплытия нужно вызвать метод `event.stopPropagation()`.
|
||||
|
||||
Например, здесь при клике на кнопку обработчик `body.onclick` не сработает:
|
||||
|
||||
```html
|
||||
<!--+ run autorun -->
|
||||
<body onclick="alert('сюда обработка не дойдёт')">
|
||||
<button onclick="event.stopPropagation()">Кликни меня</button>
|
||||
</body>
|
||||
```
|
||||
|
||||
[smart header="event.stopImmediatePropagation()"]
|
||||
Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.
|
||||
|
||||
То есть, `stopPropagation` препятствует продвижению события дальше, но на текущем элементе все обработчики отработают.
|
||||
|
||||
Для того, чтобы полностью остановить обработку, современные браузеры поддерживают метод `event.stopImmediatePropagation()`. Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.
|
||||
[/smart]
|
||||
|
||||
[warn header="Не прекращайте всплытие без необходимости!"]
|
||||
Всплытие -- это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной.
|
||||
|
||||
Зачастую прекращение всплытия создаёт свои подводные камни, которые потом приходится обходить.
|
||||
|
||||
Например:
|
||||
<ol>
|
||||
<li>Мы делаем меню. Оно обрабатывает клики на своих элементах и делает для них `stopPropagation`. Вроде бы, всё работает.</li>
|
||||
<li>Позже мы решили отслеживать все клики в окне, для какой-то своей функциональности, к примеру, для статистики -- где вообще у нас кликают люди. Например, Яндекс.Метрика так делает, если включить соответствующую опцию.</li>
|
||||
<li>Над областью, где клики убиваются `stopPropagation`, статистика работать не будет! Получилась "мёртвая зона".</li>
|
||||
</ol>
|
||||
|
||||
Проблема в том, что `stopPropagation` убивает всякую возможность отследить событие сверху, а это бывает нужно для реализации чего-нибудь "эдакого", что к меню отношения совсем не имеет.
|
||||
[/warn]
|
||||
|
||||
## Погружение
|
||||
|
||||
В современном стандарте, кроме "всплытия" событий, предусмотрено ещё и "погружение".
|
||||
|
||||
Оно гораздо менее востребовано, но иногда, очень редко, знание о нём может быть полезным.
|
||||
|
||||
[cut]
|
||||
|
||||
## Три стадии прохода события
|
||||
|
||||
Кроме всплытия, есть ещё стадии прохода события.
|
||||
|
||||
В соответствии со стандартом, их три:
|
||||
|
||||
<ol>
|
||||
<li>Событие сначала идет сверху вниз. Эта стадия называется *"стадия перехвата"* (capturing stage).</li>
|
||||
<li>Событие достигло целевого элемента. Это -- *"стадия цели"* (target stage).</li>
|
||||
<li>После этого событие начинает всплывать. Это -- *"стадия всплытия"* (bubbling stage).</li>
|
||||
</ol>
|
||||
|
||||
В [стандарте DOM Events 3](http://www.w3.org/TR/DOM-Level-3-Events/) это продемонстрировано так:
|
||||
|
||||
<img src="eventflow.png">
|
||||
|
||||
То есть, при клике на `TD` событие путешествует по цепочке родителей сначала вниз к элементу ("погружается"), а потом наверх ("всплывает"), по пути задействуя обработчики.
|
||||
|
||||
**Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.**
|
||||
|
||||
Обработчики, добавленные через `on...`, ничего не знают о стадии перехвата, а начинают работать со всплытия.
|
||||
|
||||
**Чтобы поймать событие на стадии перехвата, нужно использовать третий аргумент `addEventListener`:**
|
||||
|
||||
<ul>
|
||||
<li>Если аргумент `true`, то событие будет перехвачено по дороге вниз.</li>
|
||||
<li>Если аргумент `false`, то событие будет поймано при всплытии.</li>
|
||||
</ul>
|
||||
|
||||
Стадия цели, обозначенная на рисунке цифрой `(2)`, особо не обрабатывается, так как обработчики, назначаемые обоими этими способами, срабатывают также на целевом элементе.
|
||||
|
||||
## Примеры
|
||||
|
||||
В примере ниже на `form, div, p` стоят те же обработчики, что и раньше, но на этот раз -- на стадии погружения.
|
||||
|
||||
Чтобы увидеть перехват в действии, кликните на элементе `P`:
|
||||
|
||||
[example height=220 src="capture"]
|
||||
|
||||
Обработчики сработают в порядке "сверху-вниз": `FORM` -> `DIV` -> `P`.
|
||||
|
||||
JS-код здесь такой:
|
||||
|
||||
```js
|
||||
var elems = document.querySelectorAll('form,div,p');
|
||||
|
||||
// на каждый элемент повесить обработчик на стадии перехвата
|
||||
for(var i=0; i<elems.length; i++) {
|
||||
elems[i].addEventListener("click", highlightThis, true);
|
||||
}
|
||||
```
|
||||
|
||||
Никто не мешает назначить обработчики для обеих стадий, вот так:
|
||||
|
||||
```js
|
||||
var elems = document.querySelectorAll('form,div,p');
|
||||
|
||||
for(var i=0; i<elems.length; i++) {
|
||||
elems[i].addEventListener("click", highlightThis, true);
|
||||
elems[i].addEventListener("click", highlightThis, false);
|
||||
}
|
||||
```
|
||||
|
||||
Кликните по внутреннему элементу `P`, чтобы увидеть порядок прохода события:
|
||||
|
||||
[example height=220 src="both"]
|
||||
|
||||
Должно быть `FORM` -> `DIV` -> `P` -> `P` -> `DIV` -> `FORM`. Заметим, что элемент `P` участвует в обоих стадиях.
|
||||
|
||||
Как видно из примера, один и тот же обработчик можно назначить на разные стадии. При этом номер текущей стадии он, при необходимости, может получить из свойства `event.eventPhase` (=1, если погружение, =3, если всплытие).
|
||||
|
||||
[smart header="Есть события, которые не всплывают, но которые можно перехватить"]
|
||||
Есть события, которые можно поймать только на стадии перехвата, а на стадии всплытия -- нельзя..
|
||||
|
||||
Например, таково событие фокусировки на элементе [onfocus](/focus-blur).
|
||||
[/smart]
|
||||
|
||||
|
||||
## Отличия IE8-
|
||||
|
||||
Чтобы было проще ориентироваться, я собрал отличия IE8-, которые имеют отношение ко всплытию, в одну секцию.
|
||||
|
||||
Их знание понадобится, если вы решите писать на чистом JS, без фреймворков и вам понадобится поддержка IE8-.
|
||||
|
||||
<dl>
|
||||
<dt>Нет свойства `event.currentTarget`</dt>
|
||||
<dd>Обратим внимание, что при назначении обработчика через `onсвойство` у нас есть `this`, поэтому `event.currentTarget`, как правило, не нужно, а вот при назначении через `attachEvent` обработчик не получает `this`, так что текущий элемент, если нужен, можно будет взять лишь из замыкания.</dd>
|
||||
<dt>Вместо `event.target` в IE8- используется `event.srcElement`</dt>
|
||||
<dd>Если мы пишем обработчик, который будет поддерживать и IE8- и современные браузеры, то можно начать его так:
|
||||
|
||||
```js
|
||||
elem.onclick = function(event) {
|
||||
event = event || window.event;
|
||||
var target = event.target || event.srcElement;
|
||||
|
||||
// ... теперь у нас есть объект события и target
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Для остановки всплытия используется код `event.cancelBubble=true`.</dt>
|
||||
<dd>Кросс-браузерно остановить всплытие можно так:
|
||||
|
||||
```js
|
||||
event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Далее в учебнике мы будем использовать стандартные свойства и вызовы, поскольку добавление этих строк, обеспечивающих совместимость -- достаточно простая и очевидная задача.
|
||||
|
||||
Ещё раз хотелось бы заметить -- эти отличия понадобятся при написании JS-кода с поддержкой IE8- без фреймворков. Почти все JS-фреймворки обеспечивают кросс-браузерную поддержку `target`, `currentTarget` и `stopPropagation()`.
|
||||
|
||||
## Итого
|
||||
|
||||
Алгоритм:
|
||||
<ul>
|
||||
<li>При наступлении события -- элемент, на котором оно произошло, помечается как "целевой" (`event.target`).</li>
|
||||
<li>Далее событие сначала двигается вниз от корня документа к `event.target`, по пути вызывая обработчики, поставленные через `addEventListener(...., true)`.</li>
|
||||
<li>Далее событие двигается от `event.target` вверх к корню документа, по пути вызывая обработчики, поставленные через `on*` и `addEventListener(...., false)`.</li>
|
||||
</ul>
|
||||
|
||||
Каждый обработчик имеет доступ к свойствам события:
|
||||
<ul>
|
||||
<li>`event.target` -- самый глубокий элемент, на котором прозошло событие.</li>
|
||||
<li>`event.currentTarget` (=`this`) -- элемент, на котором в данный момент сработал обработчик (до которого "доплыло" событие).</li>
|
||||
<li>`event.eventPhase` -- на какой фазе он сработал (погружение =1, всплытие = 3).</li>
|
||||
</ul>
|
||||
|
||||
Любой обработчик может остановить событие вызовом `event.stopPropagation()`, но делать это не рекомендуется, так как в дальнейшем это событие может понадобиться, иногда для самых неожиданных вещей.
|
||||
|
||||
В современной разработке стадия погружения используется очень редко.
|
||||
|
||||
Этому есть две причины:
|
||||
<ol>
|
||||
<li>Историческая, так как IE лишь с версии 9 в полной мере поддерживает современный стандарт.</li>
|
||||
<li>Разумная -- когда происходит событие, то разумно дать возможность первому сработать обработчику на самом элементе, поскольку он наиболее конкретен. Код, который поставил обработчик именно на этот элемент, знает максимум деталей о том, что это за элемент, чем он занимается, и обработчик через замыкание, скорее всего, имеет к ним доступ.
|
||||
|
||||
Далее имеет смысл передать обработку события родителю -- он тоже понимает, что происходит, но уже менее детально, далее -- выше, и так далее, до самого объекта `document`, обработчик на котором реализовывает самую общую функциональность уровня документа.</li>
|
||||
</ol>
|
28
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/example.css
Executable file
28
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/example.css
Executable file
|
@ -0,0 +1,28 @@
|
|||
form {
|
||||
background-color: green;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: blue;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
p {
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 0;
|
||||
}
|
14
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/index.html
Executable file
14
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/index.html
Executable file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="example.css">
|
||||
|
||||
<form>FORM
|
||||
<div>DIV
|
||||
<p>P</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
13
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/script.js
Executable file
13
2-ui/2-events-and-interfaces/4-event-bubbling/both.view/script.js
Executable file
|
@ -0,0 +1,13 @@
|
|||
|
||||
var elems = document.querySelectorAll('form,div,p');
|
||||
|
||||
for(var i=0; i<elems.length; i++) {
|
||||
elems[i].addEventListener("click", highlightThis, true);
|
||||
elems[i].addEventListener("click", highlightThis, false);
|
||||
}
|
||||
|
||||
function highlightThis() {
|
||||
this.style.backgroundColor = 'yellow';
|
||||
alert(this.tagName);
|
||||
this.style.backgroundColor = '';
|
||||
}
|
28
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/example.css
Executable file
28
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/example.css
Executable file
|
@ -0,0 +1,28 @@
|
|||
form {
|
||||
background-color: green;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: blue;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
p {
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 0;
|
||||
}
|
14
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/index.html
Executable file
14
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/index.html
Executable file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="example.css">
|
||||
|
||||
<form id="form">FORM
|
||||
<div>DIV
|
||||
<p>P</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
10
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/script.js
Executable file
10
2-ui/2-events-and-interfaces/4-event-bubbling/bubble-target.view/script.js
Executable file
|
@ -0,0 +1,10 @@
|
|||
|
||||
var form = document.querySelector('form');
|
||||
|
||||
form.onclick = function(event) {
|
||||
event.target.style.backgroundColor = 'yellow';
|
||||
|
||||
alert("target = " + event.target.tagName + ", this=" + this.tagName);
|
||||
|
||||
event.target.style.backgroundColor = '';
|
||||
};
|
28
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/example.css
Executable file
28
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/example.css
Executable file
|
@ -0,0 +1,28 @@
|
|||
form {
|
||||
background-color: green;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: blue;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
p {
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 25px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
margin: 0;
|
||||
}
|
14
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/index.html
Executable file
14
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/index.html
Executable file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="example.css">
|
||||
|
||||
<form>FORM
|
||||
<div>DIV
|
||||
<p>P</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
12
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/script.js
Executable file
12
2-ui/2-events-and-interfaces/4-event-bubbling/capture.view/script.js
Executable file
|
@ -0,0 +1,12 @@
|
|||
|
||||
var elems = document.querySelectorAll('form,div,p');
|
||||
|
||||
for(var i=0; i<elems.length; i++) {
|
||||
elems[i].addEventListener("click", highlightThis, true);
|
||||
}
|
||||
|
||||
function highlightThis() {
|
||||
this.style.backgroundColor = 'yellow';
|
||||
alert(this.tagName);
|
||||
this.style.backgroundColor = '';
|
||||
}
|
BIN
2-ui/2-events-and-interfaces/4-event-bubbling/event-order-bubbling.png
Executable file
BIN
2-ui/2-events-and-interfaces/4-event-bubbling/event-order-bubbling.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
2-ui/2-events-and-interfaces/4-event-bubbling/eventflow.png
Executable file
BIN
2-ui/2-events-and-interfaces/4-event-bubbling/eventflow.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1,3 @@
|
|||
Поставьте обработчик `click` на контейнере. Он должен проверять, произошел ли клик на кнопке удаления (`target`), и если да, то удалять соответствующий ей `DIV`.
|
||||
|
||||
[edit src="solution"]Открыть в песочнице[/edit]
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="messages-container">
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
document.getElementById('messages-container').onclick = function(e) {
|
||||
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement;
|
||||
|
||||
// без цикла, т.к. мы точно знаем, что внутри нет тегов
|
||||
if (target.className != 'remove-button') return;
|
||||
|
||||
target.parentNode.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
body {
|
||||
margin: 10px auto;
|
||||
width: 470px;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding-bottom: .3em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 .5em;
|
||||
}
|
||||
.pane {
|
||||
position: relative;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
background: #edf5e1;
|
||||
}
|
||||
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(http://js.cx/clipart/delete.gif) no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-right: 20px;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="messages-container">
|
||||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* ваш код */
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
body {
|
||||
margin: 10px auto;
|
||||
width: 470px;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding-bottom: .3em;
|
||||
padding-right: 20px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 0 .5em;
|
||||
}
|
||||
.pane {
|
||||
background: #edf5e1;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
background: url(http://js.cx/clipart/delete.gif) no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Скрытие сообщения с помощью делегирования
|
||||
|
||||
[importance 5]
|
||||
|
||||
Дан список сообщений. Добавьте каждому сообщению кнопку для его удаления.
|
||||
|
||||
**Используйте делегирование событий. Один обработчик для всего.**
|
||||
|
||||
В результате, должно работать вот так(кликните на крестик):
|
||||
[iframe src="solution" height=500]
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1,109 @@
|
|||
# Схема решения
|
||||
|
||||
Дерево устроено как вложенный список.
|
||||
|
||||
Клики на все элементы можно поймать, повесив единый обработчик `onclick` на внешний `UL`.
|
||||
|
||||
Как поймать клик на заголовке? Элемент `LI` является блочным, поэтому нельзя понять, был ли клик на *тексте*, или справа от него.
|
||||
|
||||
Например, ниже -- участок дерева с выделенными рамкой узлами. Кликните справа от любого заголовка. Видите, клик ловится? А лучше бы такие клики (не на тексте) игнорировать.
|
||||
|
||||
```html
|
||||
<!--+ autorun height=190 untrusted -->
|
||||
<style>
|
||||
li { border: 1px solid green; }
|
||||
</style>
|
||||
|
||||
<ul onclick="alert(event.target)">
|
||||
<li>Млекопетающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
В примере выше видно, что проблема в верстке, в том что `LI` занимает всю ширину. Можно кликнуть справа от текста, это все еще `LI`.
|
||||
|
||||
Один из способов это поправить -- обернуть заголовки в дополнительный элемент `SPAN`, и обрабатывать только клики внутри `SPAN'ов`, получать по `SPAN'у` его родителя `LI` и ставить ему класс открыт/закрыт.
|
||||
|
||||
Напишите для этого JavaScript-код.
|
||||
|
||||
# Оборачиваем заголовки в SPAN
|
||||
|
||||
Следующий код ищет все `LI` и оборачивает текстовые узлы в `SPAN`.
|
||||
|
||||
```js
|
||||
var treeUl = document.getElementsByTagName('ul')[0];
|
||||
|
||||
var treeLis = treeUl.getElementsByTagName('li');
|
||||
|
||||
for(var i=0; i<treeLis.length; i++) {
|
||||
var li = treeLis[i];
|
||||
|
||||
var span = document.createElement('span');
|
||||
li.insertBefore(span, li.firstChild); // добавить пустой SPAN
|
||||
span.appendChild(span.nextSibling); // переместить в него заголовок
|
||||
}
|
||||
```
|
||||
|
||||
Теперь можно отслеживать клики *на заголовках*.
|
||||
|
||||
Так выглядит дерево с обёрнутыми в `SPAN` заголовками и делегированием:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=190 untrusted -->
|
||||
<style>
|
||||
span { border: 1px solid red; }
|
||||
</style>
|
||||
|
||||
<ul onclick="alert(event.target.tagName)">
|
||||
<li><span>Млекопетающие</span>
|
||||
<ul>
|
||||
<li><span>Коровы</span></li>
|
||||
<li><span>Ослы</span></li>
|
||||
<li><span>Собаки</span></li>
|
||||
<li><span>Тигры</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Так как `SPAN` -- инлайновый элемент, он всегда такого же размера как текст. Да здравствует `SPAN`!
|
||||
|
||||
В реальной жизни дерево, скорее всего, будет сразу со `SPAN`: если HTML-код дерева генерируется на сервере, то это несложно, если дерево генерируется в JavaScript -- тем более просто.
|
||||
|
||||
# Итоговое решение
|
||||
|
||||
Для делегирования нужно по клику понять, на каком узле он произошел.
|
||||
|
||||
В нашем случае у `SPAN` нет детей-элементов, поэтому не нужно подниматься вверх по цепочке родителей. Достаточно просто проверить `event.target.tagName == 'SPAN'`, чтобы понять, где был клик, и спрятать потомков.
|
||||
|
||||
```js
|
||||
var tree = document.getElementsByTagName('ul')[0];
|
||||
|
||||
tree.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
if (target.tagName != 'SPAN') {
|
||||
return; // клик был не на заголовке
|
||||
}
|
||||
|
||||
var li = target.parentNode; // получить родительский LI
|
||||
|
||||
// получить UL с потомками -- это первый UL внутри LI
|
||||
var node = li.getElementsByTagName('ul')[0];
|
||||
|
||||
if (!node) return; // потомков нет -- ничего не надо делать
|
||||
|
||||
// спрятать/показать (можно и через CSS-класс)
|
||||
node.style.display = node.style.display ? '' : 'none';
|
||||
}
|
||||
```
|
||||
|
||||
Выделение узлов жирным при наведении делается при помощи CSS-селектора `:hover`.
|
||||
|
||||
[edit src="solution"]Полное решение[/edit]
|
|
@ -0,0 +1,86 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.tree span:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree span {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul class="tree">
|
||||
<li>Животные
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
var tree = document.getElementsByTagName('ul')[0];
|
||||
|
||||
var treeLis = tree.getElementsByTagName('li');
|
||||
|
||||
/* wrap all textNodes into spans */
|
||||
for(var i=0; i<treeLis.length; i++) {
|
||||
var li = treeLis[i];
|
||||
|
||||
var span = document.createElement('span');
|
||||
li.insertBefore(span, li.firstChild);
|
||||
span.appendChild(span.nextSibling);
|
||||
}
|
||||
|
||||
/* catch clicks on whole tree */
|
||||
tree.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
if (target.tagName != 'SPAN') {
|
||||
return;
|
||||
}
|
||||
|
||||
/* now we know SPAN is clicked */
|
||||
var node = target.parentNode.getElementsByTagName('ul')[0];
|
||||
if (!node) return; // no children
|
||||
|
||||
node.style.display = node.style.display ? '' : 'none';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body>
|
||||
|
||||
<ul class="tree">
|
||||
<li>Животные
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,19 @@
|
|||
# Раскрывающееся дерево
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте дерево, которое по клику на заголовок скрывает-показывает детей:
|
||||
|
||||
[iframe border=1 src="solution"]
|
||||
|
||||
|
||||
Требования:
|
||||
<ul>
|
||||
<li>Использовать делегирование.</li>
|
||||
<li>Клик вне текста заголовка (на пустом месте) ничего делать не должен.</li>
|
||||
<li>При наведении на заголовок -- он становится жирным, реализовать через CSS.</li>
|
||||
</ul>
|
||||
|
||||
P.S. При необходимости HTML/CSS дерева можно изменить.
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1,21 @@
|
|||
# Подсказка (обработчик)
|
||||
|
||||
<ol>
|
||||
<li>Обработчик `onclick` можно повесить один, на всю таблицу или `THEAD`. Он будет игнорировать клики не на `TH`.</li>
|
||||
<li>При клике на `TH` обработчик будет получать номер из `TH`, на котором кликнули (`TH.cellIndex`) и вызывать функцию `sortColumn`, передавая ей номер колонки и тип.</li>
|
||||
<li>Функция `sortColumn(colNum, type)` будет сортировать.</li>
|
||||
</ol>
|
||||
|
||||
# Подсказка (сортировка)
|
||||
|
||||
Функция сортировки:
|
||||
|
||||
<ol>
|
||||
<li>Переносит все `TR` из `TBODY` в массив `rowsArr`</li>
|
||||
<li>Сортирует массив, используя `rowsArr.sort(compare)`, функция `compare` зависит от типа столбца.</li>
|
||||
<li>Добавляет `TR` из массива обратно в `TBODY`</li>
|
||||
</ol>
|
||||
|
||||
# Решение
|
||||
|
||||
[edit src="solution"/]
|
|
@ -0,0 +1,117 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table id="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-type="number">Возраст</th>
|
||||
<th data-type="string">Имя</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Вася</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Петя</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Женя</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Маша</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Илья</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
// сортировка таблицы
|
||||
// использовать делегирование!
|
||||
// должно быть масштабируемо:
|
||||
// код работает без изменений при добавлении новых столбцов и строк
|
||||
|
||||
var grid = document.getElementById('grid');
|
||||
|
||||
grid.onclick = function(e) {
|
||||
var target = e && e.target || window.event.srcElement;
|
||||
|
||||
if (target.tagName != 'TH') return;
|
||||
|
||||
// Если TH -- сортируем
|
||||
sortGrid(target.cellIndex, target.getAttribute('data-type'));
|
||||
};
|
||||
|
||||
function sortGrid(colNum, type) {
|
||||
var tbody = grid.getElementsByTagName('tbody')[0];
|
||||
|
||||
// Составить массив из TR
|
||||
var rowsArray = [].slice.call(tbody.rows);
|
||||
|
||||
// определить функцию сравнения, в зависимости от типа
|
||||
var compare;
|
||||
|
||||
switch(type) {
|
||||
case 'number':
|
||||
compare = function(rowA, rowB) {
|
||||
return rowA.cells[colNum].innerHTML - rowB.cells[colNum].innerHTML;
|
||||
};
|
||||
break;
|
||||
case 'string':
|
||||
compare = function(rowA, rowB) {
|
||||
return rowA.cells[colNum].innerHTML > rowB.cells[colNum].innerHTML ? 1 : -1;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// сортировать
|
||||
rowsArray.sort(compare);
|
||||
|
||||
// Убрать tbody из большого DOM документа для лучшей производительности
|
||||
grid.removeChild(tbody);
|
||||
|
||||
|
||||
// Убрать TR из TBODY.
|
||||
// Присваивание tbody.innerHTML = '' не работает в IE
|
||||
//
|
||||
// на самом деле без этих строк можно обойтись!
|
||||
// при добавлении appendChild все узлы будут сами перемещены на правильное место!
|
||||
while(tbody.firstChild) {
|
||||
tbody.removeChild(tbody.firstChild);
|
||||
}
|
||||
|
||||
|
||||
// добавить результат в нужном порядке в TBODY
|
||||
for(var i=0; i<rowsArray.length; i++) {
|
||||
tbody.appendChild(rowsArray[i]);
|
||||
}
|
||||
|
||||
grid.appendChild(tbody);
|
||||
|
||||
}
|
||||
|
||||
// P.S. В IE7 cells, cellIndex не работают, если элемент вне документа
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
th {
|
||||
cursor: pointer;
|
||||
}
|
||||
th:hover {
|
||||
background: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table id="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-type="number">Возраст</th>
|
||||
<th data-type="string">Имя</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Вася</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Петя</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Женя</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Маша</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Илья</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
/* ваш код */
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,20 @@
|
|||
# Сортировка таблицы
|
||||
|
||||
[importance 4]
|
||||
|
||||
Сделать сортировку таблицы при клике на заголовок.
|
||||
|
||||
Демо:
|
||||
[iframe border=1 src="solution"]
|
||||
|
||||
Требования:
|
||||
<ul>
|
||||
<li>Использовать делегирование.</li>
|
||||
<li>Код не должен меняться при увеличении количества столбцов или строк.</li>
|
||||
</ul>
|
||||
|
||||
[edit src="source" task/]
|
||||
|
||||
P.S. Обратите внимание, тип столбца задан атрибутом у заголовка. Это необходимо, ведь числа сортируются иначе чем строки. Соответственно, код это может использовать.
|
||||
|
||||
P.P.S. Вам помогут дополнительные [навигационные ссылки по таблицам](#dom-navigation-tables).
|
201
2-ui/2-events-and-interfaces/5-event-delegation/article.md
Normal file
201
2-ui/2-events-and-interfaces/5-event-delegation/article.md
Normal file
|
@ -0,0 +1,201 @@
|
|||
# Делегирование событий
|
||||
|
||||
Всплытие событий позволяет реализовать один из самых важных приёмов разработки -- *делегирование*.
|
||||
|
||||
Он заключается в том, что если у нас есть много элементов, события на которых нужно обрабатывать похожим образом, то вместо того, чтобы назначать обработчик каждому -- мы ставим один обработчик на их общего предка. Из него можно получить целевой элемент `event.target`, понять на каком именно потомке произошло событие и обработать его.
|
||||
|
||||
## Пример "Ба Гуа"
|
||||
|
||||
Рассмотрим пример -- <a href="http://en.wikipedia.org/wiki/Ba_gua">диаграмму "Ба Гуа"</a>. Это таблица, отражающая древнюю китайскую философию.
|
||||
|
||||
Вот она:
|
||||
[iframe height=350 src="bagua" edit link]
|
||||
|
||||
Её HTML (схематично):
|
||||
|
||||
```html
|
||||
<table>
|
||||
<tr>
|
||||
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...<strong>Northwest</strong>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
<tr>...еще 2 строки такого же вида...</tr>
|
||||
<tr>...еще 2 строки такого же вида...</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
В этой таблице всего 9 ячеек, но могло быть и 99, и даже 9999, не важно.
|
||||
|
||||
**Наша задача -- реализовать подсветку ячейки `<td>` при клике.**
|
||||
|
||||
Вместо того, чтобы назначать обработчик для каждой ячейки, мы повесим *один обработчик* на элемент `<table>`.
|
||||
|
||||
Он использует `event.target`, чтобы получить элемент, на котором произошло событие, и подсветить его.
|
||||
|
||||
Код будет таким:
|
||||
|
||||
```js
|
||||
var selectedTd;
|
||||
|
||||
*!*
|
||||
table.onclick = function(event) {
|
||||
var target = event.target; // где был клик?
|
||||
|
||||
if (target.tagName != 'TD') return; // не на TD? тогда не интересует
|
||||
|
||||
highlight(target); // подсветить TD
|
||||
};
|
||||
*/!*
|
||||
|
||||
function highlight(node) {
|
||||
if (selectedTd) {
|
||||
selectedTd.classList.remove('highlight');
|
||||
}
|
||||
selectedTd = node;
|
||||
selectedTd.classList.add('highlight');
|
||||
}
|
||||
```
|
||||
|
||||
Такой код будет работать и ему без разницы, сколько ячеек в таблице. Обработчик всё равно один. Я могу добавлять, удалять `<td>` из таблицы, менять их количество -- моя подсветка будет стабильно работать, так как обработчик стоит на `<table>`.
|
||||
|
||||
Однако, у текущей версии кода есть недостаток.
|
||||
|
||||
**Клик может быть не на том теге, который нас интересует, а внутри него.**
|
||||
|
||||
В нашем случае клик может произойти на вложенном элементе, внутри `<td>`, например на `<strong>`. Такой клик будет пойман по пути наверх, но `target` у него будет не `<td>`, а `<strong>`:
|
||||
|
||||
<img src="bagua.png">
|
||||
|
||||
**Внутри обработчика `table.onclick` мы должны найти нужный `<td>` по `event.target`.**
|
||||
|
||||
Для этого мы вручную, используя ссылку `parentNode`, будем идти вверх по иерархии родителей от `event.target` и выше и проверять:
|
||||
<ul>
|
||||
<li>Если нашли `<td>`, значит это то что нужно.</li>
|
||||
<li>Если дошли до элемента `table` и при этом `<td>` не найден, то наверное клик был вне `<td>`, например на элементе заголовка таблицы.</li>
|
||||
</ul>
|
||||
|
||||
Улучшенный обработчик `table.onclick` с циклом `while`, который этот делает:
|
||||
|
||||
```js
|
||||
table.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
// цикл двигается вверх от target к родителям до table
|
||||
while(target != table) {
|
||||
if (target.tagName == 'TD') {
|
||||
// нашли элемент, который нас интересует!
|
||||
highlight(target);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
// возможна ситуация, когда клик был вне <td>
|
||||
// если цикл дошёл до table и ничего не нашёл,
|
||||
// то обработчик просто заканчивает работу
|
||||
}
|
||||
```
|
||||
|
||||
[smart]
|
||||
Кстати, в проверке `while` можно бы было использовать `this` вместо `table`:
|
||||
|
||||
```js
|
||||
while(target != this) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Это тоже будет работать, так как в обработчике `table.onclick` значением `this` является текущий элемент, то есть `table`.
|
||||
[/smart]
|
||||
|
||||
|
||||
## Применение делегирования: действия в разметке
|
||||
|
||||
Обычно делегирование -- это средство оптимизации интерфейса. Мы используем один обработчик для *схожих* действий на однотипных элементах.
|
||||
|
||||
**Но делегирование позволяет использовать обработчик и для абсолютно разных действий.**
|
||||
|
||||
Например, нам нужно сделать меню с разными кнопками: "Сохранить", "Загрузить", "Поиск" и т.д. И есть объект с соответствующими методами: `save`, `load`, `search` и т.п...
|
||||
|
||||
Первое, что может прийти в голову -- это найти каждую кнопку и назначить ей свой обработчик среди методов объекта.
|
||||
|
||||
Но более изящно решить задачу можно путем добавления одного обработчика на всё меню, а для каждой кнопки в специальном атрибуте, который мы назовем `data-action` (можно придумать любое название, но `data-*` является валидным в HTML5), укажем, что она должна вызывать:
|
||||
|
||||
```html
|
||||
<button *!*data-action="save"*/!*>Нажмите, чтобы Сохранить</button>
|
||||
```
|
||||
|
||||
Обработчик считывает содержимое атрибута и выполняет метод. Взгляните на рабочий пример:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<div id="menu">
|
||||
<button data-action="save">Сохранить</button>
|
||||
<button data-action="load">Загрузить</button>
|
||||
<button data-action="search">Поиск</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function Menu(elem) {
|
||||
this.save = function() { alert('сохраняю'); };
|
||||
this.load = function() { alert('загружаю'); };
|
||||
this.search = function() { alert('ищу'); };
|
||||
|
||||
var self = this;
|
||||
|
||||
elem.onclick = function(e) {
|
||||
var target = e.target;
|
||||
*!*
|
||||
var action = target.getAttribute('data-action');
|
||||
if (action) {
|
||||
self[action]();
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
}
|
||||
|
||||
new Menu(menu);
|
||||
</script>
|
||||
```
|
||||
|
||||
Обратите внимание, как используется трюк с `var self = this`, чтобы сохранить ссылку на объект `Menu`. Иначе обработчик просто бы не смог вызвать методы `Menu`, потому что *его собственный `this` ссылается на элемент*.
|
||||
|
||||
Что в этом случае нам дает использование делегирования событий?
|
||||
[compare]
|
||||
+Не нужно писать код, чтобы присвоить обработчик каждой кнопке. Меньше кода, меньше времени, потраченного на инициализацию.
|
||||
+Структура HTML становится по-настоящему гибкой. Мы можем добавлять/удалять кнопки в любое время.
|
||||
+Данный подход является семантичным. Мы можем использовать классы `.action-save`, `.action-load` вместо атрибута `data-action`, если захотим.
|
||||
[/compare]
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Делегирование событий -- это здорово! Пожалуй, это один из самых полезных приёмов для работы с DOM. Он отлично подходит, если есть много элементов, обработка которых очень схожа.
|
||||
|
||||
Алгоритм:
|
||||
<ol>
|
||||
<li>Вешаем обработчик на контейнер.</li>
|
||||
<li>В обработчике: получаем `event.target`.</li>
|
||||
<li>В обработчике: если необходимо, проходим вверх цепочку `target.parentNode`, пока не найдем нужный подходящий элемент (и обработаем его), или пока не упремся в контейнер (`this`). </li>
|
||||
</ol>
|
||||
Зачем использовать:
|
||||
[compare]
|
||||
+Упрощает инициализацию и экономит память: не нужно вешать много обработчиков.
|
||||
+Меньше кода: при добавлении и удалении элементов не нужно ставить или снимать обработчики.
|
||||
+Удобство изменений: можно массово добавлять или удалять элементы путём изменения `innerHTML`.
|
||||
[/compare]
|
||||
|
||||
Конечно, у делегирования событий есть свои ограничения.
|
||||
|
||||
[compare]
|
||||
-Во-первых, событие должно всплывать, и нельзя, чтобы какой-то промежуточный обработчик вызвал `event.stopPropagation()`.
|
||||
-Во-вторых, делегирование создает дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка невелика и не является проблемой.
|
||||
[/compare]
|
||||
|
||||
|
||||
|
||||
|
BIN
2-ui/2-events-and-interfaces/5-event-delegation/bagua.png
Executable file
BIN
2-ui/2-events-and-interfaces/5-event-delegation/bagua.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
56
2-ui/2-events-and-interfaces/5-event-delegation/bagua.view/bagua.css
Executable file
56
2-ui/2-events-and-interfaces/5-event-delegation/bagua.view/bagua.css
Executable file
|
@ -0,0 +1,56 @@
|
|||
#bagua-table th {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#bagua-table td {
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: bottom;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
#bagua-table .nw {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
#bagua-table .n {
|
||||
background: #03f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .ne {
|
||||
background: #ff6;
|
||||
}
|
||||
|
||||
#bagua-table .w {
|
||||
background: #ff0;
|
||||
}
|
||||
#bagua-table .c {
|
||||
background: #60c;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .e {
|
||||
background: #09f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .sw {
|
||||
background: #963;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .s {
|
||||
background: #f60;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .se {
|
||||
background: #0c3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .highlight {
|
||||
background: red;
|
||||
}
|
66
2-ui/2-events-and-interfaces/5-event-delegation/bagua.view/index.html
Executable file
66
2-ui/2-events-and-interfaces/5-event-delegation/bagua.view/index.html
Executable file
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="bagua.css">
|
||||
|
||||
|
||||
<table id="bagua-table">
|
||||
<tr>
|
||||
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="nw"><strong>Northwest</strong><br>Metal<br>Silver<br>Elders
|
||||
</td>
|
||||
<td class="n"><strong>North</strong><br>Water<br>Blue<br>Change
|
||||
</td>
|
||||
<td class="ne"><strong>Northeast</strong><br>Earth<br>Yellow<br>Direction
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="w"><strong>West</strong><br>Metal<br>Gold<br>Youth
|
||||
</td>
|
||||
<td class="c"><strong>Center</strong><br>All<br>Purple<br>Harmony
|
||||
</td>
|
||||
<td class="e"><strong>East</strong><br>Wood<br>Blue<br>Future
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="sw"><strong>Southwest</strong><br>Earth<br>Brown<br>Tranquility
|
||||
</td>
|
||||
<td class="s"><strong>South</strong><br>Fire<br>Orange<br>Fame
|
||||
</td>
|
||||
<td class="se"><strong>Southeast</strong><br>Wood<br>Green<br>Romance
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<script>
|
||||
var table = document.getElementById('bagua-table');
|
||||
|
||||
var selectedTd;
|
||||
|
||||
table.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
while(target != this) {
|
||||
if (target.tagName == 'TD') {
|
||||
highlight(target);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function highlight(node) {
|
||||
if (selectedTd) {
|
||||
selectedTd.classList.remove('highlight');
|
||||
}
|
||||
selectedTd = node;
|
||||
selectedTd.classList.add('highlight');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
76
2-ui/2-events-and-interfaces/6-behavior/article.md
Normal file
76
2-ui/2-events-and-interfaces/6-behavior/article.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Приём проектирования "поведение"
|
||||
|
||||
Шаблон проектирования "поведение" (behavior) позволяет задавать хитрые обработчики на элементе *декларативно*, установкой специальных HTML-атрибутов и классов.
|
||||
[cut]
|
||||
|
||||
Например, хочется, чтобы при клике на один элемент показывался другой. Конечно, можно поставить обработчик в атрибуте `onclick` или даже описать его где-нибудь в JS-коде страницы.
|
||||
|
||||
Но есть решение другое, и очень изящное.
|
||||
|
||||
## Описание
|
||||
|
||||
Приём проектирования "поведение" состоит из двух частей:
|
||||
<ol>
|
||||
<li>Элементу ставится атрибут, описывающий его поведение.</li>
|
||||
<li>При помощи делегирования ставится обработчик на документ, который ловит все клики и, если элемент имеет нужный атрибут, производит нужное действие.
|
||||
</ol>
|
||||
|
||||
## Пример
|
||||
|
||||
Например, я хочу, чтобы при клике на один элемент скрывался/показывался другой.
|
||||
|
||||
Конечно, можно написать соответствующий обработчик в JavaScript.
|
||||
|
||||
А что, если подобная задача возникает часто?
|
||||
|
||||
Хотелось бы получить более простой способ задания такого *поведения*, например -- *декларативно*, при помощи особого атрибута.
|
||||
|
||||
**Сделаем так, что при клике на элемент с атрибутом `data-toggle-id` будет скрываться/показываться элемент с заданным `id`.**
|
||||
|
||||
```html
|
||||
<!--+ autorun run height=100 -->
|
||||
<button *!*data-toggle-id="subscribe-mail"*/!*>
|
||||
Показать форму подписки
|
||||
</button>
|
||||
|
||||
<form id="subscribe-mail" style="display:none">
|
||||
Ваша почта: <input type="email">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
document.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
var id = target.getAttribute('data-toggle-id');
|
||||
if (!id) return;
|
||||
|
||||
var elem = document.getElementById(id);
|
||||
|
||||
elem.style.display = (elem.style.display == 'none') ? "" : "none";
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
**При помощи JavaScript мы добавили "поведение" -- возможность через атрибут указывать, что делает элемент.**
|
||||
|
||||
[smart header="Не только атрибут"]
|
||||
Для своих целей мы можем использовать в HTML любые атрибуты, но стандарт рекомендует для своих целей называть атрибуты `data-*`.
|
||||
|
||||
В обработчике `document.onclick` мы могли бы проверять не атрибут, а класс или что-то ещё.
|
||||
[/smart]
|
||||
|
||||
Обратите внимание: обработчик поставлен на `document`, то есть клик на любом элементе страницы пройдёт через него. Теперь для того, чтобы добавить скрытие-раскрытие любому элементу -- даже не надо знать JavaScript, можно просто написать атрибут.
|
||||
|
||||
Также для добавления обработчиков на `document` рекомендуется использовать `addEventListener`, чтобы можно было добавлять несколько различных поведений на документ.
|
||||
|
||||
## Итого
|
||||
|
||||
Шаблон "поведение" удобен тем, что сколь угодно сложное JavaScript-поведение можно "навесить" на элемент одним лишь атрибутом. А можно -- несколькими атрибутами на связанных элементах.
|
||||
|
||||
Здесь мы рассмотрели базовый пример, который можно как угодно модифицировать и масштабировать. Важно не переусердствовать.
|
||||
|
||||
**Приём разработки "поведение" рекомендуется использовать для расширения возможностей разметки, как альтернативу мини-фрагментам JavaScript.**
|
||||
|
||||
Далее у нас ещё будут задачи, где мы реализуем этот приём разработки.
|
|
@ -0,0 +1,42 @@
|
|||
Дело в том, что обработчик из атрибута `onclick` делается браузером как функция с заданным телом.
|
||||
|
||||
То есть, он будет таким:
|
||||
|
||||
```js
|
||||
function(event) {
|
||||
handler()
|
||||
}
|
||||
```
|
||||
|
||||
При этом возвращаемое `handler` значение никак не используется и не влияет на результат.
|
||||
|
||||
Рабочий вариант:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<script>
|
||||
function handler() {
|
||||
alert("...");
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href="http://w3.org" onclick="*!*return handler()*/!*">w3.org</a>
|
||||
```
|
||||
|
||||
Альтернатива -- передать и использовать объект события для вызова `event.preventDefault()` (или кросс-браузерного варианта для поддержки старых IE).
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<script>
|
||||
*!*
|
||||
function handler(event) {
|
||||
alert("...");
|
||||
event.preventDefault ? event.preventDefault() : (event.returnValue=false);
|
||||
}
|
||||
*/!*
|
||||
</script>
|
||||
|
||||
<a href="http://w3.org" onclick="*!*handler(event)*/!*">w3.org</a>
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Почему не работает return false?
|
||||
|
||||
[importance 3]
|
||||
|
||||
Почему в этом документе `return false` не работает?
|
||||
|
||||
```html
|
||||
<!--+ autorun run -->
|
||||
<script>
|
||||
function handler() {
|
||||
alert("...");
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href="http://w3.org" onclick="handler()">w3.org</a>
|
||||
```
|
||||
|
||||
По замыслу, переход на `w3.org` при клике должен отменяться. Однако, на самом деле он происходит.
|
||||
|
||||
В чём дело и как поправить, сохранив `onclick` в HTML?
|
|
@ -0,0 +1,29 @@
|
|||
Это -- классическая задача на тему делегирования.
|
||||
|
||||
В реальной жизни, мы можем перехватить событие и создать 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-атрибуте, так как свойство может отличаться, оно обязано содержать полный валидный адрес.
|
||||
|
||||
[edit src="solution"]Полное решение[/edit].
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="contents">
|
||||
<p>
|
||||
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('contents').onclick = function(evt) {
|
||||
var evt = evt || event
|
||||
var target = evt.target || evt.srcElement
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="contents">
|
||||
<p>
|
||||
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
# Поймайте переход по ссылке
|
||||
|
||||
[importance 5]
|
||||
|
||||
Сделайте так, чтобы при клике на ссылки внутри <code><DIV id="contents"></code> пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу и если он не хочет, то прерывать переход по ссылке.
|
||||
|
||||
Так это должно работать:
|
||||
|
||||
[iframe height=100 border=1 src="solution"]
|
||||
|
||||
Детали:
|
||||
<ul>
|
||||
<li>Содержимое блока `DIV` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование.</li>
|
||||
<li>Содержимое может содержать вложенные теги, *в том числе внутри ссылок*, например, `<a href=".."><i>...</i></a>`.</li>
|
||||
</ul>
|
||||
|
||||
[edit src="source" task/]
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
Решение состоит в том, чтобы добавить обработчик на контейнер `#thumbs` и отслеживать клики на ссылках.
|
||||
|
||||
Когда происходит событие, обработчик должен изменять `src` `#largeImg` на `href` ссылки и заменять `alt` на ее `title`.
|
||||
|
||||
Код решения:
|
||||
|
||||
```js
|
||||
var largeImg = document.getElementById('largeImg');
|
||||
|
||||
document.getElementById('thumbs').onclick = function(e) {
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement;
|
||||
|
||||
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`.
|
||||
|
||||
[edit src="solution"]Полное решение[/edit]
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font: 75%/120% Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: bold 190%/100% Arial, Helvetica, sans-serif;
|
||||
margin: 0 0 .2em;
|
||||
}
|
||||
h2 em {
|
||||
font: normal 80%/100% Arial, Helvetica, sans-serif;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
#largeImg {
|
||||
border: solid 1px #ccc;
|
||||
width: 550px;
|
||||
height: 400px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#thumbs a {
|
||||
border: solid 1px #ccc;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
padding: 3px;
|
||||
margin: 2px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#thumbs a:hover {
|
||||
border-color: #FF9900;
|
||||
}
|
||||
|
||||
#thumbs li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#thumbs {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Галерея</title>
|
||||
<link rel="stylesheet" type="text/css" href="gallery.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p><img id="largeImg" src="http://js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
|
||||
|
||||
<ul id="thumbs">
|
||||
<!-- При наведении на изображение показывается встроенная подсказка браузера (title) -->
|
||||
<li><a href="http://js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="http://js.cx/gallery/img2-thumb.jpg"></a></li>
|
||||
<li><a href="http://js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="http://js.cx/gallery/img3-thumb.jpg"></a></li>
|
||||
<li><a href="http://js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="http://js.cx/gallery/img4-thumb.jpg"></a></li>
|
||||
<li><a href="http://js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="http://js.cx/gallery/img5-thumb.jpg"></a></li>
|
||||
<li><a href="http://js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="http://js.cx/gallery/img6-thumb.jpg"></a></li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
var largeImg = document.getElementById('largeImg');
|
||||
|
||||
var thumbs = document.getElementById('thumbs');
|
||||
|
||||
thumbs.onclick = function(e) {
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/* предзагрузка */
|
||||
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>
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font: 75%/120% Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: bold 190%/100% Arial, Helvetica, sans-serif;
|
||||
margin: 0 0 .2em;
|
||||
}
|
||||
h2 em {
|
||||
font: normal 80%/100% Arial, Helvetica, sans-serif;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
#largeImg {
|
||||
border: solid 1px #ccc;
|
||||
width: 550px;
|
||||
height: 400px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#thumbs a {
|
||||
border: solid 1px #ccc;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
padding: 3px;
|
||||
margin: 2px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#thumbs a:hover {
|
||||
border-color: #FF9900;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Галерея</title>
|
||||
<link rel="stylesheet" type="text/css" href="gallery.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p><img id="largeImg" src="http://js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
|
||||
|
||||
<div id="thumbs">
|
||||
<!-- При наведении на изображение показывается встроенная подсказка браузера (title) -->
|
||||
<a href="http://js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="http://js.cx/gallery/img2-thumb.jpg"></a>
|
||||
<a href="http://js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="http://js.cx/gallery/img3-thumb.jpg"></a>
|
||||
<a href="http://js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="http://js.cx/gallery/img4-thumb.jpg"></a>
|
||||
<a href="http://js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="http://js.cx/gallery/img5-thumb.jpg"></a>
|
||||
<a href="http://js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="http://js.cx/gallery/img6-thumb.jpg"></a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
# Галерея изображений
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте галерею изображений, в которой основное изображение изменяется при клике на уменьшенный вариант.
|
||||
|
||||
Результат должен выглядеть так:
|
||||
|
||||
[iframe src="solution" height=600]
|
||||
|
||||
Для обработки событий используйте делегирование, т.е. не более одного обработчика.
|
||||
|
||||
[edit src="source" task/]
|
||||
|
||||
P.S. Обратите внимание -- клик может быть как на маленьком изображении `IMG`, так и на `A` вне него. При этом `event.target` будет, соответственно, либо `IMG`, либо `A`.
|
||||
|
||||
Дополнительно:
|
||||
|
||||
<ul>
|
||||
<li>Если получится -- сделайте предзагрузку больших изображений, чтобы при клике они появлялись сразу.</li>
|
||||
<li>Всё ли в порядке с семантической вёрсткой в HTML исходного документа? Если нет -- поправьте, чтобы было, как нужно.</li>
|
||||
</ul>
|
152
2-ui/2-events-and-interfaces/7-default-browser-action/article.md
Normal file
152
2-ui/2-events-and-interfaces/7-default-browser-action/article.md
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Действия браузера по умолчанию
|
||||
|
||||
Многие события влекут за собой действие браузера.
|
||||
|
||||
Например:
|
||||
<ul>
|
||||
<li>Клик по ссылке инициирует переход на новый URL</li>
|
||||
<li>Нажатие на кнопку "отправить" в форме -- посылку ее на сервер</li>
|
||||
<li>Двойной клик на тексте -- инициирует его выделение.</li>
|
||||
</ul>
|
||||
|
||||
**Зачастую, мы полностью обрабатываем событие в JavaScript, и такое действие браузера нам не нужно.**
|
||||
|
||||
К счастью, его можно отменить.
|
||||
|
||||
[cut]
|
||||
## Отмена действия браузера
|
||||
|
||||
Есть два способа отменить действие браузера:
|
||||
<ul>
|
||||
<li>**Основной способ -- это воспользоваться объектом события. Для отмены действия браузера существует стандартный метод `event.preventDefault()`.**</li>
|
||||
<li>Если же обработчик назначен через `on...` (не через `addEventListener/attachEvent`), то можно просто вернуть `false` из обработчика.</li>
|
||||
</ul>
|
||||
|
||||
В следующем примере при клике по ссылке переход не произойдет:
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<a href="/" onclick="return false">Нажми здесь</a>
|
||||
или
|
||||
<a href="/" onclick="event.preventDefault()">здесь</a>
|
||||
```
|
||||
|
||||
[warn header="Возвращать `true` не нужно"]
|
||||
Вообще говоря, значение, которое возвращает обработчик, игнорируется.
|
||||
|
||||
Единственное исключение -- это `return false` из обработчика, назначенного через `onсобытие`.
|
||||
|
||||
Иногда в коде начинающих разработчиков можно увидеть `return` других значений. Но они не нужны и никак не обрабатываются.
|
||||
[/warn]
|
||||
|
||||
### Пример: меню
|
||||
|
||||
Рассмотрим задачу, когда нужно создать меню для сайта, например такое:
|
||||
|
||||
```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>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Данный пример при помощи CSS может выводиться так:
|
||||
|
||||
[iframe height=70 src="menu" link edit]
|
||||
|
||||
**Все элементы меню являются ссылками, то есть тегами `<a>`.**
|
||||
|
||||
Это потому, что некоторые посетители очень любят сочетание "правый клик - открыть в новом окне". Да, мы можем использовать и `<button>` и `<span>`, но если правый клик не работает -- это их огорчает. Кроме того, если на сайт зайдёт поисковик, то по ссылке из `<a href="...">` он перейдёт, а выполнить сложный JavaScript и получить результат -- вряд ли захочет.
|
||||
|
||||
**Значение `<a href="...">` -- это "запасной вариант", для правого клика и для поисковиков, а обычно клик будет обрабатываться JavaScript.**
|
||||
|
||||
Например, вот так:
|
||||
|
||||
```js
|
||||
menu.onclick = function(event) {
|
||||
if (event.target.nodeName != 'A') return;
|
||||
|
||||
var href = event.target.getAttribute('href');
|
||||
alert(href); // может быть подгрузка с сервера, генерация интерфейса и т.п.
|
||||
|
||||
*!*
|
||||
return false; // отменить переход по url
|
||||
*/!*
|
||||
};
|
||||
```
|
||||
|
||||
В конце `return false`, иначе браузер перейдёт по адресу из `href`.
|
||||
|
||||
Так как мы применили делегирование, то меню может увеличиваться, можно добавить вложенные списки `ul/li`, стилизовать их при помощи CSS -- меню продолжит работать.
|
||||
|
||||
## Другие действия браузера
|
||||
|
||||
Действий браузера по умолчанию достаточно много.
|
||||
|
||||
Вот некоторые примеры событий, которые вызывают действие браузера:
|
||||
<ul>
|
||||
<li>`mousedown` -- нажатие кнопкой мыши в то время как курсор находится на тексте начинает его выделение.</li>
|
||||
<li>`click` на `<input type="checkbox">` -- ставит или убирает галочку.</li>
|
||||
<li>`submit` -- при нажатии на `<input type="submit">` в форме данные отправляются на сервер.</li>
|
||||
<li>`wheel` -- движение колёсика мыши инициирует прокрутку.</li>
|
||||
<li>`keydown` -- при нажатии клавиши в поле ввода появляется символ.</li>
|
||||
<li>`contextmenu` -- при правом клике показывается контекстное меню браузера.</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
|
||||
Все эти действия можно отменить, если мы хотим обработать событие исключительно при помощи JavaScript.
|
||||
|
||||
[warn header="События могут быть связаны между собой"]
|
||||
Некоторые события естественным образом вытекают друг из друга.
|
||||
|
||||
Например, нажатие мышкой `mousedown` на поле ввода `<input>` приводит к фокусировке внутрь него. Если отменить действие `mousedown`, то и фокуса не будет.
|
||||
|
||||
Попробуйте нажать мышкой на первый `<input>` -- произойдёт событие `onfocus`. Это обычная ситуация.
|
||||
|
||||
Но если нажать на второй, то фокусировки не произойдёт.
|
||||
|
||||
```html
|
||||
<!--+ run autorun -->
|
||||
<input value="Фокус работает" onfocus="this.value=''">
|
||||
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Кликни меня">
|
||||
```
|
||||
|
||||
...С другой стороны, во второй `<input>` можно перейти с первого нажатием клавиши [key Tab], и тогда фокусировка сработает. То есть, дело здесь именно в `onmousedown="return false"`.
|
||||
[/warn]
|
||||
|
||||
## Особенности IE8-
|
||||
|
||||
В IE8- для отмены действия по умолчанию нужно назначить свойство `event.returnValue = false`.
|
||||
|
||||
Кроссбраузерный код для отмены действия по умолчанию:
|
||||
|
||||
```js
|
||||
element.onclick = function(event) {
|
||||
event = event || window.event;
|
||||
|
||||
if (event.preventDefault) { // если метод существует
|
||||
event.preventDefault(); // то вызвать его
|
||||
} else { // иначе вариант IE<9:
|
||||
event.returnValue = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Можно записать в одну строку:
|
||||
|
||||
```js
|
||||
...
|
||||
event.preventDefault ? event.preventDefault() : (event.returnValue=false);
|
||||
...
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Браузер имеет встроенные действия при ряде событий -- переход по ссылке, отправка формы и т.п. Как правило, их можно отменить.</li>
|
||||
<li>Есть два способа отменить действие по умолчанию: первый -- использовать `event.preventDefault()` (IE<9: `event.returnValue=false`), второй -- `return false` из обработчика. Второй способ работает только если обработчик назначен через `onсобытие`.</li>
|
||||
</ul>
|
||||
|
||||
|
19
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/index.html
Executable file
19
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/index.html
Executable file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="menu.css"/>
|
||||
</head>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<script src="menu.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
25
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/menu.css
Executable file
25
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/menu.css
Executable file
|
@ -0,0 +1,25 @@
|
|||
.menu li {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu > li a {
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
font: 14px/100% Arial, Helvetica, sans-serif;
|
||||
padding: .5em 2em .55em;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,.3);
|
||||
border-radius: .5em;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.2);
|
||||
color: #d9eef7;
|
||||
border: solid 1px #0076a3;
|
||||
background: #0095cd;
|
||||
}
|
||||
|
||||
.menu > li:hover a {
|
||||
text-decoration: none;
|
||||
background: #007ead;
|
||||
}
|
8
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/menu.js
Executable file
8
2-ui/2-events-and-interfaces/7-default-browser-action/menu.view/menu.js
Executable file
|
@ -0,0 +1,8 @@
|
|||
menu.onclick = function(event) {
|
||||
if (event.target.nodeName != 'A') return;
|
||||
|
||||
var href = event.target.getAttribute('href');
|
||||
alert(href);
|
||||
|
||||
return false; // prevent url change
|
||||
};
|
384
2-ui/2-events-and-interfaces/8-dispatch-events/article.md
Normal file
384
2-ui/2-events-and-interfaces/8-dispatch-events/article.md
Normal file
|
@ -0,0 +1,384 @@
|
|||
# Генерация событий на элементах
|
||||
|
||||
Можно не только слушать браузерные события, но и генерировать их самому.
|
||||
|
||||
Это нужно довольно редко, преимущественно -- для целей автоматического тестирования.
|
||||
|
||||
[cut]
|
||||
|
||||
## Конструктор Event
|
||||
|
||||
Вначале рассмотрим современный способ генерации событий, по стандарту [DOM 4](http://www.w3.org/TR/dom/#introduction-to-dom-events). Он поддерживается всеми браузерами, кроме IE11-. А далее рассмотрим устаревшие варианты, поддерживаемые IE.
|
||||
|
||||
**Объект события создаётся при помощи конструктора [Event](http://www.w3.org/TR/dom/#event).**
|
||||
|
||||
Синтаксис:
|
||||
|
||||
```js
|
||||
var event = new Event(тип события[, флаги]);
|
||||
```
|
||||
|
||||
Где:
|
||||
<ul>
|
||||
<li>*Тип события* -- может быть как своим, так и встроенным, к примеру `"click"`.</li>
|
||||
<li>*Флаги* -- объект вида `{ bubbles: true/false, cancelable: true/false }`, где свойство `bubbles` указывает, всплывает ли событие, а `cancelable` -- можно ли отменить действие по умолчанию.
|
||||
|
||||
Не обязателен, по умолчанию `{bubbles: false, cancelable: false}`.</li>
|
||||
</ul>
|
||||
|
||||
### Метод dispatchEvent
|
||||
|
||||
Затем, чтобы инициировать событие, запускается `elem.dispatchEvent(event)`.
|
||||
|
||||
Событие обрабатывается "как обычно", передаётся обработчикам, всплывает... Этот метод возвращает `false`, если событие было отменено при помощи `preventDefault()`. Конечно, отмена возможна лишь если событие изначально было создано с флагом `cancelable`.
|
||||
|
||||
При просмотре примера ниже кнопка будет нажата скриптом:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<button id="elem" onclick="alert('Клик');">Автоклик</button>
|
||||
|
||||
<script>
|
||||
var event = new Event("click");
|
||||
elem.dispatchEvent(event);
|
||||
</script>
|
||||
```
|
||||
|
||||
[smart header="Как отличить реальное нажатие от скриптового?"]
|
||||
В целях безопасности иногда хорошо бы знать -- инициировано ли действие посетителем или это кликнул скрипт.
|
||||
|
||||
Единственный способ, которым код может отличить реальное нажатие от программного, является проверка свойства `event.isTrusted`.
|
||||
|
||||
Оно на момент написания статьи поддерживается IE и Firefox и равно `true`, если посетитель кликнул сам, и всегда `false` -- если событие инициировал скрипт.
|
||||
[/smart]
|
||||
|
||||
Браузер автоматически ставит следующие свойства объекта `event`:
|
||||
|
||||
<ul>
|
||||
<li>`isTrusted: false` -- означает, что событие сгенерировано скриптом, это свойство изменить невозможно.</li>
|
||||
<li>`target: null` -- это свойство ставится автоматически позже при `dispatchEvent`.</li>
|
||||
<li>`type: тип события` -- первый аргумент `new Event`.</li>
|
||||
<li>`bubbles`, `cancelable` -- по второму аргументу `new Event`.</li>
|
||||
</ul>
|
||||
|
||||
Другие свойства события, если они нужны, например координаты для события мыши -- можно присвоить в объект события позже, например:
|
||||
|
||||
```js
|
||||
var event = new Event("click");
|
||||
event.clientX = 100;
|
||||
event.clientY = 100;
|
||||
```
|
||||
|
||||
### Пример с hello
|
||||
|
||||
Можно генерировать события с любыми названиями.
|
||||
|
||||
Для примера сгенерируем совершенно новое событие `"hello"`:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<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>
|
||||
```
|
||||
|
||||
Обратите внимание:
|
||||
<ol>
|
||||
<li>Обработчик события `hello` стоит на `document`. Мы его поймаем на всплытии.</li>
|
||||
<li>Вызов `event.preventDefault()` приведёт к тому, что `dispatchEvent` вернёт `false`.</li>
|
||||
<li>Чтобы событие всплывало и его можно было отменить, указан второй аргумент `new Event`.</li>
|
||||
</ol>
|
||||
|
||||
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, они создаются и работают совершенно одинаково.
|
||||
|
||||
## Конструкторы MouseEvent, KeyboardEvent и другие
|
||||
|
||||
Для конкретных типов событий есть свои конструкторы.
|
||||
|
||||
Вот список конструкторов для различных событий интерфейса которые можно найти в спецификации [UI Event](http://www.w3.org/TR/uievents/):
|
||||
<ul>
|
||||
<li>`UIEvent`</li>
|
||||
<li>`FocusEvent`</li>
|
||||
<li>`MouseEvent`</li>
|
||||
<li>`WheelEvent`</li>
|
||||
<li>`KeyboardEvent`</li>
|
||||
<li>`CompositionEvent`</li>
|
||||
</ul>
|
||||
|
||||
Вместо `new Event("click")` можно вызвать `new MouseEvent("click")`.
|
||||
|
||||
**Конкретный конструктор позволяет указать стандартные свойства для данного типа события.**
|
||||
|
||||
Например:
|
||||
|
||||
```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
|
||||
*/!*
|
||||
```
|
||||
|
||||
...То есть, "мышиные" свойства можно сразу же в конструкторе указать только если это `MouseEvent`, а `Event` их игнорирует.
|
||||
|
||||
**Использование конкретного конструктора не является обязательным, можно обойтись `Event`.**
|
||||
|
||||
Свойства можно присвоить и явно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
|
||||
|
||||
Полный список свойств по типам событий вы найдёте в спецификации, например для `MouseEvent`: [MouseEvent Constructor](http://www.w3.org/TR/uievents/#constructor-mouseevent).
|
||||
|
||||
## Свои события
|
||||
|
||||
Для генерации встроенных событий существуют описанные выше конструкторы, а для генерации своих, нестандартных, событий существует конструктор [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+
|
||||
|
||||
В предыдущем стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events) была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
|
||||
|
||||
Она поддерживается как современными браузерами, так и IE9+. Для генерации событий используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
|
||||
|
||||
Объект события создаётся вызовом `document.createEvent`:
|
||||
|
||||
```js
|
||||
var event = document.createEvent(eventInterface);
|
||||
```
|
||||
|
||||
Аргументы:
|
||||
<ul>
|
||||
<li>`eventInterface` -- это тип события, например `MouseEvent`, `FocusEvent`, `KeyboardEvent`. В [секции 5 DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#events-module) есть подробный список, какое событие к какому интерфейсу относится.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
**На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.**
|
||||
|
||||
Далее событие нужно инициализовать:
|
||||
|
||||
```js
|
||||
event.initEvent(type, boolean bubbles, boolean cancelable);
|
||||
```
|
||||
|
||||
Аргументы:
|
||||
<ul>
|
||||
<li>`type` -- тип события, например `"click"`.</li>
|
||||
<li>`bubbles` -- всплывает ли событие.</li>
|
||||
<li>`cancelable` -- можно ли отменить событие`.</li>
|
||||
</ul>
|
||||
|
||||
Эти два кода аналогичны:
|
||||
|
||||
```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`, но это нужно проверять в конкретных случаях, обычно это не работает или работает некорректно.
|
||||
[/smart]
|
||||
|
||||
## Антистандарт: 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` настраивать нельзя, браузер использует стандартные для данного типа событий.
|
||||
|
||||
## Кросс-браузерный пример
|
||||
|
||||
Для поддержки IE9+ достаточно использовать методы `document.createEvent` и `event.initEvent`, как показано выше, и всё будет хорошо.
|
||||
|
||||
Если же нужен 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>IE8- может генерировать только встроенные события.</li>
|
||||
</ul>
|
||||
|
||||
**Несмотря на техническую возможность генерировать браузерные события -- пользоваться ей стоит с большой осторожностью.**
|
||||
|
||||
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.
|
||||
|
||||
Как правило события имеет смысл генерировать:
|
||||
<ul>
|
||||
<li>Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.</li>
|
||||
<li>Либо для автоматического тестирования, чтобы скриптом "нажать на кнопку" и посмотреть, произошло ли нужное действие.</li>
|
||||
<li>Либо при создании своих "элементов интерфейса". Например, никто не мешает при помощи JavaScript создать из `<div class="calendar">` красивый календарь и генерировать на нём событие `change` при выборе даты. Эту тему мы разовьём позже.</li>
|
||||
</ul>
|
3
2-ui/2-events-and-interfaces/index.md
Normal file
3
2-ui/2-events-and-interfaces/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Основы работы с событиями
|
||||
|
||||
Введение в браузерные события, общие свойства всех событий и приёмы работы с ними.
|
Loading…
Add table
Add a link
Reference in a new issue