This commit is contained in:
Ilya Kantor 2014-10-26 22:10:13 +03:00
parent 06f61d8ce8
commit f301cb744d
2271 changed files with 103162 additions and 0 deletions

View file

@ -0,0 +1,12 @@
Алгоритм решения такой.
Только численный ввод в поле с суммой разрешаем, повесив обработчик на `keypress`.
Отслеживаем события изменения для перевычисления результатов:
<ul>
<li>На `input`: событие `input` и дополнительно `propertychange/keyup` для совместимости со старыми IE.</li>
<li>На `checkbox`: событие `click` вместо `change` для совместимости с IE<9.</li>
<li>На `select`: событие `change`.</li>
</ul>
[edit src="solution"]Открыть решение в песочнице[/edit]

View file

@ -0,0 +1 @@
{"name":"percent","plunk":"CHrMn00gt4Jnv8EMClgl"}

View file

@ -0,0 +1,146 @@
<!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() { // для IE<9 изменение значения, кроме удаления
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 @@
{"name":"percent","plunk":"CHrMn00gt4Jnv8EMClgl"}

View file

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

View file

@ -0,0 +1 @@
{"name":"percent-src","plunk":"y6inm9m0B7q9tdVC98YT"}

View file

@ -0,0 +1,95 @@
<!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>
// вспомогательная функция для получения символа из события 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,208 @@
# Изменение: change, input, propertychange
На элементах формы происходят события клавиатуры и мыши, но есть и несколько других, особенных событий.
## Событие 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`, описанным далее.
[/warn]
## Событие propertychange
Это событие происходит только в старых IE, до версии 11, при любом изменении свойства. Оно позволяет отлавливать изменение тут же и используется, преимущественно, для исправления ошибок в старых 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>
```
Это событие также срабатывает при изменении значения текстового элемента, но в IE9 у него ошибка: оно не срабатывает при удалении символов.
## Событие 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>
```
## События 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`-декоратора, описанного в задаче [](/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` не работает при удалении.
## Итого
События изменения данных:
<table class="bordered">
<tr>
<th>Событие</th>
<th>Описание</th>
<th>Особенности</th>
</tr>
<tr>
<td>`change`</td>
<td>Изменение значения любого элемента формы. Для текстовых элементов срабатывает при потере фокуса.</td>
<td>В IE8- на чекбоксах ждет потери фокуса, поэтому для мгновенной реакции ставят также `onclick`-обработчик или `onpropertychange`.</td>
</tr>
<tr>
<td>`input`</td>
<td>Событие срабатывает только на текстовых элементах. Оно не ждет потери фокуса, в отличие от `change`.</td>
<td>В IE8- не поддерживается, в IE9 не работает при удалении символов.</td>
</tr>
<tr>
<td>`propertychange`</td>
<td>Только для IE10-. Универсальное событие для отслеживания изменения свойств элементов. Имя изменённого свойства содержится в `event.propertyName`. Используют для мгновенной реакции на изменение значения в старых IE.
</td>
<td>В IE9 не срабатывает при удалении символов.</td>
</tr>
<tr>
<td>`cut/copy/paste`</td>
<td>Срабатывают при вставке/копировании/удалении текста. В них можно отменить действие браузера, и тогда вставке/копирования/удаления не произойдёт.</td>
<td>Вставляемое значение получить нельзя: на момент срабатывания события в элементе всё ещё *старое* значение, а новое недоступно.</td>
</tr>
</table>
Ещё особенность: в IE8- события `change`, `propertychange`, `cut` и аналогичные не всплывают. То есть, обработчики нужно назначать на сам элемент, без делегирования.