This commit is contained in:
Ilya Kantor 2017-03-15 00:43:43 +03:00
parent aeb74092b6
commit 1f61c2ab1d
100 changed files with 4120 additions and 659 deletions

View file

@ -0,0 +1,24 @@
Решение:
```html run
<select>
<option value="Rock">Рок</option>
<option value="Blues" selected>Блюз</option>
</select>
<script>
var select = document.body.children[0];
// 1)
var selectedOption = select.options[select.selectedIndex];
alert( selectedOption.value );
// 2)
var newOption = new Option("Classic", "Классика");
select.appendChild(newOption);
// 3)
newOption.selected = true;
</script>
```

View file

@ -0,0 +1,21 @@
importance: 5
---
# Добавьте опцию к селекту
Есть селект:
```html
<select>
<option value="Rock">Рок</option>
<option value="Blues" selected>Блюз</option>
</select>
```
При помощи JavaScript:
1. Выведите значение и текст текущей выбранной опции.
2. Добавьте опцию: `<option value="Classic">Классика</option>`.
3. Сделайте её выбранной.

View file

@ -0,0 +1,276 @@
# Form properties and methods [todo]
Forms and control elements, such as `<input>` have a lot of special properties and events.
Working with forms can be much more convenient if we know them.
[cut]
## Navigation: form and elements
Document forms are members of the special collection `document.forms`.
That's a *named* collection: we can use both the name and the number to get the form.
```js no-beautify
document.forms.my - the form with name="my"
document.forms[0] - the first form in the document
```
When we have a form, then any element is available in the named collection `form.elements`.
For instance:
```html run height=40
<form name="my">
<input name="one" value="1">
<input name="two" value="2">
</form>
<script>
// get the form
let form = document.forms.my; // <form name="my"> element
// get the element
let elem = form.elements.one; // <input name="one"> element
alert( elem.value ); // 1
</script>
```
There may be multiple elements with the same name, that's often the case with radio buttons.
In that case `form.elements[name]` is a collection, for instance:
```html run height=40
<form>
<input type="radio" *!*name="age"*/!* value="10">
<input type="radio" *!*name="age"*/!* value="20">
</form>
<script>
let form = document.forms[0];
let ageElems = form.elements.age;
alert(ageElems[0].value); // 10, the first input value
</script>
```
These navigation properties do not depend on the tag structure. All elements, no matter how deep they are in the form, are available in `form.elements`.
````smart header="Fieldsets as \"subforms\""
A form may have one or many `<fieldset>` elements inside it. They also support the `elements` property.
For instance:
```html run height=80
<body>
<form id="form">
<fieldset name="userFields">
<legend>info</legend>
<input name="login" type="text">
</fieldset>
</form>
<script>
alert(form.elements.login); // <input name="login">
*!*
let fieldset = form.elements.userFields;
alert(fieldset); // HTMLFieldSetElement
// we can get the input both from the form and from the fieldset
alert(fieldset.elements.login == form.elements.login); // true
*/!*
</script>
</body>
```
````
````warn header="Shorter notation: `form.name`"
There's a shorter notation: we can access the element as `form[index/name]`.
Instead of `form.elements.login` we can write `form.login`.
That also works, but there's a minor issue: if we access an element, and then change its `name`, then it is still available under the old name (as well as under the new one).
That's easy to see in an example:
```html run height=40
<form id="form">
<input name="login">
</form>
<script>
alert(form.elements.login == form.login); // true, the same <input>
form.login.name = "username"; // change the name of the input
// form.elements updated the name:
alert(form.elements.login); // undefined
alert(form.elements.username); // input
*!*
// the direct access now can use both names: the new one and the old one
alert(form.username == form.login); // true
*/!*
</script>
```
That's usually not a problem, because we rarely change names of form elements.
````
## Backreference: element.form
For any element, the form is available as `element.form`. So a form references all elements, and elements
reference the form.
Here's the picture:
![](form-navigation.png)
For instance:
```html run height=40
<form id="form">
<input type="text" name="login">
</form>
<script>
*!*
// form -> element
let login = form.login;
// element -> form
alert(login.form); // HTMLFormElement
*/!*
</script>
```
## Values: input and textarea
Normally, we can read the value as `input.value` or `input.checked` (for radio)
Для большинства типов `input` значение ставится/читается через свойство `value`.
```js
input.value = "Новое значение";
textarea.value = "Новый текст";
```
```warn header="Не используйте `textarea.innerHTML`"
Для элементов `textarea` также доступно свойство `innerHTML`, но лучше им не пользоваться: оно хранит только HTML, изначально присутствовавший в элементе, и не меняется при изменении значения.
```
Исключения -- `input type="checkbox"` и `input type="radio"`
**Текущее "отмеченное" состояние для `checkbox` и `radio` находится в свойстве `checked` (`true/false`).**
```js
if (input.checked) {
alert( "Чекбокс выбран" );
}
```
## Элементы select и option
Селект в JavaScript можно установить двумя путями: поставив значение `select.value`, либо установив свойство `select.selectedIndex` в номер нужной опции.:
```js
select.selectedIndex = 0; // первая опция
```
Установка `selectedIndex = -1` очистит выбор.
**Список элементов-опций доступен через `select.options`.**
Если `select` допускает множественный выбор (атрибут `multiple`), то значения можно получить/установить, сделав цикл по `select.options`. При этом выбранные опции будут иметь свойство `option.selected = true`.
Пример:
```html run
<form name="form">
<select name="genre" *!*multiple*/!*>
<option value="blues" selected>Мягкий блюз</option>
<option value="rock" selected>Жёсткий рок</option>
<option value="classic">Классика</option>
</select>
</form>
<script>
var form = document.forms[0];
var select = form.elements.genre;
for (var i = 0; i < select.options.length; i++) {
var option = select.options[i];
*!*
if(option.selected) {
alert( option.value );
}
*/!*
}
</script>
```
Спецификация: [the select element](https://html.spec.whatwg.org/multipage/forms.html#the-select-element).
````smart header="`new Option`"
В стандарте [the option element](https://html.spec.whatwg.org/multipage/forms.html#the-option-element) есть любопытный короткий синтаксис для создания элемента с тегом `option`:
```js
option = new Option(text, value, defaultSelected, selected);
```
Параметры:
- `text` -- содержимое,
- `value` -- значение,
- `defaultSelected` и `selected` поставьте в `true`, чтобы сделать элемент выбранным.
Его можно использовать вместо `document.createElement('option')`, например:
```js
var option = new Option("Текст", "value");
// создаст <option value="value">Текст</option>
```
Такой же элемент, но выбранный:
```js
var option = new Option("Текст", "value", true, true);
```
````
```smart header="Дополнительные свойства `option`"
У элементов `option` также есть особые свойства, которые могут оказаться полезными (см. [the option element](https://html.spec.whatwg.org/multipage/forms.html#the-option-element)):
`selected`
: выбрана ли опция
`index`
: номер опции в списке селекта
`text`
: Текстовое содержимое опции (то, что видит посетитель).
```
## Итого
Свойства для навигации по формам:
`document.forms`
: Форму можно получить как `document.forms[name/index]`.
`form.elements`
: Элементы в форме: `form.elements[name/index]`. Каждый элемент имеет ссылку на форму в свойстве `form`. Свойство `elements` также есть у `<fieldset>`.
Значение элементов читается/ставится через `value` или `checked`.
Для элемента `select` можно задать опцию по номеру через `select.selectedIndex` и перебрать опции через `select.options`. При этом выбранные опции (в том числе при мультиселекте) будут иметь свойство `option.selected = true`.
Спецификация: [HTML5 Forms](https://html.spec.whatwg.org/multipage/forms.html).

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

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

View file

@ -0,0 +1,74 @@
<!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

@ -0,0 +1,48 @@
<!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

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

View file

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

View file

@ -0,0 +1,65 @@
<!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

@ -0,0 +1,42 @@
<!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

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

View file

@ -0,0 +1,99 @@
# 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

@ -0,0 +1,61 @@
<!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

@ -0,0 +1,27 @@
#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

@ -0,0 +1,22 @@
<!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

@ -0,0 +1,27 @@
#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

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

View file

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

View file

@ -0,0 +1,56 @@
/* общие стили для таблицы */
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

@ -0,0 +1,78 @@
<!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

@ -0,0 +1,23 @@
.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

@ -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.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

@ -0,0 +1,56 @@
/* общие стили для таблицы */
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

@ -0,0 +1,78 @@
<!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

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

View file

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

View file

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

View file

@ -0,0 +1,21 @@
# Вёрстка
Для вёрстки можно использовать отрицательный `margin` у текста с подсказкой.
Решение в плане вёрстка есть в решении задачи <info: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);
}
}
```

View file

@ -0,0 +1,33 @@
<!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

@ -0,0 +1,17 @@
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

@ -0,0 +1,21 @@
<!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

@ -0,0 +1,17 @@
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

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

View file

@ -0,0 +1,86 @@
# Алгоритм
JavaScript не имеет доступа к текущему состоянию `key:CapsLock`. При загрузке страницы не известно, включён он или нет.
Но мы можем догадаться о его состоянии из событий:
1. Проверив символ, полученный по `keypress`. Символ в верхнем регистре без нажатого `key:Shift` означает, что включён `key:CapsLock`. Аналогично, символ в нижнем регистре, но с `key:Shift` говорят о включенном `key:CapsLock`. Свойство `event.shiftKey` показывает, нажат ли `key:Shift`. Так мы можем точно узнать, нажат ли `key:CapsLock`.
2. Проверять `keydown`. Если нажат CapsLock (скан-код равен `20`), то переключить состояние, но лишь в том случае, когда оно уже известно.
Под Mac так делать не получится, поскольку клавиатурные события с CapsLock [работают некорректно](info:keyboard-events#keyboard-events-order).
Имея состояние `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, чтобы уберечь его от неправильного ввода.
1. Для начала, когда пользователь сфокусировался на поле, мы должны вывести предупреждение о CapsLock, если он включен.
2. Пользователь начинает ввод. Каждое событие `keypress` всплывает до обработчика `document.keypress`, который обновляет состояние `capsLockEnabled`.
Мы не можем использовать событие `input.onkeypress`, для отображения состояния пользователю, потому что оно сработает *до* `document.onkeypress` (из-за всплытия) и, следовательно, до того, как мы узнаем состояние `key:CapsLock`.
Есть много способов решить эту проблему. Можно, например, назначить обработчик состояния CapsLock на событие `input.onkeyup`. То есть, индикация будет с задержкой, но это несущественно.
Альтернативное решение -- добавить на `input` такой же обработчик, как и на `document.onkeypress`.
3. ...И наконец, пользователь убирает фокус с поля. Предупреждение может быть видно, если `key:CapsLock` включен, но так как пользователь уже ушел с поля, то нам нужно спрятать предупреждение.
Код проверки поля:
```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>
```

View file

@ -0,0 +1,77 @@
<!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

@ -0,0 +1,19 @@
<!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

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

View file

@ -0,0 +1,322 @@
# Фокусировка: focus/blur
Говорят, что элемент "получает фокус", когда посетитель фокусируется на нём. Обычно фокусировка автоматически происходит при нажатии на элементе мышкой, но также можно перейти на нужный элемент клавиатурой -- через клавишу `key:Tab`, нажатие пальцем на планшете и так далее.
Момент получения фокуса и потери очень важен.
При получении фокуса мы можем подгрузить данные для автодополнения, начать отслеживать изменения. При потере -- проверить данные, которые ввёл посетитель.
Кроме того, иногда полезно "вручную", из JavaScript перевести фокус на нужный элемент, например, на поле в динамически созданной форме.
[cut]
## События focus/blur
Событие `focus` вызывается тогда, когда пользователь фокусируется на элементе, а `blur` -- когда фокус исчезает, например посетитель кликает на другом месте экрана.
Давайте сразу посмотрим на них в деле, используем для проверки ("валидации") введённых в форму значений.
В примере ниже:
- Обработчик `onblur` проверяет, что в поле введено число, если нет -- показывает ошибку.
- Обработчик `onfocus`, если текущее состояние поля ввода -- "ошибка" -- скрывает её (потом при `onblur` будет повторная проверка).
В примере ниже, если набрать что-нибудь в поле "возраст" и завершить ввод, нажав `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` при фокусе на первом элементе -- переведёт его на второй.
Исключением являются специальные значения:
- `tabindex="0"` делает элемент всегда последним.
- `tabindex="-1"` означает, что клавиша `key:Tab` будет элемент игнорировать.
**Любой элемент поддерживает фокусировку, если у него есть `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`, только они всплывают.
У них две особенности:
- Не поддерживаются Firefox (хотя поддерживаются даже старейшими IE), см. <https://bugzilla.mozilla.org/show_bug.cgi?id=687787>.
- Должны быть назначены не через `on`-свойство, а при помощи `elem.addEventListener`.
Из-за отсутствия подержки 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` происходят при получении и снятия фокуса с элемента.
У них есть особенности:
- Они не всплывают. Но на фазе перехвата их можно перехватить. Это странно, но это так, не спрашивайте почему.
Везде, кроме Firefox, поддерживаются всплывающие альтернативы `focusin/focusout`.
- По умолчанию многие элементы не могут получить фокус. Например, если вы кликните по `DIV`, то фокусировка на нем не произойдет.
Но это можно изменить, если поставить элементу атрибут `tabIndex`. Этот атрибут также дает возможность контролировать порядок перехода при нажатии `key:Tab`.
```smart header="Текущий элемент: `document.activeElement`"
Кстати, текущий элемент, на котором фокус, доступен как `document.activeElement`.
```

View file

@ -0,0 +1,9 @@
Алгоритм решения такой.
Только численный ввод в поле с суммой разрешаем, повесив обработчик на `keypress`.
Отслеживаем события изменения для перевычисления результатов:
- На `input`: событие `input` и дополнительно `propertychange/keyup` для совместимости со старыми IE.
- На `checkbox`: событие `click` вместо `change` для совместимости с IE8-.
- На `select`: событие `change`.

View file

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
td select,
td input {
width: 150px;
}
#diagram td {
vertical-align: bottom;
text-align: center;
padding: 10px;
}
#diagram div {
margin: auto;
}
</style>
</head>
<body>
Калькулятор процентов, из расчёта 12% годовых.
<form name="calculator">
<table>
<tr>
<td>Сумма</td>
<td>
<input name="money" type="text" value="10000">
</td>
</tr>
<tr>
<td>Срок в месяцах</td>
<td>
<select name="months">
<option value="3">3 (минимум)</option>
<option value="6">6 (полгода)</option>
<option value="12" selected>12 (год)</option>
<option value="18">18 (1.5 года)</option>
<option value="24">24 (2 года)</option>
<option value="30">30 (2.5 года)</option>
<option value="36">36 (3 года)</option>
</select>
</td>
</tr>
<tr>
<td>С капитализацией</td>
<td>
<input name="capitalization" type="checkbox">
</td>
</tr>
</table>
</form>
<table id="diagram">
<tr>
<th>Было:</th>
<th>Станет:</th>
</tr>
<tr>
<th id="money-before"></th>
<th id="money-after"></th>
</tr>
<td>
<div style="background: red;width:40px;height:100px"></div>
</td>
<td>
<div style="background: green;width:40px;height:0" id="height-after"></div>
</td>
</table>
<script>
// event.type должен быть keypress
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; // специальная клавиша
}
var form = document.forms.calculator;
var moneyElem = form.elements.money;
moneyElem.onkeypress = function(e) {
e = e || event;
var chr = getChar(e);
if (e.ctrlKey || e.altKey || chr == null) return; // специальная клавиша
if (chr < '0' || chr > '9') return false;
}
// клавиатура, вставить/вырезать клавиатурой
moneyElem.onkeyup = calculate;
// любые действия, кроме IE. В IE9 также работает, кроме удаления
moneyElem.oninput = calculate;
moneyElem.onpropertychange = function() { // для IE8- изменение значения, кроме удаления
event.propertyName == "value" && calculate();
}
var capitalizationElem = form.elements.capitalization;
capitalizationElem.onclick = calculate;
var monthsElem = form.elements.months;
monthsElem.onchange = calculate;
function calculate() {
var sum = +moneyElem.value;
if (!sum) return;
var monthlyIncrease = 0.01;
if (!capitalizationElem.checked) {
sum = sum * (1 + monthlyIncrease * monthsElem.value);
} else {
for (var i = 0; i < monthsElem.value; i++) {
// 1000 1010 1020.1
sum = sum * (1 + monthlyIncrease);
}
}
sum = Math.round(sum);
var height = sum / moneyElem.value * 100 + 'px';
document.getElementById('height-after').style.height = height;
document.getElementById('money-before').innerHTML = moneyElem.value;
document.getElementById('money-after').innerHTML = sum;
}
calculate();
</script>
</body>
</html>

View file

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
td select,
td input {
width: 150px;
}
#diagram td {
vertical-align: bottom;
text-align: center;
padding: 10px;
}
#diagram div {
margin: auto;
}
</style>
</head>
<body>
Калькулятор процентов, из расчёта 12% годовых.
<form name="calculator">
<table>
<tr>
<td>Сумма</td>
<td>
<input name="money" type="text" value="10000">
</td>
</tr>
<tr>
<td>Срок в месяцах</td>
<td>
<select name="months">
<option value="3">3 (минимум)</option>
<option value="6">6 (полгода)</option>
<option value="12" selected>12 (год)</option>
<option value="18">18 (1.5 года)</option>
<option value="24">24 (2 года)</option>
<option value="30">30 (2.5 года)</option>
<option value="36">36 (3 года)</option>
</select>
</td>
</tr>
<tr>
<td>С капитализацией</td>
<td>
<input name="capitalization" type="checkbox">
</td>
</tr>
</table>
</form>
<table id="diagram">
<tr>
<th>Было:</th>
<th>Станет:</th>
</tr>
<tr>
<th id="money-before"></th>
<th id="money-after"></th>
</tr>
<tr>
<td>
<div style="background: red;width:40px;height:100px"></div>
</td>
<td>
<div style="background: green;width:40px;height:0" id="height-after"></div>
</td>
</tr>
</table>
<script>
// вспомогательная функция для получения символа из события keypress
// (вдруг понадобится))
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; // специальная клавиша
}
// ваш код...
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
importance: 5
---
# Автовычисление процентов по вкладу
Создайте интерфейс для автоматического вычисления процентов по вкладу.
Ставка фиксирована: 12% годовых. При включённом поле "капитализация" -- проценты приплюсовываются к сумме вклада каждый месяц ([сложный процент](http://damoney.ru/finance/slozniy-procent.php)).
Пример:
[iframe src="solution" height="350" border="1"]
Технические требования:
- В поле с суммой должно быть нельзя ввести не-цифру. При этом пусть в нём работают специальные клавиши и сочетания Ctrl-X/Ctrl-V.
- Изменения в форме отражаются в результатах сразу.

View file

@ -0,0 +1,185 @@
# Изменение: change, input, cut, copy, paste
На элементах формы происходят события клавиатуры и мыши, но есть и несколько других, особенных событий.
## Событие change
Событие [change](http://www.w3.org/TR/html5/forms.html#event-input-change) происходит по окончании изменении значения элемента формы, когда это изменение зафиксировано.
Для текстовых элементов это означает, что событие произойдёт не при каждом вводе, а при потере фокуса.
Например, пока вы набираете что-то в текстовом поле ниже -- события нет. Но как только вы уведёте фокус на другой элемент, например, нажмёте кнопку -- произойдет событие `onchange`.
```html autorun height=40
<input type="text" onchange="alert(this.value)">
<input type="button" value="Кнопка">
```
Для остальных же элементов: `select`, `input type=checkbox/radio` оно срабатывает сразу при выборе значения.
```warn header="Поздний `onchange` в IE8-"
В IE8- `checkbox/radio` при изменении мышью не инициируют событие сразу, а ждут потери фокуса.
Для того, чтобы видеть изменения `checkbox/radio` тут же -- в IE8- нужно повесить обработчик на событие `click` (оно произойдет и при изменении значения с клавиатуры) или воспользоваться событием `propertychange`, описанным далее.
```
## Событие input
Событие `input` срабатывает *тут же* при изменении значения текстового элемента и поддерживается всеми браузерами, кроме IE8-.
В IE9 оно поддерживается частично, а именно -- *не возникает при удалении символов* (как и `onpropertychange`).
Пример использования (не работает в IE8-):
```html autorun height=40
<input type="text"> oninput: <span id="result"></span>
<script>
var input = document.body.children[0];
input.oninput = function() {
document.getElementById('result').innerHTML = input.value;
};
</script>
```
В современных браузерах `oninput` -- самое главное событие для работы с элементом формы. Именно его, а не `keydown/keypress` следует использовать.
Если бы ещё не проблемы со старыми IE... Впрочем, их можно решить при помощи события `propertychange`.
## IE10-, событие propertychange
Это событие происходит только в IE10-, при любом изменении свойства. Оно позволяет отлавливать изменение тут же. Оно нестандартное, и его основная область использования -- исправление недочётов обработки событий в старых IE.
Если поставить его на `checkbox` в IE8-, то получится "правильное" событие `change`:
```html autorun height=40
<input type="checkbox"> Чекбокс с "onchange", работающим везде одинаково
<script>
var checkbox = document.body.children[0];
if ("onpropertychange" in checkbox) {
// старый IE
*!*
checkbox.onpropertychange = function() {
// проверим имя изменённого свойства
if (event.propertyName == "checked") {
alert( checkbox.checked );
}
};
*/!*
} else {
// остальные браузеры
checkbox.onchange = function() {
alert( checkbox.checked );
};
}
</script>
```
Это событие также срабатывает при изменении значения текстового элемента. Поэтому его можно использовать в старых IE вместо `oninput`.
К сожалению, в IE9 у него недочёт: оно не срабатывает при удалении символов. Поэтому сочетания `onpropertychange` + `oninput` недостаточно, чтобы поймать любое изменение поля в старых IE. Далее мы рассмотрим пример, как это можно сделать иначе.
## События cut, copy, paste
Эти события используются редко. Они происходят при вырезании/вставке/копировании значения.
К сожалению, кросс-браузерного способа получить данные, которые вставляются/копируются, не существует, поэтому их основное применение -- это отмена соответствующей операции.
Например, вот так:
```html autorun height=40
<input type="text" id="input"> event: <span id="result"></span>
<script>
input.oncut = input.oncopy = input.onpaste = function(event) {
result.innerHTML = event.type + ' ' + input.value;
return false;
};
</script>
```
## Пример: поле с контролем СМС
Как видим, событий несколько и они взаимно дополняют друг друга.
Посмотрим, как их использовать, на примере.
Сделаем поле для СМС, рядом с которым должно показываться число символов, обновляющееся при каждом изменении поля.
Как такое реализовать?
Событие `input` идеально решит задачу во всех браузерах, кроме IE9-. Собственно, если IE9- нам не нужен, то на этом можно и остановиться.
### IE9-
В IE8- событие `input` не поддерживается, но, как мы видели ранее, есть `onpropertychange`, которое может заменить его.
Что же касается IE9 -- там поддерживаются и `input` и `onpropertychange`, но они оба не работают при удалении символов. Поэтому мы будем отслеживать удаление при помощи `keyup` на `key:Delete` и `key:BackSpace` . А вот удаление командой "вырезать" из меню -- сможет отловить лишь `oncut`.
Получается вот такая комбинация:
```html autorun run height=60
<input type="text" id="sms"> символов: <span id="result"></span>
<script>
function showCount() {
result.innerHTML = sms.value.length;
}
sms.onkeyup = sms.oninput = showCount;
sms.onpropertychange = function() {
if (event.propertyName == "value") showCount();
}
sms.oncut = function() {
setTimeout(showCount, 0); // на момент oncut значение еще старое
};
</script>
```
Здесь мы добавили вызов `showCount` на все события, которые могут приводить к изменению значения. Да, иногда изменение будет обрабатываться несколько раз, но зато с гарантией. А лишние вызовы легко убрать, например, при помощи `throttle`-декоратора, описанного в задаче <info:task/throttle>.
**Есть и совсем другой простой, но действенный вариант: через `setInterval` регулярно проверять значение и, если оно слишком длинное, обрезать его.**
Чтобы сэкономить ресурсы браузера, мы можем начинать отслеживание по `onfocus`, а прекращать -- по `onblur`, вот так:
```html autorun height=60
<input type="text" id="sms"> символов: <span id="result"></span>
<script>
var timerId;
sms.onfocus = function() {
var lastValue = sms.value;
timerId = setInterval(function() {
if (sms.value != lastValue) {
showCount();
lastValue = sms.value;
}
}, 20);
};
sms.onblur = function() {
clearInterval(timerId);
};
function showCount() {
result.innerHTML = sms.value.length;
}
</script>
```
Обратим внимание -- весь этот "танец с бубном" нужен только для поддержки IE8-, в которых не поддерживается `oninput` и IE9, где `oninput` не работает при удалении.
## Итого
События изменения данных:
| Событие | Описание | Особенности |
|---------|----------|-------------|
| `change`| Изменение значения любого элемента формы. Для текстовых элементов срабатывает при потере фокуса. | В IE8- на чекбоксах ждет потери фокуса, поэтому для мгновенной реакции ставят также <code>onclick</code>-обработчик или <code>onpropertychange</code>. |
| `input` | Событие срабатывает только на текстовых элементах. Оно не ждет потери фокуса, в отличие от <code>change</code>. | В IE8- не поддерживается, в IE9 не работает при удалении символов. |
| `propertychange` | Только для IE10-. Универсальное событие для отслеживания изменения свойств элементов. Имя изменённого свойства содержится в `event.propertyName`. Используют для мгновенной реакции на изменение значения в старых IE. | В IE9 не срабатывает при удалении символов. |
| `cut/copy/paste` | Срабатывают при вставке/копировании/удалении текста. Если в их обработчиках отменить действие браузера, то вставки/копирования/удаления не произойдёт. | Вставляемое значение получить нельзя: на момент срабатывания события в элементе всё ещё *старое* значение, а новое недоступно. |
Ещё особенность: в IE8- события `change`, `propertychange`, `cut` и аналогичные не всплывают. То есть, обработчики нужно назначать на сам элемент, без делегирования.

View file

@ -0,0 +1,21 @@
Модальное окно делается путём добавления к документу `DIV`, полностью перекрывающего документ и имеющего больший `z-index`.
В результате все клики будут доставаться этому `DIV'у`:
Стиль:
```css
#cover-div {
position: fixed;
top: 0;
left: 0;
z-index: 9000;
width: 100%;
height: 100%;
background-color: gray;
opacity: 0.3;
}
```
Самой форме можно дать еще больший `z-index`, чтобы она была над `DIV'ом`. Мы не помещаем форму в контейнер, чтобы она не унаследовала полупрозрачность.

View file

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#prompt-form {
display: inline-block;
padding: 5px 5px 5px 70px;
width: 200px;
border: 1px solid black;
background: white url(https://js.cx/clipart/prompt.png) no-repeat left 5px;
vertical-align: middle;
}
#prompt-form-container {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
display: none;
width: 100%;
height: 100%;
text-align: center;
}
#prompt-form-container:before {
display: inline-block;
height: 100%;
content: '';
vertical-align: middle;
}
#cover-div {
position: fixed;
top: 0;
left: 0;
z-index: 9000;
width: 100%;
height: 100%;
background-color: gray;
opacity: 0.3;
}
#prompt-form input[name="text"] {
display: block;
margin: 5px;
width: 180px;
}
</style>
</head>
<body style="height:3000px">
<h1>Нажмите на кнопку ниже</h1>
<input type="button" value="Нажмите для показа формы ввода" id="show-button">
<div id="prompt-form-container">
<form id="prompt-form">
<div id="prompt-message"></div>
<input name="text" type="text">
<input type="submit" value="Ок">
<input type="button" name="cancel" value="Отмена">
</form>
</div>
<script>
// Показать полупрозрачный DIV, затеняющий всю страницу
// (а форма будет не в нем, а рядом с ним, чтобы не полупрозрачная)
function showCover() {
var coverDiv = document.createElement('div');
coverDiv.id = 'cover-div';
document.body.appendChild(coverDiv);
}
function hideCover() {
document.body.removeChild(document.getElementById('cover-div'));
}
function showPrompt(text, callback) {
showCover();
var form = document.getElementById('prompt-form');
var container = document.getElementById('prompt-form-container');
document.getElementById('prompt-message').innerHTML = text;
form.elements.text.value = '';
function complete(value) {
hideCover();
container.style.display = 'none';
document.onkeydown = null;
callback(value);
}
form.onsubmit = function() {
var value = form.elements.text.value;
if (value == '') return false; // игнорировать пустой submit
complete(value);
return false;
};
form.elements.cancel.onclick = function() {
complete(null);
};
document.onkeydown = function(e) {
if (e.keyCode == 27) { // escape
complete(null);
}
};
var lastElem = form.elements[form.elements.length - 1];
var firstElem = form.elements[0];
lastElem.onkeydown = function(e) {
if (e.keyCode == 9 && !e.shiftKey) {
firstElem.focus();
return false;
}
};
firstElem.onkeydown = function(e) {
if (e.keyCode == 9 && e.shiftKey) {
lastElem.focus();
return false;
}
};
container.style.display = 'block';
form.elements.text.focus();
}
document.getElementById('show-button').onclick = function() {
showPrompt("Введите что-нибудь<br>...умное :)", function(value) {
alert("Вы ввели: " + value);
});
};
</script>
</body>
</html>

View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#prompt-form {
display: inline-block;
padding: 5px 5px 5px 70px;
width: 200px;
border: 1px solid black;
background: white url(https://js.cx/clipart/prompt.png) no-repeat left 5px;
vertical-align: middle;
}
#prompt-form-container {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
text-align: center;
}
#prompt-form-container:before {
display: inline-block;
height: 100%;
content: '';
vertical-align: middle;
}
#prompt-form input[name="text"] {
display: block;
margin: 5px;
width: 180px;
}
</style>
</head>
<body>
<div id="prompt-form-container">
<form id="prompt-form">
<div id="prompt-message">Введите, пожалуйста...
<br>Что-то..</div>
<input name="text" type="text">
<input type="submit" value="Ок">
<input type="button" name="cancel" value="Отмена">
</form>
</div>
</body>
</html>

View file

@ -0,0 +1,33 @@
importance: 5
---
# Модальное диалоговое окно
Создайте функцию `showPrompt(text, callback)`, которая выводит форму для ввода с сообщением `text` и кнопками `ОК/Отмена`.
- При отправке формы (OK/ввод в текстовом поле) -- должна вызываться функция `callback` со значением поля.
- При нажатии на `Отмена` или на клавишу `key:Esc` -- должна вызываться функция `callback(null)`. Клавиша `key:Esc` должна закрывать форму всегда, даже если поле для ввода сообщения не в фокусе.
Особенности реализации:
- Форма должна показываться в центре окна (и оставаться в центре при изменении его размеров, а также при прокрутке окна!).
- Текст может состоять из нескольких строк, возможен любой HTML
- При показе формы остальные элементы страницы использовать нельзя, не работают другие кнопки и т.п, это окно -- *модальное*.
- При показе формы -- сразу фокус на `INPUT` для ввода.
- Нажатия `key:Tab`/`key:Shift+Tab` переключают в цикле только по полям формы, они не позволяют переключиться на другие элементы страницы.
Пример использования:
```js
showPrompt("Введите что-нибудь<br>... умное :)", function(value) {
alert( value );
});
```
Демо в ифрейме:
[iframe src="solution" height=160 border=1]
Исходный HTML/CSS для формы с готовым fixed-позиционированием - в песочнице.

View file

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
td select,
td input {
width: 150px;
}
label {
display: block;
}
.error input,
.error textarea {
border: 1px solid red;
}
.error {
color: red;
}
</style>
</head>
<body>
<form>
<table>
<tr>
<td>От кого</td>
<td>
<input name="from" type="text">
</td>
</tr>
<tr>
<td>Ваш пароль</td>
<td>
<input name="password" type="password">
</td>
</tr>
<tr>
<td>Повторите пароль</td>
<td>
<input name="password2" type="password">
</td>
</tr>
<tr>
<td>Куда</td>
<td>
<select name="to">
<option></option>
<option value="1">Отдел снабжения</option>
<option value="2">Отдел разработки</option>
<option value="3">Директору</option>
</select>
</td>
</tr>
</table>
Сообщение:
<label>
<textarea name="message" style="display:block;width:400px;height:80px"></textarea>
</label>
<input type="button" onclick="validate(this.form)" value="Проверить">
</form>
<script>
function showError(container, errorMessage) {
container.className = 'error';
var msgElem = document.createElement('span');
msgElem.className = "error-message";
msgElem.innerHTML = errorMessage;
container.appendChild(msgElem);
}
function resetError(container) {
container.className = '';
if (container.lastChild.className == "error-message") {
container.removeChild(container.lastChild);
}
}
function validate(form) {
var elems = form.elements;
resetError(elems.from.parentNode);
if (!elems.from.value) {
showError(elems.from.parentNode, ' Укажите от кого.');
}
resetError(elems.password.parentNode);
if (!elems.password.value) {
showError(elems.password.parentNode, ' Укажите пароль.');
} else if (elems.password.value != elems.password2.value) {
showError(elems.password.parentNode, ' Пароли не совпадают.');
}
resetError(elems.to.parentNode);
if (!elems.to.value) {
showError(elems.to.parentNode, ' Укажите, куда.');
}
resetError(elems.message.parentNode);
if (!elems.message.value) {
showError(elems.message.parentNode, ' Отсутствует текст.');
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
td select,
td input {
width: 150px;
}
label {
display: block;
}
/* ваши стили */
</style>
</head>
<body>
<form>
<table>
<tr>
<td>От кого</td>
<td>
<input name="from" type="text">
</td>
</tr>
<tr>
<td>Ваш пароль</td>
<td>
<input name="password" type="password">
</td>
</tr>
<tr>
<td>Повторите пароль</td>
<td>
<input name="password2" type="password">
</td>
</tr>
<tr>
<td>Куда</td>
<td>
<select name="to">
<option></option>
<option value="1">Отдел снабжения</option>
<option value="2">Отдел разработки</option>
<option value="3">Директору</option>
</select>
</td>
</tr>
</table>
Сообщение:
<label>
<textarea name="message" style="display:block;width:400px;height:100px"></textarea>
</label>
<input type="button" onclick="validate(this.form)" value="Проверить">
</form>
<script>
function validate(form) {
/* ваш код */
}
/* ваш код */
</script>
</body>
</html>

View file

@ -0,0 +1,17 @@
importance: 3
---
# Валидация формы
Напишите функцию `validate(form)`, которая проверяет содержимое формы по клику на кнопку "Проверить".
Ошибки:
1. Одно из полей не заполнено.
2. Пароли не совпадают.
Ошибка должна сопровождаться сообщением у поля. Например:
[iframe height=280 src="solution"]

View file

@ -0,0 +1,56 @@
# Формы: отправка, событие и метод submit
Событие `submit` возникает при отправке формы. Наиболее частое его применение -- это *валидация* (проверка) формы перед отправкой.
Метод `submit` позволяет инициировать отправку формы из JavaScript, без участия пользователя. Далее мы рассмотрим детали их использования.
[cut]
## Событие submit
Чтобы отправить форму на сервер, у посетителя есть два способа:
1. **Первый -- это нажать кнопку `<input type="submit">` или `<input type="image">`.**
2. **Второй -- нажать Enter, находясь на каком-нибудь поле.**
Какой бы способ ни выбрал посетитель -- будет сгенерировано событие `submit`. Обработчик в нём может проверить данные и, если они неверны, то вывести ошибку и сделать `event.preventDefault()` -- тогда форма не отправится на сервер.
Например, в таком HTML оба способа выведут `alert`, форма не будет отправлена:
```html autorun height=80 no-beautify
<form onsubmit="alert('submit!');return false">
Первый: Enter в текстовом поле <input type="text" value="Текст"><br>
Второй: Нажать на "Отправить": <input type="submit" value="Отправить">
</form>
```
Ожидаемое поведение:
1. Перейдите в текстовое поле и нажмите Enter, будет событие, но форма не отправится на сервер благодаря `return false` в обработчике.
2. То же самое произойдет при клике на `<input type="submit">`.
````smart header="Взаимосвязь событий `submit` и `click`"
При отправке формы путём нажатия Enter на текстовом поле, на элементе `<input type="submit">` везде, кроме IE8-, генерируется событие `click`.
Это довольно забавно, учитывая что клика-то и не было.
```html autorun height=80
<form onsubmit="alert('submit');return false">
<input type="text" size="30" value="При нажатии Enter будет click">
<input type="submit" value="Submit" *!*onclick="alert('click')"*/!*>
</form>
```
````
```warn header="В IE8- событие `submit` не всплывает"
В IE8- событие `submit` не всплывает. Нужно вешать обработчик `submit` на сам элемент формы, без использования делегирования.
```
## Метод submit
Чтобы отправить форму на сервер из JavaScript -- нужно вызвать на элементе формы метод `form.submit()`.
При этом само событие `submit` не генерируется. Предполагается, что если программист вызывает метод `form.submit()`, то он выполнил все проверки.
Это используют, в частности, для искусственной генерации и отправки формы.

View file

@ -0,0 +1,3 @@
# Forms, controls
Special properties and events for forms `<form>` and controls: `<input>`, `<select>` and other.