This commit is contained in:
Ilya Kantor 2015-11-18 16:15:29 +03:00
parent 4bca225593
commit 547854a151
1655 changed files with 847 additions and 89231 deletions

View file

@ -1,3 +0,0 @@
В данном случае достаточно событий `input.focus/input.blur`.
Если бы мы хотели реализовать это на уровне документа, то применили бы делегирование и события `focusin/focusout` (эмуляцию для firefox), так как обычные `focus/blur` не всплывают.

View file

@ -1,74 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<style>
.placeholder {
color: blue;
font-family: Georgia;
}
.placeholder-tooltip {
color: blue;
font-family: Georgia;
position: fixed;
}
</style>
</head>
<body>
<p>Красивый placeholder:</p>
<input type="email" data-placeholder="E-mail">
<script>
var input = document.querySelector('[data-placeholder]');
showPlaceholder(input);
// Показать placeholder внутри input
// Также можно сделать это при помощи вёрстки, отдельным элементом
function showPlaceholder(input) {
input.classList.add('placeholder');
input.value = input.dataset.placeholder;
}
// Показать подсказку над элементом (будет вместо placeholder)
function showTooltip(input) {
var tooltip = document.createElement('span');
tooltip.innerHTML = input.dataset.placeholder;
tooltip.className = 'placeholder-tooltip';
tooltip.style.fontSize = getComputedStyle(input).fontSize;
tooltip.style.left = input.getBoundingClientRect().left + 'px';
document.body.appendChild(tooltip);
tooltip.style.top = input.getBoundingClientRect().top - tooltip.offsetHeight - 4 + 'px';
input.tooltip = tooltip;
}
input.onfocus = function() {
if (input.classList.contains('placeholder')) {
input.classList.remove('placeholder');
input.value = '';
}
showTooltip(input);
};
input.onblur = function() {
document.body.removeChild(input.tooltip);
delete input.tooltip;
// показываем placeholder обратно, если input пуст
if (input.value == '') {
showPlaceholder(input);
}
};
</script>
</body>
</html>

View file

@ -1,48 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<style>
/* стиль для input с плейсхолдером */
.placeholder {
color: blue;
font-family: Georgia;
}
/* стиль для подсказки над элементом (вместо плейсхолдера при фокусировке) */
.placeholder-tooltip {
color: blue;
font-family: Georgia;
position: fixed;
}
</style>
</head>
<body>
<p>Красивый placeholder:</p>
<input type="email" data-placeholder="E-mail">
<script>
var input = document.querySelector('[data-placeholder]');
showPlaceholder(input);
// Показать placeholder внутри input
// Также можно сделать это при помощи вёрстки, отдельным элементом
function showPlaceholder(input) {
input.classList.add('placeholder');
input.value = input.dataset.placeholder;
}
// ...ваш код для input...
</script>
</body>
</html>

View file

@ -1,19 +0,0 @@
# Улучшенный плейсхолдер
[importance 5]
Реализуйте более удобный плейсхолдер-подсказку на JavaScript через атрибут `data-placeholder`.
Правила работы плейсхолдера:
<ul>
<li>Элемент изначально содержит плейсхолдер. Специальный класс `placeholder` придает ему синий цвет.</li>
<li>При фокусировке плейсхолдер показывается уже над полем, становясь "подсказкой".</li>
<li>При снятии фокуса, подсказка убирается, если поле пустое -- плейсхолдер возвращается в него.</li>
</ul>
Демо:
[iframe src="solution" height=100]
В этой задаче плейсхолдер должен работать на одном конкретном input. Подумайте, если input много, как здесь применить делегирование?

View file

@ -1,9 +0,0 @@
Нам нужно ловить `onclick` на мышонке и в `onkeydown` на нём смотреть коды символов. При скан-кодах стрелок двигать мышонка через `position:absolute` или `position:fixed`.
Скан-коды для клавиш стрелок можно узнать, нажимая на них на [тестовом стенде](#keyboard-test-stand). Вот они: 37-38-39-40 (влево-вверх-вправо-вниз).
Проблема может возникнуть одна -- `keydown` не возникает на элементе, если на нём нет фокуса.
Чтобы фокус был -- нужно добавить мышонку атрибут `tabindex` через JS или в HTML.

View file

@ -1,65 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#mouse {
display: inline-block;
cursor: pointer;
margin: 0;
}
#mouse:focus {
outline: 1px dashed black;
}
</style>
</head>
<body>
<p>Кликните на мышонка и передвигайте его, нажимая клавиши со стрелками.</p>
<pre id="mouse" tabindex="0">
_ _
(q\_/p)
/. .\
=\_t_/= __
/ \ (
(( )) )
/\) (/\ /
\ Y /-'
nn^nn
</pre>
<script>
document.getElementById('mouse').onclick = function() {
this.style.left = this.getBoundingClientRect().left + 'px';
this.style.top = this.getBoundingClientRect().top + 'px';
this.style.position = 'fixed';
};
document.getElementById('mouse').onkeydown = function(e) {
switch (e.keyCode) {
case 37: // влево
this.style.left = parseInt(this.style.left) - this.offsetWidth + 'px';
return false;
case 38: // вверх
this.style.top = parseInt(this.style.top) - this.offsetHeight + 'px';
return false;
case 39: // вправо
this.style.left = parseInt(this.style.left) + this.offsetWidth + 'px';
return false;
case 40: // вниз
this.style.top = parseInt(this.style.top) + this.offsetHeight + 'px';
return false;
}
};
</script>
</body>
</html>

View file

@ -1,42 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#mouse {
display: inline-block;
cursor: pointer;
margin: 0;
}
#mouse:focus {
outline: 1px dashed black;
}
</style>
</head>
<body>
<p>Кликните на мышонка и передвигайте его, нажимая клавиши со стрелками.</p>
<pre id="mouse">
_ _
(q\_/p)
/. .\
=\_t_/= __
/ \ (
(( )) )
/\) (/\ /
\ Y /-'
nn^nn
</pre>
<script>
// ваш код
</script>
</body>
</html>

View file

@ -1,11 +0,0 @@
# Мышонок на "клавиатурном" приводе
[importance 4]
Кликните по мышонку. Затем нажимайте клавиши со стрелками, и он будет двигаться.
[demo src="solution"]
В этой задаче запрещается ставить обработчики куда-либо, кроме элемента `#mouse`.
Можно изменять атрибуты и классы в HTML.

View file

@ -1,100 +0,0 @@
# CSS для решения
Как видно из исходного кода, `#view` -- это `<div>`, который будет содержать результат, а `#area` - это редактируемое текстовое поле.
Так как мы преобразуем `<div>` в `<textarea>` и обратно, нам нужно сделать их практически одинаковыми с виду:
```css
#view,
#area {
height: 150px;
width: 400px;
font-family: arial;
font-size: 14px;
}
```
Текстовое поле нужно как-то выделить. Можно добавить границу, но тогда изменится блок: он увеличится в размерах и немного съедет текст.
Для того, чтобы сделать размер `#area` таким же, как и `#view`, добавим поля(padding):
```css
#view {
/* padding + border = 3px */
padding: 2px;
border: 1px solid black;
}
```
CSS для `#area` заменяет поля границами:
```css
#area {
border: 3px groove blue;
padding: 0px;
display: none;
}
```
По умолчанию, текстовое поле скрыто. Кстати, этот код убирает дополнительную рамку в ряде браузеров, которая появляется вокруг поля, когда на него попадает фокус:
```css
/*+ no-beautify */
#area:focus {
outline: none; /* убирает рамку при фокусе */
}
```
# Горячие клавиши
Чтобы отследить горячие клавиши, нам нужны их скан-коды, а не символы. Это важно, потому что горячие клавиши должны работать независимо от языковой раскладки. Поэтому, мы будем использовать <code>keydown</code>:
```js
document.onkeydown = function(e) {
if (e.keyCode == 27) { // escape
cancel();
return false;
}
if ((e.ctrlKey && e.keyCode == 'E'.charCodeAt(0)) && !area.offsetHeight) {
edit();
return false;
}
if ((e.ctrlKey && e.keyCode == 'S'.charCodeAt(0)) && area.offsetHeight) {
save();
return false;
}
};
```
В примере выше, `offsetHeight` используется для того, чтобы проверить, отображается элемент или нет. Это очень надежный способ для всех элементов, кроме `<tr>` в некоторых старых браузерах.
В отличие от простой проверки `display=='none'`, этот способ работает с элементом, спрятанным с помощью стилей, а так же для элементов, у которых скрыты родители.
# Редактирование
Следующие функции переключают режимы. HTML-код разрешен, поэтому возможна прямая трансформация в `<textarea>` и обратно.
```js
function edit() {
view.style.display = 'none';
area.value = view.innerHTML;
area.style.display = 'block';
area.focus();
}
function save() {
area.style.display = 'none';
view.innerHTML = area.value;
view.style.display = 'block';
}
function cancel() {
area.style.display = 'none';
view.style.display = 'block';
}
```

View file

@ -1,61 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<link type="text/css" rel="stylesheet" href="my.css">
<meta charset="utf-8">
</head>
<body>
<ul>
<li>Ctrl-E для начала редактирования.</li>
<li>Во время редактирования: Ctrl-S для сохранения, Esc для отмены.</li>
</ul>
HTML разрешён.
<textarea id="area"></textarea>
<div id="view">Текст</div>
<script>
document.onkeydown = function(e) {
if (e.keyCode == 27) { // escape
cancel();
return false;
}
if ((e.ctrlKey && e.keyCode == 'E'.charCodeAt(0)) && !area.offsetHeight) {
edit();
return false;
}
if ((e.ctrlKey && e.keyCode == 'S'.charCodeAt(0)) && area.offsetHeight) {
save();
return false;
}
}
function edit() {
view.style.display = 'none';
area.value = view.innerHTML;
area.style.display = 'block';
area.focus();
}
function save() {
area.style.display = 'none';
view.innerHTML = area.value;
view.style.display = 'block';
}
function cancel() {
area.style.display = 'none';
view.style.display = 'block';
}
</script>
</body>
</html>

View file

@ -1,27 +0,0 @@
#view,
#area {
height: 150px;
width: 400px;
font-family: arial;
font-size: 14px;
}
#view {
/* padding + border = 3px */
padding: 2px;
border: 1px solid black;
}
#area {
display: none;
/* replace padding with border (still 3px not to shift the contents) */
border: 3px groove blue;
padding: 0px;
}
#area:focus {
outline: none;
/* remove focus border in Safari */
}

View file

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<link type="text/css" rel="stylesheet" href="my.css">
</head>
<body>
<ul>
<li>Ctrl-E to start editing.</li>
<li>While editing: Ctrl-S to save, Esc to cancel.</li>
</ul>
<textarea id="area"></textarea>
<div id="view">Text</div>
</body>
</html>

View file

@ -1,27 +0,0 @@
#view,
#area {
height: 150px;
width: 400px;
font-family: arial;
font-size: 14px;
}
#view {
/* padding + border = 3px */
padding: 2px;
border: 1px solid black;
}
#area {
display: none;
/* replace padding with border (still 3px not to shift the contents) */
border: 3px groove blue;
padding: 0px;
}
#area:focus {
outline: none;
/* remove focus border in Safari */
}

View file

@ -1,14 +0,0 @@
# Горячие клавиши
[importance 5]
Создайте `<div>`, который при нажатии [key Ctrl+E] превращается в `<textarea>`.
Изменения, внесенные в поле, можно сохранить обратно в `<div>` сочетанием клавиш [key Ctrl+S], при этом `<div>` получит в виде HTML содержимое `<textarea>`.
Если же нажать [key Esc], то `<textarea>` снова превращается в `<div>`, изменения не сохраняются.
[demo src="solution"].

View file

@ -1,7 +0,0 @@
<ol>
<li>При клике -- заменяем `innerHTML` ячейки на `<textarea>` с размерами "под ячейку", без рамки.</li>
<li>В `textarea.value` присваиваем содержимое ячейки.</li>
<li>Фокусируем посетителя на ячейке вызовом `focus()`.</li>
<li>Показываем кнопки OK/CANCEL под ячейкой.</li>
</ol>

View file

@ -1,56 +0,0 @@
/* общие стили для таблицы */
th {
text-align: center;
font-weight: bold;
}
td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: middle;
padding: 10px;
}
.nw {
background-color: #999;
}
.n {
background-color: #03f;
color: #fff;
}
.ne {
background-color: #ff6;
}
.w {
background-color: #ff0;
}
.c {
background-color: #60c;
color: #fff;
}
.e {
background-color: #09f;
color: #fff;
}
.sw {
background-color: #963;
color: #fff;
}
.s {
background-color: #f60;
color: #fff;
}
.se {
background-color: #0c3;
color: #fff;
}

View file

@ -1,78 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<link rel="stylesheet" href="bagua.css">
<link rel="stylesheet" href="my.css">
<p>Кликните на ячейке для начала редактирования. Когда закончите -- нажмите OK или CANCEL.</p>
<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 src="script.js"></script>
</body>
</html>

View file

@ -1,23 +0,0 @@
.edit-td .edit-area {
border: none;
margin: 0;
padding: 0;
display: block;
resize: none;
/* remove resizing handle in Firefox */
outline: none;
/* remove outline on focus in Chrome */
overflow: auto;
/* remove scrollbar in IE */
}
.edit-controls {
position: absolute;
}
.edit-td {
position: relative;
padding: 0;
}

View file

@ -1,62 +0,0 @@
var table = document.getElementById('bagua-table');
var editingTd;
table.onclick = function(event) {
var target = event.target;
while (target != table) {
if (target.className == 'edit-cancel') {
finishTdEdit(editingTd.elem, false);
return;
}
if (target.className == 'edit-ok') {
finishTdEdit(editingTd.elem, true);
return;
}
if (target.nodeName == 'TD') {
if (editingTd) return; // already editing
makeTdEditable(target);
return;
}
target = target.parentNode;
}
}
function makeTdEditable(td) {
editingTd = {
elem: td,
data: td.innerHTML
};
td.classList.add('edit-td'); // td, not textarea! the rest of rules will cascade
var textArea = document.createElement('textarea');
textArea.style.width = td.clientWidth + 'px';
textArea.style.height = td.clientHeight + 'px';
textArea.className = 'edit-area';
textArea.value = td.innerHTML;
td.innerHTML = '';
td.appendChild(textArea);
textArea.focus();
td.insertAdjacentHTML("beforeEnd",
'<div class="edit-controls"><button class="edit-ok">OK</button><button class="edit-cancel">CANCEL</button></div>'
);
}
function finishTdEdit(td, isOk) {
if (isOk) {
td.innerHTML = td.firstChild.value;
} else {
td.innerHTML = editingTd.data;
}
td.classList.remove('edit-td'); // remove edit class
editingTd = null;
}

View file

@ -1,56 +0,0 @@
/* общие стили для таблицы */
th {
text-align: center;
font-weight: bold;
}
td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: middle;
padding: 10px;
}
.nw {
background-color: #999;
}
.n {
background-color: #03f;
color: #fff;
}
.ne {
background-color: #ff6;
}
.w {
background-color: #ff0;
}
.c {
background-color: #60c;
color: #fff;
}
.e {
background-color: #09f;
color: #fff;
}
.sw {
background-color: #963;
color: #fff;
}
.s {
background-color: #f60;
color: #fff;
}
.se {
background-color: #0c3;
color: #fff;
}

View file

@ -1,78 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<link rel="stylesheet" href="bagua.css">
<link rel="stylesheet" href="my.css">
<p>Кликните на ячейке для начала редактирования. Когда закончите -- нажмите OK или CANCEL.</p>
<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 src="script.js"></script>
</body>
</html>

View file

@ -1 +0,0 @@
/* ваши стили */

View file

@ -1,3 +0,0 @@
var table = document.getElementById('bagua-table');
/* ваш код */

View file

@ -1,16 +0,0 @@
# Редактирование TD по клику
[importance 5]
Сделать ячейки таблицы `td` редактируемыми по клику.
<ul>
<li>При клике -- ячейка `<td>` превращается в редактируемую, можно менять HTML. Размеры ячеек при этом не должны меняться.</li>
<li>В один момент может редактироваться одна ячейка.</li>
<li>При редактировании под ячейкой появляются кнопки для приема и отмена редактирования, только клик на них заканчивает редактирование.</li>
</ul>
Демо:
[iframe src="solution"]

View file

@ -1,22 +0,0 @@
# Вёрстка
Для вёрстки можно использовать отрицательный `margin` у текста с подсказкой.
Решение в плане вёрстка есть в решении задачи [](/task/position-text-into-input).
# Решение
```js
placeholder.onclick = function() {
input.focus();
}
// onfocus сработает и вызове input.focus() и при клике на input
input.onfocus = function() {
if (placeholder.parentNode) {
placeholder.parentNode.removeChild(placeholder);
}
}
```
[edit src="solution"]Открыть полный код решения[/edit]

View file

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>Добро пожаловать</div>
<input type="password" id="input">
<div id="placeholder">Скажи пароль, друг</div>
<div>.. и заходи</div>
<script>
var placeholder = document.getElementById('placeholder');
var input = document.getElementById('input');
placeholder.onclick = function() {
input.focus();
}
input.onfocus = function() {
placeholder.parentNode && placeholder.parentNode.removeChild(placeholder);
}
</script>
</body>
</html>

View file

@ -1,17 +0,0 @@
body {
font: 14px/14px Arial, sans-serif;
}
input {
font: 14px/14px Arial, sans-serif;
width: 12em;
padding: 0;
margin: 0;
}
#placeholder {
font: 14px/14px Arial, sans-serif;
position: absolute;
margin: -1.2em 0 0 0.2em;
color: red;
}

View file

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>Добро пожаловать</div>
<input type="password" id="input">
<div id="placeholder">Скажи пароль, друг</div>
<div>.. и заходи</div>
</body>
</html>

View file

@ -1,17 +0,0 @@
body {
font: 14px/14px Arial, sans-serif;
}
input {
font: 14px/14px Arial, sans-serif;
width: 12em;
padding: 0;
margin: 0;
}
#placeholder {
font: 14px/14px Arial, sans-serif;
position: absolute;
margin: -1.2em 0 0 0.2em;
color: red;
}

View file

@ -1,11 +0,0 @@
# Красивый плейсхолдер для INPUT
[importance 5]
Создайте для `<input type="password">` красивый, стилизованный плейсхолдер, например (кликните на тексте):
[iframe src="solution" height=90]
При клике плейсхолдер просто исчезает и дальше не показывается.

View file

@ -1,91 +0,0 @@
# Алгоритм
JavaScript не имеет доступа к текущему состоянию [key CapsLock]. При загрузке страницы не известно, включён он или нет.
Но мы можем догадаться о его состоянии из событий:
<ol>
<li>Проверив символ, полученный по `keypress`. Символ в верхнем регистре без нажатого [key Shift] означает, что включён [key CapsLock]. Аналогично, символ в нижнем регистре, но с [key Shift] говорят о включенном [key CapsLock]. Свойство `event.shiftKey` показывает, нажат ли [key Shift]. Так мы можем точно узнать, нажат ли [key CapsLock].</li>
<li>Проверять `keydown`. Если нажат CapsLock (скан-код равен `20`), то переключить состояние, но лишь в том случае, когда оно уже известно.
Под Mac так делать не получится, поскольку клавиатурные события с CapsLock [работают некорректно](#keyboard-events-order).</li>
</ol>
Имея состояние `CapsLock` в переменной, можно при фокусировке на `INPUT` выдавать предупреждение.
Отслеживать оба события: `keydown` и `keypress` хорошо бы на уровне документа, чтобы уже на момент входа в поле ввода мы знали состояние CapsLock.
Но при вводе сразу в нужный `input` событие `keypress` событие доплывёт до `document` и поставит состояние CapsLock *после того, как сработает на `input`*. Как это обойти -- подумайте сами.
# Решение
При загрузке страницы, когда еще ничего не набрано, мы ничего не знаем о состоянии [key CapsLock], поэтому оно равно `null`:
```js
var capsLockEnabled = null;
```
Когда нажата клавиша, мы можем попытаться проверить, совпадает ли регистр символа и состояние [key Shift]:
```js
document.onkeypress = function(e) {
var chr = getChar(e);
if (!chr) return; // специальная клавиша
if (chr.toLowerCase() == chr.toUpperCase()) {
// символ, который не имеет регистра, такой как пробел,
// мы не можем использовать для определения состояния CapsLock
return;
}
capsLockEnabled = (chr.toLowerCase() == chr && e.shiftKey) || (chr.toUpperCase() == chr && !e.shiftKey);
}
```
Когда пользователь нажимает [key CapsLock], мы должны изменить его текущее состояние. Но мы можем сделать это только если знаем, что был нажат [key CapsLock].
Например, когда пользователь открыл страницу, мы не знаем, включен ли [key CapsLock]. Затем, мы получаем событие `keydown` для [key CapsLock]. Но мы все равно не знаем его состояния, был ли [key CapsLock] *выключен* или, наоборот, включен.
```js
if (navigator.platform.substr(0, 3) != 'Mac') { // событие для CapsLock глючит под Mac
document.onkeydown = function(e) {
if (e.keyCode == 20 && capsLockEnabled !== null) {
capsLockEnabled = !capsLockEnabled;
}
};
}
```
Теперь поле. Задание состоит в том, чтобы предупредить пользователя о включенном CapsLock, чтобы уберечь его от неправильного ввода.
<ol>
<li>Для начала, когда пользователь сфокусировался на поле, мы должны вывести предупреждение о CapsLock, если он включен.</li>
<li>Пользователь начинает ввод. Каждое событие `keypress` всплывает до обработчика `document.keypress`, который обновляет состояние `capsLockEnabled`.
Мы не можем использовать событие `input.onkeypress`, для отображения состояния пользователю, потому что оно сработает *до* `document.onkeypress` (из-за всплытия) и, следовательно, до того, как мы узнаем состояние [key CapsLock].
Есть много способов решить эту проблему. Можно, например, назначить обработчик состояния CapsLock на событие `input.onkeyup`. То есть, индикация будет с задержкой, но это несущественно.
Альтернативное решение -- добавить на `input` такой же обработчик, как и на `document.onkeypress`.
</li>
<li>...И наконец, пользователь убирает фокус с поля. Предупреждение может быть видно, если [key CapsLock] включен, но так как пользователь уже ушел с поля, то нам нужно спрятать предупреждение.</li>
</ol>
Код проверки поля:
```html
<input type="text" onkeyup="checkCapsWarning(event)" onfocus="checkCapsWarning(event)" onblur="removeCapsWarning()" />
<div style="display:none;color:red" id="caps">Внимание: нажат CapsLock!</div>
<script>
function checkCapsWarning() {
document.getElementById('caps').style.display = capsLockEnabled ? 'block' : 'none';
}
function removeCapsWarning() {
document.getElementById('caps').style.display = 'none';
}
</script>
```
[edit src="solution"]Полный код решения[/edit]

View file

@ -1,77 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
Введите текст(например, пароль) с нажатым CapsLock:
<input type="text" onkeyup="checkCapsWarning(event)" onfocus="checkCapsWarning(event)" onblur="removeCapsWarning()" />
<div style="display:none;color:red" id="capsIndicator">Внимание: нажат CapsLock!</div>
<script>
/**
* Текущее состояние CapsLock
* - null : неизвестно
* - true/false : CapsLock включен/выключен
*/
var capsLockEnabled = null;
function getChar(event) {
if (event.which == null) {
if (event.keyCode < 32) return null;
return String.fromCharCode(event.keyCode) // IE
}
if (event.which != 0 && event.charCode != 0) {
if (event.which < 32) return null;
return String.fromCharCode(event.which) // остальные
}
return null; // специальная клавиша
}
if (navigator.platform.substr(0, 3) != 'Mac') { // событие для CapsLock глючит под Mac
document.onkeydown = function(e) {
if (e.keyCode == 20 && capsLockEnabled !== null) {
capsLockEnabled = !capsLockEnabled;
}
}
}
document.onkeypress = function(e) {
e = e || event;
var chr = getChar(e);
if (!chr) return // special key
if (chr.toLowerCase() == chr.toUpperCase()) {
// символ, не зависящий от регистра, например пробел
// не может быть использован для определения CapsLock
return;
}
capsLockEnabled = (chr.toLowerCase() == chr && e.shiftKey) || (chr.toUpperCase() == chr && !e.shiftKey);
}
/**
* Проверить CapsLock
*/
function checkCapsWarning() {
document.getElementById('capsIndicator').style.display = capsLockEnabled ? 'block' : 'none';
}
function removeCapsWarning() {
document.getElementById('capsIndicator').style.display = 'none';
}
</script>
</body>
</html>

View file

@ -1,19 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
Введите текст(например, пароль) с нажатым CapsLock:
<input type="text" />
<div style="display:none;color:red" id="capsIndicator">Внимание: нажат CapsLock!</div>
</body>
</html>

View file

@ -1,10 +0,0 @@
# Поле, предупреждающее о включенном CapsLock
[importance 3]
Создайте поле, которое будет предупреждать пользователя, если включен [key CapsLock]. Выключение [key CapsLock] уберёт предупреждение.
Такое поле может помочь избежать ошибок при вводе пароля.
[iframe height=80 src="solution"]

View file

@ -1,340 +0,0 @@
# Фокусировка: focus/blur
Говорят, что элемент "получает фокус", когда посетитель фокусируется на нём. Обычно фокусировка автоматически происходит при нажатии на элементе мышкой, но также можно перейти на нужный элемент клавиатурой -- через клавишу [key Tab], нажатие пальцем на планшете и так далее.
Момент получения фокуса и потери очень важен.
При получении фокуса мы можем подгрузить данные для автодополнения, начать отслеживать изменения. При потере -- проверить данные, которые ввёл посетитель.
Кроме того, иногда полезно "вручную", из JavaScript перевести фокус на нужный элемент, например, на поле в динамически созданной форме.
[cut]
## События focus/blur
Событие `focus` вызывается тогда, когда пользователь фокусируется на элементе, а `blur` -- когда фокус исчезает, например посетитель кликает на другом месте экрана.
Давайте сразу посмотрим на них в деле, используем для проверки ("валидации") введённых в форму значений.
В примере ниже:
<ul>
<li>Обработчик `onblur` проверяет, что в поле введено число, если нет -- показывает ошибку.</li>
<li>Обработчик `onfocus`, если текущее состояние поля ввода -- "ошибка" -- скрывает её (потом при `onblur` будет повторная проверка).</li>
</ul>
В примере ниже, если набрать что-нибудь в поле "возраст" и завершить ввод, нажав [key Tab] или кликнув в другое место страницы, то введённое значение будет автоматически проверено:
```html
<!--+ run autorun height=60 -->
<style> .error { border-color: red; } </style>
Введите ваш возраст: <input type="text" id="input">
<div id="error"></div>
<script>
*!*input.onblur*/!* = function() {
if (isNaN(this.value)) { // введено не число
// показать ошибку
this.className = "error";
error.innerHTML = 'Вы ввели не число. Исправьте, пожалуйста.'
}
};
*!*input.onfocus*/!* = function() {
if (this.className == 'error') { // сбросить состояние "ошибка", если оно есть
this.className = "";
error.innerHTML = "";
}
};
</script>
```
## Методы focus/blur
Методы с теми же названиями переводят/уводят фокус с элемента.
Для примера модифицируем пример выше, чтобы при неверном вводе посетитель просто не мог уйти с элемента:
```html
<!--+ run autorun height=80 -->
<style>
.error {
background: red;
}
</style>
<div>Возраст:
<input type="text" id="age">
</div>
<div>Имя:
<input type="text">
</div>
<script>
age.onblur = function() {
if (isNaN(this.value)) { // введено не число
// показать ошибку
this.classList.add("error");
*!*
//... и вернуть фокус обратно
age.focus();
*/!*
} else {
this.classList.remove("error");
}
};
</script>
```
Этот пример работает во всех браузерах, кроме Firefox ([ошибка](https://bugzilla.mozilla.org/show_bug.cgi?id=53579)).
Если ввести что-то нецифровое в поле "возраст", и потом попытаться табом или мышкой перейти на другой `<input>`, то обработчик `onblur` вернёт фокус обратно.
Обратим внимание -- если из `onblur` сделать `event.preventDefault()`, то такого же эффекта не будет, потому что `onblur` срабатывает уже *после* того, как элемент потерял фокус.
## HTML5 и CSS3 вместо focus/blur
Прежде чем переходить к более сложным примерам, использующим JavaScript, мы рассмотрим три примера, когда его использовать не надо, а достаточно современного HTML/CSS.
### Подсветка при фокусировке
Стилизация полей ввода может быть решена средствами CSS (CSS2.1), а именно -- селектором `:focus`:
```html
<!--+ autorun height=100 -->
<style>
*!*input:focus*/!* {
background: #FA6;
outline: none; /* убрать рамку */
}
</style>
<input type="text">
<p>Селектор :focus выделит элемент при фокусировке на нем и уберёт рамку, которой браузер выделяет этот элемент по умолчанию.</p>
```
В IE (включая более старые) скрыть фокус также может установка специального атрибута [hideFocus](http://msdn.microsoft.com/en-us/library/ie/ms533783.aspx).
### Автофокус
При загрузке страницы, если на ней существует элемент с атрибутом `autofocus` -- браузер автоматически фокусируется на этом элементе. Работает во всех браузерах, кроме IE9-.
```html
<!--+ run link -->
<input type="text" name="search" *!*autofocus*/!*>
```
Если нужны старые IE, то же самое может сделать JavaScript:
```html
<input type="text" name="search">
<script>
document.getElementsByName('search')[0].focus();
</script>
```
Как правило, этот атрибут используется при изначальной загрузке, для страниц поиска и так далее, где главный элемент очевиден.
### Плейсхолдер
*Плейсхолдер* -- это значение-подсказка внутри `INPUT`, которое автоматически исчезает при фокусировке и существует, пока посетитель не начал вводить текст.
Во всех браузерах, кроме IE9-, это реализуется специальным атрибутом `placeholder`:
```html
<!--+ autorun height=80 -->
<input type="text" placeholder="E-mail">
```
В некоторых браузерах этот текст можно стилизовать:
```html
<!--+ autorun height=80 -->
<style>
.my*!*::-webkit-input-placeholder*/!* {
color: red;
font-style: italic;
}
.my*!*::-moz-input-placeholder*/!* {
color: red;
font-style: italic;
}
.my*!*::-ms-input-placeholder*/!* {
color: red;
font-style: italic;
}
</style>
<input class="my" type="text" placeholder="E-mail">
Стилизованный плейсхолдер
```
## Разрешаем фокус на любом элементе: tabindex
По умолчанию не все элементы поддерживают фокусировку.
Перечень элементов немного рознится от браузера к браузеру, например, список для IE описан <a href="http://msdn.microsoft.com/en-us/library/ms536934.aspx">в MSDN</a>, одно лишь верно всегда -- заведомо поддерживают `focus/blur` те элементы, c которыми посетитель может взаимодействовать: `<button>`, `<input>`, `<select>`, `<a>` и т.д.
С другой стороны, на элементах для форматирования, таких как `<div>`, `<span>`, `<table>` -- по умолчанию сфокусироваться нельзя. Впрочем, существует способ включить фокусировку и для них.
В HTML есть атрибут `tabindex`.
Его основной смысл -- это указать номер элемента при переборе клавишей [key Tab].
То есть, если есть два элемента, первый имеет `tabindex="1"`, а второй `tabindex="2"`, то нажатие [key Tab] при фокусе на первом элементе -- переведёт его на второй.
Исключением являются специальные значения:
<ul>
<li>`tabindex="0"` делает элемент всегда последним.</li>
<li>`tabindex="-1"` означает, что клавиша [key Tab] будет элемент игнорировать.</li>
</ul>
**Любой элемент поддерживает фокусировку, если у него есть `tabindex`.**
В примере ниже есть список элементов. Кликните на любой из них и нажмите "tab".
```html
<!--+ autorun no-beautify -->
Кликните на первый элемент списка и нажмите Tab. Внимание! Дальнейшие нажатия Tab могут вывести за границы iframe'а с примером.
<ul>
<li tabindex="1">Один</li>
<li tabindex="0">Ноль</li>
<li tabindex="2">Два</li>
<li tabindex="-1">Минус один</li>
</ul>
<style>
li { cursor: pointer; }
:focus { outline: 1px dashed green; }
</style>
```
Порядок перемещения по клавише "Tab" в примере выше должен быть таким: `1 - 2 - 0` (ноль всегда последний). Продвинутые пользователи частенько используют "Tab" для навигации, и ваше хорошее отношение к ним будет вознаграждено :)
Обычно `<li>` не поддерживает фокусировку, но здесь есть `tabindex`.
## Делегирование с focus/blur
События `focus` и `blur` не всплывают.
Это грустно, поскольку мы не можем использовать делегирование с ними. Например, мы не можем сделалать так, чтобы при фокусировке в форме она вся подсвечивалась:
```html
<!--+ autorun height=100 -->
<!-- при фокусировке на форме ставим ей класс -->
<form *!*onfocus="this.className='focused'"*/!*>
<input type="text" name="name" value="Ваше имя">
<input type="text" name="surname" value="Ваша фамилия">
</form>
<style> .focused { outline: 1px solid red; } </style>
```
Пример выше не работает, т.к. при фокусировке на любом `<input>` событие `focus` срабатывает только на этом элементе и не всплывает наверх. Так что обработчик `onfocus` на форме никогда не сработает.
Что делать? Неужели мы должны присваивать обработчик каждому полю `<input>`?
**Это забавно, но хотя `focus/blur` не всплывают, они могут быть пойманы на фазе перехвата.**
Вот так сработает:
```html
<!--+ autorun height=100 -->
<form id="form">
<input type="text" name="name" value="Ваше имя">
<input type="text" name="surname" value="Ваша фамилия">
</form>
<style>
.focused {
outline: 1px solid red;
}
</style>
<script>
*!*
// ставим обработчики на фазе перехвата, последний аргумент true
form.addEventListener("focus", function() {
this.classList.add('focused');
}, true);
form.addEventListener("blur", function() {
this.classList.remove('focused');
}, true);
*/!*
</script>
```
### События focusin/focusout
События `focusin/focusout` -- то же самое, что и `focus/blur`, только они всплывают.
У них две особенности:
<ul>
<li>Не поддерживаются Firefox (хотя поддерживаются даже старейшими IE), см. [](https://bugzilla.mozilla.org/show_bug.cgi?id=687787).</li>
<li>Должны быть назначены не через `on`-свойство, а при помощи `elem.addEventListener`.</li>
</ul>
Из-за отсутствия подержки Firefox эти события используют редко. Получается, что во всех браузерах можно использовать `focus` на стадии перехвата, ну а `focusin/focusout` -- в IE8-, где стадии перехвата нет.
Подсветка формы в примере ниже работает во всех браузерах.
```html
<!--+ autorun height=60 run -->
<form name="form">
<input type="text" name="name" value="Ваше имя">
<input type="text" name="surname" value="Ваша фамилия">
</form>
<style>
.focused {
outline: 1px solid red;
}
</style>
<script>
function onFormFocus() {
this.className = 'focused';
}
function onFormBlur() {
this.className = '';
}
var form = document.forms.form;
if (form.addEventListener) {
// focus/blur на стадии перехвата срабатывают во всех браузерах
// поэтому используем их
form.addEventListener('focus', onFormFocus, true);
form.addEventListener('blur', onFormBlur, true);
} else {
// ветка для IE8-, где нет стадии перехвата, но есть focusin/focusout
form.onfocusin = onFormFocus;
form.onfocusout = onFormBlur;
}
</script>
```
## Итого
События `focus/blur` происходят при получении и снятия фокуса с элемента.
У них есть особенности:
<ul>
<li>Они не всплывают. Но на фазе перехвата их можно перехватить. Это странно, но это так, не спрашивайте почему.
Везде, кроме Firefox, поддерживаются всплывающие альтернативы `focusin/focusout`.</li>
<li>По умолчанию многие элементы не могут получить фокус. Например, если вы кликните по `DIV`, то фокусировка на нем не произойдет.
Но это можно изменить, если поставить элементу атрибут `tabIndex`. Этот атрибут также дает возможность контролировать порядок перехода при нажатии [key Tab].
</li>
</ul>
[smart header="Текущий элемент: `document.activeElement`"]
Кстати, текущий элемент, на котором фокус, доступен как `document.activeElement`.
[/smart]