update
This commit is contained in:
parent
962caebbb7
commit
87bf53d076
1825 changed files with 94929 additions and 0 deletions
|
@ -0,0 +1,52 @@
|
|||
Состояние элемента определяется наличием класса `placeholder`. Для простоты будем считать, что это -- единственный возможный класс у `INPUT'а`
|
||||
|
||||
При фокусировке, если в элементе находится плейсхолдер -- он должен исчезать:
|
||||
|
||||
```js
|
||||
input.onfocus = function() {
|
||||
if (this.className == 'placeholder') {
|
||||
prepareInput(this);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareInput(input) { // превратить элемент в простой пустой input
|
||||
input.className = '';
|
||||
input.value = '';
|
||||
}
|
||||
```
|
||||
|
||||
...Затем элемент потеряет фокус. При этом нужно возвратить плейсхолдер, но только в том случае, если элемент пустой:
|
||||
|
||||
```js
|
||||
input.onblur = function() {
|
||||
if (this.value == '') { // если пустой
|
||||
resetInput(this); // заполнить плейсхолдером
|
||||
}
|
||||
}
|
||||
|
||||
function resetInput(input) {
|
||||
input.className = 'placeholder';
|
||||
input.value = 'E-mail';
|
||||
}
|
||||
```
|
||||
|
||||
Это решение можно сделать удобнее в поддержке, если при выполнении `prepareInput` копировать значение в специальное свойство, а в `resetInput` -- восстанавливать его:
|
||||
|
||||
```js
|
||||
function prepareInput(input) {
|
||||
input.className = '';
|
||||
*!*
|
||||
input.oldValue = input.value;
|
||||
*/!*
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
function resetInput(input) {
|
||||
input.className = 'placeholder';
|
||||
input.value = input.oldValue;
|
||||
}
|
||||
```
|
||||
|
||||
Теперь, если понадобится изменить значение плейсхолдера -- это достаточно сделать в HTML, и не надо трогать JavaScript-код.
|
||||
|
||||
[edit src="solution"]Полное решение[/edit]
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
.placeholder {
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<input type="text" class="placeholder" value="E-mail">
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
var input = document.getElementsByTagName('input')[0];
|
||||
|
||||
function prepareInput(input) {
|
||||
input.className = '';
|
||||
input.oldValue = input.value;
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
function resetInput(input) {
|
||||
input.className = 'placeholder';
|
||||
input.value = input.oldValue;
|
||||
}
|
||||
|
||||
|
||||
input.onfocus = function() {
|
||||
if (this.className == 'placeholder') {
|
||||
prepareInput(this);
|
||||
}
|
||||
}
|
||||
|
||||
input.onblur = function() {
|
||||
if (this.value == '') {
|
||||
resetInput(this);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
.placeholder {
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<input type="text" class="placeholder" value="E-mail">
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
var input = document.getElementsByTagName('input')[0];
|
||||
|
||||
// ...
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
# Плейсхолдер
|
||||
|
||||
[importance 5]
|
||||
|
||||
Реализуйте плейсхолдер на JavaScript, чтобы он работал в IE9-.
|
||||
|
||||
Правила работы плейсхолдера:
|
||||
<ul>
|
||||
<li>Элемент изначально содержит плейсхолдер. Специальный класс `placeholder` придает ему серый цвет.</li>
|
||||
<li>При фокусировке плейсхолдер пропадает.</li>
|
||||
<li>При снятии фокуса, если поле пустое -- плейсхолдер возвращается.</li>
|
||||
</ul>
|
||||
|
||||
Все три пункта должны быть соблюдены кросс-браузерно. Пример:
|
||||
|
||||
[iframe src="solution" height=40]
|
||||
|
||||
[edit src="source" task/]
|
110
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/solution.md
Normal file
110
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/solution.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
# Алгоритм
|
||||
|
||||
Самый естественный алгоритм решения:
|
||||
<ol>
|
||||
<li>При клике мышонок получает фокус. Для этого нужно либо заменить `DIV` на другой тег, либо добавить ему `tabindex="-1"`.</li>
|
||||
<li>Когда на элементе фокус, то клавиатурные события будут срабатывать прямо на нём. То есть ловим `mousie.onkeydown`.
|
||||
|
||||
Мы выбираем `keydown`, потому что он позволяет во-первых отлавливать нажатия на спец. клавиши (стрелки), а во-вторых, отменить действие браузера, которым по умолчанию является прокрутка страницы.
|
||||
</li>
|
||||
<li>При нажатии на стрелки двигаем мышонка через `position:absolute` и `top/left`.</li>
|
||||
</ol>
|
||||
|
||||
Дальше решение -- попробуйте сделать сами. Возможны подводные камни :)
|
||||
|
||||
# Решение
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
При получении фокуса -- готовим мышонка к перемещению:
|
||||
|
||||
```js
|
||||
mousie.onfocus = function() {
|
||||
this.style.position = 'relative';
|
||||
this.style.left = '0px';
|
||||
this.style.top = '0px';
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Коды для клавиш стрелок можно узнать, нажимая на них на [тестовом стенде](#keyboard-test-stand). Вот они: 37-38-39-40 (влево-вверх-вправо-вниз).
|
||||
|
||||
При нажатии стрелки -- двигаем мышонка:
|
||||
|
||||
```js
|
||||
mousie.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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Обратите внимание, что действием по умолчанию для стрелок является прокрутка страницы. Поэтому, чтобы её отменить, нужно использовать `return false`.
|
||||
|
||||
Когда пользователь убирает фокус с мышки, то она перестает реагировать на клавиши. Нет нужды удалять обработчики на `blur`, потому что браузер перестанет вызывать `keydown`.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
**В решении выше есть проблема. Мышонок находится в `DIV` с `position:relative`.** Это означает, что его `left/top` являются координатами не относительно документа, а относительно позиционированного предка.
|
||||
|
||||
Что делать? Решений три.
|
||||
|
||||
<ol><li>Первое -- учесть этого позиционированного предка при вычислении `left/top`, вычитать его координаты из координат относительно документа.</li>
|
||||
<li>Второе -- сделать `position:fixed`. При этом координаты мышонка можно взять напрямую из [mousie.getBoundingClientRect()](https://developer.mozilla.org/en/DOM/element.getBoundingClientRect), т.е. все вычисления выполнять относительно окна. Это больше компьютерно-игровой подход, чем работа с документом.</li>
|
||||
<li>Третье -- переместить мышонка под `document.body` в начале движения. Тогда и с координатами всё будет в порядке. Но при этом могут "слететь" стили.</li>
|
||||
</ol>
|
||||
|
||||
Хочется верить, что первое и второе решения понятны. А вот третье более интересно, так как скрывает новые тонкости.
|
||||
|
||||
Если пойти этим путём, то в обработчик `onfocus` следует добавить перемещение мышонка под `BODY`:
|
||||
|
||||
```js
|
||||
mousie.onfocus = function() {
|
||||
var coords = getCoords(this);
|
||||
|
||||
*!*
|
||||
document.body.appendChild(this);
|
||||
*/!*
|
||||
|
||||
this.style.position = 'absolute';
|
||||
this.style.left = coords.left + 'px';
|
||||
this.style.top = coords.top + 'px';
|
||||
};
|
||||
```
|
||||
|
||||
...Но вот беда! **При `document.body.appendChild(this)` с элемента слетает фокус!**
|
||||
|
||||
Фокус нужно восстановить, чтобы ловить `keydown`. Однако некоторые браузеры, например FF и IE, не дают вызвать метод `focus()` элемента из его обработчика `onfocus`. То есть сделать это нельзя.
|
||||
|
||||
Чтобы это обойти, можно поставить обработчик не `onfocus`, а `onclick`:
|
||||
|
||||
```js
|
||||
mousie.onclick = function() {
|
||||
var coords = getCoords(this);
|
||||
this.style.position = 'absolute';
|
||||
this.style.left = coords.left + 'px';
|
||||
this.style.top = coords.top + 'px';
|
||||
|
||||
*!*
|
||||
if (this.parentNode != document.body) {
|
||||
document.body.appendChild(this);
|
||||
this.focus();
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
```
|
||||
|
||||
Обычно событие `focus` всё равно происходит *после* `click`, но здесь элемент перемещается, поэтому оно "съедается" и мы инициируем его сами вызовом `focus()`.
|
||||
|
||||
[edit src="solution"]Окончательное решение[/edit]
|
82
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/solution.view/index.html
Executable file
82
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/solution.view/index.html
Executable file
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#mousie:focus {
|
||||
outline: none;
|
||||
border: 1px dashed black;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Кликните на мышонка и передвигайте его, нажимая клавиши со стрелками.
|
||||
|
||||
<div style="position:relative;top:30px;width:50px;height:50px">
|
||||
<div style="width:41px; height:48px; background:url(http://js.cx/clipart/mousie.gif)" id="mousie" tabindex="-1"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
document.getElementById('mousie').onclick = function() {
|
||||
var coords = getCoords(this);
|
||||
this.style.position = 'absolute';
|
||||
this.style.left = coords.left + 'px';
|
||||
this.style.top = coords.top + 'px';
|
||||
|
||||
if (this.parentNode != document.body) {
|
||||
document.body.appendChild(this);
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.getElementById('mousie').onkeydown = function(e) {
|
||||
e = e || event;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------
|
||||
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docElem = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docElem.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) };
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
54
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/source.view/index.html
Executable file
54
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/source.view/index.html
Executable file
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
#mousie:focus {
|
||||
outline: none;
|
||||
border: 1px dashed black;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Кликните на мышонка и передвигайте его, нажимая клавиши со стрелками.
|
||||
|
||||
<div style="width:41px;height:48px;background:url(http://js.cx/clipart/mousie.gif)"></div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
/* ваш код */
|
||||
|
||||
|
||||
// -----------------------
|
||||
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docElem = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docElem.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) };
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
77
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/task.md
Normal file
77
2-ui/4-forms-controls/2-focus-blur/2-keyboard-mouse/task.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Мышонок на "клавиатурном" приводе
|
||||
|
||||
[importance 4]
|
||||
|
||||
Кликните по мышонку. Затем нажимайте клавиши со стрелками, и он будет двигаться.
|
||||
|
||||
<style>
|
||||
##mousie:focus {
|
||||
outline: none;
|
||||
border: 1px dashed black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="position:relative;top:20px;width:100px;height:100px">
|
||||
<div style="width:41px; height:48px;background:url(http://js.cx/clipart/mousie.gif)" id="mousie" tabindex="0"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
mousie.onclick = function() {
|
||||
var coords = getCoords(this);
|
||||
this.style.position = 'absolute';
|
||||
this.style.left = coords.left + 'px';
|
||||
this.style.top = coords.top + 'px';
|
||||
|
||||
if (this.parentNode != document.body) {
|
||||
document.body.appendChild(this);
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mousie.onkeydown = function(e) {
|
||||
e = e || event;
|
||||
|
||||
switch(e.keyCode) {
|
||||
case 40: // вниз
|
||||
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 38: // вверх
|
||||
this.style.top = parseInt(this.style.top) - this.offsetHeight + 'px';
|
||||
return false;
|
||||
case 37: // влево
|
||||
this.style.left = parseInt(this.style.left) - this.offsetWidth + 'px';
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// -----------------------
|
||||
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docElem = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docElem.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) };
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Учтите при решении, что мышонок изначально находится где-то в глубине документа, в `DIV'е` с `position:relative`.
|
||||
|
||||
[edit src="source" task/]
|
99
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.md
Normal file
99
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# CSS для решения
|
||||
|
||||
Как видно из исходного кода, `#view` -- это `DIV`, который будет содержать результат, а `#area` - это редактируемое текстовое поле.
|
||||
|
||||
Так как мы преобразуем `DIV` в `TEXTAREA` и обратно, нам нужно сделать их практически одинаковыми с виду:
|
||||
|
||||
```css
|
||||
#view, #area {
|
||||
height:150px;
|
||||
width:400px;
|
||||
font-family:arial;
|
||||
}
|
||||
```
|
||||
|
||||
Текстовое поле нужно как-то выделить. Можно добавить границу, но тогда изменится блок: он увеличится в размерах и немного съедет текст.
|
||||
|
||||
Для того, чтобы сделать размер `#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
|
||||
#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';
|
||||
}
|
||||
```
|
||||
|
||||
[edit src="solution"]Полное решение[/edit]
|
||||
|
||||
Чтобы проверить его, сфокусируйтесь на правом iframe, пожалуйста.
|
64
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.view/index.html
Executable file
64
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.view/index.html
Executable file
|
@ -0,0 +1,64 @@
|
|||
<!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>
|
||||
|
||||
area = document.getElementById('area');
|
||||
view = document.getElementById('view');
|
||||
|
||||
document.onkeydown = function(e) {
|
||||
e = e || event;
|
||||
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>
|
22
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.view/my.css
Executable file
22
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/solution.view/my.css
Executable file
|
@ -0,0 +1,22 @@
|
|||
#view, #area {
|
||||
height:150px;
|
||||
width:400px;
|
||||
font-family:arial;
|
||||
}
|
||||
|
||||
#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 */
|
||||
}
|
19
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/source.view/index.html
Executable file
19
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/source.view/index.html
Executable file
|
@ -0,0 +1,19 @@
|
|||
<!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>
|
22
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/source.view/my.css
Executable file
22
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/source.view/my.css
Executable file
|
@ -0,0 +1,22 @@
|
|||
#view, #area {
|
||||
height:150px;
|
||||
width:400px;
|
||||
font-family:arial;
|
||||
}
|
||||
|
||||
#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 */
|
||||
}
|
13
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/task.md
Normal file
13
2-ui/4-forms-controls/2-focus-blur/3-hotkeys/task.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Горячие клавиши
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте `DIV`, который при нажатии [key Ctrl+E] превращается в `TEXTAREA`.
|
||||
Изменения, внесенные в поле, можно сохранить в `DIV` сочетанием клавиш [key Ctrl+S] или же отменить нажав [key Esc]. После этого, `TEXTAREA` снова превращается в `DIV`.
|
||||
|
||||
Содержимое, сохраненное как HTML-теги должно работать.
|
||||
|
||||
[demo src="solution"].
|
||||
|
||||
[edit src="source" task/]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<ol>
|
||||
<li>При клике -- заменяем `innerHTML` ячейки на `<textarea>` с размерами "под ячейку", без рамки.</li>
|
||||
<li>В `textarea.value` присваиваем содержимое ячейки.</li>
|
||||
<li>Фокусируем посетителя на ячейке вызовом `focus()`.</li>
|
||||
<li>По `keydown` отслеживаем нажатие с `keyCode = 13` ([key Enter]) с `shiftKey` и трансформируем ячейку обратно.</li>
|
||||
</ol>
|
||||
|
||||
[edit src="solution"]Открыть в песочнице[/edit]
|
52
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/bagua.css
Executable file
52
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/bagua.css
Executable file
|
@ -0,0 +1,52 @@
|
|||
|
||||
#bagua-table th {
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#bagua-table td {
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
text-align:center;
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#bagua-table .nw {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
#bagua-table .n {
|
||||
background-color: #03f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .ne {
|
||||
background-color: #ff6;
|
||||
}
|
||||
|
||||
#bagua-table .w {
|
||||
background-color: #ff0;
|
||||
}
|
||||
#bagua-table .c {
|
||||
background-color: #60c;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .e {
|
||||
background-color: #09f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .sw {
|
||||
background-color: #963;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .s {
|
||||
background-color: #f60;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .se {
|
||||
background-color: #0c3;
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="bagua.css">
|
||||
<link type="text/css" rel="stylesheet" href="my.css">
|
||||
|
||||
Кликните на ячейке для начала редактирования. Когда закончите -- нажмите, находясь в ней, Shift+Enter.
|
||||
|
||||
<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>
|
12
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/my.css
Executable file
12
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/my.css
Executable file
|
@ -0,0 +1,12 @@
|
|||
.edit-area {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
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 */
|
||||
}
|
||||
|
35
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/script.js
Executable file
35
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/solution.view/script.js
Executable file
|
@ -0,0 +1,35 @@
|
|||
|
||||
var table = document.getElementById('bagua-table');
|
||||
|
||||
table.onclick = function(event) {
|
||||
var target = event.target;
|
||||
|
||||
while(target != table) {
|
||||
if (target.nodeName == 'TD') {
|
||||
if (target.firstChild.tagName == 'TEXTAREA') { // already editing
|
||||
return;
|
||||
}
|
||||
makeTdEditable(target);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function makeTdEditable(td) {
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.className = 'edit-area';
|
||||
textArea.value = td.innerHTML;
|
||||
textArea.onkeydown = function(e) {
|
||||
if (e.keyCode == 13 && e.shiftKey) {
|
||||
finishTdEditing(td);
|
||||
}
|
||||
}
|
||||
td.innerHTML = '';
|
||||
td.appendChild(textArea);
|
||||
textArea.focus();
|
||||
}
|
||||
|
||||
function finishTdEditing(td) {
|
||||
td.innerHTML = td.firstChild.value;
|
||||
}
|
52
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/source.view/bagua.css
Executable file
52
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/source.view/bagua.css
Executable file
|
@ -0,0 +1,52 @@
|
|||
|
||||
#bagua-table th {
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#bagua-table td {
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
text-align:center;
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#bagua-table .nw {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
#bagua-table .n {
|
||||
background-color: #03f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .ne {
|
||||
background-color: #ff6;
|
||||
}
|
||||
|
||||
#bagua-table .w {
|
||||
background-color: #ff0;
|
||||
}
|
||||
#bagua-table .c {
|
||||
background-color: #60c;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .e {
|
||||
background-color: #09f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .sw {
|
||||
background-color: #963;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .s {
|
||||
background-color: #f60;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .se {
|
||||
background-color: #0c3;
|
||||
color: #fff;
|
||||
}
|
40
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/source.view/index.html
Executable file
40
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/source.view/index.html
Executable file
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="bagua.css">
|
||||
|
||||
|
||||
<table id="bagua-table">
|
||||
<tr>
|
||||
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="nw"><strong>Northwest</strong><br>Metal<br>Silver<br>Elders
|
||||
</td>
|
||||
<td class="n"><strong>North</strong><br>Water<br>Blue<br>Change
|
||||
</td>
|
||||
<td class="ne"><strong>Northeast</strong><br>Earth<br>Yellow<br>Direction
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="w"><strong>West</strong><br>Metal<br>Gold<br>Youth
|
||||
</td>
|
||||
<td class="c"><strong>Center</strong><br>All<br>Purple<br>Harmony
|
||||
</td>
|
||||
<td class="e"><strong>East</strong><br>Wood<br>Blue<br>Future
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="sw"><strong>Southwest</strong><br>Earth<br>Brown<br>Tranquility
|
||||
</td>
|
||||
<td class="s"><strong>South</strong><br>Fire<br>Orange<br>Fame
|
||||
</td>
|
||||
<td class="se"><strong>Southeast</strong><br>Wood<br>Green<br>Romance
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
18
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/task.md
Normal file
18
2-ui/4-forms-controls/2-focus-blur/4-edit-td-click-1/task.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Редактирование TD по клику
|
||||
|
||||
[importance 4]
|
||||
|
||||
Сделать ячейки таблицы `td` редактируемыми по клику.
|
||||
|
||||
<ul>
|
||||
<li>При клике -- ячейка `td` превращается в редактируемую, можно менять HTML. Размеры ячеек при этом не должны меняться.</li>
|
||||
<li>Клик на ячейку, которая уже находится в процессе редактирования не меняет ее состояние.</li>
|
||||
<li>Сохранение ячейки и превращение обратно в ячейку таблицы -- при нажатии [key Shift+Enter], когда курсор внутри.</li>
|
||||
<li>Несколько ячеек могут быть редактируемыми одновременно.</li>
|
||||
</ul>
|
||||
|
||||
Демо:
|
||||
|
||||
[iframe src="solution"]
|
||||
|
||||
[edit src="source" task/]
|
|
@ -0,0 +1 @@
|
|||
[edit src="solution"]Открыть решение в песочнице[/edit]
|
52
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/bagua.css
Executable file
52
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/bagua.css
Executable file
|
@ -0,0 +1,52 @@
|
|||
|
||||
#bagua-table th {
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#bagua-table td {
|
||||
width: 150px;
|
||||
white-space: nowrap;
|
||||
text-align:center;
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#bagua-table .nw {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
#bagua-table .n {
|
||||
background-color: #03f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .ne {
|
||||
background-color: #ff6;
|
||||
}
|
||||
|
||||
#bagua-table .w {
|
||||
background-color: #ff0;
|
||||
}
|
||||
#bagua-table .c {
|
||||
background-color: #60c;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .e {
|
||||
background-color: #09f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .sw {
|
||||
background-color: #963;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#bagua-table .s {
|
||||
background-color: #f60;
|
||||
color: #fff;
|
||||
}
|
||||
#bagua-table .se {
|
||||
background-color: #0c3;
|
||||
color: #fff;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body>
|
||||
<link type="text/css" rel="stylesheet" href="bagua.css">
|
||||
<link type="text/css" rel="stylesheet" href="my.css">
|
||||
|
||||
|
||||
Кликните на ячейке для начала редактирования. Когда закончите -- нажмите, находясь в ней, Shift+Enter или кликните на OK/CANCEL.
|
||||
|
||||
<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>
|
16
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/my.css
Executable file
16
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/my.css
Executable file
|
@ -0,0 +1,16 @@
|
|||
.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;
|
||||
}
|
62
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/script.js
Executable file
62
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/solution.view/script.js
Executable file
|
@ -0,0 +1,62 @@
|
|||
|
||||
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.className += ' 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();
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.innerHTML = '<button class="edit-ok">OK</button><button class="edit-cancel">CANCEL</button>';
|
||||
controls.className = 'edit-controls';
|
||||
td.appendChild(controls);
|
||||
}
|
||||
|
||||
function finishTdEdit(td, isOk) {
|
||||
if (isOk) {
|
||||
td.innerHTML = td.firstChild.value;
|
||||
} else {
|
||||
td.innerHTML = editingTd.data;
|
||||
}
|
||||
td.className = td.className.replace(' edit-td', ''); // remove edit class
|
||||
editingTd = null;
|
||||
}
|
||||
|
19
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/task.md
Normal file
19
2-ui/4-forms-controls/2-focus-blur/5-edit-td-click-2/task.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Редактирование TD по клику, часть 2
|
||||
|
||||
[importance 4]
|
||||
|
||||
Сделать ячейки таблицы `td` редактируемыми по клику.
|
||||
|
||||
Поменяйте алгоритм из задачи [](/task/edit-td-click-1) на следующий:
|
||||
<ul>
|
||||
<li>В один момент может редактироваться одна ячейка.</li>
|
||||
<li>При редактировании под ячейкой появляются кнопки для приема и отмена редактирования, только клик на них заканчивает редактирование.</li>
|
||||
</ul>
|
||||
|
||||
При решении этой задачи - максимально использовать делегирование.
|
||||
|
||||
Демо:
|
||||
|
||||
[iframe src="solution"]
|
||||
|
||||
В качестве исходного варианта возьмите решение задачи [](/task/edit-td-click-1).
|
|
@ -0,0 +1,22 @@
|
|||
# Вёрстка
|
||||
|
||||
Для вёрстки можно использовать отрицательный `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]
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
input {
|
||||
margin: 0;
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
#placeholder {
|
||||
color: red;
|
||||
|
||||
margin: -1.3em 0 0 0.2em;
|
||||
}
|
||||
</style>
|
||||
</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>
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div>Добро пожаловать</div>
|
||||
<input type="password" id="input">
|
||||
|
||||
<div id="placeholder">Скажи пароль, друг</div>
|
||||
|
||||
<div>.. и заходи</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
# Красивый плейсхолдер для INPUT
|
||||
|
||||
[importance 5]
|
||||
|
||||
Создайте для `<input type="password">` красивый, стилизованный плейсхолдер, например (кликните на тексте):
|
||||
|
||||
[iframe src="solution" height=90]
|
||||
|
||||
[edit src="source" task/]
|
||||
|
||||
При клике плейсхолдер просто исчезает и дальше не показывается.
|
|
@ -0,0 +1,91 @@
|
|||
# Алгоритм
|
||||
|
||||
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]
|
|
@ -0,0 +1,78 @@
|
|||
<!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) {
|
||||
e = e || event;
|
||||
|
||||
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>
|
|
@ -0,0 +1,16 @@
|
|||
<!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>
|
|
@ -0,0 +1,11 @@
|
|||
# Поле, предупреждающее о включенном CapsLock
|
||||
|
||||
[importance 3]
|
||||
|
||||
Создайте поле, которое будет предупреждать пользователя, если включен [key CapsLock]. Выключение [key CapsLock] уберёт предупреждение.
|
||||
|
||||
Такое поле может помочь избежать ошибок при вводе пароля.
|
||||
|
||||
[iframe height=80 src="solution"]
|
||||
|
||||
[edit src="source" task/]
|
307
2-ui/4-forms-controls/2-focus-blur/article.md
Normal file
307
2-ui/4-forms-controls/2-focus-blur/article.md
Normal file
|
@ -0,0 +1,307 @@
|
|||
# Фокусировка: 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 { background: 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>
|
||||
```
|
||||
|
||||
Если в примере выше ввести что-то нецифровое в поле "возраст", и потом попытаться табом или мышкой перейти на другой `<input>`, то обработчик `onblur` вернёт фокус обратно.
|
||||
|
||||
Обратим внимание -- если из `onblur` сделать `event.preventDefault()`, то такого же эффекта не будет, потому что `onblur` срабатывает уже *после* того, как элемент потерял фокус.
|
||||
|
||||
## HTML5 и CSS3 вместо focus/blur
|
||||
|
||||
Прежде чем переходить к более сложным примерам, использующим JavaScript, мы рассмотрим три примера, когда его использовать не надо, а достаточно современного HTML/CSS.
|
||||
|
||||
### Подсветка при фокусировке
|
||||
|
||||
Стилизация полей ввода может быть решена средствами CSS (CSS2.1), а именно -- селектором `:focus`:
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<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 -->
|
||||
<input type="text" placeholder="E-mail">
|
||||
```
|
||||
|
||||
В некоторых браузерах этот текст можно стилизовать:
|
||||
|
||||
```html
|
||||
<!--+ autorun -->
|
||||
<style>
|
||||
.my*!*::-webkit-input-placeholder*/!*, .my*!*::-moz-placeholder*/!* {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<input class="my" type="text" placeholder="E-mail">
|
||||
Стилизуется везде, кроме IE
|
||||
```
|
||||
|
||||
## Разрешаем фокус на любом элементе: 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 -->
|
||||
Кликните на первый элемент списка и нажмите 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 { border: 1px dashed green; outline: 0; }
|
||||
</style>
|
||||
```
|
||||
|
||||
Порядок перемещения по клавише "Tab" в примере выше должен быть таким: `1 - 2 - 0` (ноль всегда последний). Продвинутые пользователи частенько используют "Tab" для навигации, и ваше хорошее отношение к ним будет вознаграждено :)
|
||||
|
||||
Обычно `<li>` не поддерживает фокусировку, но здесь есть `tabindex`.
|
||||
|
||||
## Делегирование с focus/blur
|
||||
|
||||
События `focus` и `blur` не всплывают.
|
||||
|
||||
Это грустно, поскольку мы не можем использовать делегирование с ними. Например, мы не можем сделалать так, чтобы при фокусировке в форме она вся подсвечивалась:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<form *!*onfocus="this.className='focused'"*/!*>
|
||||
<input type="text" name="name" value="Ваше имя">
|
||||
<input type="text" name="surname" value="Ваша фамилия">
|
||||
</form>
|
||||
|
||||
<style> .focused { border: 2px solid red; } </style>
|
||||
```
|
||||
|
||||
В примере выше стоит обработчик `onfocus` на форме, но он не работает, т.к. при фокусировке на любом `INPUT` событие `focus` срабатывает только на этом элементе и не всплывает наверх.
|
||||
|
||||
Что делать? Неужели мы должны присваивать обработчик каждому полю?
|
||||
|
||||
**Это забавно, но хотя `focus/blur` не всплывают, они могут быть пойманы на фазе перехвата.**
|
||||
|
||||
Вот так сработает:
|
||||
|
||||
```html
|
||||
<!--+ autorun height=auto -->
|
||||
<form id="form">
|
||||
<input type="text" name="name" value="Ваше имя">
|
||||
<input type="text" name="surname" value="Ваша фамилия">
|
||||
</form>
|
||||
|
||||
<style> .focused { border: 2px 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, хотя поддерживаются даже IE6+.</li>
|
||||
<li>Во всех браузерах, кроме IE, должны быть назначены не через `on`-свойство, а при помощи `elem.addEventListener`.</li>
|
||||
</ul>
|
||||
|
||||
Для кросс-браузерной поддержки фокуса с делегированием можно использовать сочетать эти события с фазой перехвата.
|
||||
|
||||
Подсветка формы в примере ниже работает во всех браузерах.
|
||||
|
||||
```html
|
||||
<!--+ autorun height=60 run -->
|
||||
<form name="form">
|
||||
<input type="text" name="name" value="Ваше имя">
|
||||
<input type="text" name="surname" value="Ваша фамилия">
|
||||
</form>
|
||||
<style>
|
||||
.focused { border: 2px solid red; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function onFormFocus() { this.className = 'focused'; }
|
||||
function onFormBlur() { this.className = ''; }
|
||||
|
||||
var form = document.forms.form;
|
||||
|
||||
if (form.addEventListener) {
|
||||
form.addEventListener('focus', onFormFocus, true);
|
||||
form.addEventListener('blur', onFormBlur, true);
|
||||
} else { // IE8-
|
||||
form.onfocusin = onFormFocus;
|
||||
form.onfocusout = onFormBlur;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Итого
|
||||
События `focus/blur` происходят при получении и снятия фокуса с элемента.
|
||||
|
||||
У них есть особенности:
|
||||
<ul>
|
||||
<li>Они не всплывают. Но на фазе перехвата их можно перехватить. Это странно, но это так, не спрашивайте почему.
|
||||
|
||||
Везде, кроме Firefox, поддерживаются всплывающие альтернативы `focusin/focusout`.</li>
|
||||
<li>По умолчанию многие элементы не могут получить фокус. Например, если вы кликните по `DIV`, то фокусировка на нем не произойдет.
|
||||
|
||||
Но это можно изменить, если поставить элементу атрибут `tabIndex`. Этот атрибут также дает возможность контролировать порядок перехода при нажатии [key Tab].
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Текущий элемент, на котором фокус, доступен как `document.activeElement`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue