renovations
This commit is contained in:
parent
e1d099ae97
commit
53d9080aad
50 changed files with 653 additions and 471 deletions
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 142 KiB |
|
@ -4,7 +4,7 @@
|
|||
<title>metric-scroll-width-height.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs>
|
||||
<rect id="path-1" x="46" y="23" width="324" height="498"></rect>
|
||||
<rect id="path-1" x="46" y="23" width="324" height="500"></rect>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
|
||||
<stop stop-color="#FFFFFF" offset="0%"></stop>
|
||||
<stop stop-color="#F1F1F1" offset="100%"></stop>
|
||||
|
@ -19,26 +19,29 @@
|
|||
<tspan x="66" y="102" font-size="16" font-weight="normal">originating technologies, the most well </tspan>
|
||||
<tspan x="66" y="121" font-size="16" font-weight="normal">known being JavaScript (Netscape) and </tspan>
|
||||
<tspan x="66" y="140" font-size="16" font-weight="normal">JScript (Microsoft). The language was </tspan>
|
||||
<tspan x="66" y="159" font-size="16" font-weight="normal">invented by Brendan Eich at Netscape and </tspan>
|
||||
<tspan x="66" y="178" font-size="16" font-weight="normal">first appeared in that company’s Navigator </tspan>
|
||||
<tspan x="66" y="197" font-size="16" font-weight="normal">2.0 browser. It has appeared in all </tspan>
|
||||
<tspan x="66" y="216" font-size="16" font-weight="normal">subsequent browsers from Netscape and in </tspan>
|
||||
<tspan x="66" y="235" font-size="16" font-weight="normal">all browsers from Microsoft starting with </tspan>
|
||||
<tspan x="66" y="254" font-size="16" font-weight="normal">Internet Explorer 3.0.</tspan>
|
||||
<tspan x="66" y="273" font-size="16" font-weight="normal">The development of this Standard started in </tspan>
|
||||
<tspan x="66" y="292" font-size="16" font-weight="normal">November 1996. The first edition of this </tspan>
|
||||
<tspan x="66" y="311" font-size="16" font-weight="normal">Ecma Standard was adopted by the Ecma </tspan>
|
||||
<tspan x="66" y="330" font-size="16" font-weight="normal">General Assembly of June 1997.</tspan>
|
||||
<tspan x="66" y="349" font-size="16" font-weight="normal">That Ecma Standard was submitted to ISO/</tspan>
|
||||
<tspan x="66" y="368" font-size="16" font-weight="normal">IEC JTC 1 for adoption under the fast-track </tspan>
|
||||
<tspan x="66" y="387" font-size="16" font-weight="normal">procedure, and approved as international </tspan>
|
||||
<tspan x="66" y="406" font-size="16" font-weight="normal">standard ISO/IEC 16262, in April 1998. </tspan>
|
||||
<tspan x="66" y="425" font-size="16" font-weight="normal">The Ecma General Assembly of June 1998 </tspan>
|
||||
<tspan x="66" y="444" font-size="16" font-weight="normal">approved the second edition of ECMA-262 </tspan>
|
||||
<tspan x="66" y="463" font-size="16" font-weight="normal">to keep it fully aligned with ISO/IEC </tspan>
|
||||
<tspan x="66" y="482" font-size="16" font-weight="normal">16262. Changes between the first and the </tspan>
|
||||
<tspan x="66" y="501" font-size="16" font-weight="normal">second edition are editorial in nature.</tspan>
|
||||
<tspan x="66" y="159" font-size="16" font-weight="normal">invented by Brendan Eich at Netscape </tspan>
|
||||
<tspan x="66" y="178" font-size="16" font-weight="normal">and first appeared in that company’s </tspan>
|
||||
<tspan x="66" y="197" font-size="16" font-weight="normal">Navigator 2.0 browser. It has appeared in </tspan>
|
||||
<tspan x="66" y="216" font-size="16" font-weight="normal">all subsequent browsers from Netscape </tspan>
|
||||
<tspan x="66" y="235" font-size="16" font-weight="normal">and in all browsers from Microsoft </tspan>
|
||||
<tspan x="66" y="254" font-size="16" font-weight="normal">starting with Internet Explorer 3.0.</tspan>
|
||||
<tspan x="66" y="273" font-size="16" font-weight="normal">The development of this Standard started </tspan>
|
||||
<tspan x="66" y="292" font-size="16" font-weight="normal">in November 1996. The first edition of </tspan>
|
||||
<tspan x="66" y="311" font-size="16" font-weight="normal">this Ecma Standard was adopted by the </tspan>
|
||||
<tspan x="66" y="330" font-size="16" font-weight="normal">Ecma General Assembly of June 1997.</tspan>
|
||||
<tspan x="66" y="349" font-size="16" font-weight="normal">That Ecma Standard was submitted to </tspan>
|
||||
<tspan x="66" y="368" font-size="16" font-weight="normal">ISO/IEC JTC 1 for adoption under the </tspan>
|
||||
<tspan x="66" y="387" font-size="16" font-weight="normal">fast-track procedure, and approved as </tspan>
|
||||
<tspan x="66" y="406" font-size="16" font-weight="normal">international standard ISO/IEC 16262, in </tspan>
|
||||
<tspan x="66" y="425" font-size="16" font-weight="normal">April 1998. The Ecma General </tspan>
|
||||
<tspan x="66" y="444" font-size="16" font-weight="normal">Assembly of June 1998 approved the </tspan>
|
||||
<tspan x="66" y="463" font-size="16" font-weight="normal">second edition of ECMA-262 to keep it </tspan>
|
||||
<tspan x="66" y="482" font-size="16" font-weight="normal">fully aligned with ISO/IEC 16262. </tspan>
|
||||
<tspan x="66" y="501" font-size="16" font-weight="normal">Changes between the first and the second </tspan>
|
||||
<tspan x="66" y="520" font-size="16" font-weight="normal">edition are editorial in nature.</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-15" fill-opacity="0.8" fill="#FFFFFF" sketch:type="MSShapeGroup" x="58" y="410" width="312" height="111"></rect>
|
||||
<rect id="Rectangle-14" fill-opacity="0.8" fill="#FFFFFF" sketch:type="MSShapeGroup" x="58" y="35" width="312" height="89"></rect>
|
||||
<path d="M370,148 L370,388 L46,388 L46,148 L370,148 Z M21,123 L395,123 L395,413 L21,413 L21,123 Z" id="Rectangle-1" fill-opacity="0.88" fill="#E8C48F" sketch:type="MSShapeGroup"></path>
|
||||
<g id="Rectangle-2">
|
||||
<use stroke="none" sketch:type="MSShapeGroup" xlink:href="#path-1"></use>
|
||||
|
@ -51,12 +54,10 @@
|
|||
<tspan x="459.289062" y="272" fill="#3B86C4">723px</tspan>
|
||||
</text>
|
||||
<path d="M370.5,22 L448.639999,22" id="Line-27" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" stroke-dasharray="3,6" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M370.5,521 L448.639999,521" id="Line-26" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" stroke-dasharray="3,6" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M439,24.5 L439,518.5" id="Line-25" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-25-decoration-1" d="M439,25.3 C440.05,29.08 440.95,32.32 442,36.1 C439.9,36.1 438.1,36.1 436,36.1 C437.05,32.32 437.95,29.08 439,25.3 C439,25.3 439,25.3 439,25.3 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
<path id="Line-25-decoration-2" d="M439,518.5 C440.05,514.72 440.95,511.48 442,507.7 C439.9,507.7 438.1,507.7 436,507.7 C437.05,511.48 437.95,514.72 439,518.5 C439,518.5 439,518.5 439,518.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
<rect id="Rectangle-14" fill-opacity="0.8" fill="#FFFFFF" sketch:type="MSShapeGroup" x="58" y="33" width="312" height="89"></rect>
|
||||
<rect id="Rectangle-15" fill-opacity="0.8" fill="#FFFFFF" sketch:type="MSShapeGroup" x="58" y="411" width="312" height="89"></rect>
|
||||
<path d="M370.5,524 L448.639999,524" id="Line-26" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" stroke-dasharray="3,6" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M439,25.5 L439,519.5" id="Line-25" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-25-decoration-1" d="M439,26.3 C440.05,30.08 440.95,33.32 442,37.1 C439.9,37.1 438.1,37.1 436,37.1 C437.05,33.32 437.95,30.08 439,26.3 C439,26.3 439,26.3 439,26.3 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
<path id="Line-25-decoration-2" d="M439,519.5 C440.05,515.72 440.95,512.48 442,508.7 C439.9,508.7 438.1,508.7 436,508.7 C437.05,512.48 437.95,515.72 439,519.5 C439,519.5 439,519.5 439,519.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
<path d="M49.6399994,109.09 L348.999985,109.09" id="Line-39" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-39-decoration-1" d="M50.4399994,109.09 C54.2199994,108.04 57.4599994,107.14 61.2399994,106.09 C61.2399994,108.19 61.2399994,109.99 61.2399994,112.09 C57.4599994,111.04 54.2199994,110.14 50.4399994,109.09 C50.4399994,109.09 50.4399994,109.09 50.4399994,109.09 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
<path id="Line-39-decoration-2" d="M348.639999,109.09 C344.859999,108.04 341.619999,107.14 337.839999,106.09 C337.839999,108.19 337.839999,109.99 337.839999,112.09 C341.619999,111.04 344.859999,110.14 348.639999,109.09 C348.639999,109.09 348.639999,109.09 348.639999,109.09 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square"></path>
|
||||
|
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.9 KiB |
|
@ -0,0 +1,24 @@
|
|||
<ul>
|
||||
<li>`top` -- это `pageYOffset`.</li>
|
||||
<li>`bottom` -- это `pageYOffset` плюс высота видимой части `documentElement.clientHeight`.</li>
|
||||
<li>`height` -- полная высота документа, её вычисление давно в главе [](/metrics-window).</li>
|
||||
</ul>
|
||||
|
||||
Итого:
|
||||
|
||||
```js
|
||||
function getDocumentScroll() {
|
||||
var scrollHeight = Math.max(
|
||||
document.body.scrollHeight, document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight, document.documentElement.offsetHeight,
|
||||
document.body.clientHeight, document.documentElement.clientHeight
|
||||
);
|
||||
|
||||
return {
|
||||
top: pageYOffset,
|
||||
bottom: pageYOffset + document.documentElement.clientHeight,
|
||||
height: scrollHeight
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Область видимости для документа
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите функцию `getDocumentScroll()`, которая возвращает объект с информацией о текущей прокрутке и области видимости.
|
||||
|
||||
Свойства объекта-результата:
|
||||
|
||||
<ul>
|
||||
<li>`top` -- координата верхней границы видимой части (относительно документа).</li>
|
||||
<li>`bottom` -- координата нижней границы видимой части (относительно документа).</li>
|
||||
<li>`height` -- полная высота документа, включая прокрутку.</li>
|
||||
</ul>
|
||||
|
||||
В этой задаче учитываем только вертикальную прокрутку: горизонтальная делается аналогично, а нужна сильно реже.
|
|
@ -282,7 +282,7 @@ elem.lastElementChild === body.children[body.children.length-1]
|
|||
Это может привести к сюрпризам при использовании свойства `children`, поэтому HTML-комментарии либо убирают либо используют фреймворк, к примеру, jQuery, который даёт свои методы перебора и отфильтрует их.
|
||||
[/warn]
|
||||
|
||||
## Особые ссылки для таблиц
|
||||
## Особые ссылки для таблиц [#dom-navigation-tables]
|
||||
|
||||
У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации.
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
Сначала можно найти ссылки, например, при помощи `document.querySelectorAll('a')`, а затем выбрать из них нужные.
|
||||
|
||||
Затем определимся -- что использовать для проверки адреса ссылки: свойство `href` или атрибут `getAttribute('href')`?
|
||||
|
||||
Различие между ними заключается в том, что свойство будет содержать полный путь ссылки, а атрибут -- значение, указанное в HTML.
|
||||
|
||||
Если открыть страницу локально, на диске, то для `<a href="/tutorial">` значения будут такими:
|
||||
<ul>
|
||||
<li>`a.getAttribute('href') == "/tutorial"`.</li>
|
||||
<li>`a.href == "file:///tutorial"` (возможно, в пути будет также буква диска).</li>
|
||||
</ul>
|
||||
|
||||
Здесь нужен именно атрибут, хотя бы потому, что в свойстве все ссылки уже с хостом и протоколом, а нам надо понять, был ли протокол в `href` или нет.
|
||||
|
||||
Правила определения:
|
||||
<ul>
|
||||
<li>Cсылки без `href` и без протокола `://` являются заведомо внутренними.</li>
|
||||
<li>Там, где протокол есть -- проверяем, начинается ли адрес с `http://internal.com`.</li>
|
||||
</ul>
|
||||
|
||||
Итого, код может быть таким:
|
||||
```js
|
||||
var links = document.querySelectorAll('a');
|
||||
|
||||
for(var i=0; i<links.length; i++) {
|
||||
|
||||
var a = links[i];
|
||||
|
||||
var href = a.getAttribute('href');
|
||||
|
||||
if (!href) continue; // нет атрибута
|
||||
|
||||
if (href.indexOf('://') == -1) continue; // без протокола
|
||||
|
||||
if (href.indexOf('http://internal.com') === 0) continue; // внутренняя
|
||||
|
||||
a.classList.add('external');
|
||||
}
|
||||
```
|
||||
|
||||
...Но, как это часто бывает, знание CSS может упростить задачу. Удобнее и эффективнее здесь -- указать проверки для `href` прямо в CSS-селекторе:
|
||||
|
||||
```js
|
||||
// ищем все ссылки, у которых в href есть протокол,
|
||||
// но адрес начинается не с http://internal.com
|
||||
var css = 'a[href*="://"]:not([href^="http://internal.com"])';
|
||||
var links = document.querySelectorAll(css);
|
||||
|
||||
for(var i=0; i<links.length; i++) {
|
||||
links[i].classList.add('external');
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
.external {
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<a name="list">список</a>
|
||||
<ul>
|
||||
<li><a href="http://google.com">http://google.com</a></li>
|
||||
<li><a href="/tutorial">/tutorial.html</a></li>
|
||||
<li><a href="local/path">local/path</a></li>
|
||||
<li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
|
||||
<li><a href="http://nodejs.org">http://nodejs.org</a></li>
|
||||
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var css = 'a[href*="://"]:not([href^="http://internal.com"])';
|
||||
var links = document.querySelectorAll(css);
|
||||
|
||||
for(var i=0; i<links.length; i++) {
|
||||
links[i].classList.add('external');
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
# Поставьте класс ссылкам
|
||||
|
||||
[importance 3]
|
||||
|
||||
Сделайте желтыми внешние ссылки, добавив им класс `external`.
|
||||
|
||||
Все ссылки без `href`, без протокола и начинающиеся с `http://internal.com` считаются внутренними.
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<style>
|
||||
.external { background-color: yellow }
|
||||
</style>
|
||||
|
||||
<a name="list">список</a>
|
||||
<ul>
|
||||
<li><a href="http://google.com">http://google.com</a></li>
|
||||
<li><a href="/tutorial">/tutorial.html</a></li>
|
||||
<li><a href="local/path">local/path</a></li>
|
||||
<li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
|
||||
<li><a href="http://nodejs.org">http://nodejs.org</a></li>
|
||||
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
|
||||
Результат:
|
||||
[iframe border=1 height=180 src="solution"]
|
||||
|
|
@ -189,9 +189,9 @@ for(var i=0; i<elems.length; i++) {
|
|||
Как видно из примера, один и тот же обработчик можно назначить на разные стадии. При этом номер текущей стадии он, при необходимости, может получить из свойства `event.eventPhase` (=1, если погружение, =3, если всплытие).
|
||||
|
||||
[smart header="Есть события, которые не всплывают, но которые можно перехватить"]
|
||||
Есть события, которые можно поймать только на стадии перехвата, а на стадии всплытия -- нельзя..
|
||||
Бывают события, которые можно поймать только на стадии перехвата, а на стадии всплытия -- нельзя..
|
||||
|
||||
Например, таково событие фокусировки на элементе [onfocus](/focus-blur).
|
||||
Например, таково событие фокусировки на элементе [onfocus](/focus-blur). Конечно, это большая редкость, такое исключение существует по историческим причинам.
|
||||
[/smart]
|
||||
|
||||
|
||||
|
@ -228,9 +228,9 @@ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
|
|||
</dd>
|
||||
</dl>
|
||||
|
||||
Далее в учебнике мы будем использовать стандартные свойства и вызовы, поскольку добавление этих строк, обеспечивающих совместимость -- достаточно простая и очевидная задача.
|
||||
Далее в учебнике мы будем использовать стандартные свойства и вызовы, поскольку добавление этих строк, обеспечивающих совместимость -- достаточно простая и очевидная задача. Кроме того, никто не мешает подключить полифилл.
|
||||
|
||||
Ещё раз хотелось бы заметить -- эти отличия понадобятся при написании JS-кода с поддержкой IE8- без фреймворков. Почти все JS-фреймворки обеспечивают кросс-браузерную поддержку `target`, `currentTarget` и `stopPropagation()`.
|
||||
Ещё раз хотелось бы заметить -- эти отличия нужно знать при написании JS-кода с поддержкой IE8- без фреймворков. Почти все JS-фреймворки обеспечивают кросс-браузерную поддержку `target`, `currentTarget` и `stopPropagation()`.
|
||||
|
||||
## Итого
|
||||
|
||||
|
@ -254,8 +254,8 @@ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true);
|
|||
|
||||
Этому есть две причины:
|
||||
<ol>
|
||||
<li>Историческая, так как IE лишь с версии 9 в полной мере поддерживает современный стандарт.</li>
|
||||
<li>Разумная -- когда происходит событие, то разумно дать возможность первому сработать обработчику на самом элементе, поскольку он наиболее конкретен. Код, который поставил обработчик именно на этот элемент, знает максимум деталей о том, что это за элемент, чем он занимается, и обработчик через замыкание, скорее всего, имеет к ним доступ.
|
||||
<li>Историческая -- так как IE лишь с версии 9 в полной мере поддерживает современный стандарт.</li>
|
||||
<li>Разумная -- когда происходит событие, то разумно дать возможность первому сработать обработчику на самом элементе, поскольку он наиболее конкретен. Код, который поставил обработчик именно на этот элемент, знает максимум деталей о том, что это за элемент, чем он занимается.
|
||||
|
||||
Далее имеет смысл передать обработку события родителю -- он тоже понимает, что происходит, но уже менее детально, далее -- выше, и так далее, до самого объекта `document`, обработчик на котором реализовывает самую общую функциональность уровня документа.</li>
|
||||
</ol>
|
||||
|
|
|
@ -26,3 +26,8 @@ p {
|
|||
line-height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 25px;
|
||||
font-size: 16px;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="messages.css">
|
||||
<link rel="stylesheet" href="messages.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
|
@ -11,34 +11,33 @@
|
|||
<div class="pane">
|
||||
<h3>Лошадь</h3>
|
||||
<p>Домашняя лошадь — животное семейства непарнокопытных, одомашненный и единственный сохранившийся подвид дикой лошади, вымершей в дикой природе, за исключением небольшой популяции лошади Пржевальского.</p>
|
||||
<span class="remove-button"></span>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Осёл</h3>
|
||||
<p>Домашний осёл или ишак — одомашненный подвид дикого осла, сыгравший важную историческую роль в развитии хозяйства и культуры человека. Все одомашненные ослы относятся к африканским ослам.</p>
|
||||
<span class="remove-button"></span>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
<div class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
<span class="remove-button"></span>
|
||||
<button class="remove-button">[x]</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
document.getElementById('messages-container').onclick = function(e) {
|
||||
var container = document.getElementById('messages-container');
|
||||
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement;
|
||||
container.onclick = function(event) {
|
||||
if (!event.target.classList.contains('remove-button')) return;
|
||||
|
||||
// без цикла, т.к. мы точно знаем, что внутри нет тегов
|
||||
if (target.className != 'remove-button') return;
|
||||
|
||||
target.parentNode.style.display = 'none';
|
||||
event.target.parentNode.hidden = !event.target.parentNode.hidden;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -2,34 +2,36 @@ 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 {
|
||||
position: relative;
|
||||
background: #edf5e1;
|
||||
padding: 10px 20px 10px;
|
||||
border-top: solid 2px #c4df9b;
|
||||
background: #edf5e1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
font-size: 110%;
|
||||
top: 0;
|
||||
color: red;
|
||||
right: 10px;
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(http://js.cx/clipart/delete.gif) no-repeat;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
|
|
@ -7,28 +7,22 @@
|
|||
|
||||
<body>
|
||||
|
||||
<div id="messages-container">
|
||||
Кнопка для удаления: <button class="remove-button">[x]</button>
|
||||
|
||||
<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 class="pane">
|
||||
<h3>Корова, а также пара слов о диком быке, о волах и о тёлках. </h3>
|
||||
<p>Коро́ва — самка домашнего быка, одомашненного подвида дикого быка, парнокопытного жвачного животного семейства полорогих. Самцы вида называются быками, молодняк — телятами, кастрированные самцы — волами. Молодых (до первой стельности) самок называют тёлками.</p>
|
||||
<span class="remove-button"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* ваш код */
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,31 +2,31 @@ 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;
|
||||
font-size: 110%;
|
||||
color: red;
|
||||
right: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
background: url(http://js.cx/clipart/delete.gif) no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
**Используйте делегирование событий. Один обработчик для всего.**
|
||||
|
||||
В результате, должно работать вот так(кликните на крестик):
|
||||
[iframe src="solution" height=500]
|
||||
[iframe src="solution" height=400]
|
||||
|
||||
|
|
|
@ -95,12 +95,12 @@ tree.onclick = function(event) {
|
|||
var li = target.parentNode; // получить родительский LI
|
||||
|
||||
// получить UL с потомками -- это первый UL внутри LI
|
||||
var node = li.getElementsByTagName('ul')[0];
|
||||
var childrenContainer = li.getElementsByTagName('ul')[0];
|
||||
|
||||
if (!node) return; // потомков нет -- ничего не надо делать
|
||||
if (!childrenContainer) return; // потомков нет -- ничего не надо делать
|
||||
|
||||
// спрятать/показать (можно и через CSS-класс)
|
||||
node.style.display = node.style.display ? '' : 'none';
|
||||
childrenContainer.hidden = !childrenContainer.hidden;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.tree span:hover {
|
||||
.tree span:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
.tree span {
|
||||
}
|
||||
.tree span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
@ -73,11 +73,11 @@ tree.onclick = function(event) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* now we know SPAN is clicked */
|
||||
var node = target.parentNode.getElementsByTagName('ul')[0];
|
||||
if (!node) return; // no children
|
||||
/* now we know the SPAN is clicked */
|
||||
var childrenContainer = target.parentNode.getElementsByTagName('ul')[0];
|
||||
if (!childrenContainer) return; // no children
|
||||
|
||||
node.style.display = node.style.display ? '' : 'none';
|
||||
childrenContainer.hidden = !childrenContainer.hidden;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -16,5 +16,4 @@
|
|||
<li>Добавляет `TR` из массива обратно в `TBODY`</li>
|
||||
</ol>
|
||||
|
||||
# Решение
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Сделать сортировку таблицы при клике на заголовок.
|
||||
|
||||
Демо:
|
||||
[iframe border=1 src="solution"]
|
||||
[iframe border=1 src="solution" height=180]
|
||||
|
||||
Требования:
|
||||
<ul>
|
||||
|
|
|
@ -32,9 +32,9 @@
|
|||
|
||||
**Наша задача -- реализовать подсветку ячейки `<td>` при клике.**
|
||||
|
||||
Вместо того, чтобы назначать обработчик для каждой ячейки, мы повесим *один обработчик* на элемент `<table>`.
|
||||
Вместо того, чтобы назначать обработчик для каждой ячейки, которых может быть очень много -- мы повесим *единый обработчик* на элемент `<table>`.
|
||||
|
||||
Он использует `event.target`, чтобы получить элемент, на котором произошло событие, и подсветить его.
|
||||
Он будет использовать `event.target`, чтобы получить элемент, на котором произошло событие, и подсветить его.
|
||||
|
||||
Код будет таким:
|
||||
|
||||
|
@ -60,19 +60,30 @@ function highlight(node) {
|
|||
}
|
||||
```
|
||||
|
||||
Такой код будет работать и ему без разницы, сколько ячеек в таблице. Обработчик всё равно один. Я могу добавлять, удалять `<td>` из таблицы, менять их количество -- моя подсветка будет стабильно работать, так как обработчик стоит на `<table>`.
|
||||
Такому коду нет разницы, сколько ячеек в таблице. Обработчик всё равно один. Я могу добавлять, удалять `<td>` из таблицы, менять их количество -- моя подсветка будет стабильно работать, так как обработчик стоит на `<table>`.
|
||||
|
||||
Однако, у текущей версии кода есть недостаток.
|
||||
|
||||
**Клик может быть не на том теге, который нас интересует, а внутри него.**
|
||||
|
||||
В нашем случае клик может произойти на вложенном элементе, внутри `<td>`, например на `<strong>`. Такой клик будет пойман по пути наверх, но `target` у него будет не `<td>`, а `<strong>`:
|
||||
В нашем случае, если взглянуть на HTML таблицы внимательно, видно, что ячейка содержит вложенные теги, например `<strong>`:
|
||||
|
||||
```html
|
||||
<td>
|
||||
*!*
|
||||
<strong>Northwest</strong>
|
||||
*/!*
|
||||
...Metal..Silver..Elders...
|
||||
</td>
|
||||
```
|
||||
|
||||
Естественно, клик может произойти внутри `<td>`, на элементе `<strong>`. Такой клик будет пойман единым обработчиком, но `target` у него будет не `<td>`, а `<strong>`:
|
||||
|
||||
<img src="bagua.png">
|
||||
|
||||
**Внутри обработчика `table.onclick` мы должны найти нужный `<td>` по `event.target`.**
|
||||
Внутри обработчика `table.onclick` мы должны по `event.target` разобраться, в каком именно `<td>` был клик.
|
||||
|
||||
Для этого мы вручную, используя ссылку `parentNode`, будем идти вверх по иерархии родителей от `event.target` и выше и проверять:
|
||||
Для этого мы, используя ссылку `parentNode`, будем идти вверх по иерархии родителей от `event.target` и выше и проверять:
|
||||
<ul>
|
||||
<li>Если нашли `<td>`, значит это то что нужно.</li>
|
||||
<li>Если дошли до элемента `table` и при этом `<td>` не найден, то наверное клик был вне `<td>`, например на элементе заголовка таблицы.</li>
|
||||
|
@ -112,11 +123,32 @@ while(target != this) {
|
|||
Это тоже будет работать, так как в обработчике `table.onclick` значением `this` является текущий элемент, то есть `table`.
|
||||
[/smart]
|
||||
|
||||
Можно для этого использовать и метод `closest`, при поддержке браузером:
|
||||
|
||||
```js
|
||||
table.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
var td = event.target.closest('td');
|
||||
if (!td) return; // клик вне <td>, не интересует
|
||||
|
||||
// если клик на td, но вне этой таблицы (возможно при вложенных таблицах)
|
||||
// то не интересует
|
||||
if (!table.contains(td)) return;
|
||||
|
||||
// нашли элемент, который нас интересует!
|
||||
highlight(td);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Применение делегирования: действия в разметке
|
||||
|
||||
Обычно делегирование -- это средство оптимизации интерфейса. Мы используем один обработчик для *схожих* действий на однотипных элементах.
|
||||
|
||||
Выше мы это делали для обработки кликов на `<td>`.
|
||||
|
||||
**Но делегирование позволяет использовать обработчик и для абсолютно разных действий.**
|
||||
|
||||
Например, нам нужно сделать меню с разными кнопками: "Сохранить", "Загрузить", "Поиск" и т.д. И есть объект с соответствующими методами: `save`, `load`, `search` и т.п...
|
||||
|
@ -132,7 +164,7 @@ while(target != this) {
|
|||
Обработчик считывает содержимое атрибута и выполняет метод. Взгляните на рабочий пример:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<!--+ autorun height=60 -->
|
||||
<div id="menu">
|
||||
<button data-action="save">Сохранить</button>
|
||||
<button data-action="load">Загрузить</button>
|
||||
|
@ -162,13 +194,13 @@ new Menu(menu);
|
|||
</script>
|
||||
```
|
||||
|
||||
Обратите внимание, как используется трюк с `var self = this`, чтобы сохранить ссылку на объект `Menu`. Иначе обработчик просто бы не смог вызвать методы `Menu`, потому что *его собственный `this` ссылается на элемент*.
|
||||
Обратите внимание, как используется трюк с `var self = this`, чтобы сохранить ссылку на объект `Menu`. Иначе обработчик просто бы не смог вызвать методы `Menu`, потому что его собственный `this` ссылается на элемент.
|
||||
|
||||
Что в этом случае нам дает использование делегирования событий?
|
||||
[compare]
|
||||
+Не нужно писать код, чтобы присвоить обработчик каждой кнопке. Меньше кода, меньше времени, потраченного на инициализацию.
|
||||
+Структура HTML становится по-настоящему гибкой. Мы можем добавлять/удалять кнопки в любое время.
|
||||
+Данный подход является семантичным. Мы можем использовать классы `.action-save`, `.action-load` вместо атрибута `data-action`, если захотим.
|
||||
+Данный подход является семантичным. Также можно использовать классы `.action-save`, `.action-load` вместо атрибута `data-action`.
|
||||
[/compare]
|
||||
|
||||
|
||||
|
@ -180,7 +212,7 @@ new Menu(menu);
|
|||
<ol>
|
||||
<li>Вешаем обработчик на контейнер.</li>
|
||||
<li>В обработчике: получаем `event.target`.</li>
|
||||
<li>В обработчике: если необходимо, проходим вверх цепочку `target.parentNode`, пока не найдем нужный подходящий элемент (и обработаем его), или пока не упремся в контейнер (`this`). </li>
|
||||
<li>В обработчике: если `event.target` или один из его родителей в контейнере (`this`) -- интересующий нас элемент -- обработать его.</li>
|
||||
</ol>
|
||||
Зачем использовать:
|
||||
[compare]
|
||||
|
@ -192,8 +224,8 @@ new Menu(menu);
|
|||
Конечно, у делегирования событий есть свои ограничения.
|
||||
|
||||
[compare]
|
||||
-Во-первых, событие должно всплывать, и нельзя, чтобы какой-то промежуточный обработчик вызвал `event.stopPropagation()`.
|
||||
-Во-вторых, делегирование создает дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка невелика и не является проблемой.
|
||||
-Во-первых, событие должно всплывать. Нельзя, чтобы какой-то промежуточный обработчик вызвал `event.stopPropagation()` до того, как событие доплывёт до нужного элемента.
|
||||
-Во-вторых, делегирование создает дополнительную нагрузку на браузер, ведь обработчик запускается, когда событие происходит в любом месте контейнера, не обязательно на элементах, которые нам интересны. Но обычно эта нагрузка настолько пустяковая, её даже не стоит принимать во внимание.
|
||||
[/compare]
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px; /* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
padding: 10px 20px;
|
||||
|
||||
/* красивости... */
|
||||
border: 1px solid #b3c9ce;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font: italic 14px/1.3 arial, sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-shadow: 3px 3px 3px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
|
||||
<p>Прокрутите страницу, чтобы ссылки были вверху и проверьте, правильно ли показываются подсказки.</p>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var showingTooltip;
|
||||
|
||||
document.onmouseover = function(e) {
|
||||
var target = e.target;
|
||||
|
||||
var tooltip = target.getAttribute('data-tooltip');
|
||||
if (!tooltip) return;
|
||||
|
||||
var tooltipElem = document.createElement('div');
|
||||
tooltipElem.className = 'tooltip';
|
||||
tooltipElem.innerHTML = tooltip;
|
||||
document.body.appendChild(tooltipElem);
|
||||
|
||||
var coords = target.getBoundingClientRect();
|
||||
|
||||
var left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth)/2;
|
||||
if (left < 0) left = 0; // не вылезать за левую границу окна
|
||||
|
||||
var top = coords.top - tooltipElem.offsetHeight - 5;
|
||||
if (top < 0) { // не вылезать за верхнюю границу окна
|
||||
top = coords.top + target.offsetHeight + 5;
|
||||
}
|
||||
|
||||
tooltipElem.style.left = left + 'px';
|
||||
tooltipElem.style.top = top + 'px';
|
||||
|
||||
showingTooltip = tooltipElem;
|
||||
};
|
||||
|
||||
document.onmouseout = function(e) {
|
||||
|
||||
if (showingTooltip) {
|
||||
document.body.removeChild(showingTooltip);
|
||||
showingTooltip = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px; /* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
|
||||
/* ваши стили */
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
|
||||
<p>Прокрутите страницу, чтобы ссылки были вверху и проверьте, правильно ли показываются подсказки.</p>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
// ваш код
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,33 @@
|
|||
# Поведение "подсказка"
|
||||
|
||||
[importance 5]
|
||||
|
||||
При наведении мыши на элемент, на нём возникает событие `mouseover`, при удалении мыши с элемента -- событие `mouseout`.
|
||||
|
||||
Зная это, напишите JS-код, который будет делать так, что при наведении на элемент, если у него есть атрибут `data-tooltip` -- над ним будет показываться подсказка с содержимым этого атрибута.
|
||||
|
||||
Например, две кнопки:
|
||||
|
||||
```html
|
||||
<button data-tooltip="подсказка длиннее, чем элемент">Короткая кнопка</button>
|
||||
<button data-tooltip="HTML<br>подсказка">Ещё кнопка</button>
|
||||
```
|
||||
|
||||
Результат в ифрейме с документом:
|
||||
|
||||
[iframe src="solution" height=200 border=1]
|
||||
|
||||
В этой задаче можно полагать, что в элементе с атрибутом `data-tooltip` -- только текст, то есть нет подэлементов.
|
||||
|
||||
Детали оформления:
|
||||
|
||||
<ul>
|
||||
<li>Подсказка должна появляться при наведении на элемент, по центру и на небольшом расстоянии сверху. При уходе курсора с элемента -- исчезать.</li>
|
||||
<li>Текст подсказки брать из значения атрибута `data-tooltip`. Это может быть произвольный HTML.</li>
|
||||
<li>Оформление подсказки должно задаваться CSS.</li>
|
||||
<li>Подсказка не должна вылезать за границы экрана, в том числе если страница частично прокручена. Если нельзя показать сверху -- показывать снизу элемента.</li>
|
||||
</ul>
|
||||
|
||||
Важно: нужно использовать приём разработки "поведение", то есть поставить обработчик (точнее два) на `document`, а не на каждый элемент.
|
||||
|
||||
Плюс этого подхода -- динамически добавленные в DOM позже элементы автоматически получат этот функционал.
|
|
@ -1,12 +1,9 @@
|
|||
# Приём проектирования "поведение"
|
||||
|
||||
Шаблон проектирования "поведение" (behavior) позволяет задавать хитрые обработчики на элементе *декларативно*, установкой специальных HTML-атрибутов и классов.
|
||||
|
||||
[cut]
|
||||
|
||||
Например, хочется, чтобы при клике на один элемент показывался другой. Конечно, можно поставить обработчик в атрибуте `onclick` или даже описать его где-нибудь в JS-коде страницы.
|
||||
|
||||
Но есть решение другое, и очень изящное.
|
||||
|
||||
## Описание
|
||||
|
||||
Приём проектирования "поведение" состоит из двух частей:
|
||||
|
@ -17,15 +14,38 @@
|
|||
|
||||
## Пример
|
||||
|
||||
Например, я хочу, чтобы при клике на один элемент скрывался/показывался другой.
|
||||
Например, добавим "поведение", которое всем элементам, у которых стоит атрибут `data-counter`, будет при клике увеличивать значение на `1`:
|
||||
|
||||
Конечно, можно написать соответствующий обработчик в JavaScript.
|
||||
```html
|
||||
<!--+ run autorun height=60 -->
|
||||
Счётчик:
|
||||
<button data-counter>1</button>
|
||||
Ещё счётчик:
|
||||
<button data-counter>2</button>
|
||||
|
||||
А что, если подобная задача возникает часто?
|
||||
<script>
|
||||
document.onclick = function(event) {
|
||||
if (!event.target.hasAttribute('data-counter')) return;
|
||||
|
||||
Хотелось бы получить более простой способ задания такого *поведения*, например -- *декларативно*, при помощи особого атрибута.
|
||||
var counter = event.target;
|
||||
|
||||
**Сделаем так, что при клике на элемент с атрибутом `data-toggle-id` будет скрываться/показываться элемент с заданным `id`.**
|
||||
counter.value++;
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Если запустить HTML-код выше, то при клике на каждую кнопку -- её значение будет увеличиваться.
|
||||
|
||||
Конечно, нам важны не счётчики, а общий подход, который они демонстрируют.
|
||||
|
||||
Элементов `data-counter` может быть сколько угодно. Новые могут добавляться в HTML в любой момент. При помощи делегирования мы, фактически, добавили новый "псевдо-стандартный" атрибут в HTML, который добавляет элементу новую возможность ("поведение").
|
||||
|
||||
|
||||
## Ещё пример
|
||||
|
||||
Добавим ещё поведение.
|
||||
|
||||
Сделаем так, что при клике на элемент с атрибутом `data-toggle-id` будет скрываться/показываться элемент с заданным `id`:
|
||||
|
||||
```html
|
||||
<!--+ autorun run height=100 -->
|
||||
|
@ -33,7 +53,7 @@
|
|||
Показать форму подписки
|
||||
</button>
|
||||
|
||||
<form id="subscribe-mail" style="display:none">
|
||||
<form id="subscribe-mail" hidden>
|
||||
Ваша почта: <input type="email">
|
||||
</form>
|
||||
|
||||
|
@ -47,23 +67,28 @@
|
|||
|
||||
var elem = document.getElementById(id);
|
||||
|
||||
elem.style.display = (elem.style.display == 'none') ? "" : "none";
|
||||
elem.hidden = !elem.hidden;
|
||||
};
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
|
||||
**При помощи JavaScript мы добавили "поведение" -- возможность через атрибут указывать, что делает элемент.**
|
||||
Ещё раз заметим, что мы сделали.
|
||||
|
||||
Теперь для того, чтобы добавить скрытие-раскрытие любому элементу -- даже не надо знать JavaScript, можно просто написать атрибут `data-toggle-id`.
|
||||
|
||||
Это бывает очень удобно -- не нужно писать JavaScript-код для каждого элемента, который должен служить такой кнопкой. Просто используем поведение.
|
||||
|
||||
Обратите внимание: обработчик поставлен на `document`, клик на любом элементе страницы пройдёт через него, так что поведение определено глобально.
|
||||
|
||||
[smart header="Не только атрибут"]
|
||||
Для своих целей мы можем использовать в HTML любые атрибуты, но стандарт рекомендует для своих целей называть атрибуты `data-*`.
|
||||
|
||||
В обработчике `document.onclick` мы могли бы проверять не атрибут, а класс или что-то ещё.
|
||||
В обработчике `document.onclick` мы могли бы проверять не атрибут, а класс или что-то ещё, но с атрибутом -- проще и понятнее всего.
|
||||
|
||||
Также для добавления обработчиков на `document` рекомендуется использовать `addEventListener`, чтобы можно было добавить более одного обработчика для типа события.
|
||||
[/smart]
|
||||
|
||||
Обратите внимание: обработчик поставлен на `document`, то есть клик на любом элементе страницы пройдёт через него. Теперь для того, чтобы добавить скрытие-раскрытие любому элементу -- даже не надо знать JavaScript, можно просто написать атрибут.
|
||||
|
||||
Также для добавления обработчиков на `document` рекомендуется использовать `addEventListener`, чтобы можно было добавлять несколько различных поведений на документ.
|
||||
|
||||
## Итого
|
||||
|
||||
|
@ -71,6 +96,5 @@
|
|||
|
||||
Здесь мы рассмотрели базовый пример, который можно как угодно модифицировать и масштабировать. Важно не переусердствовать.
|
||||
|
||||
**Приём разработки "поведение" рекомендуется использовать для расширения возможностей разметки, как альтернативу мини-фрагментам JavaScript.**
|
||||
Приём разработки "поведение" рекомендуется использовать для расширения возможностей разметки, как альтернативу мини-фрагментам JavaScript.
|
||||
|
||||
Далее у нас ещё будут задачи, где мы реализуем этот приём разработки.
|
|
@ -1,10 +1,10 @@
|
|||
Дело в том, что обработчик из атрибута `onclick` делается браузером как функция с заданным телом.
|
||||
|
||||
То есть, он будет таким:
|
||||
То есть, в данном случае он будет таким:
|
||||
|
||||
```js
|
||||
function(event) {
|
||||
handler()
|
||||
handler() // тело взято из атрибута onclick
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -24,7 +24,7 @@ function(event) {
|
|||
<a href="http://w3.org" onclick="*!*return handler()*/!*">w3.org</a>
|
||||
```
|
||||
|
||||
Альтернатива -- передать и использовать объект события для вызова `event.preventDefault()` (или кросс-браузерного варианта для поддержки старых IE).
|
||||
Также можно использовать объект события для вызова `event.preventDefault()`, например:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
|
@ -32,7 +32,7 @@ function(event) {
|
|||
*!*
|
||||
function handler(event) {
|
||||
alert("...");
|
||||
event.preventDefault ? event.preventDefault() : (event.returnValue=false);
|
||||
event.preventDefault();
|
||||
}
|
||||
*/!*
|
||||
</script>
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
|
||||
По замыслу, переход на `w3.org` при клике должен отменяться. Однако, на самом деле он происходит.
|
||||
|
||||
В чём дело и как поправить, сохранив `onclick` в HTML?
|
||||
В чём дело и как поправить?
|
|
@ -2,30 +2,37 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#contents {
|
||||
padding: 5px;
|
||||
border: 1px green solid;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="contents">
|
||||
<fieldset id="contents">
|
||||
<legend>#contents</legend>
|
||||
<p>
|
||||
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
document.getElementById('contents').onclick = function(evt) {
|
||||
var evt = evt || event
|
||||
var target = evt.target || evt.srcElement
|
||||
document.getElementById('contents').onclick = function(event) {
|
||||
|
||||
function handleLink(href) {
|
||||
var isLeaving = confirm('Уйти на '+href+'?')
|
||||
if (!isLeaving) return false
|
||||
var isLeaving = confirm('Уйти на '+href+'?');
|
||||
if (!isLeaving) return false;
|
||||
}
|
||||
|
||||
var target = event.target;
|
||||
|
||||
while(target != this) {
|
||||
if (target.nodeName == 'A') {
|
||||
return handleLink(target.getAttribute('href'));
|
||||
}
|
||||
target = target.parentNode
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,14 +2,21 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#contents {
|
||||
padding: 5px;
|
||||
border: 1px green solid;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="contents">
|
||||
<fieldset id="contents">
|
||||
<legend>#contents</legend>
|
||||
<p>
|
||||
Как насчет почитать <a href="http://wikipedia.org">Википедию</a>, или посетить <a href="http://w3.org"><i>W3.org</i></a> и узнать про современные стандарты?
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[importance 5]
|
||||
|
||||
Сделайте так, чтобы при клике на ссылки внутри <code><DIV id="contents"></code> пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу и если он не хочет, то прерывать переход по ссылке.
|
||||
Сделайте так, чтобы при клике на ссылки внутри элемента `#contents` пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу и если он не хочет, то прерывать переход по ссылке.
|
||||
|
||||
Так это должно работать:
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
|||
|
||||
Детали:
|
||||
<ul>
|
||||
<li>Содержимое блока `DIV` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование.</li>
|
||||
<li>Содержимое `#contents` может быть загружено динамически и присвоено при помощи `innerHTML`. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование.</li>
|
||||
<li>Содержимое может содержать вложенные теги, *в том числе внутри ссылок*, например, `<a href=".."><i>...</i></a>`.</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Галерея</title>
|
||||
<link rel="stylesheet" type="text/css" href="gallery.css">
|
||||
<link rel="stylesheet" href="gallery.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
|
@ -25,8 +25,7 @@ var largeImg = document.getElementById('largeImg');
|
|||
var thumbs = document.getElementById('thumbs');
|
||||
|
||||
thumbs.onclick = function(e) {
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement;
|
||||
var target = e.target;
|
||||
|
||||
while(target != this) {
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Галерея</title>
|
||||
<link rel="stylesheet" type="text/css" href="gallery.css">
|
||||
<link rel="stylesheet" href="gallery.css">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
|
||||
Для обработки событий используйте делегирование, т.е. не более одного обработчика.
|
||||
|
||||
|
||||
|
||||
P.S. Обратите внимание -- клик может быть как на маленьком изображении `IMG`, так и на `A` вне него. При этом `event.target` будет, соответственно, либо `IMG`, либо `A`.
|
||||
|
||||
Дополнительно:
|
||||
|
|
|
@ -1,38 +1,37 @@
|
|||
# Действия браузера по умолчанию
|
||||
|
||||
Многие события влекут за собой действие браузера.
|
||||
Многие события автоматически влекут за собой действие браузера.
|
||||
|
||||
Например:
|
||||
<ul>
|
||||
<li>Клик по ссылке инициирует переход на новый URL</li>
|
||||
<li>Нажатие на кнопку "отправить" в форме -- посылку ее на сервер</li>
|
||||
<li>Клик по ссылке инициирует переход на новый URL.</li>
|
||||
<li>Нажатие на кнопку "отправить" в форме -- отсылку ее на сервер.</li>
|
||||
<li>Двойной клик на тексте -- инициирует его выделение.</li>
|
||||
</ul>
|
||||
|
||||
**Зачастую, мы полностью обрабатываем событие в JavaScript, и такое действие браузера нам не нужно.**
|
||||
|
||||
К счастью, его можно отменить.
|
||||
Если мы обрабатываем событие в JavaScript, то зачастую такое действие браузера нам не нужно. К счастью, его можно отменить.
|
||||
|
||||
[cut]
|
||||
|
||||
## Отмена действия браузера
|
||||
|
||||
Есть два способа отменить действие браузера:
|
||||
<ul>
|
||||
<li>**Основной способ -- это воспользоваться объектом события. Для отмены действия браузера существует стандартный метод `event.preventDefault()`.**</li>
|
||||
<li>Если же обработчик назначен через `on...` (не через `addEventListener/attachEvent`), то можно просто вернуть `false` из обработчика.</li>
|
||||
<li>Если же обработчик назначен через `onсобытие` (не через `addEventListener`), то можно просто вернуть `false` из обработчика.</li>
|
||||
</ul>
|
||||
|
||||
В следующем примере при клике по ссылке переход не произойдет:
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<!--+ autorun height=60 -->
|
||||
<a href="/" onclick="return false">Нажми здесь</a>
|
||||
или
|
||||
<a href="/" onclick="event.preventDefault()">здесь</a>
|
||||
```
|
||||
|
||||
[warn header="Возвращать `true` не нужно"]
|
||||
Вообще говоря, значение, которое возвращает обработчик, игнорируется.
|
||||
Обычно значение, которое возвращает обработчик события, игнорируется.
|
||||
|
||||
Единственное исключение -- это `return false` из обработчика, назначенного через `onсобытие`.
|
||||
|
||||
|
@ -56,11 +55,11 @@
|
|||
|
||||
[iframe height=70 src="menu" link edit]
|
||||
|
||||
**Все элементы меню являются ссылками, то есть тегами `<a>`.**
|
||||
HTML-разметка сделана так, что все элементы меню являются не кнопками, а ссылками, то есть тегами `<a>`.
|
||||
|
||||
Это потому, что некоторые посетители очень любят сочетание "правый клик - открыть в новом окне". Да, мы можем использовать и `<button>` и `<span>`, но если правый клик не работает -- это их огорчает. Кроме того, если на сайт зайдёт поисковик, то по ссылке из `<a href="...">` он перейдёт, а выполнить сложный JavaScript и получить результат -- вряд ли захочет.
|
||||
|
||||
**Значение `<a href="...">` -- это "запасной вариант", для правого клика и для поисковиков, а обычно клик будет обрабатываться JavaScript.**
|
||||
Поэтому в разметке мы используем именно `<a>`, но обычно клик будет обрабатываться полностью в JavaScript, а стандартное действие браузера (переход по ссылке) -- отменяться.
|
||||
|
||||
Например, вот так:
|
||||
|
||||
|
@ -79,7 +78,7 @@ menu.onclick = function(event) {
|
|||
|
||||
В конце `return false`, иначе браузер перейдёт по адресу из `href`.
|
||||
|
||||
Так как мы применили делегирование, то меню может увеличиваться, можно добавить вложенные списки `ul/li`, стилизовать их при помощи CSS -- меню продолжит работать.
|
||||
Так как мы применили делегирование, то меню может увеличиваться, можно добавить вложенные списки `ul/li`, стилизовать их при помощи CSS -- обработчик не потребует изменений.
|
||||
|
||||
## Другие действия браузера
|
||||
|
||||
|
@ -113,6 +112,8 @@ menu.onclick = function(event) {
|
|||
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Кликни меня">
|
||||
```
|
||||
|
||||
Это потому, что отменено стандартное действие при `onmousedown`.
|
||||
|
||||
...С другой стороны, во второй `<input>` можно перейти с первого нажатием клавиши [key Tab], и тогда фокусировка сработает. То есть, дело здесь именно в `onmousedown="return false"`.
|
||||
[/warn]
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# Генерация событий на элементах
|
||||
|
||||
Можно не только слушать браузерные события, но и генерировать их самому.
|
||||
Можно не только назначать обработчики на события, но и генерировать их самому.
|
||||
|
||||
Это нужно довольно редко, преимущественно -- для целей автоматического тестирования.
|
||||
Мы будем использовать это позже для реализации компонентной архитектуры, при которой элемент, представляющий собой, к примеру, меню, генерирует события, к этому меню относящиеся -- `select` (выбран пункт меню) или `open` (меню раскрыто), и другие.
|
||||
|
||||
Кроме того, события можно генерировать для целей автоматического тестирования.
|
||||
|
||||
[cut]
|
||||
|
||||
|
@ -10,7 +12,7 @@
|
|||
|
||||
Вначале рассмотрим современный способ генерации событий, по стандарту [DOM 4](http://www.w3.org/TR/dom/#introduction-to-dom-events). Он поддерживается всеми браузерами, кроме IE11-. А далее рассмотрим устаревшие варианты, поддерживаемые IE.
|
||||
|
||||
**Объект события создаётся при помощи конструктора [Event](http://www.w3.org/TR/dom/#event).**
|
||||
Объект события в нём создаётся при помощи встроенного конструктора [Event](http://www.w3.org/TR/dom/#event).
|
||||
|
||||
Синтаксис:
|
||||
|
||||
|
@ -23,16 +25,16 @@ var event = new Event(тип события[, флаги]);
|
|||
<li>*Тип события* -- может быть как своим, так и встроенным, к примеру `"click"`.</li>
|
||||
<li>*Флаги* -- объект вида `{ bubbles: true/false, cancelable: true/false }`, где свойство `bubbles` указывает, всплывает ли событие, а `cancelable` -- можно ли отменить действие по умолчанию.
|
||||
|
||||
Не обязателен, по умолчанию `{bubbles: false, cancelable: false}`.</li>
|
||||
Флаги по умолчанию: `{bubbles: false, cancelable: false}`.</li>
|
||||
</ul>
|
||||
|
||||
### Метод dispatchEvent
|
||||
## Метод dispatchEvent
|
||||
|
||||
Затем, чтобы инициировать событие, запускается `elem.dispatchEvent(event)`.
|
||||
|
||||
Событие обрабатывается "как обычно", передаётся обработчикам, всплывает... Этот метод возвращает `false`, если событие было отменено при помощи `preventDefault()`. Конечно, отмена возможна лишь если событие изначально было создано с флагом `cancelable`.
|
||||
При этом событие срабатывает наравне с браузерными, то есть обычные браузерные обработчики на него отреагируют. Если при создании указан флаг `bubbles`, то оно будет всплывать.
|
||||
|
||||
При просмотре примера ниже кнопка будет нажата скриптом:
|
||||
При просмотре примера ниже кнопка обработчик `onclick` на кнопке сработает сам по себе, событие генерируется скриптом:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
|
@ -44,6 +46,50 @@ var event = new Event(тип события[, флаги]);
|
|||
</script>
|
||||
```
|
||||
|
||||
## Отмена действия по умолчанию
|
||||
|
||||
На сгенерированном событии, как и на встроенном браузерном, обработчик может вызвать метод `event.preventDefault()`. Тогда `dispatchEvent` возвратит `false`.
|
||||
|
||||
Остановимся здесь подробнее. Обычно такой вызов предотвращает действие браузера. В случае, если событие придумано нами -- никакого действия браузера, конечно, нет, но код, который генерирует событие, может быть заинтересован узнать, что его "отменили" и не продолжать свои действия.
|
||||
|
||||
Иначе говоря, `event.preventDefault()` является возможностью для обработчика сообщить в сгенерировавший событие код, что некие действия продолжать не надо.
|
||||
|
||||
В примере ниже функция `hide()` генерирует событие `hide` на элементе `#rabbit`, уведомляя всех интересующихся, что кролик собирается спрятаться.
|
||||
|
||||
И, если никакой обработчик не отменит действие по умолчанию, то кролик действительно исчезнет:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<pre id="rabbit">
|
||||
|\ /|
|
||||
\|_|/
|
||||
/. .\
|
||||
=\_Y_/=
|
||||
{>o<}
|
||||
</pre>
|
||||
|
||||
|
||||
<script>
|
||||
// прячемся через 2 секунды
|
||||
setTimeout(hide, 2000);
|
||||
|
||||
function hide() {
|
||||
var event = new Event("hide", {cancelable: true});
|
||||
if (!rabbit.dispatchEvent(event)) {
|
||||
alert('действие отменено');
|
||||
} else {
|
||||
rabbit.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
rabbit.addEventListener('hide', function(event) {
|
||||
if (confirm("Вызвать preventDefault?")) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
[smart header="Как отличить реальное нажатие от скриптового?"]
|
||||
В целях безопасности иногда хорошо бы знать -- инициировано ли действие посетителем или это кликнул скрипт.
|
||||
|
||||
|
@ -52,7 +98,9 @@ var event = new Event(тип события[, флаги]);
|
|||
Оно на момент написания статьи поддерживается IE и Firefox и равно `true`, если посетитель кликнул сам, и всегда `false` -- если событие инициировал скрипт.
|
||||
[/smart]
|
||||
|
||||
Браузер автоматически ставит следующие свойства объекта `event`:
|
||||
## Другие свойства событий
|
||||
|
||||
При создании события браузер автоматически ставит следующие свойства:
|
||||
|
||||
<ul>
|
||||
<li>`isTrusted: false` -- означает, что событие сгенерировано скриптом, это свойство изменить невозможно.</li>
|
||||
|
@ -64,16 +112,16 @@ var event = new Event(тип события[, флаги]);
|
|||
Другие свойства события, если они нужны, например координаты для события мыши -- можно присвоить в объект события позже, например:
|
||||
|
||||
```js
|
||||
var event = new Event("click");
|
||||
var event = new Event("click", {bubbles: true, cancelable: false});
|
||||
event.clientX = 100;
|
||||
event.clientY = 100;
|
||||
```
|
||||
|
||||
### Пример с hello
|
||||
## Пример со всплытием
|
||||
|
||||
Можно генерировать события с любыми названиями.
|
||||
Сгенерируем совершенно новое событие `"hello"` и поймаем его на `document`.
|
||||
|
||||
Для примера сгенерируем совершенно новое событие `"hello"`:
|
||||
Всё, что для этого нужно -- это флаг `bubbles`:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
|
@ -99,11 +147,11 @@ event.clientY = 100;
|
|||
<li>Чтобы событие всплывало и его можно было отменить, указан второй аргумент `new Event`.</li>
|
||||
</ol>
|
||||
|
||||
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, они создаются и работают совершенно одинаково.
|
||||
Никакой разницы между встроенными событиями (`click`) и своими (`hello`) здесь нет, их можно сгенерировать и запустить совершенно одинаково.
|
||||
|
||||
## Конструкторы MouseEvent, KeyboardEvent и другие
|
||||
|
||||
Для конкретных типов событий есть свои конструкторы.
|
||||
Для некоторых конкретных типов событий есть свои, специфические, конструкторы.
|
||||
|
||||
Вот список конструкторов для различных событий интерфейса которые можно найти в спецификации [UI Event](http://www.w3.org/TR/uievents/):
|
||||
<ul>
|
||||
|
@ -117,9 +165,9 @@ event.clientY = 100;
|
|||
|
||||
Вместо `new Event("click")` можно вызвать `new MouseEvent("click")`.
|
||||
|
||||
**Конкретный конструктор позволяет указать стандартные свойства для данного типа события.**
|
||||
**Специфический конструктор позволяет указать стандартные свойства для данного типа события.**
|
||||
|
||||
Например:
|
||||
Например, `clientX/clientY` для события мыши:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -135,7 +183,7 @@ alert(e.clientX); // 100
|
|||
*/!*
|
||||
```
|
||||
|
||||
Сравните это с обычным `Event`:
|
||||
Это нельзя было бы сделать с обычным конструктором `Event`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -147,21 +195,19 @@ var e = new Event("click", {
|
|||
});
|
||||
|
||||
*!*
|
||||
alert(e.clientX); // undefined
|
||||
alert(e.clientX); // undefined, свойство не присвоено!
|
||||
*/!*
|
||||
```
|
||||
|
||||
...То есть, "мышиные" свойства можно сразу же в конструкторе указать только если это `MouseEvent`, а `Event` их игнорирует.
|
||||
Обычный конструктор `Event` не знает про "мышиные" свойства, поэтому их игнорирует.
|
||||
|
||||
**Использование конкретного конструктора не является обязательным, можно обойтись `Event`.**
|
||||
|
||||
Свойства можно присвоить и явно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
|
||||
Впрочем, использование конкретного конструктора не является обязательным, можно обойтись `Event`, а свойства записать в объект отдельно, после конструктора. Здесь это скорее вопрос удобства и желания следовать правилам. События, которые генерирует браузер, всегда имеют правильный тип.
|
||||
|
||||
Полный список свойств по типам событий вы найдёте в спецификации, например для `MouseEvent`: [MouseEvent Constructor](http://www.w3.org/TR/uievents/#constructor-mouseevent).
|
||||
|
||||
## Свои события
|
||||
|
||||
Для генерации встроенных событий существуют описанные выше конструкторы, а для генерации своих, нестандартных, событий существует конструктор [CustomEvent](http://www.w3.org/TR/dom/#customevent).
|
||||
Для генерации своих, нестандартных, событий, хоть и можно использовать конструктор `Event`, но существует и специфический конструктор [CustomEvent](http://www.w3.org/TR/dom/#customevent).
|
||||
|
||||
Технически, он абсолютно идентичен `Event`, кроме небольшой детали: у второго аргумента-объекта есть дополнительное свойство `detail`, в котором можно указывать информацию для передачи в событие.
|
||||
|
||||
|
@ -190,9 +236,13 @@ alert(e.clientX); // undefined
|
|||
|
||||
## Старое API для IE9+
|
||||
|
||||
В предыдущем стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events) была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
|
||||
Способ генерации событий, описанный выше, не поддерживается в IE11-, там нужен другой, более старый способ, описанный в стандарте [DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events).
|
||||
|
||||
Она поддерживается как современными браузерами, так и IE9+. Для генерации событий используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
|
||||
В нём была предусмотрена [иерархия событий](http://www.w3.org/TR/DOM-Level-3-Events/#event-interfaces), с различными методами инициализации.
|
||||
|
||||
Она поддерживается как современными браузерами, так и IE9+. Там используется немного другой синтаксис, но по возможностям -- всё то же самое, что и в современном стандарте.
|
||||
|
||||
Можно использовать этот немного устаревший способ, если нужно поддерживать IE9+. Далее мы на его основе создадим полифилл.
|
||||
|
||||
Объект события создаётся вызовом `document.createEvent`:
|
||||
|
||||
|
@ -206,7 +256,7 @@ var event = document.createEvent(eventInterface);
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
**На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.**
|
||||
На практике можно всегда использовать самый общий интерфейс: `document.createEvent("Event")`.
|
||||
|
||||
Далее событие нужно инициализовать:
|
||||
|
||||
|
@ -306,9 +356,37 @@ void initMouseEvent (
|
|||
</script>
|
||||
```
|
||||
|
||||
Браузер, по стандарту, может сгенерировать отсутствующие свойства самостоятельно, например `pageX`, но это нужно проверять в конкретных случаях, обычно это не работает или работает некорректно.
|
||||
Браузер, по стандарту, может сгенерировать отсутствующие свойства самостоятельно, например `pageX`, но это нужно проверять в конкретных случаях, иногда это не работает или работает некорректно, так что лучше указать все.
|
||||
[/smart]
|
||||
|
||||
## Полифилл CustomEvent
|
||||
|
||||
Для поддержки `CustomEvent` в IE9+ можно сделать небольшой полифилл:
|
||||
|
||||
```js
|
||||
try {
|
||||
new CustomEvent("IE has CustomEvent, but doesn't support constructor");
|
||||
} catch (e) {
|
||||
|
||||
window.CustomEvent = function(event, params) {
|
||||
var evt;
|
||||
params = params || {
|
||||
bubbles: false,
|
||||
cancelable: false,
|
||||
detail: undefined
|
||||
};
|
||||
evt = document.createEvent("CustomEvent");
|
||||
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
||||
return evt;
|
||||
};
|
||||
|
||||
CustomEvent.prototype = Object.create(window.Event.prototype);
|
||||
}
|
||||
```
|
||||
|
||||
Здесь мы сначала проверяем -- в IE9-11 есть `CustomEvent`, но его нельзя создать через `new`, будет ошибка. В этом случае заменяем браузерную реализацию на свою, совместимую.
|
||||
|
||||
|
||||
## Антистандарт: IE8-
|
||||
|
||||
В совсем старом IE были "свои" методы `document.createEventObject()` и `elem.fireEvent()`.
|
||||
|
@ -340,39 +418,20 @@ void initMouseEvent (
|
|||
|
||||
Параметры `bubbles` и `cancelable` настраивать нельзя, браузер использует стандартные для данного типа событий.
|
||||
|
||||
## Кросс-браузерный пример
|
||||
Существуют полифиллы для генерации произвольных событий и для IE8-, но они, по сути, полностью подменяют встроенную систему обработки событий браузером. И кода это требует тоже достаточно много.
|
||||
|
||||
Для поддержки IE9+ достаточно использовать методы `document.createEvent` и `event.initEvent`, как показано выше, и всё будет хорошо.
|
||||
Альтернатива -- фреймворк, например jQuery, который также реализует свою мощную систему работы с событиями, доступную через методы jQuery.
|
||||
|
||||
Если же нужен IE8, то подойдёт такой код:
|
||||
|
||||
```js
|
||||
function trigger(elem, type){
|
||||
if (document.createEvent) {
|
||||
var event = document.createEvent('Event') :
|
||||
event.initEvent(type);
|
||||
return elem.dispatchEvent(event);
|
||||
}
|
||||
|
||||
var event = document.createEventObject();
|
||||
return elem.fireEvent("on"+type, event);
|
||||
}
|
||||
|
||||
// использование:
|
||||
trigger(elem, "click");
|
||||
```
|
||||
|
||||
Конечно, надо иметь в виду, что в IE8 события можно использовать только встроенные, а `bubbles` и `cancelable` поставить нельзя.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Все браузеры, кроме IE, позволяют генерировать любые события, следуя стандарту DOM4.</li>
|
||||
<li>IE9+ тоже справляется, если использовать вызовы более старого стандарта, и имеет в итоге тот же функционал.</li>
|
||||
<li>Все браузеры, кроме IE9-11, позволяют генерировать любые события, следуя стандарту DOM4.</li>
|
||||
<li>В IE9+ поддерживается более старый стандарт, можно легко сделать полифилл, например для `CustomEvent` он рассмотрен в этой главе.</li>
|
||||
<li>IE8- может генерировать только встроенные события.</li>
|
||||
</ul>
|
||||
|
||||
**Несмотря на техническую возможность генерировать браузерные события -- пользоваться ей стоит с большой осторожностью.**
|
||||
Несмотря на техническую возможность генерировать встроенные браузерные события типа `click` или `keydown` -- пользоваться ей стоит с большой осторожностью.
|
||||
|
||||
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.
|
||||
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px; /* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position:absolute;
|
||||
z-index:100; /* подсказка должна перекрывать другие элементы */
|
||||
padding: 10px 20px;
|
||||
|
||||
/* красивости... */
|
||||
border: 1px solid #b3c9ce;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font: italic 14px/1.3 arial, sans-serif;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-shadow: 3px 3px 3px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<a href="#" id="link" data-tooltip="подсказка длиннее, чем элемент">Короткая ссылка</a>
|
||||
<a href="#" id="link2" data-tooltip="HTML<br>подсказка">Еще ссылка</a>
|
||||
|
||||
<p>Прокрутите страницу и проверьте, правильно ли показывается подсказка</p>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var showingTooltip;
|
||||
|
||||
document.onmouseover = function(e) {
|
||||
e = e || event;
|
||||
var target = e.target || e.srcElement;
|
||||
|
||||
var tooltip = target.getAttribute('data-tooltip');
|
||||
if (!tooltip) return;
|
||||
|
||||
var tooltipElem = document.createElement('div');
|
||||
tooltipElem.className = 'tooltip';
|
||||
tooltipElem.innerHTML = tooltip;
|
||||
document.body.appendChild(tooltipElem);
|
||||
|
||||
var coords = getCoords(target);
|
||||
|
||||
// не вылезаем за пределы экрана
|
||||
var scroll = getPageScroll();
|
||||
|
||||
var left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth)/2^0;
|
||||
if (left < scroll.left) left = scroll.left; // не вылезать за левую границу экрана
|
||||
|
||||
var top = coords.top - tooltipElem.offsetHeight - 5;
|
||||
if (top < scroll.top) {
|
||||
top = coords.top + target.offsetHeight + 5;
|
||||
}
|
||||
|
||||
tooltipElem.style.left = left + 'px';
|
||||
tooltipElem.style.top = top + 'px';
|
||||
|
||||
showingTooltip = tooltipElem;
|
||||
};
|
||||
|
||||
document.onmouseout = function(e) {
|
||||
|
||||
if (showingTooltip) {
|
||||
document.body.removeChild(showingTooltip);
|
||||
showingTooltip = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docEl = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
}
|
||||
|
||||
function getPageScroll() {
|
||||
if (window.pageXOffset != undefined) {
|
||||
return {
|
||||
left: pageXOffset,
|
||||
top: pageYOffset
|
||||
}
|
||||
}
|
||||
|
||||
var html = document.documentElement;
|
||||
var body = document.body;
|
||||
|
||||
var top = html.scrollTop || body && body.scrollTop || 0;
|
||||
top -= html.clientTop;
|
||||
|
||||
var left = html.scrollLeft || body && body.scrollLeft || 0;
|
||||
left -= html.clientLeft;
|
||||
|
||||
return { top: top, left: left };
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,72 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
height: 2000px; /* подсказка должна работать независимо от прокрутки */
|
||||
}
|
||||
|
||||
/* ваши стили */
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
<p>ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя ЛяЛяЛя</p>
|
||||
|
||||
<a href="#" id="link" data-tooltip="подсказка длиннее, чем элемент">Короткая ссылка</a>
|
||||
<a href="#" id="link2" data-tooltip="HTML<br>подсказка">Еще ссылка</a>
|
||||
|
||||
<p>Прокрутите страницу и проверьте, правильно ли показывается подсказка</p>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
// ваш код
|
||||
|
||||
// ------------ вспомогательные функции
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docEl = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
}
|
||||
|
||||
function getPageScroll() {
|
||||
if (window.pageXOffset != undefined) {
|
||||
return {
|
||||
left: pageXOffset,
|
||||
top: pageYOffset
|
||||
}
|
||||
}
|
||||
|
||||
var html = document.documentElement;
|
||||
var body = document.body;
|
||||
|
||||
var top = html.scrollTop || body && body.scrollTop || 0;
|
||||
top -= html.clientTop;
|
||||
|
||||
var left = html.scrollLeft || body && body.scrollLeft || 0;
|
||||
left -= html.clientLeft;
|
||||
|
||||
return { top: top, left: left };
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||
# Поведение "подсказка"
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите JS-код, который будет показывать всплывающую подсказку над элементом, если у него есть атрибут `data-tooltip`.
|
||||
|
||||
Например, две ссылки:
|
||||
|
||||
```html
|
||||
<a href="#" id="link" data-tooltip="подсказка длиннее, чем элемент">
|
||||
Короткая ссылка
|
||||
</a>
|
||||
|
||||
<a href="#" id="link2" data-tooltip="HTML<br>подсказка">
|
||||
Еще ссылка
|
||||
</a>
|
||||
```
|
||||
|
||||
Результат в ифрейме с документом:
|
||||
|
||||
[iframe src="solution" height=200 border=1]
|
||||
|
||||
<ul>
|
||||
<li>Подсказка должна появляться при наведении на элемент, по центру и на небольшом расстоянии сверху. При уходе курсора с элемента -- исчезать.</li>
|
||||
<li>Текст подсказки брать из значения атрибута `data-tooltip`. Это может быть произвольный HTML.</li>
|
||||
<li>Оформление подсказки должно задаваться CSS.</li>
|
||||
<li>Подсказка не должна вылезать за границы экрана, в том числе если страница частично прокручена. Если нельзя показать сверху -- показывать снизу элемента.</li>
|
||||
</ul>
|
||||
|
||||
**Если хотите -- в этой задаче для простоты можно считать, что у элемента, на котором "висит" подсказка, нет детей.**
|
||||
|
||||
В исходном документе есть вспомогательные функции: [](#getPageScroll) для определения текущей прокрутки страницы и [](#getCoords) -- для определения координат элемента относительно страницы.
|
||||
|
||||
[edit task src="source"/].
|
||||
|
||||
P.S. Эта задача -- на практическое применение ["Шаблона проектирования \"поведение\""](/behavior).
|
Loading…
Add table
Add a link
Reference in a new issue